Project: BibleWeb — Atmospheric Background System Date: 2026-03-29 Context: Pixel art ambient scenes with animated water, campfire flicker, floating particles, and dust effects. Primary audience includes older users (55+, 65+, 75+).
Animated atmospheric backgrounds introduce real accessibility risk for several overlapping user groups: people with photosensitive epilepsy, vestibular disorders, cognitive disabilities, and age-related visual processing decline. The good news is that ambient, atmospheric motion (slow-moving water, floating dust, gentle particles) sits far from the danger zones if implemented correctly. The primary obligations are: (1) respect prefers-reduced-motion, (2) keep flicker rates below 3 Hz, (3) provide a pause/disable control, and (4) ensure the background never competes visually with foreground content.
Requirement: Web pages must not contain anything that flashes more than three times in any one-second period, OR the flash must fall below the general flash and red flash thresholds.
| Effect | Risk Level | Notes |
|---|---|---|
| Campfire flicker | Medium | If flicker simulates real flame (10–20 Hz), this is a direct WCAG 2.3.1 violation |
| Animated water shimmer | Low | Slow oscillation, not a sharp luminance switch |
| Floating dust/particles | Low | Individual particles are small; combined area unlikely to exceed threshold |
| Particle burst events | Medium | Sudden particle explosions could cross area threshold |
Campfire is the critical case. Real campfire flickers at roughly 8–12 Hz. A pixel art campfire that animates at full luminance contrast at that frequency over a large screen area would fail WCAG 2.3.1. The fix is either: (a) cap the flicker frequency to no more than 3 Hz, or (b) ensure the flame occupies less than 341 × 256 pixels of the viewport, or (c) use low-contrast luminance changes (under 10% relative luminance swing) to keep it sub-threshold even at higher frequencies.
Requirement: Web pages must not contain anything that flashes more than three times in any one-second period — with no safe area exception.
Level AAA removes the area escape hatch entirely. To meet AAA, nothing on the page may flash faster than 3 Hz regardless of size. For a Bible study app aiming to serve older and disabled users well, targeting AAA here is achievable and worth doing — it simply means capping campfire animation at ≤3 Hz.
Requirement: Motion animation triggered by interaction can be disabled, unless the animation is essential to the functionality or information being conveyed.
This criterion covers animations that respond to user input. In our app this includes:
The preferred implementation is to honour prefers-reduced-motion and disable or replace these triggered animations. A second acceptable approach is an in-app toggle. Background animations that play continuously (ambient loops) are addressed more by WCAG 2.2.2 and 2.3.1 than by 2.3.3, but the prefers-reduced-motion response should cover all of them together.
Requirement: For any moving or blinking content that starts automatically, lasts more than 5 seconds, and is presented alongside other content, the user must have a mechanism to pause, stop, or hide it.
Our atmospheric backgrounds are continuous ambient loops that run for the entire session — clearly beyond 5 seconds. This means WCAG 2.2.2 compliance is mandatory, not optional:
prefers-reduced-motion to automatically stop it (this alone satisfies the intent, though a visible control is more discoverable)The control must not trap keyboard focus or interfere with page usability. A small, accessible icon button in the corner of the scene is sufficient.
prefers-reduced-motion is a CSS Level 5 media feature that reflects the user's OS-level preference to reduce motion. It is set in:
/* Default: animations play */
.scene-canvas {
animation: float 4s ease-in-out infinite;
}
/* Reduced motion: stop all non-essential animation */
@media (prefers-reduced-motion: reduce) {
.scene-canvas,
.particle,
.campfire-sprite,
.water-shimmer {
animation: none;
transition: none;
}
}
The recommended pattern for our pixel art scenes is progressive enhancement: define the static version as the base, then add animation for users who have not expressed a preference:
/* Base: static scene, no motion */
.campfire { /* static sprite frame */ }
/* Enhancement: animate if no preference */
@media (prefers-reduced-motion: no-preference) {
.campfire { animation: flicker 0.3s steps(3) infinite; }
}
Since our pixel art scenes render in Canvas or via requestAnimationFrame, CSS alone will not stop them. JS detection is essential:
// Detect preference
const prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
// React to changes at runtime (user changes OS setting while page is open)
const motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
motionQuery.addEventListener('change', (e) => {
if (e.matches) {
stopAllSceneAnimations();
renderStaticSceneFrame();
} else {
startSceneAnimationLoop();
}
});
// lib/stores/motion.svelte.ts
import { readable } from 'svelte/store';
export const reducedMotion = readable(false, (set) => {
if (typeof window === 'undefined') return;
const query = window.matchMedia('(prefers-reduced-motion: reduce)');
set(query.matches);
const handler = (e: MediaQueryListEvent) => set(e.matches);
query.addEventListener('change', handler);
return () => query.removeEventListener('change', handler);
});
Then in any scene component, bind animation start/stop to this store.
Do not simply kill everything — that can strip meaningful feedback. Instead:
| Animation Type | Reduced Motion Behaviour |
|---|---|
| Ambient background loop (water, dust, wind) | Stop entirely; show single static frame |
| Campfire flicker | Replace with static warm glow (orange tinted static frame) |
| Scene transition (fade in/out) | Keep opacity fade; remove scale/translate |
| Particle burst on verse tap | Replace with instant appearance; no physics |
| Loading spinner | Replace with static icon + "Loading…" text |
Values: more, less, custom, no-preference. Most relevant is more, triggered by macOS "Increase Contrast" or when forced-colors is active with a high foreground/background ratio (≥7:1).
For our animated backgrounds: high contrast mode users need text to remain readable against whatever background is shown. The background scenes should never interfere with text legibility. Recommended approach: when prefers-contrast: more is active, significantly darken and desaturate the background or replace it with a solid near-black, and ensure all overlaid text uses a system-color-compatible class.
In Windows High Contrast mode (forced-colors: active), the browser overrides CSS color properties with system colors. Standard CSS backgrounds and borders are affected, but Canvas and WebGL content is not automatically overridden — it continues to render in the colors your JavaScript draws.
This means:
<canvas> element, forced-colors mode will not apply a high-contrast palette to it@media (forced-colors: active) {
.scene-canvas {
/* Hide or heavily dim the canvas so it doesn't fight system colors */
opacity: 0.15;
}
.scene-overlay {
background: Canvas;
color: CanvasText;
}
}
Consider providing a completely non-canvas fallback (a solid background using system colors) when forced-colors: active.
As many as 35% of adults aged 40+ in the United States have experienced some form of vestibular dysfunction. Vestibular disorders affect the inner ear and disrupt the brain's interpretation of motion and spatial orientation. Visual motion on screen can trigger: vertigo, nausea, migraines, disorientation, vomiting.
The highest-risk patterns for vestibular users are:
Our ambient backgrounds involve slow, continuous motion in a fixed background layer with no parallax (Bible study content scrolls in the foreground). This is a lower-risk profile. The key safeguards are:
Photosensitive epilepsy can be triggered by:
The most dangerous frequencies are 15–25 Hz (centre of the hazard range). Below 3 Hz is generally considered safe. Red flashing is more epileptogenic than other colours.
A pixel art campfire with a looping 3-frame animation cycling at ~10 fps = ~3.3 Hz. This is in the danger zone. Safe implementation:
The Photosensitive Epilepsy Analysis Tool (PEAT) from the TRACE Center at University of Maryland is the standard free tool:
For Canvas animations, record a screen capture of the scene and run it through PEAT before shipping.
| Change | Impact on Animation |
|---|---|
| Reduced contrast sensitivity | Low-contrast ambient effects may be invisible or straining |
| Slower visual processing | Fast animations cause confusion; user can't track what happened |
| Reduced ability to filter visual noise | Animated backgrounds compete more strongly with foreground text |
| Increased motion sensitivity | Older adults more susceptible to vestibular responses from motion |
| Cognitive load increases | Working memory is taxed; any background distraction has higher cost |
| Higher rates of photosensitivity | Epilepsy and seizure disorders increase in prevalence with age |
Recent age-friendly animation research (2024–2025) identified these preferences for elderly users:
The 55+ Bible study audience is precisely the population where: (a) vestibular dysfunction is common, (b) visual processing is slower, (c) background distraction has higher cognitive cost, and (d) the likelihood of photosensitive conditions is elevated. The atmospheric background must be:
Unnecessary animations, auto-play media, and environmental motion increase extraneous cognitive load — cognitive effort spent processing irrelevant information that competes with the primary task (reading scripture, studying notes).
Key findings from cognitive load research:
ADA Title III (public accommodations) and ADA Title II (state/local government) both require equal access for people with disabilities. Courts have consistently ruled that websites and apps are "places of public accommodation" under Title III. The DOJ finalized ADA Title II web rules in April 2024, explicitly requiring WCAG 2.1 Level AA compliance for government websites by April 2026.
Section 508 governs federal government digital content. It references WCAG 2.1 Level AA as the technical standard. For a commercial SaaS app like BibleWeb, Section 508 is not directly applicable but sets the industry benchmark.
EN 301 549 v3.2.1 (2021) is the harmonised European standard for ICT accessibility. It incorporates WCAG 2.1 in full. The European Accessibility Act (EAA) mandates compliance with EN 301 549 for digital products and services sold in the EU, with enforcement beginning June 2025. The next version (v4.1.1, expected 2026) will include WCAG 2.2 AA.
Animation-specific legal exposure maps directly to WCAG criteria: a WCAG 2.3.1 failure (flashing content triggering a seizure) is the most actionable litigation vector. There are documented cases of users filing complaints and lawsuits over content that triggered seizures or caused physical harm.
WCAG 2.1 Level AA compliance is the legally defensible floor. For animations this means:
When animations are disabled (via prefers-reduced-motion or user toggle), show the most visually appealing static version, not a blank canvas:
Static scene render — a single representative frame of the pixel art scene. The campfire is a warm ember glow. The water is a flat lake. The night sky has stars but no movement. This preserves the atmosphere without any motion.
Blurred/faded scene — if the static frame looks too stark, apply a slight CSS blur (filter: blur(1px)) and reduced opacity (opacity: 0.6) to soften it into a background texture without looking broken.
Solid color with gradient — if the user is in forced-colors mode or very high contrast mode, fall back to a solid dark background using system Canvas color. No scene at all.
<!-- SceneBackground.svelte -->
<script lang="ts">
import { reducedMotion } from '$lib/stores/motion.svelte';
import { onMount } from 'svelte';
let canvas: HTMLCanvasElement;
let animating = $state(false);
onMount(() => {
// Render single static frame immediately
renderStaticFrame(canvas);
// Only start animation loop if motion is acceptable
if (!$reducedMotion) {
animating = true;
startAnimationLoop(canvas);
}
// React to runtime changes
return reducedMotion.subscribe((reduced) => {
if (reduced && animating) {
animating = false;
stopAnimationLoop();
renderStaticFrame(canvas); // Freeze on a clean frame
} else if (!reduced && !animating) {
animating = true;
startAnimationLoop(canvas);
}
});
});
</script>
<canvas
bind:this={canvas}
class="scene-bg"
aria-hidden="true"
role="presentation"
/>
The background canvas should always have aria-hidden="true" — it is purely decorative and screen readers must not announce it. Role presentation or none reinforces this.
Per WCAG 2.2.2, provide a visible in-app control:
<button
aria-label="Pause background animation"
aria-pressed={!animating}
class="scene-toggle"
onclick={toggleAnimation}
>
{animating ? '⏸' : '▶'}
</button>
The button must be keyboard-accessible, have a visible focus indicator, and be large enough for touch users (minimum 44 × 44 CSS px per WCAG 2.5.5).
| Tool | Purpose | Platform |
|---|---|---|
| PEAT (trace.umd.edu/peat) | Flash frequency and area analysis of screen recordings | Windows desktop |
| Harding FPA | Professional broadcast-grade photosensitivity analysis | Licensed |
| axe DevTools | General WCAG automated audit | Browser extension |
| WAVE (wave.webaim.org) | General WCAG audit including some motion flags | Browser extension |
| Browser DevTools | Toggle prefers-reduced-motion in Rendering panel (Chrome/Firefox) |
Built-in |
| Lighthouse | General accessibility audit | Built-in Chrome DevTools |
prefers-reduced-motion: reduce emulated — confirm Canvas stops, static frame rendersforced-colors: active) — confirm text remains readable, canvas is suppressed| Requirement | Standard | Priority | Action |
|---|---|---|---|
| Cap campfire flicker to ≤3 Hz | WCAG 2.3.1 (A) | Critical | Limit animation frame rate |
| Provide pause/stop control | WCAG 2.2.2 (A) | Critical | Add visible toggle button |
| Honour prefers-reduced-motion | WCAG 2.3.3 (AAA) | High | JS matchMedia + stop Canvas loop |
| Static scene fallback | WCAG 2.2.2 + best practice | High | Freeze on single clean frame |
| Suppress Canvas in forced-colors | Best practice | Medium | CSS opacity rule |
| Dim background vs. foreground text | Cognitive/WCAG 1.4.3 | High | Background opacity ≤0.6 |
| aria-hidden on canvas | WCAG 1.3.1 (A) | High | Already: decorative element |
| Focus Mode (solid background) | Cognitive accessibility | Medium | User-toggled setting |
| Run PEAT before shipping | QA process | High | Record + analyse each scene |