From 2711d94663d383317c216b55b62fb0f5d890d450 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 02:48:44 +0000 Subject: [PATCH 1/2] Initial plan From 38f77a6b1056bad5d2e8b56a84dbcca0afdcf4b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 03:03:20 +0000 Subject: [PATCH 2/2] Refactor CSS: split style.css into sub-files, add design tokens, update game stylesheets Agent-Logs-Url: https://github.com/acrosman/BrainSpeedExercises/sessions/b9387d83-e383-4d0b-bcde-f2a4e90cb04a Co-authored-by: acrosman <2972053+acrosman@users.noreply.github.com> --- app/games/_template/style.css | 42 +- app/games/directional-processing/style.css | 61 +- app/games/fast-piggie/style.css | 160 +---- app/games/field-of-view/style.css | 147 ++--- app/games/high-speed-memory/style.css | 157 +---- app/games/object-track/style.css | 15 +- app/games/orbit-sprite-memory/style.css | 75 ++- app/games/otter-stop/style.css | 151 +---- app/games/sound-sweep/style.css | 64 +- app/style.css | 678 +-------------------- app/styles/base.css | 68 +++ app/styles/game-card.css | 115 ++++ app/styles/game-shared.css | 209 +++++++ app/styles/history.css | 279 +++++++++ app/styles/layout.css | 50 ++ app/styles/variables.css | 82 +++ 16 files changed, 1109 insertions(+), 1244 deletions(-) create mode 100644 app/styles/base.css create mode 100644 app/styles/game-card.css create mode 100644 app/styles/game-shared.css create mode 100644 app/styles/history.css create mode 100644 app/styles/layout.css create mode 100644 app/styles/variables.css diff --git a/app/games/_template/style.css b/app/games/_template/style.css index f57d624..3f4c474 100644 --- a/app/games/_template/style.css +++ b/app/games/_template/style.css @@ -1,7 +1,12 @@ /* ── Template Game ────────────────────────────────────────────────────────── */ -/* All selectors are prefixed with .game-template to avoid collisions. */ -/* Shared welcome panel and end panel styles come from app/style.css */ -/* (.game-welcome, .game-end-panel, .game-results, .game-btn). */ +/* + * All selectors are prefixed with .game-template to avoid collisions. + * Shared welcome panel, end panel, results table, and button styles all come + * from app/style.css (.game-welcome, .game-end-panel, .game-results, .game-btn). + * This file only contains layout and presentation rules that are unique to this + * game. Use the CSS custom properties defined in app/styles/variables.css + * instead of hard-coding colour values. + */ .game-template { display: flex; @@ -18,7 +23,7 @@ margin: 0 0 0.5rem; font-size: 1.75rem; font-weight: 700; - color: #1a1a1a; + color: var(--text-heading); } /* ── Instructions panel ───────────────────────────────────────────────────── */ @@ -39,14 +44,14 @@ display: flex; align-items: center; justify-content: center; - background: #f5f5f5; - border: 2px solid #ccc; - border-radius: 4px; + background: var(--bg-subtle); + border: 2px solid var(--border-color); + border-radius: var(--radius-sm); width: 100%; } .game-template__placeholder { - color: #666; + color: var(--text-subtle); font-style: italic; } @@ -63,21 +68,6 @@ /* in app/style.css. Add any game-specific overrides here. */ /* ── Buttons ──────────────────────────────────────────────────────────────── */ -/* Base button styles come from .game-btn in app/style.css. */ -/* Add any game-specific button overrides here. */ - -.game-template__btn { - /* inherits from .game-btn */ - border: 2px solid transparent; - cursor: pointer; -} - -.game-template__btn--stop { - background: #c0392b; - color: #fff; - border-color: #922b21; -} - -.game-template__btn--stop:hover { - background: #922b21; -} +/* All shared button styles come from .game-btn in app/style.css. */ +/* Use .game-btn--primary and .game-btn--secondary in HTML; avoid adding */ +/* game-specific button classes unless a genuine visual override is needed. */ diff --git a/app/games/directional-processing/style.css b/app/games/directional-processing/style.css index c478920..138aa1d 100644 --- a/app/games/directional-processing/style.css +++ b/app/games/directional-processing/style.css @@ -1,13 +1,23 @@ +/* ── Directional Processing ──────────────────────────────────────────────── */ +/* + * All selectors are prefixed with .directional-processing or .dp- to avoid + * collisions. Shared panel and button styles (.game-welcome, .game-end-panel, + * .game-results, .game-btn) come from app/style.css — they are NOT duplicated + * here. CSS custom properties are defined in app/styles/variables.css. + */ + .directional-processing { - color: #1f2933; + color: var(--text-base); } /* ── Panels ──────────────────────────────────────────────────────────────────── */ +/* .dp-panel is used for both the instructions and end panels alongside the */ +/* shared .game-welcome / .game-end-panel classes. */ .dp-panel { - background: #f2f4f7; - border: 1px solid #c9d1d9; - border-radius: 10px; + background: var(--bg-body); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); padding: 1rem; margin-top: 1rem; } @@ -34,8 +44,8 @@ width: 100%; height: auto; aspect-ratio: 1 / 1; - border: 2px solid #616b75; - border-radius: 8px; + border: 2px solid var(--text-subtle); + border-radius: var(--radius-lg); background: rgb(128, 128, 128); } @@ -45,7 +55,7 @@ position: absolute; inset: 0; pointer-events: none; - border-radius: 8px; + border-radius: var(--radius-lg); opacity: 0; z-index: 3; } @@ -91,20 +101,20 @@ height: 56px; font-size: 1rem; font-weight: 700; - border: 2px solid #5f6974; - border-radius: 8px; - background: #e5e7eb; - color: #1f2933; + border: 2px solid var(--text-subtle); + border-radius: var(--radius-lg); + background: var(--bg-muted); + color: var(--text-base); cursor: pointer; - transition: background 0.1s, transform 0.1s; + transition: background var(--transition-quick), transform var(--transition-quick); } .dp-dir-btn:hover { - background: #d1d5db; + background: var(--border-color); } .dp-dir-btn:focus-visible { - outline: 3px solid #1d4ed8; + outline: 3px solid var(--focus-ring); outline-offset: 2px; } @@ -128,7 +138,7 @@ .dp-dir-btn--selected { background: #cfe8ff; - border-color: #0f4aa3; + border-color: var(--btn-primary-bg); } /* ── Feedback region ─────────────────────────────────────────────────────────── */ @@ -150,29 +160,32 @@ } /* ── Buttons ─────────────────────────────────────────────────────────────────── */ +/* Shared button styles come from .game-btn in app/style.css. */ +/* .dp-btn is used for controls inside the active play area and may differ from */ +/* the primary/secondary shared variants. */ .dp-btn { - background: #dbe2ea; - color: #111827; - border: 1px solid #5f6974; - border-radius: 6px; + background: var(--bg-muted); + color: var(--text-base); + border: 1px solid var(--text-subtle); + border-radius: var(--radius-md); padding: 0.5rem 0.75rem; font-weight: 600; cursor: pointer; } .dp-btn--primary { - background: #0f4aa3; - color: #ffffff; - border-color: #0b3b83; + background: var(--btn-primary-bg); + color: var(--bg-card); + border-color: var(--btn-primary-border); } .dp-btn--secondary { - background: #e5e7eb; + background: var(--bg-muted); } .dp-btn:focus-visible { - outline: 3px solid #1d4ed8; + outline: 3px solid var(--focus-ring); outline-offset: 2px; } diff --git a/app/games/fast-piggie/style.css b/app/games/fast-piggie/style.css index 04b259a..6333e0c 100644 --- a/app/games/fast-piggie/style.css +++ b/app/games/fast-piggie/style.css @@ -1,3 +1,11 @@ +/* ── Fast Piggie ──────────────────────────────────────────────────────────── */ +/* + * All selectors are prefixed with .fast-piggie or .fp- to avoid collisions. + * Shared panel and button styles (.game-welcome, .game-end-panel, .game-results, + * .game-btn) come from app/style.css — they are NOT duplicated here. + * CSS custom properties are defined in app/styles/variables.css. + */ + /* Section wrapper */ .fast-piggie { display: flex; @@ -5,10 +13,8 @@ align-items: center; gap: 1rem; padding: 1.5rem; - background-color: #f8f9fa; - /* matches app body background */ - color: #212529; - /* ~14.5:1 contrast on #f8f9fa */ + background-color: var(--bg-body); + color: var(--text-base); } .fast-piggie h2 { @@ -46,12 +52,12 @@ height: 100%; border-radius: 50%; /* visual hint that it's a circle game */ - background-color: #ffffff; + background-color: var(--bg-card); cursor: crosshair; } .fp-canvas:focus-visible { - outline: 3px solid #005fcc; + outline: 3px solid var(--focus-ring); outline-offset: 4px; } @@ -74,24 +80,24 @@ @keyframes fp-flash-green { 0% { - background-color: #28a745; + background-color: var(--color-success); opacity: 0.55; } 100% { - background-color: #28a745; + background-color: var(--color-success); opacity: 0; } } @keyframes fp-flash-red { 0% { - background-color: #dc3545; + background-color: var(--color-danger-muted); opacity: 0.55; } 100% { - background-color: #dc3545; + background-color: var(--color-danger-muted); opacity: 0; } } @@ -104,132 +110,10 @@ justify-content: center; } -.fp-btn { - padding: 0.5rem 1.5rem; - font-size: 1rem; - font-weight: 600; - border: none; - border-radius: 4px; - cursor: pointer; - background-color: #005fcc; - /* primary blue */ - color: #ffffff; - /* 7.3:1 contrast on #005fcc */ - transition: background-color 0.15s ease; -} - -.fp-btn:hover { - background-color: #004aa3; -} - -.fp-btn:active { - background-color: #003d88; -} - -.fp-btn:focus-visible { - outline: 3px solid #005fcc; - outline-offset: 3px; - background-color: #004aa3; -} - -.fp-btn--secondary { - background-color: #6c757d; - /* muted grey; 4.6:1 contrast on white */ - color: #ffffff; -} - -.fp-btn--secondary:hover { - background-color: #545b62; -} - -.fp-btn--secondary:active { - background-color: #3d4349; -} - -.fp-btn--secondary:focus-visible { - outline-color: #6c757d; -} - -/* Respect the hidden attribute — never override it */ -[hidden] { - display: none !important; -} - -/* Instructions panel */ -.fp-instructions { - max-width: 520px; - background-color: #ffffff; - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 1.5rem 2rem; - text-align: left; -} - -.fp-instructions h3 { - font-size: 1.25rem; - font-weight: 700; - margin: 0 0 0.75rem; -} - -.fp-instructions p { - margin: 0 0 0.75rem; -} - -.fp-instructions ul { - margin: 0 0 1.25rem 1.25rem; - padding: 0; -} +/* ── Instructions panel ──────────────────────────────────────────────────── */ +/* Layout is provided by .game-welcome in app/style.css. */ +/* Add any game-specific overrides here. */ -.fp-instructions li { - margin-bottom: 0.4rem; -} - -.fp-instructions kbd { - display: inline-block; - padding: 0 0.35em; - font-family: monospace; - font-size: 0.875em; - border: 1px solid #adb5bd; - border-radius: 3px; - background-color: #f1f3f5; -} - -.fp-instructions .fp-btn { - display: block; - width: 100%; - padding: 0.65rem 1.5rem; - font-size: 1.1rem; -} - -/* End-game panel */ -.fp-end-panel { - max-width: 360px; - background-color: #ffffff; - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 1.5rem 2rem; - text-align: center; -} - -.fp-end-panel h3 { - font-size: 1.5rem; - font-weight: 700; - margin: 0 0 1rem; -} - -.fp-end-panel p { - font-size: 1.1rem; - margin: 0 0 0.5rem; -} - -.fp-end-panel__actions { - display: flex; - gap: 0.75rem; - flex-wrap: wrap; - justify-content: center; - margin-top: 1rem; -} - -.fp-end-panel .fp-btn { - padding: 0.65rem 1.5rem; -} +/* ── End-game panel ──────────────────────────────────────────────────────── */ +/* Layout and results are provided by .game-end-panel and .game-results */ +/* in app/style.css — only game-specific overrides belong here. */ diff --git a/app/games/field-of-view/style.css b/app/games/field-of-view/style.css index d1f5335..aa6ed12 100644 --- a/app/games/field-of-view/style.css +++ b/app/games/field-of-view/style.css @@ -1,15 +1,29 @@ +/* ── Field of View ────────────────────────────────────────────────────────── */ +/* + * All selectors are prefixed with .field-of-view or .fov- to avoid collisions. + * Shared panel and button styles (.game-welcome, .game-end-panel, .game-results, + * .game-btn, .game-trend) come from app/style.css — they are NOT duplicated + * here. CSS custom properties are defined in app/styles/variables.css. + */ + .field-of-view { - color: #1f2933; + color: var(--text-base); } +/* ── Panel wrapper ────────────────────────────────────────────────────────── */ +/* .fov-panel is used alongside the shared .game-welcome / .game-end-panel */ +/* classes; it does not override the shared layout rules. */ + .fov-panel { - background: #f2f4f7; - border: 1px solid #c9d1d9; - border-radius: 10px; + background: var(--bg-body); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); padding: 1rem; margin-top: 1rem; } +/* ── Stats bar ───────────────────────────────────────────────────────────── */ + .fov-stats { display: flex; flex-wrap: wrap; @@ -17,18 +31,22 @@ margin-bottom: 1rem; } +/* ── Game board ──────────────────────────────────────────────────────────── */ + .fov-board { width: min(88vw, 520px); aspect-ratio: 1 / 1; display: grid; gap: 0.35rem; background: #aeb4bb; - border: 2px solid #616b75; - border-radius: 8px; + border: 2px solid var(--text-subtle); + border-radius: var(--radius-lg); padding: 0.5rem; margin: 0 auto; } +/* ── Stage (board + flash overlay wrapper) ───────────────────────────────── */ + .fov-stage { position: relative; width: min(88vw, 520px); @@ -40,7 +58,7 @@ position: absolute; inset: 0; pointer-events: none; - border-radius: 8px; + border-radius: var(--radius-lg); opacity: 0; z-index: 3; } @@ -55,6 +73,8 @@ opacity: 1; } +/* ── Play area layout ────────────────────────────────────────────────────── */ + .fov-play-layout { display: flex; align-items: flex-start; @@ -84,11 +104,13 @@ border-color: #dbeafe; } +/* ── Grid cells ──────────────────────────────────────────────────────────── */ + .fov-cell { - border: 1px solid #5f6974; - border-radius: 6px; - background: #e5e7eb; - color: #1f2933; + border: 1px solid var(--text-subtle); + border-radius: var(--radius-md); + background: var(--bg-muted); + color: var(--text-base); display: grid; place-items: center; overflow: hidden; @@ -104,17 +126,17 @@ .fov-cell:focus-visible, .fov-btn:focus-visible, .fov-choice-btn:focus-visible { - outline: 3px solid #1d4ed8; + outline: 3px solid var(--focus-ring); outline-offset: 2px; } .fov-cell--selected { background: #cfe8ff; - border-color: #0f4aa3; + border-color: var(--btn-primary-bg); } .fov-cell--center { - background: #e5e7eb; + background: var(--bg-muted); } .fov-cell--center[disabled] { @@ -130,17 +152,19 @@ color: transparent; } +/* ── Scene mask overlay ──────────────────────────────────────────────────── */ + .fov-mask { position: absolute; inset: 0; height: auto; aspect-ratio: 1 / 1; - border-radius: 8px; - border: 2px solid #111827; + border-radius: var(--radius-lg); + border: 2px solid var(--text-base); background-image: url('images/Field.png'); background-position: center; background-size: cover; - color: #f9fafb; + color: var(--bg-body); text-shadow: 0 1px 2px rgba(0, 0, 0, 0.75); display: grid; place-items: center; @@ -148,6 +172,8 @@ z-index: 1; } +/* ── Response panel ──────────────────────────────────────────────────────── */ + .fov-response { margin-top: 0; width: 270px; @@ -169,12 +195,14 @@ gap: 0.5rem; } +/* ── Image choice buttons ────────────────────────────────────────────────── */ + .fov-choice-btn { width: 120px; height: 120px; - border: 2px solid #5f6974; - border-radius: 8px; - background: #f8fafc; + border: 2px solid var(--text-subtle); + border-radius: var(--radius-lg); + background: var(--bg-body); padding: 0; cursor: pointer; overflow: hidden; @@ -190,11 +218,15 @@ } .fov-choice-btn[aria-pressed='true'] { - border: 4px solid #0f4aa3; - box-shadow: 0 0 0 4px rgba(15, 74, 163, 0.28), 0 10px 18px rgba(15, 74, 163, 0.2); + border: 4px solid var(--btn-primary-bg); + box-shadow: + 0 0 0 4px rgba(15, 74, 163, 0.28), + 0 10px 18px rgba(15, 74, 163, 0.2); transform: translateY(-1px); } +/* ── Location selector ───────────────────────────────────────────────────── */ + .fov-location-header { margin: 0.75rem 0 0.25rem; font-size: 0.9rem; @@ -206,20 +238,20 @@ aspect-ratio: 1 / 1; gap: 0.2rem; background: #aeb4bb; - border: 1px solid #5f6974; - border-radius: 6px; + border: 1px solid var(--text-subtle); + border-radius: var(--radius-md); padding: 0.25rem; } .fov-loc-cell { - border: 1px solid #5f6974; - border-radius: 4px; - background: #e5e7eb; + border: 1px solid var(--text-subtle); + border-radius: var(--radius-sm); + background: var(--bg-muted); cursor: pointer; } .fov-loc-cell--center { - background: #e5e7eb; + background: var(--bg-muted); cursor: default; } @@ -229,75 +261,54 @@ .fov-loc-cell--selected { background: #cfe8ff; - border-color: #0f4aa3; + border-color: var(--btn-primary-bg); } .fov-loc-cell:focus-visible { - outline: 3px solid #1d4ed8; + outline: 3px solid var(--focus-ring); outline-offset: 2px; } +/* ── Controls ────────────────────────────────────────────────────────────── */ + .fov-controls { margin-top: 1rem; display: flex; gap: 0.75rem; } +/* ── Buttons ─────────────────────────────────────────────────────────────── */ +/* Shared button styles come from .game-btn in app/style.css. */ +/* .fov-btn is used for controls inside the active play area. */ + .fov-btn { - background: #dbe2ea; - color: #111827; - border: 1px solid #5f6974; - border-radius: 6px; + background: var(--bg-muted); + color: var(--text-base); + border: 1px solid var(--text-subtle); + border-radius: var(--radius-md); padding: 0.5rem 0.75rem; font-weight: 600; cursor: pointer; } .fov-btn--primary { - background: #0f4aa3; - color: #ffffff; - border-color: #0b3b83; + background: var(--btn-primary-bg); + color: var(--bg-card); + border-color: var(--btn-primary-border); } .fov-btn--secondary { - background: #e5e7eb; + background: var(--bg-muted); } +/* ── Feedback region ─────────────────────────────────────────────────────── */ + .fov-feedback { margin-top: 0.75rem; min-height: 1.25rem; } -.fov-trend { - margin-top: 0.75rem; - border: 1px solid #c1c9d2; - border-radius: 8px; - padding: 0.5rem 0.6rem; - background: #eef2f6; - color: #19324a; -} - -.fov-trend h4 { - margin: 0; - font-size: 0.95rem; -} - -.fov-trend__chart { - display: block; - width: 100%; - max-width: 320px; - height: auto; - min-height: 72px; - margin-top: 0.35rem; - border: 1px solid #9eabb8; - border-radius: 6px; - background: linear-gradient(180deg, #f8fafc 0%, #e5edf5 100%); -} - -.fov-trend__empty, -.fov-trend__meta { - margin: 0.5rem 0 0; -} +/* ── Responsive ──────────────────────────────────────────────────────────── */ @media (max-width: 900px) { .fov-play-layout { diff --git a/app/games/high-speed-memory/style.css b/app/games/high-speed-memory/style.css index 9e7bb69..f95e178 100644 --- a/app/games/high-speed-memory/style.css +++ b/app/games/high-speed-memory/style.css @@ -1,3 +1,11 @@ +/* ── High Speed Memory ────────────────────────────────────────────────────── */ +/* + * All selectors are prefixed with .high-speed-memory or .hsm- to avoid + * collisions. Shared panel and button styles (.game-welcome, .game-end-panel, + * .game-results, .game-btn) come from app/style.css — they are NOT duplicated + * here. CSS custom properties are defined in app/styles/variables.css. + */ + /* ── Section wrapper ─────────────────────────────────────────── */ .high-speed-memory { display: flex; @@ -5,8 +13,8 @@ align-items: center; gap: 0.75rem; padding: 0.75rem 1rem; - background-color: #f8f9fa; - color: #212529; /* ~14.5:1 contrast on #f8f9fa */ + background-color: var(--bg-body); + color: var(--text-base); /* Fill the full height of the game container */ min-height: calc(100vh - 160px); box-sizing: border-box; @@ -19,43 +27,7 @@ } /* ── Instructions panel ──────────────────────────────────────── */ -.hsm-instructions { - max-width: 540px; - background-color: #ffffff; - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 1.5rem 2rem; - text-align: left; -} - -.hsm-instructions h3 { - font-size: 1.25rem; - font-weight: 700; - margin: 0 0 0.75rem; -} - -.hsm-instructions p { - margin: 0 0 0.75rem; -} - -.hsm-instructions ul { - margin: 0 0 1.25rem 1.25rem; - padding: 0; -} - -.hsm-instructions li { - margin-bottom: 0.4rem; -} - -.hsm-instructions kbd { - display: inline-block; - padding: 0 0.35em; - font-family: monospace; - font-size: 0.875em; - border: 1px solid #adb5bd; - border-radius: 3px; - background-color: #f1f3f5; -} +/* Layout is provided by .game-welcome in app/style.css. */ .hsm-instructions__primary-label { margin: 0.75rem 0 0.25rem; @@ -67,18 +39,11 @@ width: 120px; height: 120px; object-fit: cover; - border-radius: 6px; - border: 3px solid #28a745; + border-radius: var(--radius-md); + border: 3px solid var(--color-success); margin: 0 auto 1rem; } -.hsm-instructions .hsm-btn { - display: block; - width: 100%; - padding: 0.65rem 1.5rem; - font-size: 1.1rem; -} - /* ── Game area ───────────────────────────────────────────────── */ #hsm-game-area { display: flex; @@ -123,11 +88,11 @@ .hsm-card { position: relative; border: none; - border-radius: 6px; + border-radius: var(--radius-md); cursor: pointer; overflow: hidden; background-color: #1a1a2e; - transition: transform 0.1s ease, background-color 0.15s ease; + transition: transform 0.1s ease, background-color var(--transition-fast); display: flex; align-items: center; justify-content: center; @@ -146,14 +111,14 @@ /* Face-up revealed state */ .hsm-card--revealed { - background-color: #ffffff; + background-color: var(--bg-card); } /* Matched pair: green background */ .hsm-card--matched { background-color: #d4edda; cursor: default; - outline: 2px solid #28a745; + outline: 2px solid var(--color-success); } .hsm-card--matched .hsm-card__img { @@ -163,13 +128,13 @@ /* Wrong guess: red tint (no animation per game spec) */ .hsm-card--wrong { background-color: #f8d7da; - outline: 2px solid #dc3545; + outline: 2px solid var(--color-danger-muted); } /* Empty placeholder cell (fills unused grid slot) */ .hsm-card--empty { background-color: transparent; - border: 1px dashed #dee2e6; + border: 1px dashed var(--border-color); cursor: default; pointer-events: none; } @@ -179,7 +144,7 @@ } .hsm-card:focus-visible { - outline: 3px solid #005fcc; + outline: 3px solid var(--focus-ring); outline-offset: 3px; } @@ -191,84 +156,6 @@ justify-content: center; } -/* ── Buttons ─────────────────────────────────────────────────── */ -.hsm-btn { - padding: 0.5rem 1.5rem; - font-size: 1rem; - font-weight: 600; - border: none; - border-radius: 4px; - cursor: pointer; - background-color: #005fcc; - color: #ffffff; /* 7.3:1 contrast on #005fcc */ - transition: background-color 0.15s ease; -} - -.hsm-btn:hover { - background-color: #004aa3; -} - -.hsm-btn:active { - background-color: #003d88; -} - -.hsm-btn:focus-visible { - outline: 3px solid #005fcc; - outline-offset: 3px; - background-color: #004aa3; -} - -.hsm-btn--secondary { - background-color: #6c757d; /* 4.6:1 on #ffffff */ - color: #ffffff; -} - -.hsm-btn--secondary:hover { - background-color: #545b62; -} - -.hsm-btn--secondary:active { - background-color: #3d4349; -} - -.hsm-btn--secondary:focus-visible { - outline-color: #6c757d; -} - /* ── End-game panel ──────────────────────────────────────────── */ -.hsm-end-panel { - max-width: 360px; - background-color: #ffffff; - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 1.5rem 2rem; - text-align: center; -} - -.hsm-end-panel h3 { - font-size: 1.5rem; - font-weight: 700; - margin: 0 0 1rem; -} - -.hsm-end-panel p { - font-size: 1.1rem; - margin: 0 0 0.5rem; -} - -.hsm-end-panel__actions { - display: flex; - gap: 0.75rem; - flex-wrap: wrap; - justify-content: center; - margin-top: 1rem; -} - -.hsm-end-panel .hsm-btn { - padding: 0.65rem 1.5rem; -} - -/* ── Respect hidden attribute ────────────────────────────────── */ -[hidden] { - display: none !important; -} +/* Layout and results are provided by .game-end-panel and .game-results */ +/* in app/style.css — only game-specific overrides belong here. */ diff --git a/app/games/object-track/style.css b/app/games/object-track/style.css index 0de1d47..e167dd2 100644 --- a/app/games/object-track/style.css +++ b/app/games/object-track/style.css @@ -4,6 +4,7 @@ * All selectors are prefixed with .mot- to prevent collisions with global styles. * Shared panel classes (.game-welcome, .game-end-panel, etc.) are defined in * app/style.css and are not duplicated here. + * CSS custom properties are defined in app/styles/variables.css. */ /* ── Layout ──────────────────────────────────────────────────────────────────── */ @@ -17,14 +18,14 @@ margin: 0 auto; padding: 1rem; box-sizing: border-box; - background-color: #f8f9fa; - color: #212529; /* ~14.5:1 contrast on #f8f9fa */ + background-color: var(--bg-body); + color: var(--text-base); } .mot-game__title { font-size: 1.75rem; font-weight: 700; - color: #212529; + color: var(--text-base); margin-bottom: 1rem; text-align: center; } @@ -48,7 +49,7 @@ gap: 1.25rem; font-size: 1rem; font-weight: 500; - color: #212529; + color: var(--text-base); width: 100%; } @@ -57,7 +58,7 @@ .mot-game__phase-label { font-size: 1.1rem; font-weight: 600; - color: #004ea8; /* 7.1:1 contrast on #f8f9fa — WCAG AA large + normal */ + color: var(--btn-primary-bg); /* 7.1:1 contrast on var(--bg-body) — WCAG AA */ text-align: center; min-height: 1.5em; margin: 0.25rem 0; @@ -73,8 +74,8 @@ background-size: cover; background-position: center; overflow: hidden; - border-radius: 8px; - border: 2px solid #adb5bd; + border-radius: var(--radius-lg); + border: 2px solid var(--border-color); } /* ── Controls ────────────────────────────────────────────────────────────────── */ diff --git a/app/games/orbit-sprite-memory/style.css b/app/games/orbit-sprite-memory/style.css index c350f35..134d449 100644 --- a/app/games/orbit-sprite-memory/style.css +++ b/app/games/orbit-sprite-memory/style.css @@ -1,20 +1,36 @@ +/* ── Orbit Sprite Memory ─────────────────────────────────────────────────── */ +/* + * All selectors are prefixed with .orbit-memory or .osm- to avoid collisions. + * Shared panel and button styles (.game-welcome, .game-end-panel, .game-results, + * .game-btn) come from app/style.css — they are NOT duplicated here. + * CSS custom properties are defined in app/styles/variables.css. + * + * Local game-specific palette: + * --osm-accent primary interactive colour + * --osm-accent-dark hover / active shade of --osm-accent + * --osm-border board / choice-button border + */ + .orbit-memory { - --osm-text: #1e2a33; - --osm-accent: #2b6cb0; + --osm-accent: #2b6cb0; --osm-accent-dark: #1a4f8a; - --osm-border: #ccd8df; - --osm-board: #ffffff; - --osm-focus: #005fcc; - color: var(--osm-text); - background: #f8f9fa; + --osm-border: #ccd8df; + color: var(--text-base); + background: var(--bg-body); border-radius: 18px; padding: 1rem; } +/* ── Panel wrapper ────────────────────────────────────────────────────────── */ +/* .osm-panel is used alongside .game-welcome / .game-end-panel; it */ +/* does not override the shared layout rules. */ + .osm-panel { /* Styling delegated to shared .game-welcome / .game-end-panel classes */ } +/* ── Stats bar ────────────────────────────────────────────────────────────── */ + .osm-stats { display: flex; flex-wrap: wrap; @@ -22,6 +38,8 @@ margin: 0.75rem 0; } +/* ── Target sprite display ───────────────────────────────────────────────── */ + .osm-target-wrap { display: flex; align-items: center; @@ -29,6 +47,8 @@ margin-bottom: 0.75rem; } +/* ── Game board ──────────────────────────────────────────────────────────── */ + .osm-board { position: relative; width: min(94vw, 600px); @@ -36,9 +56,11 @@ margin: 0 auto; border: 2px solid var(--osm-border); border-radius: 50%; - background: #ffffff; + background: var(--bg-card); } +/* ── Sprites ─────────────────────────────────────────────────────────────── */ + .osm-sprite { width: 96px; height: 96px; @@ -57,14 +79,16 @@ transform: translate(-50%, -50%); } +/* ── Choice buttons ──────────────────────────────────────────────────────── */ + .osm-choice-btn { position: absolute; transform: translate(-50%, -50%); width: 40px; height: 40px; border-radius: 50%; - border: 2px solid #4d6b7a; - background: #ffffff; + border: 2px solid var(--osm-border); + background: var(--bg-card); cursor: pointer; } @@ -74,7 +98,7 @@ .osm-choice-btn:focus-visible, .osm-btn:focus-visible { - outline: 3px solid var(--osm-focus); + outline: 3px solid var(--focus-ring); outline-offset: 2px; } @@ -83,6 +107,10 @@ border-color: var(--osm-accent-dark); } +/* ── Controls ────────────────────────────────────────────────────────────── */ +/* Shared .game-btn classes handle start/stop/play-again buttons; */ +/* .osm-controls is only a flex layout wrapper. */ + .osm-controls, .osm-end-actions { display: flex; @@ -91,28 +119,7 @@ margin-top: 1rem; } -.osm-btn { - border: 1px solid transparent; - border-radius: 999px; - padding: 0.55rem 1rem; - font-weight: 600; - cursor: pointer; -} - -.osm-btn--primary { - background: var(--osm-accent); - color: #fff; -} - -.osm-btn--primary:hover { - background: var(--osm-accent-dark); -} - -.osm-btn--secondary { - background: #eff4f7; - border-color: var(--osm-border); - color: var(--osm-text); -} +/* ── Board state feedback ────────────────────────────────────────────────── */ .osm-board--success { box-shadow: 0 0 0 6px rgba(43, 166, 91, 0.55); @@ -122,6 +129,8 @@ box-shadow: 0 0 0 6px rgba(198, 58, 58, 0.55); } +/* ── Responsive ──────────────────────────────────────────────────────────── */ + @media (max-width: 520px) { .osm-sprite { width: 78px; diff --git a/app/games/otter-stop/style.css b/app/games/otter-stop/style.css index 721f742..85d70f5 100644 --- a/app/games/otter-stop/style.css +++ b/app/games/otter-stop/style.css @@ -1,10 +1,10 @@ /* ── Otter Stop! ──────────────────────────────────────────────────────────── */ -/* All selectors are prefixed with .otter-stop to avoid collisions. */ - -/* Respect the hidden attribute — never override it with display rules */ -[hidden] { - display: none !important; /* stylelint-disable-line declaration-no-important */ -} +/* + * All selectors are prefixed with .otter-stop or .os- to avoid collisions. + * Shared panel and button styles (.game-welcome, .game-end-panel, .game-results, + * .game-btn) come from app/style.css — they are NOT duplicated here. + * CSS custom properties are defined in app/styles/variables.css. + */ .otter-stop { display: flex; @@ -20,38 +20,12 @@ } /* ── Instructions ────────────────────────────────────────────────────────── */ +/* Layout is provided by .game-welcome in app/style.css. */ .os-instructions { display: flex; flex-direction: column; gap: 0.75rem; - max-width: 560px; - width: 100%; -} - -.os-instructions__heading { - margin: 0; - font-size: 2rem; - color: #1a1a1a; -} - -.os-instructions__subheading { - margin: 0; - font-size: 1.25rem; - color: #222; -} - -.os-instructions__intro { - margin: 0; - font-size: 1.1rem; - color: #333; -} - -.os-instructions__list { - margin: 0; - padding-left: 1.5rem; - color: #333; - line-height: 1.7; } /* ── Stats bar ───────────────────────────────────────────────────────────── */ @@ -61,7 +35,7 @@ flex-wrap: wrap; gap: 1rem; font-size: 1rem; - color: #222; + color: var(--text-base); } .os-stat { @@ -87,8 +61,8 @@ display: flex; align-items: center; justify-content: center; - background: #f0f4f8; - border: 3px solid #c0c8d4; + background: var(--bg-subtle); + border: 3px solid var(--border-strong); border-radius: 12px; cursor: pointer; user-select: none; @@ -109,7 +83,7 @@ /* Keyboard-focus ring on the stimulus area */ .os-stimulus:focus-visible { - outline: 3px solid #005fcc; + outline: 3px solid var(--focus-ring); outline-offset: 3px; } @@ -139,7 +113,7 @@ margin: 0; font-size: 1.1rem; font-weight: 700; - color: #1a1a1a; + color: var(--text-heading); } .os-feedback__text--correct { @@ -155,74 +129,21 @@ .os-reminder { margin: 0; font-size: 0.95rem; - color: #555; + color: var(--text-muted); text-align: center; } .os-reminder kbd { display: inline-block; padding: 0.1em 0.4em; - background: #e8e8e8; - border: 1px solid #bbb; + background: var(--bg-muted); + border: 1px solid var(--border-color); border-radius: 3px; font-family: monospace; font-size: 0.9em; line-height: 1.4; } -/* ── End panel ───────────────────────────────────────────────────────────── */ - -.os-end-panel { - display: flex; - flex-direction: column; - align-items: center; - gap: 1.25rem; - width: 100%; - max-width: 420px; -} - -.os-end-panel__heading { - margin: 0; - font-size: 1.75rem; - color: #1a1a1a; -} - -.os-results { - width: 100%; - margin: 0; - border: 1px solid #c0c8d4; - border-radius: 8px; - overflow: hidden; -} - -.os-results__row { - display: flex; - justify-content: space-between; - padding: 0.5rem 1rem; - border-bottom: 1px solid #e0e0e0; -} - -.os-results__row:last-child { - border-bottom: none; -} - -.os-results__label { - font-weight: 600; - color: #333; -} - -.os-results__value { - color: #111; - font-variant-numeric: tabular-nums; -} - -.os-end-panel__actions { - display: flex; - gap: 0.75rem; - flex-wrap: wrap; - justify-content: center; -} - /* ── Controls ────────────────────────────────────────────────────────────── */ .os-controls { @@ -232,39 +153,13 @@ justify-content: center; } -/* ── Buttons — WCAG 2.2 AA contrast ─────────────────────────────────────── */ - -.os-btn { - padding: 0.55rem 1.4rem; - font-size: 1rem; - font-weight: 600; - border: 2px solid transparent; - border-radius: 6px; - cursor: pointer; - transition: background 0.15s, border-color 0.15s; -} - -.os-btn:focus-visible { - outline: 3px solid #005fcc; - outline-offset: 3px; -} - -.os-btn--primary { - background: #005fcc; - color: #fff; - border-color: #004fa3; -} - -.os-btn--primary:hover { - background: #004fa3; -} - -.os-btn--secondary { - background: #c0392b; - color: #fff; - border-color: #922b21; -} +/* ── End panel ───────────────────────────────────────────────────────────── */ +/* Layout and results are provided by .game-end-panel and .game-results */ +/* in app/style.css — only game-specific overrides belong here. */ -.os-btn--secondary:hover { - background: #922b21; +.os-end-panel__heading { + margin: 0; + font-size: 1.75rem; + font-weight: 700; + color: var(--text-heading); } diff --git a/app/games/sound-sweep/style.css b/app/games/sound-sweep/style.css index 6ff4054..c1087fd 100644 --- a/app/games/sound-sweep/style.css +++ b/app/games/sound-sweep/style.css @@ -1,13 +1,23 @@ +/* ── Sound Sweep ──────────────────────────────────────────────────────────── */ +/* + * All selectors are prefixed with .sound-sweep or .ss- to avoid collisions. + * Shared panel and button styles (.game-welcome, .game-end-panel, .game-results, + * .game-btn) come from app/style.css — they are NOT duplicated here. + * CSS custom properties are defined in app/styles/variables.css. + */ + .sound-sweep { - color: #1f2933; + color: var(--text-base); } -/* ── Panels ──────────────────────────────────────────────────────────────────── */ +/* ── Panel wrapper ────────────────────────────────────────────────────────── */ +/* .ss-panel is used alongside .game-welcome / .game-end-panel; it */ +/* does not override the shared layout rules. */ .ss-panel { - background: #f2f4f7; - border: 1px solid #c9d1d9; - border-radius: 10px; + background: var(--bg-body); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); padding: 1rem; margin-top: 1rem; } @@ -67,21 +77,21 @@ justify-content: center; gap: 0.15rem; padding: 0.6rem 0.5rem; - border: 2px solid #5f6974; - border-radius: 8px; - background: #e5e7eb; - color: #1f2933; + border: 2px solid var(--text-subtle); + border-radius: var(--radius-lg); + background: var(--bg-muted); + color: var(--text-base); cursor: pointer; font-family: inherit; - transition: background 0.1s, transform 0.1s; + transition: background var(--transition-quick), transform var(--transition-quick); } .ss-seq-btn:hover { - background: #d1d5db; + background: var(--border-color); } .ss-seq-btn:focus-visible { - outline: 3px solid #1d4ed8; + outline: 3px solid var(--focus-ring); outline-offset: 2px; } @@ -108,9 +118,9 @@ .ss-seq-btn__key { font-size: 0.7rem; - color: #6b7280; - background: #f3f4f6; - border: 1px solid #d1d5db; + color: var(--text-subtle); + background: var(--bg-subtle); + border: 1px solid var(--border-color); border-radius: 3px; padding: 0 0.25rem; font-family: monospace; @@ -135,12 +145,14 @@ } /* ── Buttons ─────────────────────────────────────────────────────────────────── */ +/* Shared button styles come from .game-btn in app/style.css. */ +/* .ss-btn is used for controls inside the active play area. */ .ss-btn { - background: #dbe2ea; - color: #111827; - border: 1px solid #5f6974; - border-radius: 6px; + background: var(--bg-muted); + color: var(--text-base); + border: 1px solid var(--text-subtle); + border-radius: var(--radius-md); padding: 0.5rem 0.75rem; font-weight: 600; cursor: pointer; @@ -148,24 +160,24 @@ } .ss-btn--primary { - background: #0f4aa3; - color: #ffffff; - border-color: #0b3b83; + background: var(--btn-primary-bg); + color: var(--bg-card); + border-color: var(--btn-primary-border); } .ss-btn--secondary { - background: #e5e7eb; + background: var(--bg-muted); } .ss-btn--icon { - background: #f3f4f6; - border-color: #d1d5db; + background: var(--bg-subtle); + border-color: var(--border-color); font-size: 0.875rem; padding: 0.35rem 0.6rem; } .ss-btn:focus-visible { - outline: 3px solid #1d4ed8; + outline: 3px solid var(--focus-ring); outline-offset: 2px; } diff --git a/app/style.css b/app/style.css index 449e3a3..60cfa53 100644 --- a/app/style.css +++ b/app/style.css @@ -1,662 +1,22 @@ -/* ── Reset / Base ───────────────────────────────────────────── */ - -/* Chart color palette — 6 accessible colors + total-bar grey */ -:root { - --chart-color-0: #005fcc; - --chart-color-1: #c9510c; - --chart-color-2: #238636; - --chart-color-3: #8250df; - --chart-color-4: #d1242f; - --chart-color-5: #0969da; - --chart-color-total: #adb5bd; -} - -/* Ensure the HTML hidden attribute is always respected, even when a class - rule sets an explicit display value. */ -[hidden] { - display: none !important; -} - -*, -*::before, -*::after { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - font-family: system-ui, -apple-system, sans-serif; - font-size: 1rem; - line-height: 1.5; - background-color: #f8f9fa; - color: #212529; /* contrast ratio ~14.5:1 on #f8f9fa */ -} - -/* ── Skip Navigation ────────────────────────────────────────── */ -.skip-link { - position: absolute; - top: -3rem; - left: 0; - z-index: 9999; - padding: 0.5rem 1rem; - background: #212529; - color: #f8f9fa; - text-decoration: none; - font-weight: 600; - border-radius: 0 0 4px 0; - transition: top 0.1s ease; -} - -.skip-link:focus { - top: 0; -} - -/* ── Screen-reader only utility ─────────────────────────────── */ -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -/* ── Focus Styles ───────────────────────────────────────────── */ -*:focus-visible { - outline: 3px solid #005fcc; - outline-offset: 2px; -} - -/* ── Layout ─────────────────────────────────────────────────── */ -header { - padding: 1rem 1.5rem; - background-color: #212529; - color: #f8f9fa; -} - -header h1 { - font-size: 1.5rem; - font-weight: 700; -} - -nav[aria-label='Game selection'] { - padding: 0.5rem 1.5rem; - background-color: #343a40; -} - -main { - padding: 1.5rem; -} - -footer { - padding: 1rem 1.5rem; - text-align: center; - font-size: 0.875rem; - color: #495057; /* contrast ratio ~7.3:1 on #f8f9fa */ - border-top: 1px solid #dee2e6; -} - -/* ── Game Selector Grid ─────────────────────────────────────── */ -#game-selector { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); - gap: 1.5rem; -} - -/* ── Game Card ──────────────────────────────────────────────── */ -.game-card { - display: flex; - flex-direction: column; - background: #ffffff; - border: 1px solid #dee2e6; - border-radius: 8px; - overflow: hidden; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); - transition: box-shadow 0.15s ease; -} - -.game-card:hover { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); -} - -.game-card img { - width: 100%; - aspect-ratio: 1 / 1; - object-fit: contain; - display: block; -} - -.game-card h2 { - font-size: 1.125rem; - font-weight: 600; - margin: 0.75rem 1rem 0.25rem; - color: #212529; /* contrast ratio ~16:1 on #ffffff */ -} - -.game-card p { - flex: 1; - margin: 0 1rem 0.75rem; - font-size: 0.9rem; - color: #495057; /* contrast ratio ~7.3:1 on #ffffff */ -} - -.game-card button { - margin: 0 1rem 1rem; - padding: 0.5rem 1rem; - background-color: #005fcc; - color: #ffffff; /* contrast ratio ~5.6:1 on #005fcc */ - border: none; - border-radius: 4px; - font-size: 1rem; - font-weight: 500; - cursor: pointer; - text-align: center; - transition: background-color 0.15s ease; -} - -.game-card button:hover { - background-color: #004aa3; -} - -.game-card button:active { - background-color: #003d8a; -} - -.game-card button:focus-visible { - outline: 3px solid #005fcc; - outline-offset: 2px; -} - -.game-card .game-high-score { - margin: 0 1rem 0.5rem; - font-size: 0.8rem; - color: #495057; -} - -/* ── Play-time summary bar ───────────────────────────────────────────────── */ -.play-time-bar { - display: flex; - align-items: center; - gap: 1rem; - padding: 0.5rem 1.5rem; - background-color: #e9ecef; - border-top: 1px solid #dee2e6; - font-size: 0.9rem; - color: #495057; -} - -.play-time-bar__label { - flex: 1; -} - -.play-time-bar__label strong { - color: #212529; - font-weight: 700; -} - -.play-time-bar__btn { - padding: 0.375rem 0.875rem; - background-color: #343a40; - color: #f8f9fa; - border: none; - border-radius: 4px; - font-size: 0.875rem; - font-weight: 500; - cursor: pointer; - transition: background-color 0.15s ease; -} - -.play-time-bar__btn:hover { - background-color: #212529; -} - -/* ── History panel (modal overlay) ──────────────────────────────────────── */ -.history-panel { - position: fixed; - inset: 0; - background-color: rgba(0, 0, 0, 0.55); - z-index: 1000; - display: none; - align-items: flex-start; - justify-content: center; - padding: 2rem 1rem; - overflow-y: auto; -} - -/* Show when the hidden attribute is absent (set by JS). */ -.history-panel:not([hidden]) { - display: flex; -} - -.history-panel__inner { - background: #ffffff; - border-radius: 8px; - width: 100%; - max-width: 900px; - max-height: 90vh; - overflow-y: auto; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.24); - display: flex; - flex-direction: column; -} - -.history-panel__header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 1rem 1.5rem; - border-bottom: 1px solid #dee2e6; - position: sticky; - top: 0; - background: #ffffff; -} - -.history-panel__header-actions { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.history-panel__title { - font-size: 1.25rem; - font-weight: 700; - margin: 0; - color: #212529; -} - -.history-panel__clear-btn { - background: none; - border: 1px solid #dc3545; - border-radius: 4px; - font-size: 0.875rem; - cursor: pointer; - padding: 0.25rem 0.75rem; - line-height: 1.5; - color: #dc3545; - transition: background-color 0.1s ease, color 0.1s ease; -} - -.history-panel__clear-btn:hover { - background-color: #dc3545; - color: #ffffff; -} - -.history-panel__close { - background: none; - border: 1px solid #dee2e6; - border-radius: 4px; - font-size: 1.125rem; - cursor: pointer; - padding: 0.25rem 0.5rem; - line-height: 1; - color: #495057; - transition: background-color 0.1s ease; -} - -.history-panel__close:hover { - background-color: #f1f3f5; -} - -.history-panel__confirm { - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - padding: 0.75rem 1.5rem; - background: #fff3cd; - border-bottom: 1px solid #ffc107; -} - -.history-panel__confirm-text { - font-size: 0.9rem; - color: #664d03; -} - -.history-panel__confirm-actions { - display: flex; - gap: 0.5rem; - flex-shrink: 0; -} - -.history-panel__confirm-cancel { - background: none; - border: 1px solid #6c757d; - border-radius: 4px; - font-size: 0.875rem; - cursor: pointer; - padding: 0.25rem 0.75rem; - color: #495057; - transition: background-color 0.1s ease; -} - -.history-panel__confirm-cancel:hover { - background-color: #e9ecef; -} - -.history-panel__confirm-ok { - background: #dc3545; - border: 1px solid #dc3545; - border-radius: 4px; - font-size: 0.875rem; - cursor: pointer; - padding: 0.25rem 0.75rem; - color: #ffffff; - transition: background-color 0.1s ease; -} - -.history-panel__confirm-ok:hover { - background-color: #bb2d3b; - border-color: #bb2d3b; -} - -.history-panel__body { - padding: 1.5rem; - flex: 1; -} - -.history-panel__empty { - color: #6c757d; - font-style: italic; - text-align: center; - padding: 2rem 0; -} - -/* ── History chart ───────────────────────────────────────────────────────── */ -.history-chart { - overflow-x: auto; - margin-bottom: 1.5rem; -} - -.history-chart__bars { - display: flex; - gap: 0.5rem; - align-items: flex-end; - height: 160px; - border-bottom: 2px solid #dee2e6; - padding-bottom: 0; - min-width: fit-content; -} - -.history-chart__group { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.25rem; - min-width: 60px; -} - -.history-chart__bar { - width: 16px; - min-height: 2px; - border-radius: 2px 2px 0 0; - transition: height 0.2s ease; -} - -.history-chart__bar--total { - background-color: var(--chart-color-total); - width: 10px; -} - -/* Per-slot bar colors (indices cycle for > 6 games). */ -.history-chart__bar--color-0 { background-color: var(--chart-color-0); } -.history-chart__bar--color-1 { background-color: var(--chart-color-1); } -.history-chart__bar--color-2 { background-color: var(--chart-color-2); } -.history-chart__bar--color-3 { background-color: var(--chart-color-3); } -.history-chart__bar--color-4 { background-color: var(--chart-color-4); } -.history-chart__bar--color-5 { background-color: var(--chart-color-5); } - -/* Legend swatch colors matching the bars above. */ -.history-chart__legend-swatch--color-0 { background-color: var(--chart-color-0); } -.history-chart__legend-swatch--color-1 { background-color: var(--chart-color-1); } -.history-chart__legend-swatch--color-2 { background-color: var(--chart-color-2); } -.history-chart__legend-swatch--color-3 { background-color: var(--chart-color-3); } -.history-chart__legend-swatch--color-4 { background-color: var(--chart-color-4); } -.history-chart__legend-swatch--color-5 { background-color: var(--chart-color-5); } - -.history-chart__label { - font-size: 0.75rem; - color: #6c757d; - white-space: nowrap; -} - -.history-chart__legend { - display: flex; - flex-wrap: wrap; - gap: 0.75rem; - margin-top: 0.75rem; - font-size: 0.8rem; -} - -.history-chart__legend-item { - display: flex; - align-items: center; - gap: 0.35rem; - color: #495057; -} - -.history-chart__legend-swatch { - display: inline-block; - width: 12px; - height: 12px; - border-radius: 2px; - flex-shrink: 0; -} - -.history-chart__legend-swatch--total { - background-color: var(--chart-color-total); -} - -/* ── History table ───────────────────────────────────────────────────────── */ -.history-table { - width: 100%; - border-collapse: collapse; - font-size: 0.875rem; -} - -.history-table th, -.history-table td { - padding: 0.5rem 0.75rem; - text-align: left; - border-bottom: 1px solid #dee2e6; -} - -.history-table th { - background-color: #f8f9fa; - font-weight: 600; - color: #212529; -} - -.history-table tbody tr:hover { - background-color: #f8f9fa; -} - -/* ── Shared Game Welcome Panel ──────────────────────────────────────────── */ -/* - * .game-welcome — shared welcome/instructions panel used across all games. - * Apply alongside any game-specific class to get the standard card layout. +/* ── Brain Speed Exercises — Main Stylesheet ───────────────────────────────── + * + * This file is the single entry point for app-level styles. + * All rules are split into focused sub-files under app/styles/ and imported + * in dependency order. Edit the appropriate sub-file rather than this file. + * + * Load order: + * 1. variables — CSS custom properties (design tokens); must be first + * 2. base — reset, body defaults, utilities, focus ring + * 3. layout — app shell (header, nav, main, footer, game-selector grid) + * 4. game-card — game tile component + play-time summary bar + * 5. history — history modal, bar chart, and summary table + * 6. game-shared — shared in-game UI (welcome panel, end panel, buttons, trend chart) */ -.game-welcome { - max-width: 560px; - width: 100%; - margin: 0 auto; - background: #ffffff; - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 1.5rem 2rem; - box-sizing: border-box; -} - -.game-welcome h3 { - font-size: 1.25rem; - font-weight: 700; - margin: 0 0 0.75rem; -} - -.game-welcome p { - margin: 0 0 0.75rem; -} - -.game-welcome ul, -.game-welcome ol { - margin: 0 0 1.25rem 1.25rem; - padding: 0; -} - -.game-welcome li { - line-height: 1.7; - margin-bottom: 0.25rem; -} - -.game-welcome kbd { - display: inline-block; - padding: 0 0.35em; - font-family: monospace; - font-size: 0.875em; - border: 1px solid #adb5bd; - border-radius: 3px; - background-color: #f1f3f5; -} - -/* ── Shared Game End Panel ──────────────────────────────────────────────── */ -/* - * .game-end-panel — shared end-of-game results panel used across all games. - * Apply alongside any game-specific class to get the standard results layout. - */ -.game-end-panel { - display: flex; - flex-direction: column; - align-items: center; - gap: 1.25rem; - width: 100%; - max-width: 420px; - margin: 0 auto; -} - -.game-end-panel h2 { - margin: 0; - font-size: 1.75rem; - font-weight: 700; - color: #1a1a1a; -} - -/* Results definition list */ -.game-results { - width: 100%; - margin: 0; - border: 1px solid #c0c8d4; - border-radius: 8px; - overflow: hidden; -} - -.game-results__row { - display: flex; - justify-content: space-between; - padding: 0.5rem 1rem; - border-bottom: 1px solid #e0e0e0; -} - -.game-results__row:last-child { - border-bottom: none; -} - -.game-results__label { - font-weight: 600; - color: #333; -} - -.game-results__value { - color: #111; - font-variant-numeric: tabular-nums; -} - -/* End panel action buttons */ -.game-end-panel__actions { - display: flex; - gap: 0.75rem; - flex-wrap: wrap; - justify-content: center; -} - -/* ── Shared Game Buttons ────────────────────────────────────────────────── */ -/* - * .game-btn — shared button style for all game screens (welcome and end panels). - * Use .game-btn--primary for "Start Game" / "Play Again" and - * .game-btn--secondary for "Return to Menu". - */ -.game-btn { - padding: 0.55rem 1.4rem; - font-size: 1rem; - font-weight: 600; - border: 2px solid transparent; - border-radius: 6px; - cursor: pointer; - transition: background 0.15s, border-color 0.15s; -} - -.game-btn:focus-visible { - outline: 3px solid #005fcc; - outline-offset: 3px; -} - -.game-btn--primary { - background: #005fcc; - color: #fff; - border-color: #004fa3; -} - -.game-btn--primary:hover { - background: #004fa3; -} - -.game-btn--secondary { - background: #c0392b; - color: #fff; - border-color: #922b21; -} - -.game-btn--secondary:hover { - background: #922b21; -} - -/* ── Shared Game Trend Chart ────────────────────────────────────────────── */ -/* - * .game-trend — shared speed/difficulty trend chart used across all games. - * Wrap the SVG polyline chart and meta text in an element with this class. - * Used inside the active game area to display real-time metric history. - */ -.game-trend { - margin-top: 0.75rem; - border: 1px solid #c1c9d2; - border-radius: 8px; - padding: 0.5rem 0.6rem; - background: #eef2f6; - color: #19324a; -} - -.game-trend h4 { - margin: 0; - font-size: 0.95rem; -} - -.game-trend__chart { - display: block; - width: 100%; - max-width: 320px; - height: auto; - min-height: 72px; - margin-top: 0.35rem; - border: 1px solid #9eabb8; - border-radius: 6px; - background: linear-gradient(180deg, #f8fafc 0%, #e5edf5 100%); -} -.game-trend__empty, -.game-trend__meta { - margin: 0.5rem 0 0; - font-size: 0.875rem; -} +@import url('./styles/variables.css'); +@import url('./styles/base.css'); +@import url('./styles/layout.css'); +@import url('./styles/game-card.css'); +@import url('./styles/history.css'); +@import url('./styles/game-shared.css'); diff --git a/app/styles/base.css b/app/styles/base.css new file mode 100644 index 0000000..594d818 --- /dev/null +++ b/app/styles/base.css @@ -0,0 +1,68 @@ +/* ── Reset / Base ──────────────────────────────────────────────────────────── + * + * Global reset, body defaults, and utility classes. + * Depends on: variables.css (must be imported first) + */ + +/* Ensure the HTML hidden attribute is always respected, even when a class + rule sets an explicit display value. */ +[hidden] { + display: none !important; +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: system-ui, -apple-system, sans-serif; + font-size: 1rem; + line-height: 1.5; + background-color: var(--bg-body); + color: var(--text-base); +} + +/* ── Skip Navigation ─────────────────────────────────────────────────────── */ + +.skip-link { + position: absolute; + top: -3rem; + left: 0; + z-index: 9999; + padding: 0.5rem 1rem; + background: var(--bg-dark); + color: var(--text-inverted); + text-decoration: none; + font-weight: 600; + border-radius: 0 0 var(--radius-sm) 0; + transition: top var(--transition-fast); +} + +.skip-link:focus { + top: 0; +} + +/* ── Screen-reader-only utility ──────────────────────────────────────────── */ + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* ── Global focus style ──────────────────────────────────────────────────── */ + +*:focus-visible { + outline: 3px solid var(--focus-ring); + outline-offset: 2px; +} diff --git a/app/styles/game-card.css b/app/styles/game-card.css new file mode 100644 index 0000000..dbea124 --- /dev/null +++ b/app/styles/game-card.css @@ -0,0 +1,115 @@ +/* ── Game Card Component ───────────────────────────────────────────────────── + * + * Styles for individual game tiles on the selection screen and the play-time + * summary bar that sits below the game list. + * Depends on: variables.css + */ + +/* ── Game card ───────────────────────────────────────────────────────────── */ + +.game-card { + display: flex; + flex-direction: column; + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); + transition: box-shadow var(--transition-fast); +} + +.game-card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); +} + +.game-card img { + width: 100%; + aspect-ratio: 1 / 1; + object-fit: contain; + display: block; +} + +.game-card h2 { + font-size: 1.125rem; + font-weight: 600; + margin: 0.75rem 1rem 0.25rem; + color: var(--text-base); +} + +.game-card p { + flex: 1; + margin: 0 1rem 0.75rem; + font-size: 0.9rem; + color: var(--text-muted); +} + +.game-card button { + margin: 0 1rem 1rem; + padding: 0.5rem 1rem; + background-color: var(--btn-primary-bg); + color: var(--bg-card); + border: none; + border-radius: var(--radius-sm); + font-size: 1rem; + font-weight: 500; + cursor: pointer; + text-align: center; + transition: background-color var(--transition-fast); +} + +.game-card button:hover { + background-color: var(--btn-primary-hover); +} + +.game-card button:active { + background-color: var(--btn-primary-active); +} + +.game-card button:focus-visible { + outline: 3px solid var(--focus-ring); + outline-offset: 2px; +} + +.game-card .game-high-score { + margin: 0 1rem 0.5rem; + font-size: 0.8rem; + color: var(--text-muted); +} + +/* ── Play-time summary bar ───────────────────────────────────────────────── */ + +.play-time-bar { + display: flex; + align-items: center; + gap: 1rem; + padding: 0.5rem 1.5rem; + background-color: var(--bg-muted); + border-top: 1px solid var(--border-color); + font-size: 0.9rem; + color: var(--text-muted); +} + +.play-time-bar__label { + flex: 1; +} + +.play-time-bar__label strong { + color: var(--text-base); + font-weight: 700; +} + +.play-time-bar__btn { + padding: 0.375rem 0.875rem; + background-color: var(--bg-nav); + color: var(--text-inverted); + border: none; + border-radius: var(--radius-sm); + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: background-color var(--transition-fast); +} + +.play-time-bar__btn:hover { + background-color: var(--bg-dark); +} diff --git a/app/styles/game-shared.css b/app/styles/game-shared.css new file mode 100644 index 0000000..325abf8 --- /dev/null +++ b/app/styles/game-shared.css @@ -0,0 +1,209 @@ +/* ── Shared Game UI Components ─────────────────────────────────────────────── + * + * Reusable UI elements shared across all game plugins: + * • .game-welcome — instructions / welcome panel + * • .game-end-panel — end-of-session results layout + * • .game-results — results definition list + * • .game-btn — shared action button (primary / secondary variants) + * • .game-trend — in-game speed/difficulty trend chart + * + * Every game's interface.html should apply these classes alongside any + * game-specific classes. Do NOT duplicate these styles in game stylesheets. + * Depends on: variables.css + */ + +/* ── Welcome / instructions panel ───────────────────────────────────────── */ +/* + * Apply .game-welcome to the instructions
shown before the game starts. + * The panel always starts with

How to Play

. + */ + +.game-welcome { + max-width: 560px; + width: 100%; + margin: 0 auto; + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: 1.5rem 2rem; + box-sizing: border-box; +} + +.game-welcome h3 { + font-size: 1.25rem; + font-weight: 700; + margin: 0 0 0.75rem; +} + +.game-welcome p { + margin: 0 0 0.75rem; +} + +.game-welcome ul, +.game-welcome ol { + margin: 0 0 1.25rem 1.25rem; + padding: 0; +} + +.game-welcome li { + line-height: 1.7; + margin-bottom: 0.25rem; +} + +.game-welcome kbd { + display: inline-block; + padding: 0 0.35em; + font-family: monospace; + font-size: 0.875em; + border: 1px solid var(--text-subtle); + border-radius: 3px; + background-color: var(--bg-subtle); +} + +/* ── End-of-session panel ────────────────────────────────────────────────── */ +/* + * Apply .game-end-panel to the container shown after the session ends. + * Use

Session Ended

as the heading. + */ + +.game-end-panel { + display: flex; + flex-direction: column; + align-items: center; + gap: 1.25rem; + width: 100%; + max-width: 420px; + margin: 0 auto; +} + +.game-end-panel h2 { + margin: 0; + font-size: 1.75rem; + font-weight: 700; + color: var(--text-heading); +} + +/* ── Results definition list ─────────────────────────────────────────────── */ +/* + * Use a
with .game-results inside .game-end-panel. + * Each row is a
containing one
and one
. + */ + +.game-results { + width: 100%; + margin: 0; + border: 1px solid var(--game-results-border); + border-radius: var(--radius-lg); + overflow: hidden; +} + +.game-results__row { + display: flex; + justify-content: space-between; + padding: 0.5rem 1rem; + border-bottom: 1px solid var(--game-results-row-border); +} + +.game-results__row:last-child { + border-bottom: none; +} + +.game-results__label { + font-weight: 600; + color: var(--game-results-label); +} + +.game-results__value { + color: var(--game-results-value); + font-variant-numeric: tabular-nums; +} + +/* ── End-panel action buttons ────────────────────────────────────────────── */ + +.game-end-panel__actions { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; + justify-content: center; +} + +/* ── Shared game buttons ─────────────────────────────────────────────────── */ +/* + * .game-btn — base style; always pair with a variant modifier. + * .game-btn--primary — "Start Game" / "Play Again" (blue). + * .game-btn--secondary — "Return to Menu" / "End Game" (red). + */ + +.game-btn { + padding: 0.55rem 1.4rem; + font-size: 1rem; + font-weight: 600; + border: 2px solid transparent; + border-radius: var(--radius-md); + cursor: pointer; + transition: background var(--transition-fast), border-color var(--transition-fast); +} + +.game-btn:focus-visible { + outline: 3px solid var(--focus-ring); + outline-offset: 3px; +} + +.game-btn--primary { + background: var(--btn-primary-bg); + color: var(--bg-card); + border-color: var(--btn-primary-border); +} + +.game-btn--primary:hover { + background: var(--btn-primary-hover); +} + +.game-btn--secondary { + background: var(--btn-danger-bg); + color: var(--bg-card); + border-color: var(--btn-danger-border); +} + +.game-btn--secondary:hover { + background: var(--btn-danger-hover); +} + +/* ── In-game speed/difficulty trend chart ────────────────────────────────── */ +/* + * Apply .game-trend to the
that wraps the SVG polyline chart. + * The inner SVG uses .game-trend__chart; status messages use .game-trend__meta / + * .game-trend__empty. + */ + +.game-trend { + margin-top: 0.75rem; + border: 1px solid var(--trend-border); + border-radius: var(--radius-lg); + padding: 0.5rem 0.6rem; + background: var(--trend-bg); + color: var(--trend-text); +} + +.game-trend h4 { + margin: 0; + font-size: 0.95rem; +} + +.game-trend__chart { + display: block; + width: 100%; + max-width: 320px; + height: auto; + min-height: 72px; + margin-top: 0.35rem; + border: 1px solid var(--trend-chart-border); + border-radius: var(--radius-md); + background: linear-gradient(180deg, #f8fafc 0%, #e5edf5 100%); +} + +.game-trend__empty, +.game-trend__meta { + margin: 0.5rem 0 0; + font-size: 0.875rem; +} diff --git a/app/styles/history.css b/app/styles/history.css new file mode 100644 index 0000000..3facf3c --- /dev/null +++ b/app/styles/history.css @@ -0,0 +1,279 @@ +/* ── History Panel ─────────────────────────────────────────────────────────── + * + * Modal overlay that displays session play history, including a bar chart and + * summary table. Also includes the play-time bar confirmation sub-panel. + * Depends on: variables.css + */ + +/* ── Modal overlay ───────────────────────────────────────────────────────── */ + +.history-panel { + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.55); + z-index: 1000; + display: none; + align-items: flex-start; + justify-content: center; + padding: 2rem 1rem; + overflow-y: auto; +} + +/* Show when the hidden attribute is absent (set by JS). */ +.history-panel:not([hidden]) { + display: flex; +} + +.history-panel__inner { + background: var(--bg-card); + border-radius: var(--radius-lg); + width: 100%; + max-width: 900px; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.24); + display: flex; + flex-direction: column; +} + +/* ── Panel header ────────────────────────────────────────────────────────── */ + +.history-panel__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem 1.5rem; + border-bottom: 1px solid var(--border-color); + position: sticky; + top: 0; + background: var(--bg-card); +} + +.history-panel__header-actions { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.history-panel__title { + font-size: 1.25rem; + font-weight: 700; + margin: 0; + color: var(--text-base); +} + +/* ── Clear-history button ────────────────────────────────────────────────── */ + +.history-panel__clear-btn { + background: none; + border: 1px solid var(--color-danger-muted); + border-radius: var(--radius-sm); + font-size: 0.875rem; + cursor: pointer; + padding: 0.25rem 0.75rem; + line-height: 1.5; + color: var(--color-danger-muted); + transition: background-color var(--transition-quick), color var(--transition-quick); +} + +.history-panel__clear-btn:hover { + background-color: var(--color-danger-muted); + color: var(--bg-card); +} + +/* ── Close button ────────────────────────────────────────────────────────── */ + +.history-panel__close { + background: none; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + font-size: 1.125rem; + cursor: pointer; + padding: 0.25rem 0.5rem; + line-height: 1; + color: var(--text-muted); + transition: background-color var(--transition-quick); +} + +.history-panel__close:hover { + background-color: var(--bg-subtle); +} + +/* ── Confirm-clear sub-panel ─────────────────────────────────────────────── */ + +.history-panel__confirm { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.75rem 1.5rem; + background: var(--color-warning-bg); + border-bottom: 1px solid var(--color-warning-border); +} + +.history-panel__confirm-text { + font-size: 0.9rem; + color: var(--color-warning-text); +} + +.history-panel__confirm-actions { + display: flex; + gap: 0.5rem; + flex-shrink: 0; +} + +.history-panel__confirm-cancel { + background: none; + border: 1px solid var(--text-subtle); + border-radius: var(--radius-sm); + font-size: 0.875rem; + cursor: pointer; + padding: 0.25rem 0.75rem; + color: var(--text-muted); + transition: background-color var(--transition-quick); +} + +.history-panel__confirm-cancel:hover { + background-color: var(--bg-muted); +} + +.history-panel__confirm-ok { + background: var(--color-danger-muted); + border: 1px solid var(--color-danger-muted); + border-radius: var(--radius-sm); + font-size: 0.875rem; + cursor: pointer; + padding: 0.25rem 0.75rem; + color: var(--bg-card); + transition: background-color var(--transition-quick); +} + +.history-panel__confirm-ok:hover { + background-color: var(--color-danger-muted-hover); + border-color: var(--color-danger-muted-hover); +} + +/* ── Panel body ──────────────────────────────────────────────────────────── */ + +.history-panel__body { + padding: 1.5rem; + flex: 1; +} + +.history-panel__empty { + color: var(--text-subtle); + font-style: italic; + text-align: center; + padding: 2rem 0; +} + +/* ── History bar chart ───────────────────────────────────────────────────── */ + +.history-chart { + overflow-x: auto; + margin-bottom: 1.5rem; +} + +.history-chart__bars { + display: flex; + gap: 0.5rem; + align-items: flex-end; + height: 160px; + border-bottom: 2px solid var(--border-color); + padding-bottom: 0; + min-width: fit-content; +} + +.history-chart__group { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; + min-width: 60px; +} + +.history-chart__bar { + width: 16px; + min-height: 2px; + border-radius: 2px 2px 0 0; + transition: height 0.2s ease; +} + +.history-chart__bar--total { + background-color: var(--chart-color-total); + width: 10px; +} + +/* Per-slot bar colours (indices cycle for > 6 games). */ +.history-chart__bar--color-0 { background-color: var(--chart-color-0); } +.history-chart__bar--color-1 { background-color: var(--chart-color-1); } +.history-chart__bar--color-2 { background-color: var(--chart-color-2); } +.history-chart__bar--color-3 { background-color: var(--chart-color-3); } +.history-chart__bar--color-4 { background-color: var(--chart-color-4); } +.history-chart__bar--color-5 { background-color: var(--chart-color-5); } + +/* Legend swatch colours matching the bars above. */ +.history-chart__legend-swatch--color-0 { background-color: var(--chart-color-0); } +.history-chart__legend-swatch--color-1 { background-color: var(--chart-color-1); } +.history-chart__legend-swatch--color-2 { background-color: var(--chart-color-2); } +.history-chart__legend-swatch--color-3 { background-color: var(--chart-color-3); } +.history-chart__legend-swatch--color-4 { background-color: var(--chart-color-4); } +.history-chart__legend-swatch--color-5 { background-color: var(--chart-color-5); } + +.history-chart__label { + font-size: 0.75rem; + color: var(--text-subtle); + white-space: nowrap; +} + +.history-chart__legend { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-top: 0.75rem; + font-size: 0.8rem; +} + +.history-chart__legend-item { + display: flex; + align-items: center; + gap: 0.35rem; + color: var(--text-muted); +} + +.history-chart__legend-swatch { + display: inline-block; + width: 12px; + height: 12px; + border-radius: 2px; + flex-shrink: 0; +} + +.history-chart__legend-swatch--total { + background-color: var(--chart-color-total); +} + +/* ── History summary table ───────────────────────────────────────────────── */ + +.history-table { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; +} + +.history-table th, +.history-table td { + padding: 0.5rem 0.75rem; + text-align: left; + border-bottom: 1px solid var(--border-color); +} + +.history-table th { + background-color: var(--bg-body); + font-weight: 600; + color: var(--text-base); +} + +.history-table tbody tr:hover { + background-color: var(--bg-body); +} diff --git a/app/styles/layout.css b/app/styles/layout.css new file mode 100644 index 0000000..8607629 --- /dev/null +++ b/app/styles/layout.css @@ -0,0 +1,50 @@ +/* ── App Shell Layout ──────────────────────────────────────────────────────── + * + * Page-level structure: header, navigation bar, main content area, footer, + * and the game-selection grid. + * Depends on: variables.css + */ + +/* ── App header ──────────────────────────────────────────────────────────── */ + +header { + padding: 1rem 1.5rem; + background-color: var(--bg-dark); + color: var(--text-inverted); +} + +header h1 { + font-size: 1.5rem; + font-weight: 700; +} + +/* ── Navigation bar ──────────────────────────────────────────────────────── */ + +nav[aria-label='Game selection'] { + padding: 0.5rem 1.5rem; + background-color: var(--bg-nav); +} + +/* ── Main content area ───────────────────────────────────────────────────── */ + +main { + padding: 1.5rem; +} + +/* ── Footer ──────────────────────────────────────────────────────────────── */ + +footer { + padding: 1rem 1.5rem; + text-align: center; + font-size: 0.875rem; + color: var(--text-muted); + border-top: 1px solid var(--border-color); +} + +/* ── Game-selector grid ──────────────────────────────────────────────────── */ + +#game-selector { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 1.5rem; +} diff --git a/app/styles/variables.css b/app/styles/variables.css new file mode 100644 index 0000000..fb9e91a --- /dev/null +++ b/app/styles/variables.css @@ -0,0 +1,82 @@ +/* ── CSS Custom Properties (Design Tokens) ─────────────────────────────────── + * + * All theme variables are defined here. Game-specific stylesheets should + * reference these variables rather than hard-coding colour values, so the + * entire application can be re-themed by editing a single file. + * + * Naming convention: ---[-] + */ + +:root { + /* ── Background palette ─────────────────────────────────────────────────── */ + --bg-body: #f8f9fa; /* App body and default panel background */ + --bg-card: #ffffff; /* Card / modal inner background */ + --bg-dark: #212529; /* App header bar */ + --bg-nav: #343a40; /* Navigation bar */ + --bg-muted: #e9ecef; /* Subtle section background (play-time bar, etc.) */ + --bg-subtle: #f1f3f5; /* Very subtle background (kbd, hover rows, etc.) */ + + /* ── Text palette ───────────────────────────────────────────────────────── */ + --text-base: #212529; /* Primary body text (~14.5:1 on --bg-body) */ + --text-muted: #495057; /* Secondary text (~7.3:1 on --bg-body) */ + --text-subtle: #6c757d; /* Tertiary / hint text */ + --text-inverted: #f8f9fa; /* Text on dark backgrounds (header, nav) */ + --text-heading: #1a1a1a; /* Game panel headings */ + + /* ── Border palette ─────────────────────────────────────────────────────── */ + --border-color: #dee2e6; /* Standard component border */ + --border-strong: #c0c8d4; /* Slightly darker border (results table) */ + --border-subtle: #e0e0e0; /* Light divider lines inside tables */ + + /* ── Focus / interactive ────────────────────────────────────────────────── */ + --focus-ring: #005fcc; /* Keyboard focus outline colour (WCAG AA) */ + + /* ── Primary action — blue ──────────────────────────────────────────────── */ + --btn-primary-bg: #005fcc; + --btn-primary-border: #004fa3; + --btn-primary-hover: #004fa3; + --btn-primary-active: #003d8a; + + /* ── Danger / secondary game action — red ───────────────────────────────── */ + --btn-danger-bg: #c0392b; + --btn-danger-border: #922b21; + --btn-danger-hover: #922b21; + + /* ── Status / alert colours ─────────────────────────────────────────────── */ + --color-danger-muted: #dc3545; /* Outline-style danger (clear buttons) */ + --color-danger-muted-hover: #bb2d3b; + --color-success: #28a745; /* Correct / matched state */ + --color-warning-bg: #fff3cd; /* Warning confirmation background */ + --color-warning-border: #ffc107; /* Warning confirmation border */ + --color-warning-text: #664d03; /* Text on warning background */ + + /* ── Shared game-component palette ─────────────────────────────────────── */ + --game-results-border: #c0c8d4; /* Border around results
*/ + --game-results-row-border: #e0e0e0; /* Row divider inside results table */ + --game-results-label: #333; /*
label text */ + --game-results-value: #111; /*
value text */ + + /* ── Speed-trend chart ──────────────────────────────────────────────────── */ + --trend-border: #c1c9d2; + --trend-bg: #eef2f6; + --trend-text: #19324a; + --trend-chart-border: #9eabb8; + + /* ── Border radii ───────────────────────────────────────────────────────── */ + --radius-sm: 4px; + --radius-md: 6px; + --radius-lg: 8px; + + /* ── Transitions ────────────────────────────────────────────────────────── */ + --transition-fast: 0.15s ease; + --transition-quick: 0.1s ease; + + /* ── Chart bar colour palette (6 accessible colours + total-bar grey) ───── */ + --chart-color-0: #005fcc; + --chart-color-1: #c9510c; + --chart-color-2: #238636; + --chart-color-3: #8250df; + --chart-color-4: #d1242f; + --chart-color-5: #0969da; + --chart-color-total: #adb5bd; +}