Add user-controlled animation preferences and reduce motion support

- Persist prefersReducedMotion and animationSpeedMultiplier in user
profile - Provide UI controls to toggle reduce motion and adjust
animation speed globally - Centralize animation preferences via provider
and useAnimationPreferences hook - Apply preferences to charts’
animations (duration, enabled/disabled) - Inline script in layout to
apply preferences early and avoid FOUC - Update CSS to respect user
preference with reduced motion overrides and variable animation speeds
This commit is contained in:
2025-08-11 17:54:53 -04:00
parent 46767ca7e2
commit a270f6c1e5
10 changed files with 1150 additions and 14 deletions

View File

@@ -91,6 +91,50 @@ export const settingsRouter = createTRPCRouter({
return user;
}),
// Get animation preferences
getAnimationPreferences: protectedProcedure.query(async ({ ctx }) => {
const user = await ctx.db.query.users.findFirst({
where: eq(users.id, ctx.session.user.id),
columns: {
prefersReducedMotion: true,
animationSpeedMultiplier: true,
},
});
return {
prefersReducedMotion: user?.prefersReducedMotion ?? false,
animationSpeedMultiplier: user?.animationSpeedMultiplier ?? 1,
};
}),
// Update animation preferences
updateAnimationPreferences: protectedProcedure
.input(
z.object({
prefersReducedMotion: z.boolean().optional(),
animationSpeedMultiplier: z
.number()
.min(0.25, "Minimum 0.25x")
.max(4, "Maximum 4x")
.optional(),
}),
)
.mutation(async ({ ctx, input }) => {
await ctx.db
.update(users)
.set({
...(input.prefersReducedMotion !== undefined && {
prefersReducedMotion: input.prefersReducedMotion,
}),
...(input.animationSpeedMultiplier !== undefined && {
animationSpeedMultiplier: input.animationSpeedMultiplier,
}),
})
.where(eq(users.id, ctx.session.user.id));
return { success: true };
}),
// Update user profile
updateProfile: protectedProcedure
.input(

View File

@@ -24,6 +24,9 @@ export const users = createTable("user", (d) => ({
image: d.varchar({ length: 255 }),
resetToken: d.varchar({ length: 255 }),
resetTokenExpiry: d.timestamp(),
// User UI/animation preferences
prefersReducedMotion: d.boolean().default(false).notNull(),
animationSpeedMultiplier: d.real().default(1).notNull(),
}));
export const usersRelations = relations(users, ({ many }) => ({