Files
wordsearch/docs/superpowers/plans/2026-05-06-website-redesign.md
Mathew Sir Guest the bestandClaude Opus 4.6 36c3c712fe 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>
2026-05-07 10:38:19 -06:00

20 KiB
Raw Permalink Blame History

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:

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:

import tornado.web


class HowToPlayHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("howtoplay.html")
  • Step 2: Create AboutHandler

Create handlers/about.py:

import tornado.web


class AboutHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("about.html")
  • Step 3: Create placeholder templates

Create templates/howtoplay.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:

{% 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:

from handlers.howtoplay import HowToPlayHandler
from handlers.about import AboutHandler

Add routes to the make_app() route list, after the /game route:

(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

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

: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
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

<!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">
            &copy; 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:

{% block active_page %}{% set active_page = "home" %}{% end %}

In templates/game.html, add after the {% extends %} line:

{% block active_page %}{% set active_page = "play" %}{% end %}

In templates/howtoplay.html, add after the {% extends %} line:

{% block active_page %}{% set active_page = "howtoplay" %}{% end %}

In templates/about.html, add after the {% extends %} line:

{% 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
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:

{% 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
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

{% 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
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

{% 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
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

{% 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
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.