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

+71
View File
@@ -0,0 +1,71 @@
import re
import sys
from pathlib import Path
MEDIA = re.compile(r'@media([^{;]*prefers-reduced-motion[^{;]*)\{')
MOTION = re.compile(r'\(\s*prefers-reduced-motion\s*:\s*(reduce|no-preference)\s*\)')
def block_end(css, start):
depth, i, quote = 1, start, None
while depth:
ch = css[i]
if quote:
if ch == '\\':
i += 1
elif ch == quote:
quote = None
elif ch in '"\'':
quote = ch
elif ch == '{':
depth += 1
elif ch == '}':
depth -= 1
i += 1
return i
def without_motion(prelude):
if ',' in prelude or re.search(r'\b(or|not|only)\b', prelude):
raise ValueError(f'@media{prelude}')
parts = [part.strip() for part in re.split(r'\band\b', prelude)]
wanted = [MOTION.fullmatch(part).group(1) for part in parts if MOTION.fullmatch(part)]
if len(wanted) != 1 or any('prefers-reduced-motion' in part and not MOTION.fullmatch(part) for part in parts):
raise ValueError(f'@media{prelude}')
return wanted[0], ' and '.join(part for part in parts if not MOTION.fullmatch(part))
def everyone_moves(css):
out, i = [], 0
while match := MEDIA.search(css, i):
out.append(css[i:match.start()])
end = block_end(css, match.end())
wanted, rest = without_motion(match.group(1))
if wanted == 'no-preference':
inner = everyone_moves(css[match.end():end - 1])
if not rest and inner.strip() and not inner.rstrip().endswith((';', '}')):
inner += ';'
out.append(f'@media {rest}{{{inner}}}' if rest else inner)
i = end
out.append(css[i:])
return ''.join(out)
failed = False
for name in sys.argv[1:]:
path = Path(name)
before = path.read_text(encoding='utf-8')
try:
after = everyone_moves(before)
except ValueError as query:
print(f'{name}: cannot rewrite {query}', file=sys.stderr)
failed = True
continue
if 'prefers-reduced-motion' in after:
print(f'{name}: a prefers-reduced-motion query is still there', file=sys.stderr)
failed = True
continue
if after != before:
path.write_text(after, encoding='utf-8')
print(f'{name}: motion now plays for everyone')
sys.exit(1 if failed else 0)
+5 -2
View File
@@ -74,7 +74,10 @@ prepare() {
cd "$ROOT"
case "${1:-build}" in
prepare) prepare ;;
build) prepare; "$TAILWIND" -i Styles/app.css -o wwwroot/css/app.css --optimize ;;
watch) prepare; exec "$TAILWIND" -i Styles/app.css -o wwwroot/css/app.css --watch ;;
build) prepare; "$TAILWIND" -i Styles/app.css -o wwwroot/css/app.css --optimize; python3 -I tools/css/motion.py wwwroot/css/app.css ;;
watch) prepare; "$TAILWIND" -i Styles/app.css -o wwwroot/css/app.css --watch 2>&1 | while IFS= read -r line; do
echo "$line"
case "$line" in Done*) python3 -I tools/css/motion.py wwwroot/css/app.css ;; esac
done ;;
*) echo "usage: tailwind.sh [prepare|build|watch]" >&2; exit 2 ;;
esac