From 52f2caae51a3bed83ef2fdfab01d65e7184d23c0 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Tue, 16 Jun 2026 07:54:13 +0200 Subject: [PATCH] docs(ui): correct the banner section to the shipped rotation behaviour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The banner UX evolved across the PR2 polish iterations past what UI_DESIGN described. Bring the section in line with the shipped behaviour: - a lone message pulses (fades out and back in) instead of sitting frozen; - a long message's scroll rewind fades (out → rewind hidden → fade the same message back in) instead of every-message-only fades; - the scroll position carries across a navigation (resumeScroll), rather than restarting at the left; - the strip reserves a constant line height (invisible spacer + absolute message layer) so it does not jump while the message is gone during the gap. ARCHITECTURE §10 already deferred these client-fade details to UI_DESIGN, so this is the only doc that drifted. Docs-only, no code change. --- docs/UI_DESIGN.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index f95410b..92841a9 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -215,14 +215,18 @@ a screen change does not replay the fade; only a real message advance fades. A * round-robin** (`createScheduler`) picks the next message: campaigns compete by their weight (each appears its weight share per cycle, evenly interleaved, not at random), and a campaign's own messages advance round-robin. One message fades in (`fadeInMs`), holds `holdMs` (a message wider than the -strip pauses `edgePauseMs`, scrolls to its right edge at `scrollPxPerSec`, pauses, and repeats while -under `holdMs`), then — with more than one message — fades out (`fadeOutMs`), waits `gapMs`, and the -next fades in. The fade (opacity on a `.fadewrap` layer) is independent of the scroll (the inner -track's transform), so a long, scrolling message still fades at both ends. A lone message stays put -(a long one keeps scrolling). A **viewport size change** (e.g. a portrait↔landscape rotation) -re-measures the current message (`remeasureBanner`, debounced), so its scroll re-evaluates for the -new width. All timings are operator-set (`/_gm/banner-settings`). Under **reduce-motion** the fades -collapse to an instant swap +strip pauses `edgePauseMs`, scrolls to its right edge at `scrollPxPerSec`, pauses, and — while still +under `holdMs` — fades out, rewinds to the start hidden, then fades the **same** message back in to +scroll again), then fades out (`fadeOutMs`), waits `gapMs`, and the next fades in. The fade (opacity +on a `.fadewrap` layer) is independent of the scroll (the inner track's transform). **Every** cycle +boundary fades — a message change, a scroll rewind, and a **lone** message (which pulses out and back +in rather than sitting frozen) — so the rhythm is uniform. So the strip does not jump in height while +the message is momentarily gone during `gapMs`, it reserves one text line with an invisible spacer +and overlays the message absolutely. When a navigation remounts the view mid-scroll, the engine +**resumes the scroll from where it had reached** (`resumeScroll`) instead of restarting it at the +left. A **viewport size change** (e.g. a portrait↔landscape rotation) re-measures the current message +(`remeasureBanner`, debounced), so its scroll re-evaluates for the new width. All timings are +operator-set (`/_gm/banner-settings`). Under **reduce-motion** the fades collapse to an instant swap and a long message does not scroll. ## Result / status iconography (`lib/result.ts`)