feat(design): the page band (B4) — one h1 per page, section headings, skip to content, filters fold on a short window (E28 S3d4)

- PageShell names the page with a visually hidden h1; a view with its own title renders it as
  PageHeading and the hidden one steps aside (contract: no hand <h1> inside the shell).
- Section captions are SectionLabel headings (h2, h3 in a dialog), ui/form Section included.
- "Skip to content" is the first Tab stop and focuses the scroller's <main>.
- Below 600px of height the band's filter row folds into "Filters · n" (Feed, Plex, server log),
  the same row shown in place.
- The band holds controls only: Downloads and Notifications lose their title cards, the Config and
  channel-discovery intros scroll with the content, the Stats tabs and the audit pager join the band,
  and the server log's toolbar sticks with the fade under it.
This commit is contained in:
2026-09-26 00:39:01 +02:00
parent 69edeecc45
commit eddeca82d3
41 changed files with 669 additions and 319 deletions
+4 -3
View File
@@ -30,6 +30,7 @@ import NavGrid from "./ui/NavGrid";
import { Chip } from "./ui/Chip";
import { Pressable } from "./ui/Pressable";
import { SURFACE } from "./ui/surfaces";
import { PageHeading } from "./ui/Headings";
// The Plex sub-router's view branches, split out of PlexBrowse. Each is invoked by PlexBrowse with
// shell callbacks as props (onBack/onPlay/onFilter/…) — no view reaches back into the shell's state,
@@ -199,7 +200,7 @@ export function PlexPersonView({
)}
</div>
<div className="min-w-0 flex-1">
<h1 className="text-xl font-semibold">{d.name}</h1>
<PageHeading className="text-xl font-semibold">{d.name}</PageHeading>
{d.known_for_department && (
<p className="text-xs font-medium text-accent">{d.known_for_department}</p>
)}
@@ -393,7 +394,7 @@ export function PlexShowView({
/>
}
>
<h1 className="text-2xl font-semibold leading-tight">{show.title}</h1>
<PageHeading className="text-2xl font-semibold leading-tight">{show.title}</PageHeading>
<div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm text-muted">
{show.year != null && (
<Filterable
@@ -655,7 +656,7 @@ export function PlexSeasonView({
<Button variant="link-muted" onClick={onBack} className="text-sm">
{d.show.title}
</Button>
<h1 className="text-2xl font-semibold leading-tight">{se.title}</h1>
<PageHeading className="text-2xl font-semibold leading-tight">{se.title}</PageHeading>
<p className="text-sm text-muted mt-1">
{t("plex.series.episodeCount", { count: se.episode_count })}
</p>