fuzzyworld

tower / commit

feat(gauge): on a phone the height readout fades once the height is still (D-098)

7a5edd5f1cb741566b346c2b798dc2c6c04c6588 · Pavel Safronov · 2026-10-01 21:27:08 -0700 · parents abc0aad0a271

Claude Code usage: this commit ~8.9K out / ~9.5M cached · cost unknown (no per-model trailer) · session 5 tally 1.2K in / 660.5K out / 205.9M cached · usage by commit and session

feat(gauge): on a phone the height readout fades once the height is still (D-098)

Below 720 px the eye-height readout fades out two seconds after the
height last changed and comes back the moment it changes or the gauge
is touched; it stays while the gauge is held. The thumb and ticks stay.
Wider screens are unchanged.

Usage: 1.2K in / 660.5K out / 205.9M cached (session tally)
rtk:   1.1M saved, 17.3% over 3103 cmds (global/lifetime)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
 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`