fix(a11y): reduced-motion for spinner/call-avatar; tap-to-skip boot; AA accent contrast

- SendingSpinClass and CallAvatarAnimation respect prefers-reduced-motion
  (static opacity fallback), matching MsgAppearClass.
- Terminal boot overlay dismisses on click/tap and is aria-hidden.
- Primary.Main: Midnight #6b7ca8 -> #6f80aa (4.29 -> 4.51:1), Lotus
  Terminal Light #c44e00 -> #ba4a00 (4.14 -> 4.52:1), hue preserved.

Fixes #84
Fixes #86
Fixes #52

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
2026-09-12 20:28:41 -04:00
co-authored by Claude Opus 5
parent 2e14980de6
commit 7aba71490a
3 changed files with 40 additions and 9 deletions
+22 -1
View File
@@ -36,25 +36,46 @@ const glowPulse = keyframes({
},
});
// Wobble/glow/spin only run under the same no-preference guard as
// MsgAppearClass below; prefers-reduced-motion users get a static state.
export const WobbleAnimation = style({
'@media': {
'(prefers-reduced-motion: no-preference)': {
animation: `${wobble} 2000ms ease-in-out`,
animationIterationCount: 'infinite',
},
},
});
export const GlowAnimation = style({
'@media': {
'(prefers-reduced-motion: no-preference)': {
animation: `${glowPulse} 2000ms ease-out`,
animationIterationCount: 'infinite',
},
},
});
export const CallAvatarAnimation = style({
'@media': {
'(prefers-reduced-motion: no-preference)': {
animation: `${wobble} 2000ms ease-in-out, ${glowPulse} 2000ms ease-out`,
animationIterationCount: 'infinite',
},
},
});
export const SendingSpinClass = style({
display: 'inline-block',
animation: `${spin} 900ms linear infinite`,
transformOrigin: 'center',
// Static fallback: dim instead of spinning when motion is reduced.
opacity: 0.6,
'@media': {
'(prefers-reduced-motion: no-preference)': {
animation: `${spin} 900ms linear infinite`,
opacity: 1,
},
},
});
const msgAppearKeyframe = keyframes({
+6 -2
View File
@@ -500,7 +500,9 @@ export const midnightTheme = createTheme(color, {
},
Primary: {
Main: '#6b7ca8',
// #6b7ca8 measured 4.29:1 against Background.Container (#111827), below
// WCAG AA 4.5:1. Lightened along the same hue/saturation to 4.51:1.
Main: '#6f80aa',
MainHover: '#7989b1',
MainActive: '#8493b8',
MainLine: '#8f9dbf',
@@ -651,7 +653,9 @@ export const lotusTerminalLightTheme = createTheme(color, {
OnContainer: '#2d3d56',
},
Primary: {
Main: '#c44e00',
// #c44e00 measured 4.14:1 against Background.Container (#edf0f5), below
// WCAG AA 4.5:1. Darkened along the same hue/saturation to 4.52:1.
Main: '#ba4a00',
MainHover: '#a33f00',
MainActive: '#8f3700',
MainLine: '#d45800',
+6
View File
@@ -34,6 +34,8 @@ export function runLotusBootSequence(force = false): void {
const overlay = document.createElement('div');
overlay.id = 'lt-boot';
// Decorative boot animation, not content — hide it from assistive tech.
overlay.setAttribute('aria-hidden', 'true');
overlay.style.cssText = [
'position:fixed',
'inset:0',
@@ -79,6 +81,7 @@ export function runLotusBootSequence(force = false): void {
const dismiss = (): void => {
clearInterval(interval);
document.removeEventListener('keydown', onKey);
overlay.removeEventListener('click', dismiss);
overlay.style.transition = 'opacity 0.4s ease';
overlay.style.opacity = '0';
setTimeout(() => overlay.remove(), 400);
@@ -88,6 +91,9 @@ export function runLotusBootSequence(force = false): void {
if (e.key === 'Escape') dismiss();
};
document.addEventListener('keydown', onKey);
// No touch/click dismiss previously existed — mouse/touch users had no way
// to skip besides the keyboard-only Escape hint.
overlay.addEventListener('click', dismiss);
let i = 0;
let text = '';