Animations, GIFs and the globe now move for every reader
Build / Build (push) Successful in 33s

decePub no longer looks at the system's "reduce motion" setting, so everything moves the same way whatever it says:
- a GIF video in a post plays by itself, muted and looping, instead of waiting for a tap; it now also starts on a page
  just opened, before the first tap, where the browser used to hold it still;
- animated custom emoji move in the emoji picker instead of showing a still picture;
- the globe turns by itself while no located post is in view, flies to the posts, and its pulses, arcs and Sun move;
- going back to the top of a feed scrolls smoothly;
- tooltips, dropdowns, menus, folding sections, toasts, loading spinners, placeholders, progress bars, radio buttons
  and ratings keep their animations: the build now takes daisyUI's reduce-motion rules out of the stylesheet
  (tools/css/motion.py, run after every Tailwind build, in watch mode too).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4YtEKqxKjKyHkFzuRVMNA
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-11-09 20:48:47 +01:00
1 parent eb4ea2947c
commit 697b2c8897
11 files changed
+113 -55

No files matched your search

+17 -8
View File
@@ -80,6 +80,10 @@ without network access. NuGet restore still uses nuget.org.
- `IncludeTailwindOutput` adds the generated file to `Content` before static web assets are resolved. Without that, a
clean-clone publish would ship `app.css` without its `.gz`/`.br` copies and outside `service-worker-assets.js`.
The deploy workflow checks for `css/app.css.gz`.
- Right after Tailwind writes `app.css`, `tools/css/motion.py` (run by `TailwindCompile` and by `tailwind.sh build` and
`watch`) deletes the blocks daisyUI emits for `prefers-reduced-motion: reduce` and unwraps its `no-preference` ones,
so its transitions and animations play for everyone. It fails the build on a query it cannot rewrite. It needs
`python3`.
- `bash tools/tailwind.sh watch` rebuilds the CSS live next to `dotnet watch`.
- Tailwind scans every file `.gitignore` doesn't exclude (minus `tools/` and `wwwroot/vendor/`), and only
generates the classes it finds written out **whole**. Never build a class name by concatenation or interpolation (`"btn-" + size`, `$"btn-{size}"`). Write
@@ -355,7 +359,9 @@ Never redeclare the base's members in a component.
paints any `[data-blurhash]`: a picture's until it loads, again when its source changes, and for good behind the
sensitive-media button), a picture's focal point (`object-position`), and an event's online link, participants and
status. A picture, video or sound that fails to load says "Media unavailable" with "Open original"
(`data-media-unavailable`), and a gifv plays only on a tap under `prefers-reduced-motion`.
(`data-media-unavailable`), and a gifv plays by itself, muted and looping, for every reader. Blazor writes `muted`
only as an attribute, which leaves the video unmuted and Chromium refuses to autoplay it before the reader's first
tap, so `main.js` sets the `muted` property of every video carrying the attribute on its `loadstart`.
- **About** (`Pages/About.razor` at `/about`, no sign-in, `data-about`; linked from NavMenu as `data-nav-about` and from
Login as `data-about-offer`): the server's name (`data-about-title`), short description and thumbnail, its long
description (`data-about-description`, `/api/v1/instance/extended_description`), contact (`data-about-contact`), rules
@@ -612,11 +618,11 @@ Never redeclare the base's members in a component.
quick choices (`Quick`), the server's emoji by category (none first) and common unicode ones by group
(`Helpers/UnicodeEmojis.cs`, single emoji only, searched by their Mastodon/GitHub shortcode and a few words). Each
choice is `data-emoji-choice` (the shortcode, or the character) and goes to `OnPick` as `:shortcode:` or the
character; it closes the picker first. A GIF shows its still picture under `prefers-reduced-motion`. The card's React
button (`data-react`) and the announcements banner's (`data-announcement-react`) react with it, after their quick
reactions; the composer's emoji button writes it where the caret is, replacing a selection, else at the end, a
shortcode with a space on each side where a letter would touch it (`AppStatusService.CaretOf` / `PlaceCaret`,
`main.js`'s `caretOf` and `placeCaret`).
character; it closes the picker first. An animated emoji moves in the picker for every reader (its `url`, never its
`static_url`). The card's React button (`data-react`) and the announcements banner's (`data-announcement-react`)
react with it, after their quick reactions; the composer's emoji button writes it where the caret is, replacing a
selection, else at the end, a shortcode with a space on each side where a letter would touch it
(`AppStatusService.CaretOf` / `PlaceCaret`, `main.js`'s `caretOf` and `placeCaret`).
- **Suggestions:** in the composer, `:` and two letters or more at the end of the words offer the matching emoji
(shortcodes starting with it first, eight at most) among the mention and hashtag suggestions, as
`data-emoji-suggestion="{shortcode}"`; one picked is written `:shortcode: `.
@@ -765,8 +771,8 @@ Never redeclare the base's members in a component.
- **The globe** (`wwwroot/js/globe.js`, an ES module imported by `GlobePanel`) runs on globe.gl, vendored in
`wwwroot/vendor/globe/` with its textures and `countries.json` (`SOURCES.md` has versions, licences and hashes; the
service worker never precaches it). It looks at the spherical mean of the located posts in view, pulses and draws an
arc to a hovered post, honours `prefers-reduced-motion`, and draws only while something moves (idle, it costs no GPU
time; `wake()` before anything that animates).
arc to a hovered post, moves the same way for every reader (camera flights, the Sun, pulses, arcs, auto-rotation),
and draws only while something moves (idle, it costs no GPU time; `wake()` before anything that animates).
- **Map.** Each pixel computes its own map coordinates from its point on the sphere (`sharpen`), so the poles don't
pinch; the maps wrap horizontally and get 8× anisotropic filtering (`keepSharp`). The day texture is NASA's, its
seam repaired by `tools/globe/prepare-day-texture.py` (dev-only, needs Pillow).
@@ -978,6 +984,9 @@ The nginx vhost and headers are in `deploy/nginx/` (it sends `X-Robots-Tag: noin
## Conventions
- **Motion plays for everyone** (owner, 9 November 2026): nothing reads `prefers-reduced-motion` or any equivalent
(`matchMedia`, `motion-safe:`/`motion-reduce:`, a flag fed from it), so animations, GIFs and gifvs, smooth scrolling
and the globe behave the same for every reader.
- Indent with tabs.
- `Nullable` is disabled and `ImplicitUsings` is enabled.
- Global Razor usings are in `_Imports.razor`.