diff --git a/.vscode/settings.json b/.vscode/settings.json index f813c91..b255cc8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,11 @@ { - "[javascript][javascriptreact][typescript][typescriptreact][css][scss][html][json][jsonc][yaml]": { + "[javascript][javascriptreact][typescript][typescriptreact][css][scss][postcss][html][json][jsonc][yaml]": { "editor.defaultFormatter": "biomejs.biome" }, + "editor.codeActionsOnSave": { + "quickFix.biome": "explicit", + "source.organizeImports.biome": "explicit" + }, "tailwindCSS.experimental.classRegex": [ ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] diff --git a/app/components/Background.tsx b/app/components/Background.tsx index ca03988..e840790 100644 --- a/app/components/Background.tsx +++ b/app/components/Background.tsx @@ -1,7 +1,8 @@ import type { ReactNode } from "react"; -import { useId } from "react"; +import { useEffect, useId, useRef } from "react"; import { cva } from "class-variance-authority"; +import { useReducedMotion } from "~/hooks/useMedia"; import { cn } from "~/libs/utils"; import { useTheme } from "./Theme"; @@ -35,12 +36,26 @@ export function Background({ }: BackgroundProps): ReactNode { const id = useId(); const theme = useTheme(); + const isReducedMotion = useReducedMotion(); + + const svgRef = useRef(null); + useEffect(() => { + const $svg = svgRef.current; + if (!$svg) return; + + if (isReducedMotion) { + $svg.pauseAnimations(); + } else { + $svg.unpauseAnimations(); + } + }, [isReducedMotion]); return ( diff --git a/app/components/BottomNav.tsx b/app/components/BottomNav.tsx index 36068ec..49cc8fd 100644 --- a/app/components/BottomNav.tsx +++ b/app/components/BottomNav.tsx @@ -50,7 +50,9 @@ export function BottomNav({ path, className }: BottomNavProps): ReactNode { link.href === path && "-translate-y-1 -rotate-2", )} > - {link.label} + + {link.label} + ))} diff --git a/app/hooks/useMedia.ts b/app/hooks/useMedia.ts new file mode 100644 index 0000000..a5a232d --- /dev/null +++ b/app/hooks/useMedia.ts @@ -0,0 +1,22 @@ +import { useSyncExternalStore } from "react"; + +export function useMedia(query: string, defaultState: boolean): boolean { + return useSyncExternalStore( + (callback) => { + const media = window.matchMedia(query); + + media.addEventListener("change", callback); + return () => { + media.removeEventListener("change", callback); + }; + }, + () => { + return window.matchMedia(query).matches; + }, + () => defaultState, + ); +} + +export function useReducedMotion(): boolean { + return useMedia("(prefers-reduced-motion: reduce)", false); +} diff --git a/app/routes/_auth.edit.tsx b/app/routes/_auth.edit.tsx index 504e6b3..4c4b5ca 100644 --- a/app/routes/_auth.edit.tsx +++ b/app/routes/_auth.edit.tsx @@ -1,5 +1,9 @@ import type { ReactNode } from "react"; export default function Page(): ReactNode { - return

キャラ編集

; + return ( +
+

キャラ編集

+
+ ); } diff --git a/app/routes/_auth.guide.tsx b/app/routes/_auth.guide.tsx index ae04653..c41073f 100644 --- a/app/routes/_auth.guide.tsx +++ b/app/routes/_auth.guide.tsx @@ -1,5 +1,9 @@ import type { ReactNode } from "react"; export default function Page(): ReactNode { - return

遊び方

; + return ( +
+

遊び方

+
+ ); } diff --git a/app/routes/_auth.play.tsx b/app/routes/_auth.play.tsx index ba45c81..b9d4a1f 100644 --- a/app/routes/_auth.play.tsx +++ b/app/routes/_auth.play.tsx @@ -11,7 +11,7 @@ export default function Page() { if (!session) return null; return ( -
+

QR code

    diff --git a/app/routes/_index/route.tsx b/app/routes/_index/route.tsx index 6b4ea25..33f7307 100644 --- a/app/routes/_index/route.tsx +++ b/app/routes/_index/route.tsx @@ -18,7 +18,10 @@ export default function Page() { }; return ( -
    +

    Game

    {myProfile ? ( diff --git a/app/tailwind.css b/app/tailwind.css index b5c61c9..dc4568c 100644 --- a/app/tailwind.css +++ b/app/tailwind.css @@ -1,3 +1,31 @@ @tailwind base; @tailwind components; @tailwind utilities; + +/** 「視差効果を減らす」が有効な場合に、アニメーションやトランジションを無効化する */ +@media (prefers-reduced-motion: reduce) { + html:focus-within { + scroll-behavior: auto; + } + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + + ::view-transition-group(*), + ::view-transition-old(*), + ::view-transition-new(*) { + animation: none !important; + } +} + +::view-transition-old(root) { + animation: none; +} +::view-transition-new(root) { + animation: none; +}