diff --git a/src/app/globals.css b/src/app/globals.css index a9cc33e69..587ea6013 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -436,6 +436,22 @@ html { scroll-padding-top: calc(4rem + env(safe-area-inset-top)); } +/* + * Interface density scales the rem baseline so every rem-based size shifts + * together. "Comfortable" is the browser default (16px) and sets no attribute, + * so the untouched default experience is unchanged. The attribute is applied on + * before first paint (layout.tsx inline script) and kept in sync by + * useAppPreferences. The unlayered 16px mobile input floor above still wins for + * form controls so iOS never zooms. + */ +html[data-density="compact"] { + font-size: 15px; +} + +html[data-density="spacious"] { + font-size: 17px; +} + body { min-height: 100%; min-height: 100svh; @@ -2204,6 +2220,26 @@ summary::-webkit-details-marker { } } +/* + * Explicit "Reduce motion" preference from the settings surface. Mirrors the + * prefers-reduced-motion suppression above so users can opt in regardless of + * their OS setting. The attribute is set on before first paint by the + * inline script in layout.tsx and kept in sync by useAppPreferences. + */ +html[data-motion="reduced"] *, +html[data-motion="reduced"] *::before, +html[data-motion="reduced"] *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; +} + +html[data-motion="reduced"] .source-capsule-hit:hover .source-capsule-face, +html[data-motion="reduced"] .source-capsule-hit[aria-expanded="true"]:hover .source-capsule-face { + transform: none !important; +} + @media (forced-colors: active) { :root, .dark { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 61fa12ce0..115c1b2ae 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -80,14 +80,16 @@ export default async function RootLayout({ {/* Applies the resolved theme before first paint on every route (standalone pages don't mount useTheme, and hydration-time toggling flashes light). Mirrors resolveThemePreference in src/lib/theme.ts: stored choice wins, - otherwise the OS preference. Key must match use-theme.ts. */} + otherwise the OS preference. Key must match use-theme.ts. The second + block applies the density/motion preferences (keys must match + use-app-preferences.ts) so an opted-in choice never flashes in. */}