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:
1 parent
eb4ea2947c
commit
697b2c8897
11 files changed
+113
-55
No files matched your search
@@ -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)
|
||||
Reference in new issue
Block a user