feat: add prefers-reduced-motion and prefers-contrast media queries (#5122) * feat: add prefers-reduced-motion and prefers-contrast media queries - Integrate OS prefers-reduced-motion into existing JS toggle system: when OS preference is active, force reduced_motion on and disable the UI checkbox with an explanatory tooltip - Add body.reduced-motion CSS rules for hardcoded animations that bypass the --animation-duration variable (text_segment fade-in, dragover pulse) - Add prefers-contrast: more media query for enhanced focus outlines Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Support translating title text * Use duration variable * Removed stream fade-in override * Combine hi-contrast selectors --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

56e995c3203d3ae621fb09c5a517ca7dfcc55282

Preston Farr <preston@prestonfarr.com>

Signed
2 files changed, +34 -1Showing whitespace changes
public/scripts/power-user.js+10 -0
@@ -518,10 +518,20 @@ function switchMessageActions() {
518}518}
519519
520function switchReducedMotion() {520function switchReducedMotion() {
521 const osReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
522 if (osReduced) {
523 power_user.reduced_motion = true;
524 }
521 jQuery.fx.off = power_user.reduced_motion;525 jQuery.fx.off = power_user.reduced_motion;
522 const overrideDuration = power_user.reduced_motion ? 0 : ANIMATION_DURATION_DEFAULT;526 const overrideDuration = power_user.reduced_motion ? 0 : ANIMATION_DURATION_DEFAULT;
523 setAnimationDuration(overrideDuration);527 setAnimationDuration(overrideDuration);
524 $('#reduced_motion').prop('checked', power_user.reduced_motion);528 $('#reduced_motion').prop('checked', power_user.reduced_motion);
529 $('#reduced_motion').prop('disabled', osReduced);
530 $('#reduced_motion').closest('label').attr('title',
531 osReduced
532 ? t`Controlled by your operating system's reduced motion setting`
533 : t`Disable animations and transitions`,
534 );
525 $('body').toggleClass('reduced-motion', power_user.reduced_motion);535 $('body').toggleClass('reduced-motion', power_user.reduced_motion);
526}536}
527537
public/style.css+24 -1
@@ -318,7 +318,7 @@ input[type='checkbox']:focus-visible {
318.dragover {318.dragover {
319 filter: brightness(1.1) saturate(1.0);319 filter: brightness(1.1) saturate(1.0);
320 outline: 3px dashed var(--SmartThemeBorderColor);320 outline: 3px dashed var(--SmartThemeBorderColor);
321 animation: pulse 0.5s infinite alternate;321 animation: pulse var(--animation-duration-3x) infinite alternate;
322}322}
323323
324.dragover.no_animation {324.dragover.no_animation {
@@ -6299,3 +6299,26 @@ body:not(.movingUI) .drawer-content.maximized {
6299 border-color: var(--error-color, #e87f7f);6299 border-color: var(--error-color, #e87f7f);
6300 background-color: rgba(241, 163, 163, 0.2);6300 background-color: rgba(241, 163, 163, 0.2);
6301}6301}
6302
6303@media (prefers-contrast: more) {
6304 :root {
6305 --interactable-outline-color: CanvasText;
6306 --interactable-outline-color-faint: CanvasText;
6307 }
6308
6309 .interactable:focus-visible,
6310 select:focus-visible,
6311 input:focus-visible,
6312 textarea:focus-visible {
6313 outline-width: 2px;
6314 }
6315
6316 .mes {
6317 border: 2px solid var(--SmartThemeBorderColor);
6318 }
6319
6320 .menu_button,
6321 .popup {
6322 border-width: 2px;
6323 }
6324}