feat(a11y): wire flash-clamp into autoScrub; document 3-flash/sec audit

Clamp each per-altitude step to the WCAG 2.3.1 floor (no-op at 1200ms, guards
a future lowering). Audit: #black fade + audio crossfade can't exceed 3/sec, no
clamp needed. boot() now seeds the gate-dismissed flag (returning visitor) so
the run-sim test isn't blocked by the new interstitial.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-06-30 09:34:10 -07:00
parent f1ce23c4fe
commit 79105a3ef9
3 changed files with 13 additions and 2 deletions
+8 -1
View File
@@ -705,7 +705,14 @@ function autoScrub(targetPos, perAltMs = PER_ALTITUDE_MS) {
if (autoRaf) cancelAnimationFrame(autoRaf);
const fromPos = pos, dist = targetPos - fromPos;
if (!dist) { setPos(targetPos); return; }
const ms = perAltMs * Math.abs(dist); // constant per-altitude speed → N steps take N× as long
// Photosensitivity floor (WCAG 2.3.1): each altitude step is one luminance
// transition (a blended crossfade), so a single step must take >= 1/3 s to stay
// <= 3 flashes/sec. The clamp is a no-op at today's 1200ms but guards the floor
// if PER_ALTITUDE_MS is ever lowered. (Audit: the only other luminance sources —
// the #black cover fade (200ms, single, >=1.2s apart) and the audio-coupled
// crossfade — cannot repeat faster than 3/sec, so they need no clamp.)
const safeAltMs = HEFFlash.clampDurationMs(perAltMs, 1);
const ms = safeAltMs * Math.abs(dist); // constant per-altitude speed → N steps take N× as long
let startTs = null;
const tick = (ts) => {
if (startTs === null) startTs = ts;
+1
View File
@@ -129,6 +129,7 @@
<script src="config.js"></script>
<script src="scrub.js"></script>
<script src="alteration.js"></script>
<script src="flash.js"></script>
<script src="i18n.js"></script>
<script src="app.js"></script>
</body>