Files
musicmouse/web/src/lib/theme.ts
Martin Bauer b8f9f6d537 Give the tab rail its own glass bar and reserve space for it
The three tab buttons now share one frosted glass pill instead of each
floating separately, and BrowseView/RoomView reserve TAB_RAIL_CLEARANCE
of right-side padding so a full-width row or grid never renders under
it - verified against the real library, where dense rows (e.g. Conni's
82 audiobooks) previously butted right up against the rail.

Also gitignore the real (non-.example) tippen-curriculum.yml and
tippen-progress.json, matching config.yml's own privacy treatment,
since the curriculum now contains real reward paths into a personal
library.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 00:16:42 +02:00

121 lines
5.2 KiB
TypeScript

/** Per-group colors and identity, and the feature toggles that have gone back and
* forth while this design was under review - flip one here rather than hunting
* through the components that read it. Most toggles are about the browse screen's
* shelf/row look, but some (`SHOW_GLASS_BLUR`, `SHOW_AMBIENCE`,
* `SHOW_DECORATIVE_ANIMATIONS`) reach further: the play view, the player bar, the
* room page and `App.tsx` all read this module too, wherever they share the same
* "cut this on weak hardware" knob.
*
* `SHOW_ROW_TITLES` and `SHOW_ROW_ICONS` both `true` reproduces the original shelf
* header - an icon badge plus label button above each row's tiles. */
import type { CSSProperties } from "react";
import type { Group } from "./search";
import type { UiState } from "./keyboard";
// ---------------------------------------------------------------- toggles --
/** Tint each shelf panel and its rows with the group's hue below. */
export const SHOW_ROW_TINT = false;
/** Show each shelf's title text ("Musik", "Hörbücher", "Podcasts") above its tiles. */
export const SHOW_ROW_TITLES = true;
/** Show the small icon badge (🎵/📖/🎙️) beside the title. Stands alone fine with
* `SHOW_ROW_TITLES` off (an icon-only header), but the original look needs both. */
export const SHOW_ROW_ICONS = true;
/** Vertical gap between shelves on the root browse screen, in px. */
export const ROW_SPACING = 25;
/** Fade the album/category grid in (with a slight upward slide) when a group or
* category is entered, and the album modal in when it opens. One animation per
* container, not per card, so cost stays flat regardless of grid size. */
export const ANIMATE_VIEW_TRANSITIONS = true;
/** Frost the glass panels/cards/rows with a real backdrop blur. `backdrop-filter` is
* one of the most GPU-expensive CSS effects in the app - a live backdrop copy+blur
* per element, repeated for every album card in the unvirtualized grid - so turn
* this off on weak hardware (e.g. an old Raspberry Pi) to fall back to a plain
* translucent background with no blur. */
export const SHOW_GLASS_BLUR = true;
/** Render the play view's animated canvas background (gradient + bubbles, both
* redrawn every frame at 60fps). Off falls back to `.stage`'s own static CSS
* gradient, which is still underneath the canvas either way. */
export const SHOW_AMBIENCE = true;
/** Run the purely decorative CSS animation loops: the room page's bubble field and
* the dolphin mascot's bob/swim. Individually cheap (opacity/transform only) but
* free to cut on weak hardware. */
export const SHOW_DECORATIVE_ANIMATIONS = true;
// ------------------------------------------------------------------ colors --
/** Per-group hue (oklch degrees). The play view's ambient water uses the same
* family (`ambienceBaseFor` in `lib/ambience.ts`), so a tinted shelf already hints
* at the mood its albums play into - and so does the book/music card tint in
* `lib/covers.ts`, which reads `GROUP_HUE.audiobooks`/`GROUP_HUE.music` too. */
export const GROUP_HUE: Record<Group, number> = {
music: 210, // today's sea - Dolphin Beats' baseline hue
audiobooks: 55, // warm amber - richer than the old shared hue (80)'s dull brown
podcasts: 300, // the one otherwise-unused family
};
export const GROUP_ICON: Record<Group, string> = {
music: "🎵",
audiobooks: "📖",
podcasts: "🎙️",
};
export const GROUP_LABEL: Record<Group, string> = {
music: "Musik",
audiobooks: "Hörbücher",
podcasts: "Podcasts",
};
/** How much horizontal space the tab rail's own glass bar claims on the right edge
* (its `right` inset plus its rendered width) - scrollable content reserves this much
* padding on that side so a full row/grid never renders underneath it. Keep in sync
* with the geometry in `TabRail.tsx`. */
export const TAB_RAIL_CLEARANCE = 88;
/** Icon/label for the vertical tab rail - one entry per `UiState["page"]`. The
* smarthome tab only renders when Home Assistant is configured (see `App.tsx`); the
* other two always show. */
export const PAGE_ICON: Record<UiState["page"], string> = {
music: "🎵",
room: "💡",
typing: "⌨️",
};
export const PAGE_LABEL: Record<UiState["page"], string> = {
music: "Musik",
room: "Mein Zimmer",
typing: "Tippen",
};
/** A shelf/row's frosted background and border, tinted with its group's hue - or,
* with `SHOW_ROW_TINT` off, `{}` so `.glass-panel`'s own neutral CSS shows through. */
export function glassTint(hue: number): CSSProperties {
if (!SHOW_ROW_TINT) return {};
return {
background: `linear-gradient(160deg, oklch(55% 0.1 ${hue} / .32), oklch(30% 0.06 ${hue} / .14))`,
borderColor: `oklch(75% 0.09 ${hue} / .4)`,
};
}
/** One list row's background, tinted with its group's hue and brighter/more opaque
* while it's the currently-playing row - or, with `SHOW_ROW_TINT` off, the same
* neutral highlight the row used before tinting existed. */
export function rowTint(hue: number, highlighted: boolean): CSSProperties {
if (!SHOW_ROW_TINT) {
return { background: `oklch(97% 0.01 210 / ${highlighted ? ".22" : ".10"})` };
}
return {
background: `oklch(${highlighted ? "62% 0.12" : "42% 0.08"} ${hue} / ${highlighted ? ".34" : ".2"})`,
borderColor: `oklch(78% 0.09 ${hue} / ${highlighted ? ".5" : ".28"})`,
};
}