Files
musicmouse/slint-frontend/ui/theme.slint
Martin Bauer 794126bfdb Port "Mein Zimmer" - the Home Assistant room panel
The second of the web front-end's three pages. Lights get a toggle, five brightness
steps and six colours; shutters get four presets, up/stop/down and a window that
reflects the position Home Assistant reports. Its own violet hue rather than the
player's teal, so the two pages read as siblings rather than one bleeding into the
other - the same reason room.css has its own tokens.

The backend keeps Home Assistant's URL and token and relays the calls, so this client
learns only which entities exist, exactly as the browser does. A 404 from GET /api/ha
means Home Assistant is not configured, which is a normal answer: the tab simply does
not appear.

Polled, not subscribed, and only while the page is on screen. HA's own auth-and-
subscribe websocket is more machinery than a room panel needs, and a lamp's state is
not worth a request every 2.5 seconds when nobody is looking at it - this device has a
music player to stay out of the way of. A tap patches the entity locally and records
when; a poll that was already in flight when the patch landed is dropped for that
entity rather than putting the lamp back to "off" until the next one catches up. That
reconciliation is the one non-obvious thing in useHomeAssistant.ts and it is carried
over for the same reason.

Nothing on a shutter card animates locally. A real cover reports its own movement and
position, and a client-side guess at where it will end up is precisely what would
fight with that - the design mockup animated it because it had no backend to ask.

Two additions beyond a straight port, both because a kiosk has no pointer:

CTRL+TAB cycles the pages. The web's tab rail is pointer-only, which sits badly with a
front-end whose README claims every screen is reachable from the keyboard. Plain TAB
was already the group cycle, and CTRL+TAB is the idiom for the outer one everywhere
else.

--page opens directly on a page, which a kiosk may well want and which is the only way
in with neither keyboard nor pointer.

Verified against the real Home Assistant: the shutter reported open and the lamp off,
and both drew that way. Nothing was switched - those entities are hardware.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 01:02:18 +02:00

108 lines
6.1 KiB
Plaintext

// The palette, converted once from the web front-end's oklch tokens
// (`web/src/styles/app.css`) into the sRGB hex Slint speaks. The oklch source value is
// kept beside each one, because that is the form the design is actually maintained in -
// if a colour needs adjusting, adjust the oklch and re-convert (`src/oklch.rs`), rather
// than nudging the hex and letting the two drift.
export global Theme {
// ------------------------------------------------------------------ colours --
out property <color> ink: #113339; // oklch(30% 0.04 210)
out property <color> paper: #eef7f9; // oklch(97% 0.01 210)
out property <color> accent: #de73bd; // oklch(70% 0.16 340)
out property <color> accent-dim: #f292d3; // oklch(78% 0.14 340)
out property <color> sea-deep: #001b21; // oklch(20% 0.045 210)
out property <color> shadow: #001017; // oklch(15% 0.05 210)
// The stage gradient, which in the web app sits under an animated canvas. There is
// no canvas here: a full-viewport repaint loop is the one cost a Pi cannot absorb,
// and `?pi=1` already removes it on the device this replaces.
out property <color> stage-top: #397d88; // oklch(55% 0.07 210)
out property <color> stage-mid: #0e4b54; // oklch(38% 0.06 210)
out property <color> text-dim: #c9dbdf; // oklch(88% 0.02 210)
out property <color> text-sub: #c0d2d5; // oklch(85% 0.02 210)
out property <color> count-pink: #7d0563; // oklch(40% 0.17 340)
out property <color> card-music: #e4f1f4; // oklch(95% 0.015 210)
out property <color> card-book: #ffd5ab; // oklch(92% 0.09 55)
out property <color> book-edge-1: #ffc298; // oklch(86% 0.09 55)
out property <color> book-edge-2: #e5a880; // oklch(78% 0.09 55)
out property <color> bar-bg: #00171e; // oklch(18% 0.05 210)
out property <color> sheet-bg: #e9f4f6; // oklch(96% 0.012 210)
out property <color> backdrop: #000e12; // oklch(15% 0.03 210)
// Two track colours, not one with an alpha: the seek bar sits on the pale stage in
// the play view and on the near-black player bar in the browse view, and a single
// translucent fill that reads correctly on one is invisible on the other.
out property <color> progress-track: #1b3236; // oklch(30% 0.03 210)
out property <color> progress-track-dark: #2e4f57;
out property <color> vol-empty: #c9dbdf; // oklch(88% 0.02 210)
// Glass, without the glass. `backdrop-filter` has no counterpart here, so these are
// exactly the web app's own `data-blur="off"` fallback: a flat translucent fill.
// That is a real loss of depth and the honest substitute for it - faking a blur by
// compositing a pre-blurred copy would reintroduce the per-frame cost the blur was
// dropped to avoid.
out property <brush> glass: @linear-gradient(160deg, #eef7f924 0%, #eef7f90d 100%);
out property <color> glass-border: #eef7f929;
out property <color> row-idle: #f7fcfd1a;
out property <color> row-active: #f7fcfd38;
// --------------------------------------------------- Mein Zimmer (violet) --
// Its own hue (~298-310) rather than the player's teal (~210), so the two pages
// read as siblings rather than one bleeding into the other. Same oklch-converted-
// once treatment as above.
out property <color> room-ink: #181322; // oklch(20% 0.03 300)
out property <color> room-paper: #f6f4fb; // oklch(97% 0.01 302)
out property <color> room-accent: #986dd0; // oklch(62% 0.15 302)
out property <color> room-card: #f3f0f9; // oklch(96% 0.012 300)
out property <color> room-top: #885cb5; // oklch(56% 0.14 305)
out property <color> room-mid: #4d2f77; // oklch(38% 0.12 300)
out property <color> room-deep: #1e0c37; // oklch(21% 0.08 298)
out property <color> room-icon-bg: #dad3e9; // oklch(88% 0.03 300)
out property <color> room-icon-fg: #3e3451; // oklch(35% 0.05 300)
out property <color> room-off-bg: #d9d5e3; // oklch(88% 0.02 300)
out property <color> room-off-fg: #736f7c; // oklch(55% 0.02 300)
out property <color> room-btn: #dcd8e6; // oklch(89% 0.02 300)
out property <color> room-btn-fg: #2c243a; // oklch(28% 0.04 300)
out property <color> room-stop: #e85854; // oklch(65% 0.18 25)
out property <color> room-toggle-off: #cbc9d0;
out property <color> room-sub: #585264; // oklch(45% 0.03 300)
out property <color> room-slat-a: #a19baf; // oklch(70% 0.03 300)
out property <color> room-slat-b: #7d778a; // oklch(58% 0.03 300)
out property <color> room-window-a: #87d1e8; // oklch(82% 0.08 220)
out property <color> room-window-b: #50afb4; // oklch(70% 0.09 200)
out property <length> room-card-width: 380px;
// ------------------------------------------------------------------- metrics --
out property <length> radius-panel: 22px;
out property <length> radius-card: 16px;
out property <length> radius-row: 12px;
out property <length> radius-sheet: 26px;
out property <length> pill: 999px;
// The album grid's track width. The web grid is `minmax(180px, 1fr)`; keeping the
// same number keeps the same number of columns at 1920, which is what the shelf
// rows and the measured card height below were both tuned against.
out property <length> card-width: 180px;
out property <length> grid-gap: 24px;
out property <length> shelf-tile: 148px;
// How much of the right edge the page must leave clear for nothing to render
// underneath the corner controls.
out property <length> rail-clearance: 88px;
// ---------------------------------------------------------------- typography --
// Heavy throughout: 700/800/900. Durations and counters use the monospace family so
// a ticking clock does not reflow the row it sits in.
out property <string> font: "Nunito";
out property <string> mono: "monospace";
out property <length> text-hero: 44px;
out property <length> text-title: 26px;
out property <length> text-heading: 20px;
out property <length> text-body: 16px;
out property <length> text-small: 13px;
out property <length> text-tiny: 12px;
}