docs: Spec-Korrekturen nach Review

- Jinja2-Variante entfernt (nur JS-Lösung)
- i18n: bestehenden Key status.mark_series verwenden
- Responsive Breakpoint 1200px berücksichtigt
- opacity-Fix nur für TV (hover:none Media-Query)
- Änderung 2/2b als atomar markiert
- focusin-Handler Sidebar-Ausschluss dokumentiert

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-03-16 10:40:29 +01:00
parent 205830f474
commit efbda20e42

View file

@ -30,30 +30,7 @@ Die Sidebar rendert alle 26 Buchstaben + `#` (27 Einträge × 36px = 972px). Buc
### Lösung ### Lösung
Im Template `series.html` (Zeile 161-167): Nur Buchstaben rendern, die tatsächlich Serien haben. Das Backend liefert bereits die Serien mit `data-letter` Attribut — wir sammeln die verfügbaren Buchstaben serverseitig oder per Jinja2-Filter. Im Template `series.html` (Zeile 161-167): Nur Buchstaben rendern, die tatsächlich Serien haben. Das Backend liefert bereits die Serien mit `data-letter` Attribut — wir sammeln die verfügbaren Buchstaben serverseitig oder per Jinja2-Filter.
**Ansatz:** Die verfügbaren Buchstaben werden im Template aus den Serien-Daten extrahiert: **Ansatz:** Per JavaScript im bestehenden IIFE — Buchstaben ohne Treffer komplett entfernen statt dimmen. Das bestehende Script (series.html, Zeile 244-253) wird angepasst:
```html
<!-- Verfügbare Buchstaben sammeln -->
{% set available_letters = [] %}
{% for s in series %}
{% set letter = s.sort_title[0:1]|upper if s.sort_title else '#' %}
{% set letter = letter if letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' else '#' %}
{% if letter not in available_letters %}
{% do available_letters.append(letter) %}
{% endif %}
{% endfor %}
<nav class="tv-alpha-sidebar" id="alpha-sidebar" ...>
{% for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#' %}
{% if letter in available_letters %}
<span class="tv-alpha-letter" data-letter="{{ letter }}"
onclick="filterByLetter('{{ letter }}')" data-focusable>{{ letter }}</span>
{% endif %}
{% endfor %}
</nav>
```
**Fallback:** Falls Jinja2 `{% do %}` nicht unterstützt wird, die Filterung per JavaScript im bestehenden IIFE durchführen:
```javascript ```javascript
// Buchstaben ohne Treffer komplett entfernen (statt nur dimmen) // Buchstaben ohne Treffer komplett entfernen (statt nur dimmen)
@ -69,7 +46,7 @@ Im Template `series.html` (Zeile 161-167): Nur Buchstaben rendern, die tatsächl
})(); })();
``` ```
**Bevorzugter Ansatz:** JavaScript-Variante — einfacher, kein Backend-Change nötig. Kein Backend-Change nötig, kein Jinja2-Template-Change.
## Änderung 2: Alphabet-Sidebar per D-Pad erreichbar machen ## Änderung 2: Alphabet-Sidebar per D-Pad erreichbar machen
@ -153,6 +130,8 @@ if (!inNav && !inSidebar && direction === "ArrowRight") {
} }
``` ```
**WICHTIG: Beide folgenden Änderungen sind atomar — sie müssen zusammen eingefügt werden!**
Zusätzlich: Die Sidebar-Elemente aus der normalen Nearest-Neighbor-Suche ausschließen (Zeile 240-243 erweitern): Zusätzlich: Die Sidebar-Elemente aus der normalen Nearest-Neighbor-Suche ausschließen (Zeile 240-243 erweitern):
```javascript ```javascript
@ -161,20 +140,37 @@ const searchEls = inNav
: focusables.filter(el => !el.closest("#tv-nav") && !el.closest(".tv-alpha-sidebar")); : focusables.filter(el => !el.closest("#tv-nav") && !el.closest(".tv-alpha-sidebar"));
``` ```
Außerdem: Im `focusin`-Handler (tv.js) die Sidebar aus dem `_lastContentFocus`-Tracking ausschließen, damit der ArrowLeft-Rücksprung zuverlässig funktioniert:
```javascript
// Im focusin-Handler: Sidebar-Elemente nicht als Content-Focus speichern
if (!el.closest("#tv-nav") && !el.closest(".tv-alpha-sidebar")) {
this._lastContentFocus = el;
}
```
**Enter-Handling:** Wenn der Nutzer in der Sidebar Enter drückt, simuliert der FocusManager einen Click auf das `data-focusable`-Element, was `onclick="filterByLetter()"` auslöst. Kein zusätzlicher Code nötig.
## Änderung 3: Episoden-Cards vergrößern ## Änderung 3: Episoden-Cards vergrößern
### Problem ### Problem
Das Episode-Grid nutzt `minmax(180px, 1fr)` (tv.css, Zeile 1821). Auf einem 1080p-TV sind die Cards zu klein und die Laufzeit-Badge wird abgeschnitten. Das Episode-Grid nutzt `minmax(180px, 1fr)` (tv.css, Zeile 1821). Auf einem 1080p-TV sind die Cards zu klein und die Laufzeit-Badge wird abgeschnitten.
### Lösung ### Lösung
Grid-Spalten vergrößern und Laufzeit-Badge anpassen: Grid-Spalten vergrößern und Laufzeit-Badge anpassen. **Achtung:** Es gibt einen `@media (min-width: 1200px)` Breakpoint (tv.css, Zeile 2019-2020) der auf `minmax(220px, 1fr)` setzt — dieser muss ebenfalls angepasst werden.
```css ```css
/* Basis (tv.css, Zeile 1821) */
.tv-episode-grid { .tv-episode-grid {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); /* war 180px */ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); /* war 180px */
gap: 1rem; /* war 0.8rem */ gap: 1rem; /* war 0.8rem */
} }
/* Responsive: 1200px+ Breakpoint (tv.css, Zeile 2019-2020) — anpassen */
@media (min-width: 1200px) {
.tv-episode-grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); } /* war 220px */
}
.tv-ep-duration { .tv-ep-duration {
font-size: 0.8rem; /* war 0.7rem */ font-size: 0.8rem; /* war 0.7rem */
padding: 3px 8px; /* war 2px 6px */ padding: 3px 8px; /* war 2px 6px */
@ -202,11 +198,14 @@ Der Mark-Button (series_detail.html, Zeile 129-132) hat `tabindex="-1"` und kein
- `tabindex="-1"` entfernen - `tabindex="-1"` entfernen
- `data-focusable` hinzufügen - `data-focusable` hinzufügen
**CSS** (tv.css): Mark-Button auf TV immer sichtbar machen: **CSS** (tv.css): Mark-Button auf TV immer sichtbar machen. Der Base-Wert `opacity: 0` bleibt für Desktop/Mobile erhalten — nur auf TV (kein Hover-Support) wird der Button dauerhaft sichtbar:
```css ```css
/* Mark-Button auf TV immer sichtbar (halbtransparent) */ /* TV-Geräte (kein Hover): Mark-Button immer leicht sichtbar */
.tv-ep-tile-mark { @media (hover: none) {
opacity: 0.5; /* war 0 — immer leicht sichtbar */ .tv-ep-tile-mark {
opacity: 0.5;
}
} }
.tv-episode-tile:hover .tv-ep-tile-mark, .tv-episode-tile:hover .tv-ep-tile-mark,
@ -242,7 +241,7 @@ Es gibt nur `markSeasonWatched()` pro Staffel (series_detail.html, Zeile 96-99).
data-series-id="{{ series.id }}" data-series-id="{{ series.id }}"
onclick="markSeriesWatched(this)"> onclick="markSeriesWatched(this)">
<span class="mark-series-icon">&#10003;</span> <span class="mark-series-icon">&#10003;</span>
<span class="mark-series-text">{{ t('series.mark_all_watched') }}</span> <span class="mark-series-text">{{ t('status.mark_series') }}</span>
</button> </button>
</div> </div>
``` ```
@ -328,25 +327,24 @@ function markSeriesWatched(btn) {
} }
``` ```
**i18n** — Neuer Übersetzungsschlüssel: **i18n** — Bestehender Schlüssel `status.mark_series` wird verwendet:
``` - `de.json`: `"mark_series": "Serie als gesehen"` (bereits vorhanden)
series.mark_all_watched = "Serie als gesehen" (de_DE) - `en.json`: `"mark_series": "Mark series as watched"` (bereits vorhanden)
series.mark_all_watched = "Mark series watched" (en_US)
``` Kein neuer i18n-Key nötig.
## Zusammenfassung der Änderungen ## Zusammenfassung der Änderungen
| # | Datei | Art | Beschreibung | | # | Datei | Art | Beschreibung |
|---|-------|-----|-------------| |---|-------|-----|-------------|
| 1 | `series.html` | JS ändern | Buchstaben ohne Serien per JS entfernen statt dimmen | | 1 | `series.html` | JS ändern | Buchstaben ohne Serien per JS entfernen statt dimmen |
| 2 | `tv.js` | Code einfügen | Explizite Sidebar-Navigation (ArrowRight→Sidebar, ArrowLeft→Content, sequentiell Up/Down) | | 2 | `tv.js` | Code einfügen | **ATOMAR:** Explizite Sidebar-Navigation + Sidebar aus Nearest-Neighbor ausschließen + focusin-Handler anpassen |
| 2b | `tv.js` | Code ändern | Sidebar aus Nearest-Neighbor-Suche ausschließen |
| 3 | `tv.css` | CSS ändern | Episode-Grid: `minmax(240px, 1fr)`, Laufzeit-Badge größer | | 3 | `tv.css` | CSS ändern | Episode-Grid: `minmax(240px, 1fr)`, Laufzeit-Badge größer |
| 4 | `series_detail.html` | HTML ändern | Mark-Button: `tabindex="-1"``data-focusable` | | 4 | `series_detail.html` | HTML ändern | Mark-Button: `tabindex="-1"``data-focusable` |
| 4b | `tv.css` | CSS ändern | Mark-Button: `opacity: 0.5` statt `0`, Focus-Ring | | 4b | `tv.css` | CSS ändern | Mark-Button: `opacity: 0.5` nur auf TV (hover:none), Focus-Ring |
| 5 | `series_detail.html` | HTML+JS einfügen | "Serie als gesehen"-Button + `markSeriesWatched()` | | 5 | `series_detail.html` | HTML+JS einfügen | "Serie als gesehen"-Button + `markSeriesWatched()` |
| 5b | `tv.css` | CSS einfügen | Styling für `.tv-mark-series-btn` | | 5b | `tv.css` | CSS einfügen | Styling für `.tv-mark-series-btn` |
| 5c | i18n | Key einfügen | `series.mark_all_watched` | | 5c | i18n | — | Bestehender Key `status.mark_series` wird verwendet (kein Change) |
## Risiken & Hinweise ## Risiken & Hinweise