docs: add landing page design spec and implementation plan
This commit is contained in:
parent
9e60fbb7cf
commit
d33864c119
370
docs/superpowers/plans/2026-04-27-landing-page.md
Normal file
370
docs/superpowers/plans/2026-04-27-landing-page.md
Normal file
@ -0,0 +1,370 @@
|
||||
# Landing Page & Shared Layout 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:** Build a shared Jinja2/Tailwind CSS layout and a module-card landing page for the University Process Hub.
|
||||
|
||||
**Architecture:** FastAPI serves HTML via `Jinja2Templates`. `base.html` defines the red navbar (`#e2001a`), a `{% block content %}` slot, and a footer. `index.html` extends it with a hero section, responsive 3-column module-card grid, and an info strip. Module metadata is a static list in `main.py` — no DB access on the landing page.
|
||||
|
||||
**Tech Stack:** FastAPI, Jinja2, Tailwind CSS Play CDN (no build step), pytest, httpx
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add dependencies and update .gitignore
|
||||
|
||||
**Files:**
|
||||
- Modify: `requirements.txt`
|
||||
- Modify: `.gitignore`
|
||||
|
||||
- [ ] **Step 1: Update `requirements.txt`**
|
||||
|
||||
```
|
||||
fastapi
|
||||
uvicorn[standard]
|
||||
pydantic-settings
|
||||
sqlalchemy
|
||||
jinja2
|
||||
pytest
|
||||
httpx
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add `.superpowers/` to `.gitignore`**
|
||||
|
||||
Append to the `# Project specific` section:
|
||||
```
|
||||
.superpowers/
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Install new dependencies**
|
||||
|
||||
```bash
|
||||
.venv/bin/pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Expected: `jinja2`, `pytest`, `httpx` installed without error.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add requirements.txt .gitignore
|
||||
git commit -m "chore: add jinja2 and test dependencies"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Create base layout template
|
||||
|
||||
**Files:**
|
||||
- Create: `app/templates/base.html`
|
||||
- Create: `app/static/.gitkeep`
|
||||
|
||||
- [ ] **Step 1: Create directories**
|
||||
|
||||
```bash
|
||||
mkdir -p app/templates app/static
|
||||
touch app/static/.gitkeep
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write `app/templates/base.html`**
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}EFI Hub{% endblock %}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: { 'efi': '#e2001a' }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-gray-100 min-h-screen flex flex-col">
|
||||
|
||||
<nav class="bg-efi h-14 flex items-center px-8 shrink-0">
|
||||
<a href="/" class="text-white font-bold text-lg tracking-tight mr-8">
|
||||
EFI<span class="opacity-70 font-normal">Hub</span>
|
||||
</a>
|
||||
{% for item in nav_items %}
|
||||
<a href="{{ item.url }}"
|
||||
class="text-white/85 hover:text-white text-sm px-4 h-14 flex items-center border-b-2
|
||||
{% if item.active %}border-white text-white{% else %}border-transparent{% endif %}">
|
||||
{{ item.label }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
<div class="ml-auto">
|
||||
<button class="text-white text-sm px-4 py-1.5 rounded border border-white/40 bg-white/15 hover:bg-white/25">
|
||||
Anmelden
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-1">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="bg-gray-100 border-t border-gray-300 px-8 py-3.5 flex items-center justify-between text-xs text-gray-400 shrink-0">
|
||||
<span class="font-semibold text-gray-500">EFI Hub</span>
|
||||
<span>Technische Hochschule Nürnberg Georg Simon Ohm · Fakultät EFI</span>
|
||||
<span>v{{ app_version }}</span>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add app/templates/base.html app/static/.gitkeep
|
||||
git commit -m "feat: add base layout template with red navbar and footer"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Create landing page template
|
||||
|
||||
**Files:**
|
||||
- Create: `app/templates/index.html`
|
||||
|
||||
- [ ] **Step 1: Write `app/templates/index.html`**
|
||||
|
||||
```html
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}University Process Hub{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="bg-white border-b border-gray-200 px-8 py-10">
|
||||
<p class="text-xs font-semibold uppercase tracking-widest text-efi mb-2">
|
||||
Fakultät EFI · TH Nürnberg
|
||||
</p>
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">University Process Hub</h1>
|
||||
<p class="text-sm text-gray-500 max-w-xl leading-relaxed">
|
||||
Modulares Informationssystem für Fakultätsprozesse.
|
||||
Startpunkt für studentische Arbeiten und Schnittstelle für interne Dienste.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="px-8 py-7">
|
||||
<p class="text-xs font-semibold uppercase tracking-widest text-gray-400 mb-4">
|
||||
Verfügbare Module
|
||||
</p>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
|
||||
{% for module in modules %}
|
||||
<div class="bg-white rounded-md border border-gray-200 shadow-sm p-5 relative overflow-hidden
|
||||
hover:shadow-md hover:border-efi transition-all duration-150 cursor-pointer">
|
||||
<div class="absolute top-0 left-0 right-0 h-0.5 bg-efi"></div>
|
||||
<div class="text-2xl mb-2">{{ module.icon }}</div>
|
||||
<div class="font-semibold text-gray-900 text-sm mb-1">{{ module.name }}</div>
|
||||
<div class="text-xs text-gray-500 leading-relaxed mb-3">{{ module.description }}</div>
|
||||
{% if module.status == "active" %}
|
||||
<span class="text-xs font-semibold px-2 py-0.5 rounded-full bg-green-50 text-green-700 border border-green-200">Aktiv</span>
|
||||
{% else %}
|
||||
<span class="text-xs font-semibold px-2 py-0.5 rounded-full bg-gray-100 text-gray-500 border border-gray-200">Geplant</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="bg-white rounded-md border border-dashed border-gray-300 p-5">
|
||||
<div class="text-2xl mb-2 opacity-30">➕</div>
|
||||
<div class="font-semibold text-gray-300 text-sm mb-1">Neues Modul</div>
|
||||
<div class="text-xs text-gray-300 leading-relaxed">Erweiterbar durch studentische Arbeiten.</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border-t border-gray-200 px-8 py-4 flex flex-wrap items-center gap-8">
|
||||
<span class="text-xs text-gray-500">API: <strong class="text-gray-800">FastAPI</strong></span>
|
||||
<span class="text-xs text-gray-500">Datenbank: <strong class="text-gray-800">{{ db_mode }}</strong></span>
|
||||
<span class="text-xs text-gray-500">WebSocket: <strong class="text-gray-800">aktiv</strong></span>
|
||||
<div class="ml-auto text-xs font-medium text-efi">● System online</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add app/templates/index.html
|
||||
git commit -m "feat: add landing page template with module grid and info strip"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Wire templates into FastAPI and write tests
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/main.py`
|
||||
- Create: `tests/__init__.py`
|
||||
- Create: `tests/test_landing.py`
|
||||
|
||||
- [ ] **Step 1: Write the failing tests**
|
||||
|
||||
```bash
|
||||
mkdir -p tests && touch tests/__init__.py
|
||||
```
|
||||
|
||||
Schreibe `tests/test_landing.py`:
|
||||
|
||||
```python
|
||||
from fastapi.testclient import TestClient
|
||||
from app.main import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_landing_returns_html():
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200
|
||||
assert "text/html" in response.headers["content-type"]
|
||||
|
||||
|
||||
def test_landing_contains_title():
|
||||
response = client.get("/")
|
||||
assert "University Process Hub" in response.text
|
||||
|
||||
|
||||
def test_landing_contains_rss_module():
|
||||
response = client.get("/")
|
||||
assert "RSS-Feed Server" in response.text
|
||||
|
||||
|
||||
def test_landing_navbar_links_present():
|
||||
response = client.get("/")
|
||||
assert "Übersicht" in response.text
|
||||
assert "RSS-Feeds" in response.text
|
||||
|
||||
|
||||
def test_landing_info_strip_shows_db_mode():
|
||||
response = client.get("/")
|
||||
assert "SQLite" in response.text or "MariaDB" in response.text
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests to confirm they fail**
|
||||
|
||||
```bash
|
||||
.venv/bin/pytest tests/test_landing.py -v
|
||||
```
|
||||
|
||||
Expected: FAIL — `GET /` returns JSON, not HTML.
|
||||
|
||||
- [ ] **Step 3: Replace `app/main.py` with template-serving version**
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI, Request, WebSocket
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from app.core.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
app = FastAPI(
|
||||
title="University Process Hub",
|
||||
root_path=settings.APP_PREFIX,
|
||||
)
|
||||
|
||||
app.mount("/static", StaticFiles(directory="app/static"), name="static")
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
|
||||
MODULES = [
|
||||
{
|
||||
"icon": "📡",
|
||||
"name": "RSS-Feed Server",
|
||||
"description": "Aggregiert und verteilt Neuigkeiten der Fakultät als standardkonformen RSS 2.0 Feed.",
|
||||
"status": "active",
|
||||
},
|
||||
{
|
||||
"icon": "📅",
|
||||
"name": "Kalender",
|
||||
"description": "Veranstaltungen, Prüfungstermine und Fristen im iCal-Format.",
|
||||
"status": "planned",
|
||||
},
|
||||
{
|
||||
"icon": "🔔",
|
||||
"name": "Benachrichtigungen",
|
||||
"description": "Push-Nachrichten und WebSocket-basierte Echtzeit-Alerts für Studierende.",
|
||||
"status": "planned",
|
||||
},
|
||||
{
|
||||
"icon": "📊",
|
||||
"name": "Auslastung",
|
||||
"description": "Raum- und Ressourcenauslastung der Fakultät in Echtzeit.",
|
||||
"status": "planned",
|
||||
},
|
||||
{
|
||||
"icon": "📚",
|
||||
"name": "Lehrveranstaltungen",
|
||||
"description": "Stundenplan-API und Kursinformationen aus dem Campus-System.",
|
||||
"status": "planned",
|
||||
},
|
||||
]
|
||||
|
||||
NAV_ITEMS = [
|
||||
{"label": "Übersicht", "url": "/", "active": True},
|
||||
{"label": "RSS-Feeds", "url": "/rss", "active": False},
|
||||
{"label": "Kalender", "url": "/kalender", "active": False},
|
||||
{"label": "Docs", "url": "/docs", "active": False},
|
||||
]
|
||||
|
||||
|
||||
def _db_mode() -> str:
|
||||
url = settings.DATABASE_URL
|
||||
return "SQLite (dev)" if url.startswith("sqlite") else "MariaDB (prod)"
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def root(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
"index.html",
|
||||
{
|
||||
"nav_items": NAV_ITEMS,
|
||||
"modules": MODULES,
|
||||
"db_mode": _db_mode(),
|
||||
"app_version": "0.1.0",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.websocket("/ws/hello/{name}")
|
||||
async def websocket_hello(websocket: WebSocket, name: str):
|
||||
await websocket.accept()
|
||||
await websocket.send_text(f"Hello, {name}!")
|
||||
await websocket.close()
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run tests to confirm they pass**
|
||||
|
||||
```bash
|
||||
.venv/bin/pytest tests/test_landing.py -v
|
||||
```
|
||||
|
||||
Expected: All 5 tests PASS.
|
||||
|
||||
- [ ] **Step 5: Verify in browser**
|
||||
|
||||
```bash
|
||||
.venv/bin/uvicorn app.main:app --reload
|
||||
```
|
||||
|
||||
Öffne `http://localhost:8000` — Navbar rot, Hero-Bereich, 5 Modul-Karten + Placeholder-Karte, Info-Strip, Footer.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add app/main.py tests/
|
||||
git commit -m "feat: serve landing page via Jinja2 with module grid"
|
||||
```
|
||||
121
docs/superpowers/specs/2026-04-27-landing-page-design.md
Normal file
121
docs/superpowers/specs/2026-04-27-landing-page-design.md
Normal file
@ -0,0 +1,121 @@
|
||||
# Design Spec: Landing Page & Shared Layout
|
||||
|
||||
**Datum:** 2026-04-27
|
||||
**Status:** Genehmigt
|
||||
|
||||
---
|
||||
|
||||
## Ziel
|
||||
|
||||
Aufbau einer Landing Page mit gemeinsamem Layout-System für alle zukünftigen Seiten des University Process Hub (UPH). Das Layout ersetzt Bootstrap der Vorversion durch Tailwind CSS.
|
||||
|
||||
---
|
||||
|
||||
## Farbpalette
|
||||
|
||||
| Rolle | Farbe | Hex |
|
||||
|---|---|---|
|
||||
| Primär / Navbar | TH-Rot | `#e2001a` |
|
||||
| Hintergrund Seite | Hellgrau | `#f5f5f5` |
|
||||
| Hintergrund Karten / Navbar-Content | Weiß | `#ffffff` |
|
||||
| Text primär | Fast-Schwarz | `#1a1a1a` |
|
||||
| Text sekundär | Mittelgrau | `#666666` |
|
||||
| Text deaktiviert / Labels | Hellgrau | `#888888` |
|
||||
| Trennlinien / Borders | Sehr hell | `#e8e8e8` |
|
||||
| Akzent hover (Karten) | Rot 12% opacity | `rgba(226,0,26,0.12)` |
|
||||
|
||||
Kein Dunkelgrau in der Navbar — Rot trägt allein die Markenpräsenz.
|
||||
|
||||
---
|
||||
|
||||
## Layout-System (Base Template)
|
||||
|
||||
**Aufbau jeder Seite (top → bottom):**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ NAVBAR (rot, 56px) │
|
||||
│ Logo · Nav-Links · Anmelden-Button │
|
||||
├─────────────────────────────────────────┤
|
||||
│ PAGE CONTENT (slot — variabel) │
|
||||
│ - Hero / Section-Header │
|
||||
│ - Hauptinhalt │
|
||||
├─────────────────────────────────────────┤
|
||||
│ FOOTER (hellgrau, 44px) │
|
||||
│ Brand · Hochschulname · Version │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Navbar-Details:**
|
||||
- Hintergrund `#e2001a`
|
||||
- Logo: `EFIHub` (Hub in 70% Opacity)
|
||||
- Nav-Links: `rgba(255,255,255,0.85)`, aktiver Link weiß mit weißem Bottom-Border
|
||||
- Login-Button: Ghost-Style (`rgba(255,255,255,0.15)`, weißer Border)
|
||||
- Höhe: `56px`, Padding `0 32px`
|
||||
|
||||
**Footer:**
|
||||
- Hintergrund `#f5f5f5`, Border-Top `#e0e0e0`
|
||||
- Dreispaltiges Layout: Brand | Hochschulname | Version
|
||||
|
||||
---
|
||||
|
||||
## Landing Page
|
||||
|
||||
### Hero-Bereich
|
||||
- Hintergrund Weiß, Border-Bottom `#e8e8e8`
|
||||
- Kleines rotes Label oben: `Fakultät EFI · TH Nürnberg`
|
||||
- H1: `University Process Hub`
|
||||
- Beschreibungstext (max. 560px Breite)
|
||||
|
||||
### Modul-Karten
|
||||
- 3-spaltiges Grid auf Desktop, responsive (2-spaltig tablet, 1-spaltig mobile)
|
||||
- Weiße Karten, Border `#e8e8e8`, 3px roter Top-Stripe
|
||||
- Hover: Schatten `rgba(226,0,26,0.12)`, Border-Farbe `#e2001a`
|
||||
- Jede Karte: Icon · Name · Kurzbeschreibung · Status-Badge
|
||||
- Status-Badges: Aktiv (grün), Geplant (grau), + leere Placeholder-Karte (gestrichelt)
|
||||
|
||||
### Info-Strip (zwischen Grid und Footer)
|
||||
- Weiß, Border-Top `#e8e8e8`
|
||||
- Systeminfos: API-Version, Datenbank-Modus, WebSocket-Status
|
||||
- Rechts: `● System online` in Rot
|
||||
|
||||
---
|
||||
|
||||
## Template-Architektur
|
||||
|
||||
**Technologie:** Jinja2 (FastAPI-nativ) + Tailwind CSS via Play CDN (kein Build-Step nötig)
|
||||
|
||||
**Dateien:**
|
||||
```
|
||||
app/
|
||||
├── templates/
|
||||
│ ├── base.html # Navbar + Footer + Tailwind-Imports
|
||||
│ └── index.html # Landing Page (extends base.html)
|
||||
└── static/ # Für custom CSS / Assets (später)
|
||||
```
|
||||
|
||||
**FastAPI-Integration:**
|
||||
- `Jinja2Templates` in `main.py` konfigurieren
|
||||
- `StaticFiles` für `app/static/` mounten
|
||||
- `GET /` liefert `index.html` mit Modul-Liste aus Config
|
||||
|
||||
**Modul-Liste:** Zur Laufzeit aus einer Liste in `config.py` oder einem statischen Dict in `main.py` — kein DB-Zugriff auf der Landing Page.
|
||||
|
||||
---
|
||||
|
||||
## Responsive Breakpoints
|
||||
|
||||
| Breakpoint | Modul-Grid |
|
||||
|---|---|
|
||||
| `< 640px` | 1 Spalte |
|
||||
| `640px – 1024px` | 2 Spalten |
|
||||
| `> 1024px` | 3 Spalten |
|
||||
|
||||
---
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Authentifizierung (separates Modul)
|
||||
- Dynamische Modul-Metadaten aus DB
|
||||
- Dark Mode
|
||||
- Animationen / Transitions
|
||||
Loading…
x
Reference in New Issue
Block a user