From 54d13d747239097cdc0c470c781ad359f7829556 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Tue, 20 Oct 2026 04:26:50 +0200 Subject: [PATCH] feat: shine a code redeemed or an event completed on its way out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A code redeemed on its last server sweeps in the game's colour and greys in place for 0.8 s, then lands in Redeemed or expired. A completed event greys and shines while its Moved to Archive notice holds, now 0.9 s rather than 1.5 s, which read as unresponsive; an ignored one only greys. Settings → Appearance gains Motion & celebrations as three What to track cards: Reduce motion (stops every animation and the fireworks, adding to the device's own setting), Fireworks, and Shine. Reduce motion locks the other two off without forgetting them. Co-Authored-By: Claude Opus 5.5 --- docs/DATA-MODEL.md | 7 + docs/DESIGN.md | 28 +++- docs/PRD.md | 16 ++- src/client/ReaderApp.tsx | 17 +-- src/client/components/Fireworks.tsx | 9 ++ .../components/redesign/AppearancePage.tsx | 98 +++++++++++--- .../components/redesign/ChecklistEvent.tsx | 26 ++++ src/client/components/redesign/CodesPage.tsx | 105 +++++++++++++-- src/client/redesign.css | 124 ++++++++++++++++-- src/client/state/appearance.ts | 16 +++ src/client/styles.css | 15 +++ test/celebrations.test.tsx | 10 +- test/codes-ui.test.tsx | 63 +++++++++ test/motion.test.tsx | 111 ++++++++++++++++ test/redesign-checklist.test.tsx | 42 ++++++ 15 files changed, 628 insertions(+), 59 deletions(-) create mode 100644 test/motion.test.tsx diff --git a/docs/DATA-MODEL.md b/docs/DATA-MODEL.md index ce7b081..dddfd55 100644 --- a/docs/DATA-MODEL.md +++ b/docs/DATA-MODEL.md @@ -832,6 +832,13 @@ means the reader has not taken this tour: the app moves them to `interface: "rel `true` means on; only `false` turns every celebration off, big and small. See `celebrationsOn` in `Fireworks.tsx`. +`prefs.shine` (2026-10-20) is its neighbour for the sweep across a code just redeemed or an event +just completed: absent or `true` is on, only `false` is off (`shineOn` in `Fireworks.tsx`). +`prefs.reduceMotion` (2026-10-20) is Appearance → Motion & celebrations → Reduce motion: only `true` +reduces motion, and it adds to the device's `prefers-reduced-motion` rather than replacing it +(`motionReduced` in `state/appearance.ts`). Neither changes an ID or a key; an older build ignores +both. + The fidelity correction adds `prefs.routineGroup` (`reset`, the absent-value default, or `game`) for the Routines page's grouping. It changes no routine IDs or placement keys. Structured is the interface default as of the owner's 2026-10-17 instruction; explicit saved Relaxed is preserved. diff --git a/docs/DESIGN.md b/docs/DESIGN.md index a38c13c..b33dc3a 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -626,8 +626,32 @@ legend on the same baseline as "Next: …". fires the small burst in the "Moved to Archive" notice; ignoring one fires nothing. - **When it fires.** Only an action taken now that completes the item or section, never arriving complete. Reduced motion removes all of it, and so does `prefs.celebrations: false` (Settings → - Appearance → Celebrations), read through `CelebrationsContext` in `Fireworks.tsx`. The row's own - tick animation (wash, pop, check) stays either way: it is feedback, not celebration. + Appearance → Motion & celebrations → Fireworks), read through `CelebrationsContext` in + `Fireworks.tsx`. The row's own tick animation (wash, pop, check) stays either way: it is feedback, + not celebration. +- **Shine (2026-10-20, owner).** Something leaving the list shines first: the dailies' light sweep + (held bright until it has crossed, as `code-sweep`, since a single row is too thin for one that + fades as it travels) and ring, in the game's colour. A code redeemed on its last server shines in + place for 0.8 s (the sweep 640 ms, the glow 560 ms), fades, and lands in "Redeemed or expired"; + ticking one of two servers only pops that box, and the code's text greys to 45% while it shines + (its Redeemed boxes stay full strength). A completed event's text greys the same while its "Moved + to Archive" notice holds, and the row shines; an ignored one greys without the shine. No fireworks + on a code: redeeming is a chore cleared, not a finish, and six in a row would be noise. Settings → + Appearance → Motion & celebrations → Shine (`prefs.shine: false`) turns the sweep off; the greying + stays, since it says where the row is going. Only the reader's own action shines, never a row + arriving. +- **Reduce motion (2026-10-20, owner).** Settings → Appearance → Motion & celebrations. + `prefs.reduceMotion: true` sets `data-motion="reduce"` on the root, and `styles.css` stops every + animation and transition and hides the fireworks. Off, it follows the device's + `prefers-reduced-motion`: a stylesheet cannot undo the device's request, so the switch can only + add to it, and its card says so. While it is on, the Fireworks and Shine cards show off, dimmed + and unpressable, with "Off while Reduce motion is on"; the saved choices underneath are kept, so + they come back as they were. +- **Motion & celebrations cards (2026-10-20, owner, mockup B of three).** The three switches are a + game's What to track cards (`.comp`, shared with `.game-settings` through `:is()`), three across + and one per row on phones, Reduce motion first: each explanation inside its own card, rather than + checkboxes with help text above and below them. The owner chose it over rows with switches (a + control type the app does not otherwise use) and nested checkboxes. ### Other pages diff --git a/docs/PRD.md b/docs/PRD.md index 4f0829c..b80f074 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -192,9 +192,9 @@ the cut are still counted in the header, still on the timeline, and one tap away **F3 — Mark completed.** Done writes durable progress and a metadata snapshot immediately. A confirmation remains for about -1.5 seconds before the row fades/collapses into Archive. Undo is available for eight seconds; -Archive Restore remains available afterwards. A failed write is disclosed and never presented as -saved. Reduced motion removes the fade. +0.9 seconds (1.5 until 2026-10-20, which read as unresponsive) before the row fades/collapses into +Archive. Undo is available for eight seconds; Archive Restore remains available afterwards. A failed +write is disclosed and never presented as saved. Reduced motion removes the fade. **F4 — Settings and visibility.** Settings has named pages: General, Your games, Appearance, Lists, Region & time, Data & sources, @@ -482,9 +482,13 @@ scheme are independent. Custom stores ground/surface/text/accent separately for rendered colours derive readable contrast without overwriting the saved choices. Per-game colours have a picker, hex field, preview and reset. Checklist dailies can occupy a separate direct-tick panel or grouped routines, with collapsed/expanded/always-expanded policies; these controls also live in -Routines. Numeric routines use native step-one inputs. A **Celebrations** switch (on by default) -covers the fireworks: a small burst for finishing one daily, routine or event, and the grand one for -finishing every daily or every routine in a game's group. Off means none at all, big or small. +Routines. Numeric routines use native step-one inputs. **Motion & celebrations** holds three cards. +**Reduce motion** (off by default, and saying what it does) stops every animation whatever the +device says; off, the device's own setting still applies. **Fireworks** (on by default) covers a +small burst for finishing one daily, routine or event, and the grand one for finishing every daily +or every routine in a game's group; off means none at all, big or small. **Shine** (on by default) +covers the sweep across a code just redeemed or an event just completed. While Reduce motion is on, +Fireworks and Shine are locked off without forgetting their setting. The earlier theme rationale below still applies to both interfaces. The app is a lit instrument panel and that is what it should be on first sight, but it is also read diff --git a/src/client/ReaderApp.tsx b/src/client/ReaderApp.tsx index 30f781a..93dd18d 100644 --- a/src/client/ReaderApp.tsx +++ b/src/client/ReaderApp.tsx @@ -9,6 +9,7 @@ import { PinnedRoutine } from "./components/redesign/PinnedRoutine.tsx"; import { DisplayTimeContext } from "./state/displayTime.ts"; import { CelebrationsContext, + shineOn, Fireworks, celebrationsOn, } from "./components/Fireworks.tsx"; @@ -65,7 +66,10 @@ import { GameMetaProvider } from "./state/gameMeta.tsx"; import { useAppUpdate } from "./state/useAppUpdate.ts"; import { ENTRIES, NOTES, newestId, unseen } from "../shared/changelog.ts"; import { + ARCHIVE_COLLAPSE_MS, + ARCHIVE_HOLD_MS, ChecklistEvent, + leavingMarks, LedgerColumns, UrgencyLegend, NextDeadline, @@ -478,11 +482,11 @@ function ReaderContent({ if (!transition) return; const t = setTimeout( () => setTransition((t) => (t ? { ...t, phase: "fade" } : null)), - 1500, + ARCHIVE_HOLD_MS, ); const collapse = setTimeout( () => setTransition((t) => (t ? { ...t, phase: "hidden" } : null)), - 1750, + ARCHIVE_COLLAPSE_MS, ); const undo = setTimeout(() => setTransition(null), 8000); return () => { @@ -620,14 +624,7 @@ function ReaderContent({ const key = pairKey(row.accountId, row.event.id), p = progress(row); return ( -
  • +
  • ): boolean { return prefs.celebrations !== false; } +/** + * Whether a redeemed code or a completed event shines on its way out + * (Settings → Appearance → Celebrations). Separate from fireworks; absent + * means on. + */ +export function shineOn(prefs: Record): boolean { + return prefs.shine !== false; +} + /** The reader's celebrations switch, provided once at the app's root. */ export const CelebrationsContext = createContext(true); diff --git a/src/client/components/redesign/AppearancePage.tsx b/src/client/components/redesign/AppearancePage.tsx index 8e79dcf..f2277a4 100644 --- a/src/client/components/redesign/AppearancePage.tsx +++ b/src/client/components/redesign/AppearancePage.tsx @@ -9,7 +9,7 @@ import { timelineLook } from "./TimelinePage.tsx"; import { ViewCards } from "./ViewCards.tsx"; import { focusBarShown } from "./FocusPicker.tsx"; import { URGENCY_STEPS, urgencyChoice } from "../../state/appearance.ts"; -import { celebrationsOn } from "../Fireworks.tsx"; +import { celebrationsOn, shineOn } from "../Fireworks.tsx"; /** The live legend's words for each step. */ const URGENCY_LABEL = { @@ -32,6 +32,8 @@ export function AppearancePage({ const p = state.prefs, patch = (patch: Record) => void onAction({ type: "prefs", patch, ...actionMeta() }); + // Reduce motion holds fireworks and shine off: both are nothing but motion. + const still = p.reduceMotion === true; const hidden = Array.isArray(p.hiddenGames) ? (p.hiddenGames as string[]) : []; @@ -354,21 +356,44 @@ export function AppearancePage({ ))} -
    -

    Celebrations

    -

    - Fireworks when you finish a daily, a routine or an event, and a bigger - show when you finish all your dailies or a game's routines. -

    -