tower / commit
feat(gauge): on a phone the height readout fades once the height is still (D-098)
README.md | 3 ++- decisions.md | 13 +++++++++++++ src/style.css | 5 +++++ src/ui/eye-gauge.ts | 27 +++++++++++++++++++++++++-- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bae33f3..10ccaaa 100644 --- a/README.md +++ b/README.md @@ -481,7 +481,8 @@ log scale: drag it, or click it and use the arrow keys. It is marked with things height — an adult, a two-storey house, the Eiffel Tower, Everest, an airliner cruising, a weather balloon, and at the top the beam itself — which light up as you pass them, and the readout by the thumb gives the height, the local gravity, and what it compares with ("≈ the Eiffel Tower", "2.0× the -Burj Khalifa"). The **Where you stand** readout in the bottom-left corner folds down to its +Burj Khalifa"). On a phone that readout fades once the height has been still for two +seconds, and comes back as soon as it changes or you touch the gauge. The **Where you stand** readout in the bottom-left corner folds down to its title with a click, and stays as you left it; on a phone it starts folded. Nothing *inside* rotates, because you, the ground and the air all turn together. The diff --git a/decisions.md b/decisions.md index 3551602..0c26a2c 100644 --- a/decisions.md +++ b/decisions.md @@ -3337,3 +3337,16 @@ twice the smallest fifth); tracks along the blocks and only there; every crop, f a grown farm taking its area in one piece, ragged, and going round ground that does not fit; and in the world, nine farms grown to 240–260 km² between them, farmland and no trees about each seed. + +## D-098 — On a phone, the height readout fades when the height is still + +**Asked for** (author, 2026-10-01): "the height label takes up a lot of space on mobile, can the +big rectangle box fade away or something?" + +**What it does** (`ui/eye-gauge.ts wake`, `style.css`). Below 720 px the eye-height readout — +the boxed height, gravity and comparison beside the gauge's thumb — fades out over 0.6 s once +the height has been still for two seconds, and comes back at once when the height changes by +more than half a percent (flying, a tour, the gauge) or the gauge is touched; it stays while the +gauge is held. The view re-telling the same height every frame does not wake it. The thumb and +the ticks stay, so the height is still read off the scale. Wider screens are unchanged: there the +box is not in the way. Screen readers are unaffected — the gauge's value text is always current. diff --git a/src/style.css b/src/style.css index 50a4f25..fb11e7d 100644 --- a/src/style.css +++ b/src/style.css @@ -250,6 +250,11 @@ input[type=range] { width: 100%; margin-top: 6px; accent-color: var(--accent); } readout by the thumb still says which one you are near. */ .gauge { top: 96px; right: 14px; } .gauge-mark span, .gauge-mark i { display: none; } + /* The readout box is a big share of a phone's view, so it fades once the height has been + still for two seconds, and comes back the moment it changes or the gauge is touched + (eye-gauge.ts wake). The thumb stays where it is: the height is still on the scale. */ + .gauge-readout { opacity: 0; transition: opacity 0.6s ease; } + .gauge-readout.awake { opacity: 1; transition: opacity 0.15s ease; } } /* The scenic tour (interior.ts): a black fade between scenes, and the scene's title. */ diff --git a/src/ui/eye-gauge.ts b/src/ui/eye-gauge.ts index a3a3076..07037af 100644 --- a/src/ui/eye-gauge.ts +++ b/src/ui/eye-gauge.ts @@ -50,6 +50,7 @@ export class EyeGauge { private readout: HTMLDivElement private marks: HTMLDivElement[] = [] private m = EYE_MIN_M + private sleep: ReturnType<typeof setTimeout> | null = null /** * `onInput` is called with the new height whenever the reader moves it; `describe` @@ -91,7 +92,11 @@ export class EyeGauge { /** Move to a height without reporting it: for the view telling the gauge. */ set(m: number): void { - this.m = Math.min(EYE_MAX_M, Math.max(EYE_MIN_M, m)) + const next = Math.min(EYE_MAX_M, Math.max(EYE_MIN_M, m)) + // A height that has really changed shows the readout again; the view re-telling the same + // height every frame does not. + if (Math.abs(next / this.m - 1) > 0.005) this.wake() + this.m = next const t = sliderFromEyeHeight(this.m) this.thumb.style.bottom = `${t * 100}%` this.readout.innerHTML = @@ -105,6 +110,20 @@ export class EyeGauge { }) } + /** + * Show the readout, and let it fade again once the height has been still for a while. On a + * phone the readout box covers a good share of the view, so it is only there while the + * height is changing or the gauge is in hand (style.css); elsewhere it stays. + */ + private wake(): void { + this.readout.classList.add('awake') + if (this.sleep) clearTimeout(this.sleep) + this.sleep = setTimeout(() => { + this.sleep = null + if (!this.active) this.readout.classList.remove('awake') + }, READOUT_STAYS_MS) + } + /** * On a short view some marks land on top of each other (Everest and the airliner are a * quarter of the scale's e-fold apart). Walking up from the ground, a mark too close to @@ -133,6 +152,7 @@ export class EyeGauge { } this.track.addEventListener('pointerdown', (e) => { this.active = true + this.wake() this.track.setPointerCapture(e.pointerId) this.track.focus() fromPointer(e) @@ -141,7 +161,7 @@ export class EyeGauge { this.track.addEventListener('pointermove', (e) => { if (this.active && this.track.hasPointerCapture(e.pointerId)) fromPointer(e) }) - const end = () => { this.active = false } + const end = () => { this.active = false; this.wake() } this.track.addEventListener('pointerup', end) this.track.addEventListener('pointercancel', end) // Keys: a step is 1% of the scale, a page 10%; Home and End go to the ends. @@ -160,6 +180,9 @@ export class EyeGauge { } } +/** How long the readout stays after the height last changed, where it fades at all. */ +export const READOUT_STAYS_MS = 2000 + export const fmtM = (m: number): string => m < 10 ? `${m.toFixed(1)} m` : m < 1000 ? `${Math.round(m)} m` : `${(m / 1000).toFixed(1)} km`