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>
Signed| @@ -518,10 +518,20 @@ function switchMessageActions() { | |||
| 518 | } | 518 | } |
| 519 | 519 | ||
| 520 | function switchReducedMotion() { | 520 | function 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 | } |
| 527 | 537 | ||
| @@ -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 | } |
| 323 | 323 | ||
| 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 | } | ||