Web frontend: hide app title and row titles, regroup help sheet, drop Ctrl+d/u
- New SHOW_APP_TITLE switch (off): the mascot + title header goes, the status pill stays on a slim row of its own. - SHOW_ROW_TITLES off; with only icons, the shelf icon moves into a column left of the tiles so an icon-only shelf is no taller than one without. - Help sheet regrouped into sections in a compact two-column grid, and reachable from a small keyboard button (not on phones or the typing page). - Ctrl+d/Ctrl+u half-page jumps removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { SHOW_APP_TITLE } from "../lib/theme";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
mascot?: string;
|
||||
@@ -15,6 +17,25 @@ export function AppHeader({
|
||||
status,
|
||||
compact = false,
|
||||
}: Props) {
|
||||
if (!SHOW_APP_TITLE) {
|
||||
// Still clear the translucent standalone-mode status bar, and keep the status
|
||||
// pill - it is the only place an offline connection shows on these screens.
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
padding: status
|
||||
? `calc(${compact ? 8 : 10}px + env(safe-area-inset-top)) 16px 0`
|
||||
: "env(safe-area-inset-top) 0 0",
|
||||
flex: "none",
|
||||
}}
|
||||
>
|
||||
{status}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const mascotSize = compact ? 44 : 64;
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -225,15 +225,20 @@ function BrowseViewImpl({
|
||||
? "4px solid var(--paper)"
|
||||
: undefined,
|
||||
outlineOffset: 2,
|
||||
// Without a title the icon has no header to sit in, so it takes a column
|
||||
// of its own beside the tiles rather than a row above them.
|
||||
...(SHOW_ROW_ICONS && !SHOW_ROW_TITLES
|
||||
? { display: "flex", alignItems: "center", gap: 12 }
|
||||
: {}),
|
||||
...glassTint(GROUP_HUE[shelfGroup]),
|
||||
}}
|
||||
>
|
||||
{(SHOW_ROW_TITLES || SHOW_ROW_ICONS) && (
|
||||
{SHOW_ROW_TITLES && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: SHOW_ROW_TITLES && SHOW_ROW_ICONS ? 10 : 6,
|
||||
gap: 10,
|
||||
marginBottom: 12,
|
||||
fontSize: 20,
|
||||
fontWeight: 900,
|
||||
@@ -241,39 +246,44 @@ function BrowseViewImpl({
|
||||
}}
|
||||
>
|
||||
{SHOW_ROW_ICONS && <span className="group-icon">{GROUP_ICON[shelfGroup]}</span>}
|
||||
{SHOW_ROW_TITLES && GROUP_LABEL[shelfGroup]}
|
||||
{SHOW_ROW_TITLES && <span style={{ opacity: 0.6, fontSize: 17 }}>›</span>}
|
||||
{GROUP_LABEL[shelfGroup]}
|
||||
<span style={{ opacity: 0.6, fontSize: 17 }}>›</span>
|
||||
</div>
|
||||
)}
|
||||
{shelfCategories.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
fontSize: 14,
|
||||
fontWeight: 700,
|
||||
color: "oklch(88% 0.02 210 / .6)",
|
||||
}}
|
||||
>
|
||||
Noch nichts hier
|
||||
</div>
|
||||
) : (
|
||||
<ShelfRow focusedIndex={focusedRow === index ? focusedCol : null}>
|
||||
{shelfCategories.map((entry, tileIndex) => (
|
||||
<CategoryTile
|
||||
key={entry.key}
|
||||
entry={entry}
|
||||
navIndex={tileIndex}
|
||||
selected={focusedRow === index && focusedCol === tileIndex}
|
||||
onClick={(event) => {
|
||||
// Otherwise this bubbles to the panel's own onClick, which would
|
||||
// open the group root instead of the category just clicked.
|
||||
event.stopPropagation();
|
||||
onEnterGroup(shelfGroup, entry.key);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ShelfRow>
|
||||
{SHOW_ROW_ICONS && !SHOW_ROW_TITLES && (
|
||||
<span className="group-icon">{GROUP_ICON[shelfGroup]}</span>
|
||||
)}
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
{shelfCategories.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
fontSize: 14,
|
||||
fontWeight: 700,
|
||||
color: "oklch(88% 0.02 210 / .6)",
|
||||
}}
|
||||
>
|
||||
Noch nichts hier
|
||||
</div>
|
||||
) : (
|
||||
<ShelfRow focusedIndex={focusedRow === index ? focusedCol : null}>
|
||||
{shelfCategories.map((entry, tileIndex) => (
|
||||
<CategoryTile
|
||||
key={entry.key}
|
||||
entry={entry}
|
||||
navIndex={tileIndex}
|
||||
selected={focusedRow === index && focusedCol === tileIndex}
|
||||
onClick={(event) => {
|
||||
// Otherwise this bubbles to the panel's own onClick, which would
|
||||
// open the group root instead of the category just clicked.
|
||||
event.stopPropagation();
|
||||
onEnterGroup(shelfGroup, entry.key);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ShelfRow>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,66 +1,104 @@
|
||||
/** The Zaubertasten sheet (F1). Mirrors lib/keyboard.ts - if a binding changes there,
|
||||
* it changes here. */
|
||||
|
||||
const KEYS: Array<[caps: string[], label: string]> = [
|
||||
[["LEER"], "Play / Pause"],
|
||||
[["SHIFT+H", "SHIFT+L"], "Song zurück / vor"],
|
||||
[["SHIFT+J", "SHIFT+K"], "Leiser / lauter"],
|
||||
[["SHIFT+M"], "Stumm"],
|
||||
[["⇧←", "⇧→"], "15 Sekunden zurück / vor"],
|
||||
[["← ↑ ↓ →"], "Auswahl bewegen"],
|
||||
[["CTRL+H/J/K/L"], "Auswahl bewegen (Raster oder Titelliste)"],
|
||||
[["CTRL+D", "CTRL+U"], "Eine halbe Seite weiter / zurück"],
|
||||
[["A-Z"], "Album oder Hörbuch suchen"],
|
||||
[["?"], "Einzelne Titel suchen"],
|
||||
[["TAB"], "Musik / Hörbücher / Podcasts"],
|
||||
[["ENTER"], "Auswahl abspielen"],
|
||||
[["⇧ENTER"], "Titelliste öffnen"],
|
||||
[["A", "0-9"], "Aktuellen Titel einer Fernbedienungs-Taste zuweisen"],
|
||||
[["F1"], "Diese Hilfe"],
|
||||
[["ESC"], "Schließen / Suche löschen"],
|
||||
import { Fragment } from "react";
|
||||
|
||||
const SECTIONS: Array<[title: string, rows: Array<[caps: string[], label: string]>]> = [
|
||||
[
|
||||
"Abspielen",
|
||||
[
|
||||
[["LEER"], "Play / Pause"],
|
||||
[["⇧H", "⇧L"], "Song zurück / vor"],
|
||||
[["⇧J", "⇧K"], "Leiser / lauter"],
|
||||
[["⇧M"], "Stumm"],
|
||||
[["⇧←", "⇧→"], "15 Sekunden zurück / vor"],
|
||||
],
|
||||
],
|
||||
[
|
||||
"Auswählen",
|
||||
[
|
||||
[["← ↑ ↓ →"], "Auswahl bewegen"],
|
||||
[["STRG+HJKL"], "Auswahl bewegen"],
|
||||
[["ENTER"], "Auswahl abspielen"],
|
||||
[["⇧ENTER"], "Titelliste öffnen"],
|
||||
[["TAB"], "Musik / Hörbücher / Podcasts"],
|
||||
],
|
||||
],
|
||||
[
|
||||
"Suchen",
|
||||
[
|
||||
[["A-Z"], "Album oder Hörbuch suchen"],
|
||||
[["?"], "Einzelne Titel suchen"],
|
||||
[["/"], "Suche öffnen / Alben ↔ Titel"],
|
||||
],
|
||||
],
|
||||
[
|
||||
"Sonstiges",
|
||||
[
|
||||
[["A", "0-9"], "Titel auf Fernbedienungs-Taste legen"],
|
||||
[["F1"], "Diese Hilfe"],
|
||||
[["ESC"], "Schließen / Suche löschen"],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
const CAP_STYLE = { minWidth: 0, padding: "3px 7px", fontSize: 11 } as const;
|
||||
|
||||
export function HelpOverlay({ onClose }: { onClose: () => void }) {
|
||||
return (
|
||||
<div className="overlay" style={{ zIndex: 5 }} onClick={onClose}>
|
||||
<div className="sheet" style={{ padding: "26px 30px", width: 330 }}>
|
||||
<div className="sheet" style={{ padding: "18px 22px" }}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 17,
|
||||
fontSize: 15,
|
||||
fontWeight: 800,
|
||||
color: "var(--ink)",
|
||||
marginBottom: 14,
|
||||
marginBottom: 4,
|
||||
letterSpacing: 0.3,
|
||||
}}
|
||||
>
|
||||
⌨️ Zaubertasten
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
|
||||
{KEYS.map(([caps, label]) => (
|
||||
<div key={label} style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
||||
<div style={{ display: "flex", gap: 4 }}>
|
||||
{caps.map((cap) => (
|
||||
<div key={cap} className="key-cap" style={{ minWidth: caps.length > 1 ? 0 : 56 }}>
|
||||
{cap}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 700, color: "oklch(35% 0.03 210)" }}>
|
||||
{label}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 16,
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
color: "oklch(50% 0.03 210 / .8)",
|
||||
textAlign: "center",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "max-content max-content",
|
||||
columnGap: 14,
|
||||
rowGap: 5,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
Tippe irgendwohin, um zu schließen
|
||||
{SECTIONS.map(([title, rows]) => (
|
||||
<Fragment key={title}>
|
||||
<div
|
||||
style={{
|
||||
gridColumn: "1 / -1",
|
||||
marginTop: 10,
|
||||
fontSize: 10,
|
||||
fontWeight: 800,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
color: "oklch(50% 0.03 210 / .8)",
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
{rows.map(([caps, label]) => (
|
||||
<Fragment key={label + caps.join()}>
|
||||
<div style={{ display: "flex", gap: 3, justifyContent: "flex-end" }}>
|
||||
{caps.map((cap) => (
|
||||
<span key={cap} className="key-cap" style={CAP_STYLE}>
|
||||
{cap}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: "oklch(35% 0.03 210)" }}>
|
||||
{label}
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user