feat: add base template with Pico CSS dark theme and home page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 09:13:28 -06:00
parent b3b34222c8
commit df7e86f2ed
6 changed files with 142 additions and 0 deletions

View File

@@ -14,6 +14,7 @@ from fastapi.templating import Jinja2Templates
from app.config import get_settings from app.config import get_settings
from app.logging_config import setup_logging from app.logging_config import setup_logging
from app.routes.health import router as health_router from app.routes.health import router as health_router
from app.routes.pages import router as pages_router
logger = structlog.get_logger(__name__) logger = structlog.get_logger(__name__)
@@ -47,6 +48,7 @@ def create_app() -> FastAPI:
# Register route modules # Register route modules
app.include_router(health_router) app.include_router(health_router)
app.include_router(pages_router)
logger.info("app_started", environment=settings.app_env) logger.info("app_started", environment=settings.app_env)

23
app/routes/pages.py Normal file
View File

@@ -0,0 +1,23 @@
"""Page routes for serving full HTML pages.
Renders Jinja2 templates for user-facing pages.
"""
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
router = APIRouter(tags=["pages"])
@router.get("/")
async def home_page(request: Request) -> HTMLResponse:
"""Render the home page.
Args:
request: The incoming HTTP request.
Returns:
Rendered home page HTML.
"""
templates = request.app.state.templates
return templates.TemplateResponse(request, "pages/home.html")

View File

@@ -0,0 +1,36 @@
/* SneakySwole custom CSS overrides
Built on top of Pico CSS dark theme.
Keep Pico mostly untouched — only override what's necessary. */
/* Slightly bolder nav styling */
nav h1 {
margin-bottom: 0;
font-weight: 700;
}
/* Active nav link indicator */
nav a[aria-current="page"] {
font-weight: 700;
text-decoration: underline;
}
/* Spacing for main content area */
main.container {
padding-top: 1rem;
padding-bottom: 2rem;
}
/* Flash message styling */
.flash-success {
color: var(--pico-ins-color);
border-left: 4px solid var(--pico-ins-color);
padding: 0.5rem 1rem;
margin-bottom: 1rem;
}
.flash-error {
color: var(--pico-del-color);
border-left: 4px solid var(--pico-del-color);
padding: 0.5rem 1rem;
margin-bottom: 1rem;
}

47
app/templates/base.html Normal file
View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}SneakySwole{% endblock %}</title>
<!-- Pico CSS (dark theme via data-theme="dark" on <html>) -->
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
<!-- Custom overrides -->
<link rel="stylesheet" href="/static/css/sneakyswole.css">
<!-- HTMX -->
<script src="https://unpkg.com/htmx.org@2.0.4"
integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+"
crossorigin="anonymous"></script>
{% block head_extra %}{% endblock %}
</head>
<body>
<header class="container">
<nav>
<ul>
<li><strong><a href="/">SneakySwole</a></strong></li>
</ul>
<ul>
{% block nav_items %}
<!-- Phase 3 adds: profile switcher, login/logout -->
{% endblock %}
</ul>
</nav>
</header>
<main class="container">
{% block flash %}{% endblock %}
{% block content %}{% endblock %}
</main>
<footer class="container">
<small>SneakySwole — Open-source workout tracking</small>
</footer>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}SneakySwole — Home{% endblock %}
{% block content %}
<hgroup>
<h1>SneakySwole</h1>
<p>Your open-source workout tracker</p>
</hgroup>
<p>Welcome to SneakySwole. Get started by logging in.</p>
{% endblock %}

22
tests/test_pages.py Normal file
View File

@@ -0,0 +1,22 @@
"""Tests for HTML page routes."""
from fastapi.testclient import TestClient
class TestHomePage:
"""Tests for GET /."""
def test_home_returns_200(self, client: TestClient) -> None:
"""GET / should return HTTP 200."""
response = client.get("/")
assert response.status_code == 200
def test_home_contains_app_name(self, client: TestClient) -> None:
"""GET / should render HTML containing the app name."""
response = client.get("/")
assert "SneakySwole" in response.text
def test_home_has_dark_theme(self, client: TestClient) -> None:
"""GET / should use the dark theme."""
response = client.get("/")
assert 'data-theme="dark"' in response.text