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:
parent
205830f474
commit
efbda20e42
1 changed files with 38 additions and 40 deletions
|
|
@ -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">✓</span>
|
<span class="mark-series-icon">✓</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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue