diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml
index b6bdef4..ea3b49e 100644
--- a/.gitea/workflows/ci.yaml
+++ b/.gitea/workflows/ci.yaml
@@ -37,6 +37,16 @@ jobs:
exit 1
fi
+ - name: Reject tracked handoff artifacts
+ run: |
+ set -euo pipefail
+ tracked="$(git ls-files '*.zip' '*.docx' 'prototype/**' 'update_v2_docx_*.py' || true)"
+ if [ -n "$tracked" ]; then
+ echo "Handoff/prototype artifacts are tracked and must be removed:"
+ echo "$tracked"
+ exit 1
+ fi
+
- name: Secret pattern scan
run: |
set -euo pipefail
diff --git a/.gitignore b/.gitignore
index 84d8e28..a8608c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@ frontend/dist/
Backend/bin/
Backend/obj/
.DS_Store
+*.zip
+*.docx
# Local Claude Code config (settings, preview launch configs)
.claude/
@@ -11,6 +13,8 @@ Backend/obj/
.design-source/
.od-skills/
audit/
+prototype/
+update_v2_docx_*.py
*.dc.html
*.dc.html.artifact.json
/frontend/public/*.html
diff --git a/README.md b/README.md
index 0f44f3a..458bb8e 100644
--- a/README.md
+++ b/README.md
@@ -1,85 +1,66 @@
# VTuber Star Awards
-Monorepo for the VTuber Star Awards MVP.
+Production monorepo for the VTuber Star Awards platform.
## Stack
-- Frontend: Vue 3, Vite, Pinia, Tailwind CSS, PrimeVue, shadcn-style Vue UI primitives
+- Frontend: Vue 3, Vite, Pinia, Tailwind CSS, PrimeVue, lucide icons
- Backend: ASP.NET Core 8, EF Core 8, PostgreSQL
+- Delivery: Gitea Actions, Docker Compose, Nginx-served frontend, ASP.NET API
-## Structure
+## Repository Structure
-- `frontend/` Vue application
-- `Backend/` ASP.NET Core API
-- `prototype/` earlier visual prototype work
-- `docker-compose.dev.yml` optional local PostgreSQL container
+- `frontend/` Vue application and public assets
+- `Backend/` ASP.NET Core API, EF Core migrations, services and endpoints
+- `.gitea/workflows/ci.yaml` build, hygiene, deploy and live verification pipeline
+- `docker-compose.dev.yml` optional local PostgreSQL container for development
-## Frontend
+Generated archives, prototype exports, Word handoff files and local document-update scripts are intentionally ignored and should not be committed.
+
+## Local Development
+
+Start the database:
```bash
-cd frontend
-npm install
-cp .env.example .env
-npm run dev
+docker compose -f docker-compose.dev.yml up -d
```
-The frontend uses a lightweight demo session flow for development and preview deployments:
-
-- Sign in from the header
-- `Viewer Login` unlocks nomination and voting
-- `Admin Login` unlocks the admin routes and management views
-- Voting and nominations can be resubmitted; the backend updates the existing user state instead of blindly duplicating submissions
-
-## Backend
-
-Development uses the same local PostgreSQL defaults as `docker-compose.dev.yml`:
-
-```text
-Host=localhost;Port=5433;Database=vtuber_star_awards_dev;Username=vtsa_dev;Password=change-me-local-only
-```
-
-Those defaults are already present in `Backend/appsettings.Development.json`. For other environments, keep `Backend/appsettings.json` empty and set `VTSA_POSTGRES` or `ConnectionStrings__Postgres`.
+Run the backend:
```bash
cd Backend
dotnet restore
-dotnet build
+dotnet ef database update
ASPNETCORE_ENVIRONMENT=Development dotnet run --urls http://127.0.0.1:5084
```
-## Local Database
-
-If Docker is available locally:
+Run the frontend:
```bash
-docker compose -f docker-compose.dev.yml up -d
-cd Backend
-dotnet ef database update
+cd frontend
+npm ci
+cp .env.example .env
+npm run dev
```
-Default dev database:
+Default development database:
```text
Host=localhost;Port=5433;Database=vtuber_star_awards_dev;Username=vtsa_dev;Password=change-me-local-only
```
-Override `VTSA_DEV_POSTGRES_USER` and `VTSA_DEV_POSTGRES_PASSWORD` before starting Docker if you want different local credentials.
+The same defaults live in `Backend/appsettings.Development.json`. For non-local environments, set `VTSA_POSTGRES` or `ConnectionStrings__Postgres`.
-If `dotnet ef database update` is unavailable in the current environment, the repository also contains a bootstrap SQL script:
+## Useful Endpoints
-```bash
-cd Backend
-psql "Host=localhost Port=5433 Database=vtuber_star_awards_dev Username=vtsa_dev Password=change-me-local-only" -f Migrations/InitialCreate.manual.sql
-```
+- Public API: `/api/public/*`
+- Admin dashboard: `/api/admin/dashboard`
+- Admin moderation and risk workflows: `/api/admin/*`
+- Sessions and auth: `/api/auth/*`
+- App health: `/api/health`
+- Database health and migrations: `/api/health/database`
-Verify the runtime wiring:
-
-```bash
-curl http://127.0.0.1:5084/api/health
-curl http://127.0.0.1:5084/api/health/database
-```
-
-Development auth/session endpoints:
+Development admin session example:
```bash
curl -X POST http://localhost:5084/api/auth/dev-login \
@@ -87,26 +68,31 @@ curl -X POST http://localhost:5084/api/auth/dev-login \
-d '{"twitchUserId":"jayuhime_demo","displayName":"Jayuhime","role":"admin"}'
```
-## Notes
+## Validation
-- Public endpoints live under `/api/public/*`
-- Admin summary endpoint lives under `/api/admin/dashboard`
-- Session endpoints live under `/api/auth/*`
-- Database connectivity and pending migrations are exposed at `/api/health/database`
-- Current frontend store falls back to static seed-like data if the API is unavailable
-- The admin dashboard now includes a lightweight risk center and audit log for suspicious submit patterns and reviewed admin actions
+Run these before pushing application changes:
-## Suggested Backend Rollout Phases
+```bash
+cd frontend
+npm run build
-1. Public participation core
- - Home, nominations, voting, clip submission, winner archive
- - Backend focus: overview/category/archive reads plus persisted user participation state
-2. Admin season setup
- - Admin years, categories, candidates, season status
- - Backend focus: season/category/candidate CRUD and current-year switching
-3. Review and moderation
- - Admin reviews, nominations inbox, clips, risk center
- - Backend focus: nomination approval/rejection, clip moderation states, risk resolution workflows
-4. Reporting and operations
- - Admin dashboard, analytics, user logs, settings health checks
- - Backend focus: richer aggregates, audit search/filtering, operational health endpoints
+cd ..
+dotnet build Backend/Backend.csproj --configuration Release
+git diff --check
+```
+
+## Deployment
+
+Pushes to `main` run `.gitea/workflows/ci.yaml`.
+
+The pipeline:
+
+1. Checks repository hygiene.
+2. Builds the backend and frontend.
+3. Syncs the repository to the production host.
+4. Builds and recreates the Docker Compose `api` and `web` services.
+5. Writes a PostgreSQL predeploy backup.
+6. Applies EF Core migrations.
+7. Verifies live health, database connectivity and frontend assets.
+
+Production runs at `https://award.noveria.net`.
diff --git a/VTuber Star Awards - Website V2.docx b/VTuber Star Awards - Website V2.docx
deleted file mode 100644
index 3ac7544..0000000
Binary files a/VTuber Star Awards - Website V2.docx and /dev/null differ
diff --git a/frontend.zip b/frontend.zip
deleted file mode 100644
index 4c215ce..0000000
Binary files a/frontend.zip and /dev/null differ
diff --git a/prototype/assets/collector-editorial-reference.png b/prototype/assets/collector-editorial-reference.png
deleted file mode 100644
index 8ec4edc..0000000
Binary files a/prototype/assets/collector-editorial-reference.png and /dev/null differ
diff --git a/prototype/index.html b/prototype/index.html
deleted file mode 100644
index ff8af29..0000000
--- a/prototype/index.html
+++ /dev/null
@@ -1,269 +0,0 @@
-
-
-
-
-
- VTuber Star Awards
-
-
-
-
-
-
-
-
-
-
-
-
-
Die groesste Community-Auszeichnung
-
VTuber Star Awards
-
- Feiere die talentiertesten VTuber, Creator und Momente des Jahres -
- gewaehlt von der Community, fuer die Community.
-
-
-
-
-
-
-
-
-
-
- Jayuhime
- Host of the Show
-
-
-
-
-
-
-
Aktuelle Phase
-
Voting
-
- Stimme in deinen Lieblingskategorien ab und unterstuetze deine
- Favoriten.
-
-
-
-
14 Tage
-
07 Std
-
32 Min
-
45 Sek
-
-
-
-
-
- ✓
- Nominierung
- 01. Mai - 31. Mai
-
-
- ★
- Voting
- 01. Jun - 30. Jun
-
-
- ✉
- Auswertung
- 01. Jul - 10. Jul
-
-
- 🏆
- Award Show
- 24. Januar 2026
-
-
-
-
-
-
-
So funktioniert's
-
-
-
-
- ✒
- 1. Nominieren
- Nominiere deine Favoriten in jeder Kategorie.
-
-
- ☑
- 2. Voten
- Stimme ab und unterstuetze deine Lieblings-Creator.
-
-
- ✦
- 3. Ergebnisse
- Die Community entscheidet - 100% deiner Stimme zaehlt.
-
-
- 🏆
- 4. Award Show
- Feiere die Gewinner bei der grossen Live-Show.
-
-
-
-
-
-
-
-
-
-
-
- 2025
-
- Die letzten Gewinner. Sieh dir an, wer im letzten Jahr die
- Community begeistert hat.
-
- Archiv entdecken
-
-
-
-
- 2025
- 2024
- 2023
- 2022
-
-
-
-
-
-
-
-
-
-
Nominieren
-
Nominiere pro Kategorie bis zu 3 VTuber.
-
- Pro Kategorie nur 1 Nominierung fuer dieselbe Person
- Insgesamt nur 2 Nominierungen pro Person
- Live-Validierung & Feedback
-
-
Jetzt nominieren
-
-
-
-
-
-
-
Voten
-
Stimme in deinen Lieblingskategorien ab.
-
- Nur 1 Stimme pro Kategorie
- Twitch Login erforderlich
- Videos, Clips & Songs ansehen
-
-
Jetzt voten
-
-
-
-
-
-
-
-
-
Haeufige Fragen
-
-
-
-
-
-
-
Bleib up to date
-
-
- Erhalte Updates zu Phasen, Regeln und der Show direkt in dein
- Postfach.
-
-
-
- Abonnieren
-
-
- Twitch
- YouTube
- X
- Instagram
- TikTok
- Discord
-
-
-
-
-
-
-
-
-
-
-
diff --git a/prototype/script.js b/prototype/script.js
deleted file mode 100644
index 7db4fad..0000000
--- a/prototype/script.js
+++ /dev/null
@@ -1,157 +0,0 @@
-const categories = [
- {
- icon: "✦",
- color: "linear-gradient(160deg, #c49aef, #8b6ce7)",
- title: "VTuber des Jahres",
- text: "Die herausragendste VTuberin des Jahres 2025.",
- },
- {
- icon: "🎙",
- color: "linear-gradient(160deg, #d7e7ff, #8ea9ef)",
- title: "Bestes Live Event",
- text: "Das beste Live Event, das die Community begeistert hat.",
- },
- {
- icon: "🎬",
- color: "linear-gradient(160deg, #f7dfb2, #dba84b)",
- title: "Bester Content Creator",
- text: "Kreativitaet, Konsistenz und Impact in einem.",
- },
- {
- icon: "🌿",
- color: "linear-gradient(160deg, #e9f3bb, #a7c95d)",
- title: "Bester New VTuber",
- text: "Rising Stars, die dieses Jahr durchgestartet sind.",
- },
- {
- icon: "🤍",
- color: "linear-gradient(160deg, #ffd4e4, #f28ab1)",
- title: "Beste Community",
- text: "Die herzlichste und aktivste Community des Jahres.",
- },
-];
-
-const winners = {
- 2025: [
- ["Hoshimi Miyu", "VTuber des Jahres", "Conveyed"],
- ["Eclipse Euphoria", "Bestes Live Event", "Conveyed"],
- ["Kurainu", "Bester Content Creator", "Conveyed"],
- ["Lumi Airi", "Bester New VTuber", "Conveyed"],
- ["Pyonkichi Kingdom", "Beste Community", "Conveyed"],
- ],
- 2024: [
- ["Miyu Astra", "VTuber des Jahres", "Winner"],
- ["Nova Hall", "Bestes Live Event", "Winner"],
- ["Shiro Ch.", "Bester Content Creator", "Winner"],
- ["Airi Bloom", "Bester New VTuber", "Winner"],
- ["Moonrelay", "Beste Community", "Winner"],
- ],
- 2023: [
- ["Tenshi Vox", "VTuber des Jahres", "Winner"],
- ["Umi Nights", "Bestes Live Event", "Winner"],
- ["Rin Atelier", "Bester Content Creator", "Winner"],
- ["Starbyte", "Bester New VTuber", "Winner"],
- ["Luna Port", "Beste Community", "Winner"],
- ],
- 2022: [
- ["Aoi Sakura", "VTuber des Jahres", "Winner"],
- ["Kitsu Stage", "Bestes Live Event", "Winner"],
- ["Kohaku Live", "Bester Content Creator", "Winner"],
- ["Mira Veil", "Bester New VTuber", "Winner"],
- ["Neko Choir", "Beste Community", "Winner"],
- ],
-};
-
-const faqs = [
- ["Wer kann nominieren und voten?", "Jede Person mit Twitch Login. Das Konto wird bei der ersten Anmeldung im Hintergrund angelegt."],
- ["Wie oft kann ich abstimmen?", "Pro Kategorie eine Stimme. Bis zur Deadline kannst du deine Auswahl erneut bearbeiten und absenden."],
- ["Wie werden Gewinner bestimmt?", "Aktuell rein community-basiert. Eine spaetere Mischung mit Jury oder Panel bleibt offen."],
- ["Was ist mit Bots und Betrug?", "Missbrauchsschutz laeuft moeglichst unsichtbar: Twitch Login, Rate Limits und interne Risiko-Flags."],
- ["Kann das Team Kategorien aendern?", "Ja. Kategorien und Unterkategorien werden pro Jahr durch das Team gepflegt und freigeschaltet."],
-];
-
-function renderCategories() {
- const root = document.getElementById("category-grid");
- root.innerHTML = categories
- .map(
- (item) => `
-
- ${item.icon}
- ${item.title}
- ${item.text}
-
- `,
- )
- .join("");
-}
-
-function renderWinners(year = "2025") {
- const root = document.getElementById("winner-grid");
- const yearLabel = document.getElementById("archive-year-label");
- yearLabel.textContent = year;
-
- root.innerHTML = winners[year]
- .map(
- ([name, category, tag], index) => `
-
-
-
- ${name}
- ${category}
-
-
- `,
- )
- .join("");
-}
-
-function winnerGradient(index) {
- const gradients = [
- "linear-gradient(160deg, rgba(208,189,255,.78), rgba(255,231,198,.72))",
- "linear-gradient(160deg, rgba(151,128,212,.84), rgba(248,222,180,.52))",
- "linear-gradient(160deg, rgba(201,192,255,.82), rgba(250,215,168,.6))",
- "linear-gradient(160deg, rgba(196,221,255,.82), rgba(250,235,195,.68))",
- "linear-gradient(160deg, rgba(255,213,225,.82), rgba(249,225,176,.62))",
- ];
- return gradients[index % gradients.length];
-}
-
-function bindYears() {
- document.querySelectorAll(".year-pill").forEach((button) => {
- button.addEventListener("click", () => {
- document.querySelectorAll(".year-pill").forEach((pill) => pill.classList.remove("is-active"));
- button.classList.add("is-active");
- renderWinners(button.dataset.year);
- });
- });
-}
-
-function renderFaq() {
- const root = document.getElementById("faq-list");
- root.innerHTML = faqs
- .map(
- ([question, answer], index) => `
-
-
- ${question}
- +
-
-
-
- `,
- )
- .join("");
-
- root.querySelectorAll(".faq-item").forEach((item) => {
- item.querySelector(".faq-button").addEventListener("click", () => {
- item.classList.toggle("is-open");
- });
- });
-}
-
-renderCategories();
-renderWinners();
-bindYears();
-renderFaq();
diff --git a/prototype/styles.css b/prototype/styles.css
deleted file mode 100644
index 70e78c0..0000000
--- a/prototype/styles.css
+++ /dev/null
@@ -1,846 +0,0 @@
-:root {
- --bg: #fffdf9;
- --surface: #ffffff;
- --surface-soft: rgba(255, 255, 255, 0.78);
- --line: rgba(146, 121, 198, 0.18);
- --line-strong: rgba(146, 121, 198, 0.28);
- --text: #40395b;
- --muted: #7a7492;
- --purple: #7f5ad6;
- --purple-strong: #6841bb;
- --gold: #dfab4e;
- --gold-soft: #f5e0b6;
- --peach: #f3c4a0;
- --pink: #f0c0d8;
- --shadow: 0 28px 70px rgba(170, 151, 214, 0.13);
- --radius-xl: 34px;
- --radius-lg: 26px;
- --radius-md: 18px;
- --max: 1440px;
-}
-
-* {
- box-sizing: border-box;
-}
-
-html {
- scroll-behavior: smooth;
-}
-
-body {
- margin: 0;
- font-family: "Manrope", sans-serif;
- color: var(--text);
- background:
- radial-gradient(circle at 12% 18%, rgba(245, 224, 182, 0.22), transparent 24%),
- radial-gradient(circle at 86% 18%, rgba(196, 188, 255, 0.18), transparent 22%),
- linear-gradient(180deg, #fffefc 0%, #fffaf4 100%);
-}
-
-body::before {
- content: "";
- position: fixed;
- inset: 0;
- pointer-events: none;
- background:
- radial-gradient(circle at 14% 30%, rgba(216, 198, 255, 0.14) 0, transparent 9px),
- radial-gradient(circle at 80% 12%, rgba(255, 210, 171, 0.16) 0, transparent 8px),
- radial-gradient(circle at 72% 42%, rgba(196, 178, 255, 0.12) 0, transparent 7px);
- opacity: 0.9;
-}
-
-.site-frame {
- width: min(calc(100% - 32px), var(--max));
- margin: 0 auto;
- padding: 18px 0 40px;
-}
-
-.topbar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 24px;
- padding: 16px 22px;
- border-radius: 999px;
- border: 1px solid var(--line);
- background: rgba(255, 255, 255, 0.74);
- backdrop-filter: blur(18px);
- box-shadow: 0 16px 36px rgba(185, 163, 223, 0.08);
- position: sticky;
- top: 10px;
- z-index: 20;
-}
-
-.brand {
- display: inline-flex;
- align-items: center;
- gap: 12px;
- color: var(--text);
- text-decoration: none;
-}
-
-.brand-star {
- display: grid;
- place-items: center;
- width: 38px;
- height: 38px;
- border-radius: 14px;
- color: var(--gold);
- background: linear-gradient(135deg, rgba(223, 171, 78, 0.15), rgba(127, 90, 214, 0.08));
- font-size: 20px;
-}
-
-.brand-copy strong,
-.brand-copy small {
- display: block;
- letter-spacing: 0.18em;
-}
-
-.brand-copy strong {
- font-size: 0.95rem;
-}
-
-.brand-copy small {
- color: var(--muted);
- font-size: 0.64rem;
-}
-
-.nav {
- display: flex;
- gap: 24px;
-}
-
-.nav a,
-.footer a {
- color: var(--text);
- text-decoration: none;
-}
-
-.nav a {
- position: relative;
- padding: 6px 0;
- font-size: 0.93rem;
-}
-
-.nav a.is-active::after,
-.nav a:hover::after {
- content: "";
- position: absolute;
- left: 0;
- right: 0;
- bottom: -8px;
- height: 2px;
- border-radius: 999px;
- background: linear-gradient(90deg, var(--gold), var(--purple));
-}
-
-.topbar-actions {
- display: flex;
- gap: 10px;
-}
-
-.ghost-button,
-.primary-button,
-.secondary-button,
-.year-pill {
- border: 0;
- cursor: pointer;
- font: inherit;
-}
-
-.ghost-button,
-.primary-button,
-.secondary-button {
- border-radius: 14px;
- padding: 13px 18px;
- font-weight: 700;
-}
-
-.ghost-button {
- background: rgba(255, 255, 255, 0.8);
- border: 1px solid var(--line);
- color: var(--text);
-}
-
-.primary-button,
-.secondary-button {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- text-decoration: none;
-}
-
-.primary-button {
- background: linear-gradient(135deg, var(--purple), var(--purple-strong));
- color: white;
- box-shadow: 0 16px 30px rgba(122, 90, 194, 0.2);
-}
-
-.secondary-button {
- background: white;
- color: var(--gold);
- border: 1px solid rgba(223, 171, 78, 0.36);
-}
-
-.hero {
- display: grid;
- grid-template-columns: 0.92fr 1.08fr;
- align-items: center;
- gap: 36px;
- padding: 42px 8px 18px;
-}
-
-.eyebrow {
- margin: 0 0 10px;
- text-transform: uppercase;
- letter-spacing: 0.18em;
- font-size: 0.77rem;
- color: var(--gold);
- font-weight: 700;
-}
-
-.eyebrow.muted {
- color: var(--muted);
-}
-
-.hero h1,
-.section-title h2,
-.show-meta h2,
-.archive-lead h3,
-.phase-copy h3 {
- font-family: "Cormorant Garamond", serif;
-}
-
-.hero h1 {
- margin: 0 0 22px;
- font-size: clamp(4.3rem, 8vw, 7rem);
- line-height: 0.9;
- letter-spacing: -0.04em;
- color: var(--purple-strong);
- max-width: 520px;
-}
-
-.hero-body {
- max-width: 460px;
- font-size: 1.18rem;
- line-height: 1.75;
- color: #60597c;
-}
-
-.hero-cta {
- display: flex;
- gap: 14px;
- margin: 34px 0 40px;
-}
-
-.show-meta h2 {
- margin: 8px 0 6px;
- font-size: 2.2rem;
- color: var(--text);
-}
-
-.show-meta p:last-child {
- margin: 0;
- color: var(--muted);
-}
-
-.hero-art {
- position: relative;
- min-height: 720px;
-}
-
-.hero-illustration {
- position: absolute;
- inset: 0;
- border-radius: 42px;
- overflow: hidden;
- background:
- radial-gradient(circle at 50% 30%, rgba(255, 255, 255, 0.74), transparent 26%),
- linear-gradient(180deg, rgba(255, 255, 255, 0.66), rgba(255, 255, 255, 0.08));
-}
-
-.hero-illustration::before {
- content: "";
- position: absolute;
- inset: 0;
- border-radius: inherit;
- border: 1px solid rgba(223, 171, 78, 0.16);
- pointer-events: none;
-}
-
-.hero-illustration img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- object-position: 68% 18%;
- transform: scale(1.2);
- filter: saturate(1.02) brightness(1.02);
-}
-
-.hero-signature {
- position: absolute;
- right: 46px;
- bottom: 44px;
- display: grid;
- gap: 4px;
- text-align: right;
-}
-
-.hero-signature span {
- font-family: "Cormorant Garamond", serif;
- font-size: 3.5rem;
- font-style: italic;
- color: #7e62c9;
-}
-
-.hero-signature small {
- color: var(--muted);
- font-size: 1rem;
-}
-
-.phase-panel,
-.how-card,
-.archive-panel,
-.cta-panel,
-.faq-panel,
-.updates-panel {
- border-radius: var(--radius-xl);
- border: 1px solid var(--line);
- background: var(--surface-soft);
- backdrop-filter: blur(18px);
- box-shadow: var(--shadow);
-}
-
-.phase-panel {
- display: grid;
- grid-template-columns: 320px 1fr;
- gap: 32px;
- padding: 34px 38px;
-}
-
-.phase-copy h3 {
- margin: 8px 0 10px;
- font-size: 3rem;
- color: var(--purple-strong);
-}
-
-.phase-copy p {
- color: var(--muted);
- line-height: 1.7;
-}
-
-.timer {
- display: flex;
- gap: 10px;
- margin-top: 20px;
-}
-
-.timer div {
- min-width: 68px;
- padding: 12px 10px;
- border-radius: 16px;
- background: rgba(245, 239, 255, 0.9);
- text-align: center;
-}
-
-.timer strong {
- display: block;
- font-size: 1.9rem;
- color: var(--purple-strong);
-}
-
-.timer span {
- color: var(--muted);
- font-size: 0.78rem;
-}
-
-.phase-track {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- align-items: center;
- gap: 18px;
-}
-
-.phase-step {
- text-align: center;
- color: var(--muted);
-}
-
-.phase-step span {
- display: inline-grid;
- place-items: center;
- width: 62px;
- height: 62px;
- border-radius: 999px;
- margin-bottom: 14px;
- border: 1px solid var(--line-strong);
- background: rgba(255, 255, 255, 0.94);
- font-size: 1.25rem;
-}
-
-.phase-step.is-active span {
- color: white;
- background: linear-gradient(135deg, #9a78eb, #7f5ad6);
- box-shadow: 0 14px 28px rgba(127, 90, 214, 0.2);
-}
-
-.phase-step.is-complete span {
- color: var(--purple);
-}
-
-.phase-step strong,
-.phase-step small {
- display: block;
-}
-
-.phase-step strong {
- margin-bottom: 6px;
- color: var(--text);
-}
-
-.section {
- padding-top: 56px;
-}
-
-.section-title {
- margin-bottom: 28px;
-}
-
-.section-title h2 {
- margin: 0;
- font-size: clamp(2.1rem, 4vw, 3.2rem);
- color: var(--purple-strong);
-}
-
-.section-title.centered {
- text-align: center;
-}
-
-.section-title.split {
- display: flex;
- justify-content: space-between;
- align-items: end;
- gap: 20px;
-}
-
-.section-title.split a {
- color: var(--purple);
- text-decoration: none;
- font-weight: 600;
-}
-
-.how-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 20px;
-}
-
-.how-card {
- padding: 28px 22px;
- text-align: center;
-}
-
-.how-icon {
- width: 86px;
- height: 86px;
- margin: 0 auto 18px;
- border-radius: 999px;
- display: grid;
- place-items: center;
- font-size: 1.85rem;
- color: var(--gold);
- background:
- radial-gradient(circle at 35% 35%, rgba(255, 255, 255, 0.94), rgba(244, 234, 255, 0.82)),
- linear-gradient(135deg, rgba(223, 171, 78, 0.14), rgba(127, 90, 214, 0.06));
- border: 1px solid rgba(223, 171, 78, 0.22);
-}
-
-.how-card h3 {
- margin: 0 0 10px;
- color: var(--text);
-}
-
-.how-card p {
- margin: 0;
- color: var(--muted);
- line-height: 1.65;
-}
-
-.category-grid {
- display: grid;
- grid-template-columns: repeat(5, 1fr);
- gap: 18px;
-}
-
-.category-card {
- border-radius: 22px;
- padding: 24px 20px;
- border: 1px solid var(--line);
- background: rgba(255, 255, 255, 0.82);
- box-shadow: 0 20px 50px rgba(197, 177, 229, 0.1);
-}
-
-.category-orb {
- width: 92px;
- height: 92px;
- border-radius: 999px;
- display: grid;
- place-items: center;
- margin: 0 auto 18px;
- color: white;
- font-size: 2rem;
- box-shadow: inset 0 0 0 10px rgba(255, 255, 255, 0.4);
-}
-
-.category-card h3 {
- margin: 0 0 10px;
- text-align: center;
- font-family: "Cormorant Garamond", serif;
- font-size: 1.7rem;
- color: var(--text);
-}
-
-.category-card p {
- margin: 0;
- text-align: center;
- color: var(--muted);
- line-height: 1.6;
-}
-
-.archive-panel {
- display: grid;
- grid-template-columns: 280px 1fr;
- gap: 26px;
- padding: 28px;
-}
-
-.archive-lead {
- padding: 10px 8px;
-}
-
-.archive-lead h3 {
- margin: 0 0 10px;
- font-size: 4rem;
- color: var(--text);
-}
-
-.archive-lead p {
- color: var(--muted);
- line-height: 1.7;
- margin-bottom: 28px;
-}
-
-.year-pills {
- display: flex;
- gap: 10px;
- margin-bottom: 22px;
-}
-
-.year-pill {
- border-radius: 999px;
- padding: 10px 16px;
- background: rgba(255, 255, 255, 0.7);
- color: var(--muted);
- border: 1px solid var(--line);
- font-weight: 700;
-}
-
-.year-pill.is-active {
- background: linear-gradient(135deg, rgba(127, 90, 214, 0.12), rgba(223, 171, 78, 0.16));
- color: var(--purple-strong);
-}
-
-.winner-grid {
- display: grid;
- grid-template-columns: repeat(5, 1fr);
- gap: 14px;
-}
-
-.winner-card {
- border-radius: 18px;
- overflow: hidden;
- border: 1px solid var(--line);
- background: rgba(255, 255, 255, 0.86);
-}
-
-.winner-thumb {
- aspect-ratio: 0.92;
- padding: 12px;
- display: flex;
- align-items: flex-end;
- background: linear-gradient(160deg, rgba(207, 191, 255, 0.54), rgba(255, 227, 190, 0.48));
-}
-
-.winner-thumb::after {
- content: attr(data-tag);
- display: inline-flex;
- padding: 4px 8px;
- border-radius: 999px;
- background: rgba(255, 238, 195, 0.92);
- color: #936313;
- font-size: 0.7rem;
- font-weight: 800;
- text-transform: uppercase;
- letter-spacing: 0.08em;
-}
-
-.winner-info {
- padding: 14px;
-}
-
-.winner-info strong,
-.winner-info small {
- display: block;
-}
-
-.winner-info strong {
- color: var(--text);
- margin-bottom: 6px;
-}
-
-.winner-info small {
- color: var(--muted);
-}
-
-.cta-row,
-.lower-grid {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 22px;
-}
-
-.cta-panel {
- display: grid;
- grid-template-columns: 1fr 220px;
- gap: 22px;
- padding: 30px;
-}
-
-.cta-panel.accent {
- background:
- radial-gradient(circle at top right, rgba(255, 240, 212, 0.36), transparent 26%),
- rgba(255, 255, 255, 0.78);
-}
-
-.cta-copy h2 {
- margin: 0 0 16px;
- font-family: "Cormorant Garamond", serif;
- font-size: 2.3rem;
- color: var(--purple-strong);
-}
-
-.cta-copy ul {
- margin: 0 0 24px;
- padding-left: 20px;
- color: var(--muted);
- line-height: 1.8;
-}
-
-.placeholder-visual {
- position: relative;
- min-height: 260px;
- border-radius: 24px;
- display: grid;
- place-items: center;
- background:
- radial-gradient(circle at 50% 30%, rgba(255, 255, 255, 0.86), transparent 40%),
- linear-gradient(180deg, rgba(205, 188, 255, 0.34), rgba(255, 255, 255, 0.3));
-}
-
-.ballot-box::before,
-.trophy-visual::before {
- content: "";
- position: absolute;
- inset: 28px;
- border-radius: 28px;
- border: 1px solid rgba(127, 90, 214, 0.1);
-}
-
-.placeholder-card {
- width: 120px;
- height: 160px;
- border-radius: 22px;
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.86), rgba(147, 118, 220, 0.46));
- box-shadow: 0 18px 40px rgba(149, 119, 206, 0.18);
- position: relative;
-}
-
-.placeholder-card::before {
- content: "✦";
- position: absolute;
- inset: 0;
- display: grid;
- place-items: center;
- font-size: 2rem;
- color: white;
-}
-
-.trophy {
- width: 122px;
- height: 152px;
- position: relative;
- border-radius: 0 0 28px 28px;
- background: linear-gradient(180deg, #f2cc82, #d89e36);
- box-shadow: inset 0 0 0 10px rgba(255, 248, 224, 0.25);
-}
-
-.trophy::before {
- content: "";
- position: absolute;
- left: 50%;
- top: -78px;
- width: 0;
- height: 0;
- border-left: 46px solid transparent;
- border-right: 46px solid transparent;
- border-bottom: 78px solid #e1b158;
- transform: translateX(-50%);
-}
-
-.faq-panel,
-.updates-panel {
- padding: 30px;
-}
-
-.faq-list {
- display: grid;
-}
-
-.faq-item {
- border-bottom: 1px solid rgba(127, 90, 214, 0.12);
-}
-
-.faq-button {
- width: 100%;
- padding: 16px 0;
- display: flex;
- justify-content: space-between;
- align-items: center;
- background: transparent;
- border: 0;
- font: inherit;
- color: var(--text);
- cursor: pointer;
-}
-
-.faq-panel-copy {
- max-height: 0;
- overflow: hidden;
- color: var(--muted);
- transition: max-height 0.28s ease;
-}
-
-.faq-item.is-open .faq-panel-copy {
- max-height: 200px;
- padding-bottom: 14px;
-}
-
-.newsletter {
- display: grid;
- grid-template-columns: 1fr auto;
- gap: 12px;
- margin-top: 20px;
-}
-
-.newsletter input {
- padding: 14px 16px;
- border-radius: 14px;
- border: 1px solid var(--line);
- background: rgba(255, 255, 255, 0.82);
- font: inherit;
-}
-
-.social-row {
- display: flex;
- flex-wrap: wrap;
- gap: 12px;
- margin-top: 22px;
- color: var(--purple);
- font-weight: 600;
-}
-
-.footer {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 20px;
- padding: 34px 8px 10px;
- color: var(--muted);
- font-size: 0.92rem;
-}
-
-.footer-links {
- display: flex;
- gap: 18px;
-}
-
-@media (max-width: 1220px) {
- .hero,
- .phase-panel,
- .archive-panel,
- .cta-row,
- .lower-grid,
- .cta-panel,
- .how-grid,
- .category-grid,
- .winner-grid {
- grid-template-columns: 1fr;
- }
-
- .nav {
- flex-wrap: wrap;
- justify-content: center;
- }
-
- .topbar,
- .footer,
- .section-title.split {
- flex-direction: column;
- align-items: flex-start;
- }
-
- .hero-art {
- min-height: 560px;
- }
-}
-
-@media (max-width: 760px) {
- .site-frame {
- width: min(calc(100% - 18px), var(--max));
- }
-
- .topbar {
- border-radius: 30px;
- padding: 18px;
- }
-
- .topbar-actions,
- .hero-cta,
- .newsletter,
- .timer {
- width: 100%;
- grid-template-columns: 1fr;
- flex-direction: column;
- }
-
- .hero {
- padding-top: 28px;
- }
-
- .hero h1 {
- font-size: clamp(3.2rem, 17vw, 5rem);
- }
-
- .hero-art {
- min-height: 420px;
- }
-
- .hero-signature {
- right: 20px;
- bottom: 20px;
- }
-
- .hero-signature span {
- font-size: 2.6rem;
- }
-}
diff --git a/update_v2_docx_auth.py b/update_v2_docx_auth.py
deleted file mode 100644
index 289d512..0000000
--- a/update_v2_docx_auth.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from docx import Document
-from docx.shared import Pt
-
-
-DOC_PATH = "/Users/azu/Desktop/VTubeAwards/VTuber Star Awards - Website V2.docx"
-
-
-def add_paragraph(doc: Document, text: str, style: str | None = None):
- paragraph = doc.add_paragraph(style=style)
- paragraph.paragraph_format.space_after = Pt(5)
- paragraph.paragraph_format.line_spacing = 1.1
- paragraph.add_run(text)
-
-
-def main():
- doc = Document(DOC_PATH)
-
- doc.add_heading("26. Session- und Login-Stand", level=1)
- add_paragraph(
- doc,
- "Der aktuelle Entwicklungsstand enthaelt nun eine echte, datenbankgestuetzte Session-Schicht statt rein manueller Demo-User-IDs in den Produktflows. Fuer die Entwicklung wird ein leichtgewichtiges Dev-Login verwendet, das Viewer- und Admin-Sessions erstellt und damit die End-to-End-Kopplung zwischen Frontend, Backend und Datenbank abbildet.",
- )
- add_paragraph(doc, "Aktueller Umfang", style="Heading 2")
- add_paragraph(
- doc,
- "Viewer-Sessions schalten Nominierung und Voting frei; Admin-Sessions schalten den Admin-Bereich und die Season-/Category-Verwaltung frei. Die Public-Submit-Endpunkte koennen die User-Identitaet nun direkt aus der Session lesen, und die Admin-Endpunkte sind serverseitig gegen nicht autorisierte Zugriffe abgesichert.",
- )
- add_paragraph(doc, "Produktive Weiterentwicklung", style="Heading 2")
- add_paragraph(
- doc,
- "Diese Session-Schicht ist bewusst so angelegt, dass sie spaeter durch echten Twitch OAuth ersetzt werden kann, ohne den restlichen App-Flow neu modellieren zu muessen. Damit ist der MVP bereits als zusammenhaengende Application nutzbar, waehrend die produktive Authentifizierung als naechster Ausbaupunkt klar vorbereitet ist.",
- )
-
- doc.save(DOC_PATH)
-
-
-if __name__ == "__main__":
- main()
diff --git a/update_v2_docx_implementation.py b/update_v2_docx_implementation.py
deleted file mode 100644
index f97aaad..0000000
--- a/update_v2_docx_implementation.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from docx import Document
-from docx.shared import Pt
-
-
-DOC_PATH = "/Users/azu/Desktop/VTubeAwards/VTuber Star Awards - Website V2.docx"
-
-
-def add_paragraph(doc: Document, text: str, style: str | None = None):
- paragraph = doc.add_paragraph(style=style)
- paragraph.paragraph_format.space_after = Pt(5)
- paragraph.paragraph_format.line_spacing = 1.1
- paragraph.add_run(text)
-
-
-def main():
- doc = Document(DOC_PATH)
-
- doc.add_heading("24. Umsetzungsstand Sprint Update", level=1)
- add_paragraph(
- doc,
- "Dieses Sprint-Update dokumentiert den aktuellen technischen Umsetzungsstand fuer die zuvor priorisierten Punkte 1 und 3: lokales PostgreSQL-/Migration-Setup fuer die Entwicklung sowie die naehere Angleichung des Frontends an die freigegebene Collector-Editorial-Richtung.",
- )
-
- add_paragraph(doc, "Backend / Punkt 1", style="Heading 2")
- add_paragraph(
- doc,
- "Die erste EF-Core-Migration wurde bereits erzeugt. Zusaetzlich wurde das lokale Setup so vorbereitet, dass die API ihre PostgreSQL-Verbindung per appsettings oder Umgebungsvariable VTSA_POSTGRES beziehen kann. Fuer die lokale Entwicklung wurde ausserdem eine optionale docker-compose.dev.yml fuer PostgreSQL hinterlegt. Ein eigener Health-Endpunkt fuer die Datenbank liefert den Verbindungsstatus sowie eventuell noch ausstehende Migrationen.",
- )
-
- add_paragraph(doc, "Frontend / Punkt 3", style="Heading 2")
- add_paragraph(
- doc,
- "Die Startseite und zentrale Produktflaechen wurden visuell weiter in Richtung Collector Editorial verfeinert: mehr White Space, ruhigere Karten, staerkere typografische Hierarchie, eine hochwertigere AppShell sowie ein klarer getrenntes, host-getriebenes Hero-Panel. Auch Nominierungs-, Voting- und Admin-Flaechen wurden in dieselbe Formsprache ueberfuehrt, damit die Seite nicht nur auf der Landingpage, sondern ueber den gesamten MVP hinweg konsistent premium wirkt.",
- )
-
- add_paragraph(doc, "Offene technische Hinweise", style="Heading 2")
- add_paragraph(
- doc,
- "In der aktuellen Arbeitsumgebung konnte keine laufende lokale PostgreSQL-Instanz gestartet werden, da Docker bzw. lokale PostgreSQL-Binaries hier nicht verfuegbar waren. Der Code- und Setup-Stand ist jedoch so vorbereitet, dass die Migrationen direkt angewendet werden koennen, sobald lokal eine Datenbankinstanz bereitsteht.",
- )
-
- doc.save(DOC_PATH)
-
-
-if __name__ == "__main__":
- main()
diff --git a/update_v2_docx_runtime.py b/update_v2_docx_runtime.py
deleted file mode 100644
index 3b81a59..0000000
--- a/update_v2_docx_runtime.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from docx import Document
-from docx.shared import Pt
-
-
-DOC_PATH = "/Users/azu/Desktop/VTubeAwards/VTuber Star Awards - Website V2.docx"
-
-
-def add_paragraph(doc: Document, text: str, style: str | None = None):
- paragraph = doc.add_paragraph(style=style)
- paragraph.paragraph_format.space_after = Pt(5)
- paragraph.paragraph_format.line_spacing = 1.1
- paragraph.add_run(text)
-
-
-def main():
- doc = Document(DOC_PATH)
-
- doc.add_heading("25. Laufzeitstand Application", level=1)
- add_paragraph(
- doc,
- "Der MVP steht nun als zusammenhaengende Application aus Frontend, Backend und PostgreSQL-Entwicklungsdatenbank. Die Public API laeuft gegen die lokale Dev-Datenbank, Frontend-Build und Backend-Build kompilieren erfolgreich, und die wichtigsten Produktfluesse wurden gegen die laufende API verifiziert.",
- )
- add_paragraph(doc, "Verifizierte Fluesse", style="Heading 2")
- add_paragraph(
- doc,
- "Public Overview, Kategorieauslieferung, Nominierungs-Submit und Voting-Submit wurden erfolgreich gegen die lokale API geprueft. Zusaetzlich wurden Admin-Dashboard, Seasons-Liste, Season-Detail sowie Season-Update und Category-Create gegen die laufende Dev-Datenbank verifiziert.",
- )
- add_paragraph(doc, "Admin-Umfang im aktuellen Stand", style="Heading 2")
- add_paragraph(
- doc,
- "Das Admin-Panel bildet aktuell Kennzahlen, Season-Auswahl, Phase/Current-Status sowie die Pflege der Award-Kategorien pro Jahr ab. Damit ist die in diesem Dokument festgelegte team-gesteuerte Season-/Category-Verwaltung fuer den MVP technisch umgesetzt.",
- )
- add_paragraph(doc, "Lokales Datenbank-Setup", style="Heading 2")
- add_paragraph(
- doc,
- "Das Projekt verwendet lokal eine eigene PostgreSQL-Entwicklungsinstanz auf Port 5433, um Kollisionen mit bereits laufenden lokalen Datenbanken zu vermeiden. Fuer die Erstinitialisierung existieren sowohl EF-Core-Migrationen als auch ein manuelles SQL-Bootstrap-Skript als Fallback.",
- )
-
- doc.save(DOC_PATH)
-
-
-if __name__ == "__main__":
- main()
diff --git a/update_v2_docx_stack.py b/update_v2_docx_stack.py
deleted file mode 100644
index 7635901..0000000
--- a/update_v2_docx_stack.py
+++ /dev/null
@@ -1,137 +0,0 @@
-from docx import Document
-from docx.shared import Pt
-
-
-DOC_PATH = "/Users/azu/Desktop/VTubeAwards/VTuber Star Awards - Website V2.docx"
-
-
-def add_paragraph(doc: Document, text: str):
- p = doc.add_paragraph()
- p.paragraph_format.space_after = Pt(6)
- p.paragraph_format.line_spacing = 1.1
- p.add_run(text)
-
-
-def add_bullet(doc: Document, text: str):
- p = doc.add_paragraph(style="List Bullet")
- p.paragraph_format.space_after = Pt(4)
- p.paragraph_format.line_spacing = 1.15
- p.add_run(text)
-
-
-def add_number(doc: Document, text: str):
- p = doc.add_paragraph(style="List Number")
- p.paragraph_format.space_after = Pt(4)
- p.paragraph_format.line_spacing = 1.15
- p.add_run(text)
-
-
-def add_table(doc: Document, headers: list[str], rows: list[list[str]]):
- table = doc.add_table(rows=1, cols=len(headers))
- table.style = "Table Grid"
- for index, header in enumerate(headers):
- table.rows[0].cells[index].text = header
- for row in rows:
- cells = table.add_row().cells
- for index, value in enumerate(row):
- cells[index].text = value
-
-
-def main():
- doc = Document(DOC_PATH)
-
- doc.add_heading("15. Festgelegte Produktentscheidungen seit V2", level=1)
- add_bullet(doc, "Award-Kategorien und Unterkategorien werden nicht hart im Code festgelegt, sondern vom Team pro Jahr im Admin-Bereich gepflegt.")
- add_bullet(doc, "Das Endergebnis ist fuer den aktuellen MVP-Stand rein community-basiert. Eine spaetere Jury-/Panel-Gewichtung bleibt technisch als Erweiterung offen.")
- add_bullet(doc, "Die technische Umsetzung wird als Monorepo mit getrenntem Frontend und Backend aufgebaut.")
- add_bullet(doc, "Dieses Dokument bleibt die fachliche Referenz fuer weitere Iterationen. Code und Architektur sollen sich an den hier beschriebenen Produktregeln orientieren.")
-
- doc.add_heading("16. Finaler Tech-Stack fuer die Umsetzung", level=1)
- add_paragraph(
- doc,
- "Die Implementierung wird in zwei klar getrennten Anwendungen aufgebaut: ein Vue-basiertes Frontend fuer Public- und Admin-Oberflaechen sowie ein ASP.NET-Core-Backend fuer API, Datenmodell und Persistenz. Damit bleibt die Awards-Plattform spaeter erweiterbar fuer Auth, Moderation, CMS und Analytics."
- )
- add_table(
- doc,
- ["Schicht", "Technologie", "Rolle im System"],
- [
- ["Frontend", "Vue 3 + Vite", "SPA fuer Landingpage, Voting, Nominierung, Gewinnerarchiv und Admin-Oberflaechen"],
- ["State", "Pinia", "Zentraler Client-State fuer Overview-, Voting-, Nominierungs- und Admin-Daten"],
- ["Styling", "Tailwind CSS", "Token-basierte Layout-, Spacing- und Komponenten-Styling-Schicht"],
- ["UI Library", "PrimeVue", "Tabellen, Accordions, Form Controls und datenlastige Admin-Bausteine"],
- ["UI Patterns", "shadcn-inspirierte Vue Komponenten", "Buttons, Cards und leichtgewichtige, konsistente Primitive fuer das Designsystem"],
- ["Backend", "ASP.NET Core 8", "REST API, Domänenlogik, Validierung und Integrationsschicht"],
- ["ORM", "EF Core 8", "Mapping des Awards-Datenmodells und Query-/Persistenzlogik"],
- ["Datenbank", "PostgreSQL", "Primäre relationale Persistenz fuer Seasons, Kategorien, Kandidaten, Nominierungen und Votes"],
- ],
- )
-
- doc.add_heading("17. Frontend-Architektur", level=1)
- add_paragraph(
- doc,
- "Das Frontend wird als Vite-Vue-Anwendung mit Router und Pinia aufgebaut. Die oeffentlichen Bereiche und der Admin-Bereich teilen sich dieselbe Design-Basis, werden aber als getrennte Views organisiert. PrimeVue kommt dort zum Einsatz, wo datenlastige oder interaktive Controls den Entwicklungsaufwand senken; leichtgewichtige Basis-UI wie Buttons und Cards werden als shadcn-artige Vue-Primitives lokal gepflegt."
- )
- add_number(doc, "AppShell mit globaler Navigation, Branding und CTA-Struktur fuer oeffentliche und interne Oberflaechen.")
- add_number(doc, "Router-basierte Views fuer Home, Nominierung, Voting, Gewinner und Admin.")
- add_number(doc, "Pinia Store als API-Orchestrator fuer Overview, Saisonkategorien, Gewinnerarchiv und Admin Dashboard.")
- add_number(doc, "API-Layer mit konfigurierbarer Backend-URL ueber Vite-Environment-Variable.")
- add_number(doc, "Fallback-Daten im Frontend, damit UI-Entwicklung und Demo-Zustaende nicht vom sofort verfuegbaren Backend abhaengen.")
-
- doc.add_heading("18. Backend-Architektur", level=1)
- add_paragraph(
- doc,
- "Das Backend wird als ASP.NET-Core-Web-API mit EF Core 8 und PostgreSQL aufgebaut. Die erste Version priorisiert ein sauberes Domänenmodell und lesbare Public-/Admin-Endpunkte vor Auth-, Moderations- oder CMS-Komplexität. Die API ist damit spaeter gut erweiterbar fuer Twitch OAuth, Session-Management, Missbrauchsschutz und Review-Workflows."
- )
- add_table(
- doc,
- ["Bereich", "Implementationsidee"],
- [
- ["Public API", "Overview fuer Landingpage, saisonbezogene Kategorien, Gewinnerarchiv, Nominierungs- und Voting-Endpunkte"],
- ["Admin API", "Dashboard-Metriken, Aktivitaeten und spaeter Management-Endpunkte fuer Kategorien, Reviews und Risk Flags"],
- ["Persistenz", "EF-Core-DbContext mit PostgreSQL Provider und relationalem Datenmodell"],
- ["Seed Data", "Starterdaten fuer aktuelle und vergangene Seasons, Kategorien, Kandidaten und Gewinner"],
- ["CORS", "Explizite Freigabe fuer lokale Frontend-Hosts waehrend der Entwicklung"],
- ],
- )
-
- doc.add_heading("19. Finales Datenmodell fuer den MVP", level=1)
- add_bullet(doc, "Season: Jahr, Phase, Show-Datum, Community-only-Flag und Zeitfenster fuer Nominierung/Voting/Review.")
- add_bullet(doc, "Category: saisongebundene Kategorie mit Grouping, Beschreibung, Sortierung und Nominierungslimits.")
- add_bullet(doc, "Candidate: saison- und kategorienbezogene Kandidatenbasis fuer Voting und Archiv.")
- add_bullet(doc, "AwardResult: Gewinnerabbildung fuer Archiv und Rueckblicke.")
- add_bullet(doc, "Nomination: vom User eingereichte Nominierungen inkl. Freitext-/Alias-Faellen.")
- add_bullet(doc, "VoteBallot + VoteEntry: eingereichte Votes pro User und Kategorie.")
-
- doc.add_heading("20. API-Schnittstellen fuer den aktuellen Stand", level=1)
- add_bullet(doc, "GET /api/public/overview - Landingpage- und Event-Zusammenfassung.")
- add_bullet(doc, "GET /api/public/seasons/{year}/categories - Kategorien und Kandidaten eines Jahres.")
- add_bullet(doc, "GET /api/public/seasons/{year}/winners - Gewinnerarchiv je Jahr.")
- add_bullet(doc, "POST /api/public/nominations - Speichern von Nominierungs-Entwuerfen/Einreichungen.")
- add_bullet(doc, "POST /api/public/votes - Speichern eines Voting-Ballots.")
- add_bullet(doc, "GET /api/admin/dashboard - Kennzahlen und Aktivitaeten fuer die Admin-Uebersicht.")
-
- doc.add_heading("21. Projektstruktur im Repository", level=1)
- add_bullet(doc, "frontend/ - Vue 3 + Vite Anwendung")
- add_bullet(doc, "Backend/ - ASP.NET Core 8 Web API")
- add_bullet(doc, "prototype/ - fruehere visuelle Prototyping-Artefakte und Designexperimente")
- add_bullet(doc, "README.md - lokaler Startpunkt fuer Setup und Entwicklungsworkflow")
-
- doc.add_heading("22. Implementierungsstatus", level=1)
- add_bullet(doc, "Frontend-Grundgeruest aufgebaut: Router, Pinia Store, AppShell, Public Views und Admin View.")
- add_bullet(doc, "PrimeVue und Tailwind in die Frontend-Basis integriert.")
- add_bullet(doc, "shadcn-artige Basis-Komponenten fuer Buttons und Cards lokal angelegt.")
- add_bullet(doc, "Backend-Web-API mit EF-Core-Domänenmodell, Seed-Daten und Public-/Admin-Endpunkten implementiert.")
- add_bullet(doc, "Frontend und Backend kompilieren erfolgreich. Datenbank-Migrationen und produktive Auth-/Session-Flows sind der naechste technische Schritt.")
-
- doc.add_heading("23. Empfohlene naechste technische Schritte", level=1)
- add_number(doc, "PostgreSQL lokal oder in einer Dev-Umgebung bereitstellen und erste EF-Core-Migration erzeugen.")
- add_number(doc, "Twitch OAuth serverseitig ins Backend integrieren und Session-Management anschliessen.")
- add_number(doc, "Nominierungs- und Voting-Endpunkte um echte User-Identitaet, Limits und Idempotenz erweitern.")
- add_number(doc, "Admin-Endpunkte fuer Season-/Category-Management, Reviews und Risk Flags ausbauen.")
- add_number(doc, "Frontend-Design auf final freigegebene visuelle Richtung und Assets angleichen.")
-
- doc.save(DOC_PATH)
-
-
-if __name__ == "__main__":
- main()