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
+1
-1
@@ -46,7 +46,7 @@ export function scrollToOffset(rootId, top) {
|
||||
}
|
||||
|
||||
export function scrollToTop(rootId) {
|
||||
document.getElementById(rootId)?.scrollTo({ top: 0, behavior: matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth" })
|
||||
document.getElementById(rootId)?.scrollTo({ top: 0, behavior: "smooth" })
|
||||
}
|
||||
|
||||
export function dispose() {
|
||||
|
||||
+11
-16
@@ -9,7 +9,7 @@
|
||||
// night map (city lights) where it is down, blended across a twilight band, and brighter or dimmer with the Earth–Sun
|
||||
// distance. It shows now, or, while a post is hovered or focused, the moment that post was written; the Sun moves there
|
||||
// and back smoothly. Land borders are thin lines, and country names sit inside each country in the reader's language,
|
||||
// more of them and larger as the camera comes closer. Motion follows prefers-reduced-motion. It renders only while
|
||||
// more of them and larger as the camera comes closer. It renders only while
|
||||
// something moves (a camera flight, the Sun moving, a hovered post's pulse, auto-rotation, the reader dragging or
|
||||
// zooming) and never while the page or the globe is hidden: an idle globe costs no GPU time. No .NET round-trip on
|
||||
// scroll or hover: the posts' places come in one call per feed change (setPosts), the cards are matched by
|
||||
@@ -21,7 +21,6 @@ import { createInstruments } from "./instruments.js"
|
||||
const vendor = new URL("../vendor/globe/", import.meta.url).href
|
||||
const textures = { day: vendor + "earth-blue-marble.jpg", night: vendor + "earth-night.jpg" }
|
||||
const countriesUrl = vendor + "countries.json"
|
||||
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)")
|
||||
// a touch screen: a tap on a card opens its thread there (main.js), so cards are pinned with a mouse only
|
||||
const canHover = matchMedia("(hover: hover)")
|
||||
|
||||
@@ -218,7 +217,7 @@ function resize() {
|
||||
if (state.view)
|
||||
scheduleView()
|
||||
else
|
||||
state.globe.pointOfView({ ...state.globe.pointOfView(), altitude: 2.6 * shape }, reducedMotion.matches ? 0 : 600)
|
||||
state.globe.pointOfView({ ...state.globe.pointOfView(), altitude: 2.6 * shape }, 600)
|
||||
}
|
||||
state.shape = shape
|
||||
}
|
||||
@@ -243,7 +242,7 @@ async function build(width, height) {
|
||||
.pointRadius(p => 0.25 + Math.min(Math.sqrt(p.count), 6) * 0.12)
|
||||
.pointColor(p => colour(p.src))
|
||||
.pointLabel(p => label(p))
|
||||
.pointsTransitionDuration(reducedMotion.matches ? 0 : 600)
|
||||
.pointsTransitionDuration(600)
|
||||
.ringColor(r => t => r.colour.replace(/[\d.]+\)$/, `${(1 - t).toFixed(2)})`))
|
||||
.ringMaxRadius(r => r.radius)
|
||||
.ringPropagationSpeed(r => r.speed)
|
||||
@@ -254,7 +253,7 @@ async function build(width, height) {
|
||||
.arcDashLength(0.45)
|
||||
.arcDashGap(0.25)
|
||||
.arcDashInitialGap(1)
|
||||
.arcDashAnimateTime(reducedMotion.matches ? 0 : 1600)
|
||||
.arcDashAnimateTime(1600)
|
||||
.htmlLat(d => d.lat)
|
||||
.htmlLng(d => d.lng)
|
||||
.htmlAltitude(d => d.kind === "country" ? 0.004 : 0.12)
|
||||
@@ -419,13 +418,10 @@ function loadNight() {
|
||||
image.src = textures.night
|
||||
}
|
||||
|
||||
// The Sun moves to a new moment: its subsolar point travels there over about a second (the shorter way round), or
|
||||
// jumps with reduced motion.
|
||||
// The Sun moves to a new moment: its subsolar point travels there over about a second (the shorter way round).
|
||||
function aimSun(sun) {
|
||||
state.sun = sun
|
||||
state.sunUniforms.sunIntensity.value = sun.irradiance
|
||||
if (reducedMotion.matches)
|
||||
state.sunPoint = { lat: sun.lat, lng: sun.lng }
|
||||
state.sunUniforms.sunDirection.value = directionOf(state.sunPoint)
|
||||
state.instruments?.setMoment(sun, state.focus)
|
||||
state.instruments?.setSun(state.sunPoint)
|
||||
@@ -659,7 +655,6 @@ function watchTheme() {
|
||||
|
||||
function watchVisibility() {
|
||||
listen(document, "visibilitychange", () => pause(document.visibilityState !== "visible" || !hasSize()))
|
||||
listen(reducedMotion, "change", () => scheduleView())
|
||||
}
|
||||
|
||||
function listen(target, type, listener) {
|
||||
@@ -832,13 +827,13 @@ function drawRings() {
|
||||
const rings = []
|
||||
const hovered = state.hovered && state.points.get(state.hovered)
|
||||
if (placed(hovered))
|
||||
rings.push({ lat: hovered.lat, lng: hovered.lng, radius: 4, speed: 3, period: reducedMotion.matches ? 100000 : 900, colour: colour(hovered.src, 1) })
|
||||
rings.push({ lat: hovered.lat, lng: hovered.lng, radius: 4, speed: 3, period: 900, colour: colour(hovered.src, 1) })
|
||||
else if (hovered && Number.isFinite(hovered.beforeLat))
|
||||
rings.push({ lat: hovered.beforeLat, lng: hovered.beforeLng, radius: 3, speed: 1.5, period: reducedMotion.matches ? 100000 : 1400, colour: colour("country", 1) })
|
||||
rings.push({ lat: hovered.beforeLat, lng: hovered.beforeLng, radius: 3, speed: 1.5, period: 1400, colour: colour("country", 1) })
|
||||
const reach = Math.max(0, ...[...state.points.values()].filter(p => p.src === "nearyou").map(p => p.range ?? 0))
|
||||
const reader = [...state.points.values()].find(p => p.src === "nearyou")
|
||||
if (reader && reach > 0)
|
||||
rings.push({ lat: reader.lat, lng: reader.lng, radius: reach / 111, speed: reach / 111 / 2, period: reducedMotion.matches ? 100000 : 2400, colour: colour("nearyou", 0.8) })
|
||||
rings.push({ lat: reader.lat, lng: reader.lng, radius: reach / 111, speed: reach / 111 / 2, period: 2400, colour: colour("nearyou", 0.8) })
|
||||
state.globe.ringsData(rings)
|
||||
}
|
||||
|
||||
@@ -894,7 +889,7 @@ function updateView() {
|
||||
state.view = null
|
||||
state.spreadKm = 0
|
||||
state.instruments?.setView(null, 0, [], { located: 0, visible: state.visible.size })
|
||||
controls.autoRotate = !reducedMotion.matches
|
||||
controls.autoRotate = true
|
||||
wake()
|
||||
return
|
||||
}
|
||||
@@ -921,8 +916,8 @@ function updateView() {
|
||||
function fly(view, ms) {
|
||||
if (!view || !state?.globe)
|
||||
return
|
||||
wake((reducedMotion.matches ? 0 : ms) + 400)
|
||||
state.globe.pointOfView(view, reducedMotion.matches ? 0 : ms)
|
||||
wake(ms + 400)
|
||||
state.globe.pointOfView(view, ms)
|
||||
}
|
||||
|
||||
// A column taller than wide shows less of the globe across: the camera moves back to keep it whole.
|
||||
|
||||
Reference in new issue
Block a user