mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 13:18:58 +00:00
feat(moon): the moon with any background, and its status in General settings (#718)
The owner decided on 30.09 that the moon is not part of the "Follow the Sun" environment but a switch of its own: with a static background (global or a space's own) the card showed no moon even with the switch on, and the switch said nothing about why the moon was missing right now. With a static background there is no environment, so the moon stands in its own layer, `.hp-moon-sky`: the first child of `.stage` / `.hp-static-stage`, the whole scene, no z-index, filter or will-change, under the plan by DOM order, fading with the #101 View weight. Inside is the very #661 element, so place, size, art and fades are unchanged, and a background switch moves it to its new parent in the same render without a flicker. The phase comes from the same `resolveDayCycle`, computed only while the moon is on and on View; without `sun.sun` both cards keep their 30 s clock ticker and re-render only when the phase changes (the environment is still compared by its whole fingerprint). General settings get a second caption line under the moon switch (`data-moon-status`): one snapshot per opening, judged by the lazy chunk as if the switch were on, first reason wins (no home, day, below 3°, under 3 %), numbers rounded and clamped below the threshold they missed. `moonStatus` decides "shown" with the same `moonShownAt` as the element. It lives in a WeakMap beside the draft, so it never makes the dialog dirty; a closed opening's result is dropped. The dialog loads the chunk through the gate's loader (`withMoon`), now shared by every caller while a load is in flight, so there is still one fingerprint check and one retry token. Bundle (same build, against origin/dev): initial View 300 072 -> 300 248 B gzip (+176 B, under the 500 B of the spec; budget and ceiling not raised); lazy editor 238 558 -> 238 991 B (+433 B, the line and English strings); lazy moon 11 385 -> 11 712 B (+327 B, layer CSS and status). `src/moon.ts` stays out of the initial and the editor graph; bundle-budget now refuses an editor/moon overlap. Monolith metrics: hostRefs 4 885 -> 4 888 — the three `host.` reads of `src/editors/moon-status.ts` (hass, `_settingsDialog`, requestUpdate) through its own three-member interface, not the editor port; the other five metrics are unchanged. houseplan-editor-runtime.ts grows by two lines (import, call). Tests: AC9/AC10/AC15 and the sky layer in test/moon.test.mjs (the #661 "static -> nothing" check inverted), AC14 and the opening lifecycle in test/moon-settings.test.mjs, smokes demo/smoke_moon_static.mjs (AC1-AC6; AC1 and AC3 were red on dev) and demo/smoke_moon_status.mjs (AC11/AC12), AC7 in smoke_daycycle_layer_budget. Golden: two new scenes (static-bg-moon-gibbous-white-light, static-bg-moon-crescent-south-dark, matrix v70), the harness checks the moon's parent by background and waits for the status line in the General settings frames. Four new mutants; the clock ticker one is a browser guard (201 at the guideline of 200). Issue: #718 User-Visible: yes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
+86
-30
@@ -376,26 +376,26 @@ The wash lives where the Flat `.sunlayer` lives (floor group, above room fills
|
||||
and Glow) as `.sunlayer.iso-sunwash`, one `.iso-sunbeam[data-opening]` per lit
|
||||
window. Witness: `demo/smoke_iso_sun.mjs`.
|
||||
|
||||
## Moon — `settings.moon` (#661)
|
||||
## Moon — `settings.moon` (#661, any background since #718)
|
||||
|
||||
At dawn, dusk and night on the "Follow the Sun" background, the moon in its
|
||||
current phase stands in the top-left corner of the scene: a thin crescent, a
|
||||
half, a full disc. General settings › Sun and Moon › «Moon over the plan at
|
||||
dusk and night» switches it for the whole installation.
|
||||
At dawn, dusk and night, with any background, the moon in its current phase
|
||||
stands in the top-left corner of the scene: a thin crescent, a half, a full
|
||||
disc. General settings › Sun and Moon › «Moon over the plan at dusk and night»
|
||||
switches it for the whole installation; there is no per-space moon switch.
|
||||
|
||||
**When it is shown** — all at once, otherwise there is no moon:
|
||||
|
||||
- `settings.moon === true` (global; absent, `false` or anything else is off);
|
||||
- the effective `bg_mode` of the space is `daynight` — the moon lives in the
|
||||
four-phase environment, so a space with its own `static` has no moon, and
|
||||
there is no per-space moon switch;
|
||||
- the environment phase is `dawn`, `dusk` or `night` (the same
|
||||
`resolveDayCycle`, browser-clock fallback included);
|
||||
- the day-cycle phase is `dawn`, `dusk` or `night`: `resolveDayCycle(hass,
|
||||
now)`, whatever the background — with a valid `sun.sun` (`dayCycleSunOf`:
|
||||
finite azimuth and elevation, boolean `rising`) day is elevation ≥ 6°
|
||||
(exactly 6° is day), otherwise the browser-local clock, day 08:00–18:00. The
|
||||
effective `bg_mode` of the space is not a condition (#718 K1);
|
||||
- the topocentric altitude is ≥ 3° (`MOON_ELEVATION_MIN = RAY_ELEVATION_MIN`);
|
||||
- the illuminated fraction is ≥ 3 % (`MOON_MIN_ILLUMINATION`, about ±1.5 days
|
||||
around new moon);
|
||||
- a View surface: View, kiosk or `houseplan-space-card` (editors have no
|
||||
environment);
|
||||
- a View surface: View, kiosk, panel or `houseplan-space-card` (editors, the
|
||||
PDF export and the space dialog preview have no moon);
|
||||
- `hass.config.latitude/longitude` are finite numbers.
|
||||
|
||||
**Where the numbers come from.** Home Assistant publishes no moon altitude
|
||||
@@ -423,12 +423,29 @@ quantised to 0.01 only so the element changes when the fingerprint does. The
|
||||
dark side is the same art at 8 % opacity.
|
||||
|
||||
**Place, size, layer.** Fixed top-left corner, box `min(200px, 25cqmin)` of the
|
||||
scene (the environment is the size container), inset 5 % of the box; it does
|
||||
not move with pan or zoom and never takes the pointer. It is the last child of
|
||||
`.hp-day-cycle-env`: above the phase gradients and the sun glow, below the plan
|
||||
paper, rooms, devices, labels and UI. A plan that fills the scene covers the
|
||||
moon partly or entirely — that is the environment's norm, like the sun glow
|
||||
(owner decision 7).
|
||||
scene, inset 5 % of the box; it does not move with pan or zoom and never takes
|
||||
the pointer. Over "Follow the Sun" it is the last child of `.hp-day-cycle-env`:
|
||||
above the phase gradients and the sun glow, below the plan paper, rooms,
|
||||
devices, labels and UI. A plan that fills the scene covers the moon partly or
|
||||
entirely — that is the environment's norm, like the sun glow (owner
|
||||
decision 7).
|
||||
|
||||
**Static background (#718 K3).** No environment is created — no
|
||||
`.hp-day-cycle-env`, no `daycycle`/`phase-*` classes, no
|
||||
`.hp-paper-outline-svg`; the scene keeps the chosen colour or the theme's. The
|
||||
same `.hp-moon` element stands in its own layer `<div class="hp-moon-sky"
|
||||
aria-hidden="true">`, the first child of `.stage` (full card) or
|
||||
`.hp-static-stage` (space card) — where the environment would be. The layer is
|
||||
the whole scene (`position:absolute; inset:0; overflow:hidden;
|
||||
pointer-events:none; container-type:size`), so the box and place are the same;
|
||||
it has no `z-index`, `filter` or `will-change` and lies under the plan by DOM
|
||||
order (`.zoomwrap` and the space card's plan are `z-index:1`). The layer's
|
||||
`opacity` is the View weight of the #101 transition, as the environment's;
|
||||
editors have neither. Switching between `daynight` and `static` (a space tab,
|
||||
the background segment previewed in the open dialog, a config push) moves the
|
||||
element to its new parent in the same render, in its final state, on the same
|
||||
box — no flicker. The chunk renders and styles the layer; until it is here
|
||||
there is no layer.
|
||||
|
||||
**Movement.** Opacity only, 2 s on the background curve (`RAY_FADE_MS`),
|
||||
none under `prefers-reduced-motion`: rising through 3°, setting through it,
|
||||
@@ -439,13 +456,47 @@ the ticker keeps the last rendered inputs, and an equal fingerprint
|
||||
(`visible | k`) costs no render. A card that renders without a moon, leaves the
|
||||
page or is hidden drops or pauses its ticker.
|
||||
|
||||
**Weight.** Astronomy, art (≈ 9 KB gzip) and template are one lazy chunk,
|
||||
`moon-runtime-*`, loaded when the moon is switched on, the environment exists
|
||||
and it is not daytime; until it arrives there is no moon, a failed load is
|
||||
retried at most every 30 s through the exact-build loader of the isometric
|
||||
runtime. The initial View graph carries only the gate (`src/moon-gate.ts`).
|
||||
One element, no CSS filter and no `will-change`: the composited layer count
|
||||
does not change (`demo/smoke_daycycle_layer_budget.mjs`).
|
||||
With a static background the phase for the moon comes from the same
|
||||
`resolveDayCycle` (`moonSkyState` in `src/moon-gate.ts`), computed only while
|
||||
the moon is on and the surface is View. With `sun.sun` it follows the Home
|
||||
Assistant state updates the cards already re-render on. Without it the card
|
||||
keeps its 30 s clock ticker (`_syncDayCycleClock`, both cards) and re-renders
|
||||
only when the phase changes (`dayCycleClock`: the environment is compared by
|
||||
its whole fingerprint, the moon's sky by its phase), so the moon leaves at
|
||||
08:00 and comes at 18:00 without a state update. The moon switched off or an
|
||||
editor: no phase, no ticker.
|
||||
|
||||
**Weight.** Astronomy, art (≈ 9 KB gzip), template, the static-background
|
||||
layer and the status function are one lazy chunk, `moon-runtime-*`, loaded
|
||||
when the moon is switched on on a View surface and it is not daytime — with any
|
||||
background — and when General settings open (for the status line, by day and
|
||||
with the moon off too). One load per page through the gate's loader
|
||||
(`withMoon`): a chunk of another build is never installed, a failed load is
|
||||
retried at most every 30 s, and every caller meanwhile waits for the same load.
|
||||
The initial View graph carries only the gate (`src/moon-gate.ts`); the editor
|
||||
graph only the status line and its strings (`src/editors/moon-status.ts`) —
|
||||
`src/moon.ts` is in neither (`scripts/bundle-budget.mjs` refuses an overlap of
|
||||
the moon graph with the initial or the editor graph). One element, no CSS
|
||||
filter and no `will-change`: the composited layer count does not change, with
|
||||
either background (`demo/smoke_daycycle_layer_budget.mjs`).
|
||||
|
||||
**Status line (#718 K7).** Under the switch in General settings a second
|
||||
caption line, inside `aria-describedby`, no `aria-live`, anchored
|
||||
`data-moon-status="shown|no_home|day_sun|day_clock|low|new"`: «Now: shown (24°
|
||||
above the horizon, 79% lit).» or «Now: not shown (reason).», the first reason
|
||||
that holds — no home coordinates; day (by `sun.sun` with its elevation, or by
|
||||
the clock); the moon below 3°; under 3 % lit. It is judged once per opening on
|
||||
a snapshot taken when the dialog opens (`now`, `hass.config`, `sun.sun`), as
|
||||
if the switch were on — the moon no longer depends on the background, so one
|
||||
status serves the installation; the switch, «Reset» and the background segment
|
||||
do not change it. `moonStatus` in `src/moon.ts` decides «shown» with the same
|
||||
`moonShownAt` as the element (AC10 checks the equivalence every hour of a
|
||||
month), rounds to whole numbers and keeps a hidden reason's number below its
|
||||
threshold (2.6° reads «2°»). The status lives beside the draft, never in it:
|
||||
the line arriving leaves «Save» disabled. While the chunk loads, or when it
|
||||
failed, there is no line; a closed opening's result is dropped. The line
|
||||
belongs to the browser the dialog is open in — a wall tablet with another
|
||||
clock or time zone may differ.
|
||||
|
||||
**Limits (documented, not bugs).**
|
||||
|
||||
@@ -455,6 +506,8 @@ does not change (`demo/smoke_daycycle_layer_budget.mjs`).
|
||||
not follow the hemisphere (owner decision): the crescent is "vertical".
|
||||
- Earthshine is not modelled; the dark side is an 8 % silhouette for legibility.
|
||||
- The moon does not follow its azimuth — fixed corner of the scene.
|
||||
- One art for every background (owner decision, #718): on a mid-grey custom
|
||||
colour the disc has less contrast than on white or at night.
|
||||
- A dense layout (plan filling the screen) shows only the part of the disc in
|
||||
the margins.
|
||||
- Coordinates come from Home Assistant: an installation that kept the default
|
||||
@@ -504,11 +557,14 @@ saving General settings removes it.
|
||||
- `src/day-cycle-render.ts` — the shared constant environment layers and
|
||||
plan-outline variables for full and static cards.
|
||||
- `src/moon.ts`, `src/moon-runtime.ts`, `src/moon-gate.ts`,
|
||||
`src/moon-art.generated.ts` — the moon (#661): pure astronomy and mask, the
|
||||
lazy chunk with the element and its ticker, the initial-graph gate, the
|
||||
generated art (`node scripts/generate-moon-assets.mjs` from
|
||||
`assets/moon/houseplan-1.0.0`); unit-tested in `test/moon.test.mjs`,
|
||||
end-to-end in `demo/smoke_moon.mjs`.
|
||||
`src/moon-art.generated.ts` — the moon (#661, #718): pure astronomy, mask and
|
||||
status, the lazy chunk with the element, the static-background layer and the
|
||||
ticker, the initial-graph gate with the page-wide loader, the generated art
|
||||
(`node scripts/generate-moon-assets.mjs` from `assets/moon/houseplan-1.0.0`);
|
||||
`src/editors/moon-status.ts` — the General settings line. Unit-tested in
|
||||
`test/moon.test.mjs` and `test/moon-settings.test.mjs`, end-to-end in
|
||||
`demo/smoke_moon.mjs`, `demo/smoke_moon_static.mjs` and
|
||||
`demo/smoke_moon_status.mjs`.
|
||||
- `src/houseplan-card.ts` — the memoised wedge layer, four-phase background
|
||||
lifecycle, and both settings dialogs (compass dial included).
|
||||
- `src/space-render.ts` / `src/space-card.ts` — static-card environment and
|
||||
|
||||
Reference in New Issue
Block a user