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

- - - -
-

Die Show findet statt am

-

24. Januar 2026

-

Live auf YouTube & Twitch

-
-
- -
-
- -
-
- Jayuhime - Host of the Show -
-
-
- -
-
-

Aktuelle Phase

-

Voting

-

- Stimme in deinen Lieblingskategorien ab und unterstuetze deine - Favoriten. -

- -
-
14Tage
-
07Std
-
32Min
-
45Sek
-
-
- -
-
- - 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.

-
-
-
- - - -
-
-

Gewinner Archiv

- Alle Jahre ansehen → -
- -
- - -
-
- - - - -
-
-
-
-
- -
-
-
-

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

- - -
-
-
- - -
- - - - 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) => ` -
- -
-

${answer}

-
-
- `, - ) - .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()