feat: add scoreboard with player names, per-category leaderboards, and themed tables
Add player_name column to games (migration included), with regex-based validation and anti-abuse sanitization. New /scoreboard page shows recent games from localStorage, popular categories with play counts, and top-10 per-category leaderboards sorted by completion time. Also includes two-click reverse word matching, base URL prefix support for reverse-proxy hosting, BasePageHandler refactoring, themed table CSS for all 5 themes, and comprehensive test coverage for player names and scoreboard API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
9319f08875
commit
36c3c712fe
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,763 @@
|
||||
# Website Redesign Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Redesign the word search frontend with a dark indigo palette, pill-style navbar, footer, two new content pages (How to Play, About), and emoji/icon accents.
|
||||
|
||||
**Architecture:** Enhance existing Tornado template hierarchy in place. `base.html` gets navbar restructure, Bootstrap Icons CDN, semantic HTML, and a footer. Child templates set an `active_page` block for nav highlighting. Two new handlers + templates for the new pages. CSS swaps teal variables for indigo palette.
|
||||
|
||||
**Tech Stack:** Python/Tornado, Bootstrap 5.3 (dark mode), Bootstrap Icons (CDN), pytest + pytest-tornasync
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-05-06-website-redesign-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
### Modified
|
||||
| File | Responsibility |
|
||||
|------|---------------|
|
||||
| `tests/e2e/test_hello.py` | E2e tests for all routes including new ones |
|
||||
| `app.py` | Route registration — add `/howtoplay` and `/about` |
|
||||
| `static/css/theme.css` | Full palette swap + new component styles |
|
||||
| `templates/base.html` | Shared layout: navbar, footer, Bootstrap Icons, active page logic |
|
||||
| `templates/main.html` | Homepage hero with emoji and coral CTA |
|
||||
| `templates/game.html` | Game board card restyled for new palette |
|
||||
|
||||
### Created
|
||||
| File | Responsibility |
|
||||
|------|---------------|
|
||||
| `handlers/howtoplay.py` | HowToPlayHandler — renders howtoplay.html |
|
||||
| `handlers/about.py` | AboutHandler — renders about.html |
|
||||
| `templates/howtoplay.html` | How to Play instructions page |
|
||||
| `templates/about.html` | About page with app description and authors |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add e2e tests for new routes
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/e2e/test_hello.py`
|
||||
|
||||
- [ ] **Step 1: Add tests for `/howtoplay` and `/about` routes**
|
||||
|
||||
Replace the contents of `tests/e2e/test_hello.py` with:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
|
||||
from app import make_app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
return make_app()
|
||||
|
||||
|
||||
async def test_homepage_returns_200(http_server_client):
|
||||
response = await http_server_client.fetch("/")
|
||||
assert response.code == 200
|
||||
|
||||
|
||||
async def test_game_returns_200(http_server_client):
|
||||
response = await http_server_client.fetch("/game")
|
||||
assert response.code == 200
|
||||
|
||||
|
||||
async def test_howtoplay_returns_200(http_server_client):
|
||||
response = await http_server_client.fetch("/howtoplay")
|
||||
assert response.code == 200
|
||||
|
||||
|
||||
async def test_about_returns_200(http_server_client):
|
||||
response = await http_server_client.fetch("/about")
|
||||
assert response.code == 200
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests to verify new tests fail**
|
||||
|
||||
Run: `uv run pytest tests/e2e/ -v`
|
||||
|
||||
Expected: `test_homepage_returns_200` PASS, `test_game_returns_200` PASS, `test_howtoplay_returns_200` FAIL (404), `test_about_returns_200` FAIL (404)
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Create handlers and register routes
|
||||
|
||||
**Files:**
|
||||
- Create: `handlers/howtoplay.py`
|
||||
- Create: `handlers/about.py`
|
||||
- Create: `templates/howtoplay.html` (minimal placeholder)
|
||||
- Create: `templates/about.html` (minimal placeholder)
|
||||
- Modify: `app.py`
|
||||
|
||||
- [ ] **Step 1: Create HowToPlayHandler**
|
||||
|
||||
Create `handlers/howtoplay.py`:
|
||||
|
||||
```python
|
||||
import tornado.web
|
||||
|
||||
|
||||
class HowToPlayHandler(tornado.web.RequestHandler):
|
||||
def get(self):
|
||||
self.render("howtoplay.html")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Create AboutHandler**
|
||||
|
||||
Create `handlers/about.py`:
|
||||
|
||||
```python
|
||||
import tornado.web
|
||||
|
||||
|
||||
class AboutHandler(tornado.web.RequestHandler):
|
||||
def get(self):
|
||||
self.render("about.html")
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Create placeholder templates**
|
||||
|
||||
Create `templates/howtoplay.html`:
|
||||
|
||||
```html
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Word Search — How to Play{% end %}
|
||||
|
||||
{% block content %}
|
||||
<h2>How to Play</h2>
|
||||
<p>Instructions coming soon.</p>
|
||||
{% end %}
|
||||
```
|
||||
|
||||
Create `templates/about.html`:
|
||||
|
||||
```html
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Word Search — About{% end %}
|
||||
|
||||
{% block content %}
|
||||
<h2>About</h2>
|
||||
<p>About page coming soon.</p>
|
||||
{% end %}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Register routes in app.py**
|
||||
|
||||
In `app.py`, add imports after the existing handler imports:
|
||||
|
||||
```python
|
||||
from handlers.howtoplay import HowToPlayHandler
|
||||
from handlers.about import AboutHandler
|
||||
```
|
||||
|
||||
Add routes to the `make_app()` route list, after the `/game` route:
|
||||
|
||||
```python
|
||||
(r"/howtoplay", HowToPlayHandler),
|
||||
(r"/about", AboutHandler),
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Run tests to verify all pass**
|
||||
|
||||
Run: `uv run pytest tests/e2e/ -v`
|
||||
|
||||
Expected: All 4 tests PASS
|
||||
|
||||
- [ ] **Step 6: Lint and commit**
|
||||
|
||||
Run: `uv run black . && uv run flake8`
|
||||
|
||||
```bash
|
||||
git add handlers/howtoplay.py handlers/about.py templates/howtoplay.html templates/about.html app.py tests/e2e/test_hello.py
|
||||
git commit -m "feat: add howtoplay and about routes with placeholder templates"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Swap CSS palette and add component styles
|
||||
|
||||
**Files:**
|
||||
- Modify: `static/css/theme.css`
|
||||
|
||||
- [ ] **Step 1: Replace full contents of `static/css/theme.css`**
|
||||
|
||||
```css
|
||||
:root {
|
||||
--indigo-darkest: #1a1a2e;
|
||||
--indigo-dark: #16213e;
|
||||
--indigo-mid: #0f3460;
|
||||
--coral: #e94560;
|
||||
--coral-light: #ff6b81;
|
||||
--gold: #f5c518;
|
||||
--text-primary: #e0e0e0;
|
||||
--text-muted: #a0aec0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--indigo-darkest);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Navbar */
|
||||
.navbar {
|
||||
background-color: var(--indigo-dark);
|
||||
border-bottom: 1px solid var(--indigo-mid);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
color: var(--coral);
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.navbar-brand:hover {
|
||||
color: var(--coral-light);
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link {
|
||||
color: var(--text-muted);
|
||||
border-radius: 6px;
|
||||
padding: 0.4rem 1rem;
|
||||
margin: 0 0.15rem;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link:hover {
|
||||
color: #ffffff;
|
||||
background-color: rgba(15, 52, 96, 0.5);
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link.active {
|
||||
color: #ffffff;
|
||||
background-color: var(--indigo-mid);
|
||||
}
|
||||
|
||||
/* Hero */
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 3rem 1rem;
|
||||
}
|
||||
|
||||
.hero-emoji {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
color: #ffffff;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.hero .lead {
|
||||
color: var(--text-muted);
|
||||
max-width: 500px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn-accent {
|
||||
background-color: var(--coral);
|
||||
border-color: var(--coral);
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-accent:hover {
|
||||
background-color: var(--coral-light);
|
||||
border-color: var(--coral-light);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
background-color: var(--indigo-dark);
|
||||
border-color: var(--indigo-mid);
|
||||
}
|
||||
|
||||
/* Content pages */
|
||||
.content-section {
|
||||
background-color: var(--indigo-dark);
|
||||
border: 1px solid var(--indigo-mid);
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-color: var(--indigo-mid);
|
||||
color: var(--gold);
|
||||
border-radius: 50%;
|
||||
font-weight: 700;
|
||||
font-size: 0.875rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.step-item p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tip-card {
|
||||
background-color: rgba(15, 52, 96, 0.4);
|
||||
border-left: 3px solid var(--gold);
|
||||
border-radius: 4px;
|
||||
padding: 1rem 1.25rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.site-footer {
|
||||
border-top: 1px solid var(--indigo-mid);
|
||||
padding: 1.25rem 0;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the app still renders**
|
||||
|
||||
Run: `uv run pytest tests/e2e/ -v`
|
||||
|
||||
Expected: All 4 tests PASS (CSS changes don't break templates)
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add static/css/theme.css
|
||||
git commit -m "style: swap teal palette for dark indigo with coral accents"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Restructure base.html
|
||||
|
||||
**Files:**
|
||||
- Modify: `templates/base.html`
|
||||
|
||||
- [ ] **Step 1: Replace full contents of `templates/base.html`**
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Word Search{% end %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YcnS/1WR6zNiELmFp5mGg7gCjSP54e0TpoS"
|
||||
crossorigin="anonymous">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ static_url('css/theme.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
{% block active_page %}{% end %}
|
||||
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">
|
||||
<i class="bi bi-search"></i> Word Search
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'home' %}active{% end %}"
|
||||
href="/">
|
||||
<i class="bi bi-house-door"></i> Home
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'play' %}active{% end %}"
|
||||
href="/game">
|
||||
<i class="bi bi-controller"></i> Play
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'howtoplay' %}active{% end %}"
|
||||
href="/howtoplay">
|
||||
<i class="bi bi-question-circle"></i> How to Play
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_page == 'about' %}active{% end %}"
|
||||
href="/about">
|
||||
<i class="bi bi-info-circle"></i> About
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-4">
|
||||
{% block content %}{% end %}
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
© 2026 Word Search
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
**Note on active page logic:** Tornado templates don't support `{% set %}` like Jinja2. The `{% block active_page %}{% end %}` block is intentionally empty in the base. Each child template overrides it with `{% block active_page %}{% set active_page = "home" %}{% end %}`. Tornado's `{% set %}` creates a template variable accessible in the rest of the template including the base.
|
||||
|
||||
- [ ] **Step 2: Update child templates to set active_page**
|
||||
|
||||
In `templates/main.html`, add after the `{% extends %}` line:
|
||||
|
||||
```html
|
||||
{% block active_page %}{% set active_page = "home" %}{% end %}
|
||||
```
|
||||
|
||||
In `templates/game.html`, add after the `{% extends %}` line:
|
||||
|
||||
```html
|
||||
{% block active_page %}{% set active_page = "play" %}{% end %}
|
||||
```
|
||||
|
||||
In `templates/howtoplay.html`, add after the `{% extends %}` line:
|
||||
|
||||
```html
|
||||
{% block active_page %}{% set active_page = "howtoplay" %}{% end %}
|
||||
```
|
||||
|
||||
In `templates/about.html`, add after the `{% extends %}` line:
|
||||
|
||||
```html
|
||||
{% block active_page %}{% set active_page = "about" %}{% end %}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run tests**
|
||||
|
||||
Run: `uv run pytest tests/e2e/ -v`
|
||||
|
||||
Expected: All 4 tests PASS
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add templates/base.html templates/main.html templates/game.html templates/howtoplay.html templates/about.html
|
||||
git commit -m "feat: restructure base template with navbar pills, footer, and active page logic"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Restyle homepage hero
|
||||
|
||||
**Files:**
|
||||
- Modify: `templates/main.html`
|
||||
|
||||
- [ ] **Step 1: Replace content block in `templates/main.html`**
|
||||
|
||||
Full file contents:
|
||||
|
||||
```html
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block active_page %}{% set active_page = "home" %}{% end %}
|
||||
|
||||
{% block title %}Word Search — Home{% end %}
|
||||
|
||||
{% block content %}
|
||||
<header class="hero">
|
||||
<div class="hero-emoji">🧩</div>
|
||||
<h1 class="display-4 fw-bold mb-3">Word Search</h1>
|
||||
<p class="lead mb-4">
|
||||
Find hidden words in a grid of letters. Words can appear horizontally,
|
||||
vertically, or diagonally. How fast can you find them all?
|
||||
</p>
|
||||
<a href="/game" class="btn btn-accent btn-lg px-5">🎮 Start Game</a>
|
||||
</header>
|
||||
{% end %}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests**
|
||||
|
||||
Run: `uv run pytest tests/e2e/ -v`
|
||||
|
||||
Expected: All 4 tests PASS
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add templates/main.html
|
||||
git commit -m "style: restyle homepage hero with emoji and coral CTA"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Update game page for new palette
|
||||
|
||||
**Files:**
|
||||
- Modify: `templates/game.html`
|
||||
|
||||
- [ ] **Step 1: Replace full contents of `templates/game.html`**
|
||||
|
||||
```html
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block active_page %}{% set active_page = "play" %}{% end %}
|
||||
|
||||
{% block title %}Word Search — Play{% end %}
|
||||
|
||||
{% block content %}
|
||||
<h2 class="mb-4">🎯 Game Board</h2>
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<p class="text-muted">Game board coming soon.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% end %}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests**
|
||||
|
||||
Run: `uv run pytest tests/e2e/ -v`
|
||||
|
||||
Expected: All 4 tests PASS
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add templates/game.html
|
||||
git commit -m "style: update game page with active nav and emoji heading"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Create How to Play page content
|
||||
|
||||
**Files:**
|
||||
- Modify: `templates/howtoplay.html`
|
||||
|
||||
- [ ] **Step 1: Replace full contents of `templates/howtoplay.html`**
|
||||
|
||||
```html
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block active_page %}{% set active_page = "howtoplay" %}{% end %}
|
||||
|
||||
{% block title %}Word Search — How to Play{% end %}
|
||||
|
||||
{% block content %}
|
||||
<h2 class="mb-4">📖 How to Play</h2>
|
||||
|
||||
<div class="content-section">
|
||||
<h3 class="mb-4">Step-by-Step Instructions</h3>
|
||||
|
||||
<div class="step-item">
|
||||
<span class="step-number">1</span>
|
||||
<p>
|
||||
📋 <strong>Check the word list</strong> — Look at the list of words
|
||||
displayed alongside the grid. These are the words you need to find.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-item">
|
||||
<span class="step-number">2</span>
|
||||
<p>
|
||||
🔍 <strong>Scan for the first letter</strong> — Pick a word from the
|
||||
list and scan the grid for its first letter. This gives you a starting
|
||||
point.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-item">
|
||||
<span class="step-number">3</span>
|
||||
<p>
|
||||
↗️ <strong>Look in all directions</strong> — Words can run
|
||||
horizontally, vertically, or diagonally — and they can go forward or
|
||||
backward. Check all eight directions from your starting letter.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-item">
|
||||
<span class="step-number">4</span>
|
||||
<p>
|
||||
👆 <strong>Select the word</strong> — Click or tap the first letter of
|
||||
the word, then click the last letter to highlight the entire word.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-item">
|
||||
<span class="step-number">5</span>
|
||||
<p>
|
||||
✅ <strong>Words get crossed off</strong> — When you correctly find a
|
||||
word, it gets crossed off the list so you can track your progress.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-item">
|
||||
<span class="step-number">6</span>
|
||||
<p>
|
||||
🏆 <strong>Find them all to win!</strong> — The puzzle is complete when
|
||||
every word on the list has been found. Try to beat your best time!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="mb-3">💡 Tips</h3>
|
||||
|
||||
<div class="tip-card">
|
||||
<strong>Start with uncommon letters</strong> — Letters like Q, X, Z, and J
|
||||
appear less often in the grid, making words with those letters easier to spot.
|
||||
</div>
|
||||
|
||||
<div class="tip-card">
|
||||
<strong>Scan systematically</strong> — Work through the grid row by row or
|
||||
column by column rather than jumping around randomly.
|
||||
</div>
|
||||
|
||||
<div class="tip-card">
|
||||
<strong>Look for word shapes</strong> — Sometimes you can spot diagonal words
|
||||
by the distinctive slanted pattern they make in the grid.
|
||||
</div>
|
||||
{% end %}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests**
|
||||
|
||||
Run: `uv run pytest tests/e2e/ -v`
|
||||
|
||||
Expected: All 4 tests PASS
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add templates/howtoplay.html
|
||||
git commit -m "feat: add How to Play page with step-by-step instructions and tips"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Create About page content
|
||||
|
||||
**Files:**
|
||||
- Modify: `templates/about.html`
|
||||
|
||||
- [ ] **Step 1: Replace full contents of `templates/about.html`**
|
||||
|
||||
```html
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block active_page %}{% set active_page = "about" %}{% end %}
|
||||
|
||||
{% block title %}Word Search — About{% end %}
|
||||
|
||||
{% block content %}
|
||||
<h2 class="mb-4">ℹ️ About</h2>
|
||||
|
||||
<div class="content-section">
|
||||
<h3>🧩 What is Word Search?</h3>
|
||||
<p>
|
||||
Word Search is a browser-based puzzle game where you find hidden words in a
|
||||
grid of letters. Words can be placed horizontally, vertically, or
|
||||
diagonally — forwards or backwards. The backend generates puzzles,
|
||||
validates your selections, and tracks your progress in real time.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="content-section">
|
||||
<h3>🛠️ Built With</h3>
|
||||
<ul>
|
||||
<li><strong>Python</strong> — core language</li>
|
||||
<li><strong>Tornado</strong> — async web framework</li>
|
||||
<li><strong>Bootstrap 5</strong> — responsive UI</li>
|
||||
<li><strong>Bootstrap Icons</strong> — interface icons</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="content-section">
|
||||
<h3>✍️ Authors</h3>
|
||||
<ul>
|
||||
<li><strong>Mathew Guest</strong> — creator and developer</li>
|
||||
<li><strong>🤖 Claude</strong> (Anthropic) — AI co-author</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="content-section">
|
||||
<h3>📂 Source Code</h3>
|
||||
<p>
|
||||
The source code for this project will be available on GitHub.
|
||||
<em>Link coming soon.</em>
|
||||
</p>
|
||||
</div>
|
||||
{% end %}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests**
|
||||
|
||||
Run: `uv run pytest tests/e2e/ -v`
|
||||
|
||||
Expected: All 4 tests PASS
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add templates/about.html
|
||||
git commit -m "feat: add About page with app description, tech stack, and authors"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Final verification
|
||||
|
||||
- [ ] **Step 1: Run full test suite**
|
||||
|
||||
Run: `uv run pytest -v`
|
||||
|
||||
Expected: All tests PASS (unit + e2e)
|
||||
|
||||
- [ ] **Step 2: Lint and format**
|
||||
|
||||
Run: `uv run black . && uv run flake8`
|
||||
|
||||
Expected: No errors
|
||||
|
||||
- [ ] **Step 3: Start the server and visually verify with Playwright**
|
||||
|
||||
Start the app: `uv run python app.py &`
|
||||
|
||||
Use Playwright MCP tools to:
|
||||
1. Navigate to `http://localhost:8888` — verify indigo palette, navbar with 4 pill-style links, "Home" is active, hero section with puzzle emoji, coral "Start Game" button, footer with copyright
|
||||
2. Navigate to `http://localhost:8888/game` — verify "Play" is active in navbar, game card renders
|
||||
3. Navigate to `http://localhost:8888/howtoplay` — verify "How to Play" is active, 6 numbered steps visible, 3 tip cards visible
|
||||
4. Navigate to `http://localhost:8888/about` — verify "About" is active, 4 content sections visible (What is, Built With, Authors, Source Code)
|
||||
5. Resize browser to mobile width — verify navbar collapses to hamburger menu
|
||||
|
||||
- [ ] **Step 4: Stop the server and commit any fixes**
|
||||
|
||||
Kill the background server process. If any visual issues were found and fixed, commit the fixes.
|
||||
Reference in New Issue
Block a user