diff --git a/packages/app/src/index.css b/packages/app/src/index.css index db868801aa9..d1eb442a50b 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -178,7 +178,7 @@ transform-origin: 50% 50%; } - @media (prefers-reduced-motion: reduce) { + :root[data-motion="reduce"] { [data-slot="titlebar-update-loader"] { animation: none; } @@ -262,7 +262,7 @@ --session-dropzone-card: var(--v2-background-bg-layer-01); } - @media (prefers-reduced-motion: reduce) { + :root[data-motion="reduce"] { [data-component="session-dropzone"][data-visible="true"], [data-component="session-dropzone"][data-visible="true"] [data-slot="session-dropzone-content"], [data-component="session-dropzone"][data-visible="true"] [data-slot="session-dropzone-upload"], diff --git a/packages/app/src/new-session/pin-board.tsx b/packages/app/src/new-session/pin-board.tsx index f8760e2a382..e79a6ec3054 100644 --- a/packages/app/src/new-session/pin-board.tsx +++ b/packages/app/src/new-session/pin-board.tsx @@ -1,6 +1,7 @@ import { onCleanup, onMount } from "solid-js" import { createQuads, type Quads, type Rgba } from "./pin-quads" import { PinSound } from "./pin-sound" +import { prefersReducedMotion } from "@opencode/ui/motion" // The wordmark as a 78×14 pin grid: 1 = counter shadow, 2 = "open", 3 = "code". const GRID = [ @@ -98,7 +99,7 @@ function mountPinBoard(back: HTMLCanvasElement, front: HTMLCanvasElement, logo: const stage = back.parentElement! // The pins behind the composer and the ones popped in front of it are each one layer of quads. const layers = { back: createQuads(back, () => wake()), front: createQuads(front, () => wake()) } - const still = matchMedia("(prefers-reduced-motion: reduce)").matches + const still = prefersReducedMotion() const pins: Pin[] = GRID.flatMap((row, r) => [...row].flatMap((ch, c) => (ch === "." ? [] : [createPin(c, r, Number(ch))])), ) diff --git a/packages/app/src/runtime/i18n/en.ts b/packages/app/src/runtime/i18n/en.ts index fd7030312a0..df60d4ce141 100644 --- a/packages/app/src/runtime/i18n/en.ts +++ b/packages/app/src/runtime/i18n/en.ts @@ -753,6 +753,11 @@ export const dict = { "settings.general.row.interfaceMode.inbox.description": "Every session across your servers, sorted by what needs you next.", "settings.general.row.interfaceMode.experimental": "Experimental", + "settings.appearance.row.reduceMotion.title": "Reduce motion", + "settings.appearance.row.reduceMotion.description": "Reduce animations or match your system", + "settings.appearance.row.reduceMotion.system": "System", + "settings.appearance.row.reduceMotion.on": "On", + "settings.appearance.row.reduceMotion.off": "Off", "settings.notifications.description": "Choose when to receive notifications and hear sounds", "settings.shortcuts.description": "Customize shortcuts for common actions", "settings.servers.description": "Manage server connections", diff --git a/packages/app/src/session/composer/activity-chips.css b/packages/app/src/session/composer/activity-chips.css index 205209ffd35..646f1382658 100644 --- a/packages/app/src/session/composer/activity-chips.css +++ b/packages/app/src/session/composer/activity-chips.css @@ -56,7 +56,7 @@ margin: 4px -4px; } - @media (prefers-reduced-motion: reduce) { + :root[data-motion="reduce"] & { animation: none; } } diff --git a/packages/app/src/session/timeline/virtualizer.tsx b/packages/app/src/session/timeline/virtualizer.tsx index a78d1e92ce3..8f0e1042a3e 100644 --- a/packages/app/src/session/timeline/virtualizer.tsx +++ b/packages/app/src/session/timeline/virtualizer.tsx @@ -28,6 +28,7 @@ import { createMediaQuery } from "@solid-primitives/media" import type { createTimelineProjection } from "./projection" import { observeElementOffsetReconnectAware } from "./observe-element-offset" import { filterVirtualIndexes } from "./virtual-items" +import { prefersReducedMotion } from "@opencode/ui/motion" const fallbackItemSize = 60 @@ -869,7 +870,7 @@ export function createTimelineVirtualizer(input: Input) { "box-shadow": "var(--v2-elevation-raised), 0px 2px 8px var(--v2-background-bg-base)", }} onClick={() => - input.onResumeScroll(matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth") + input.onResumeScroll(prefersReducedMotion() ? "auto" : "smooth") } >