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>
764 lines
20 KiB
Markdown
764 lines
20 KiB
Markdown
# 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.
|