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.
-