Skip to content

Commit 0664937

Browse files
author
catlog22
committed
feat: add gradient effects settings and enhance theme customization options
1 parent 369b470 commit 0664937

8 files changed

Lines changed: 435 additions & 21 deletions

File tree

ccw/frontend/src/components/shared/ThemeSelector.tsx

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,21 @@ import { generateThemeFromHue } from '@/lib/colorGenerator';
2020
*/
2121
export function ThemeSelector() {
2222
const { formatMessage } = useIntl();
23-
const { colorScheme, resolvedTheme, customHue, isCustomTheme, setColorScheme, setTheme, setCustomHue } = useTheme();
23+
const {
24+
colorScheme,
25+
resolvedTheme,
26+
customHue,
27+
isCustomTheme,
28+
gradientLevel,
29+
enableHoverGlow,
30+
enableBackgroundAnimation,
31+
setColorScheme,
32+
setTheme,
33+
setCustomHue,
34+
setGradientLevel,
35+
setEnableHoverGlow,
36+
setEnableBackgroundAnimation,
37+
} = useTheme();
2438

2539
// Local state for preview hue (uncommitted changes)
2640
const [previewHue, setPreviewHue] = useState<number | null>(customHue);
@@ -251,6 +265,74 @@ export function ThemeSelector() {
251265
</div>
252266
)}
253267

268+
{/* Gradient Effects Settings */}
269+
<div>
270+
<h3 className="text-sm font-medium text-text mb-3">
271+
{formatMessage({ id: 'theme.gradient.title' })}
272+
</h3>
273+
274+
{/* Gradient Level Selection */}
275+
<div className="space-y-4">
276+
<div
277+
className="flex gap-2"
278+
role="radiogroup"
279+
aria-label={formatMessage({ id: 'theme.gradient.title' })}
280+
>
281+
{(['off', 'standard', 'enhanced'] as const).map((level) => (
282+
<button
283+
key={level}
284+
onClick={() => setGradientLevel(level)}
285+
role="radio"
286+
aria-checked={gradientLevel === level}
287+
className={`
288+
flex-1 px-3 py-2 rounded-lg text-sm font-medium
289+
transition-all duration-200 border-2
290+
${gradientLevel === level
291+
? 'border-accent bg-surface shadow-md'
292+
: 'border-border bg-bg hover:bg-surface'
293+
}
294+
focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2
295+
`}
296+
>
297+
{formatMessage({ id: `theme.gradient.${level}` })}
298+
</button>
299+
))}
300+
</div>
301+
302+
{/* Hover Glow Checkbox */}
303+
<label className="flex items-center gap-3 cursor-pointer">
304+
<input
305+
type="checkbox"
306+
checked={enableHoverGlow}
307+
onChange={(e) => setEnableHoverGlow(e.target.checked)}
308+
className="
309+
w-4 h-4 rounded border-border text-accent
310+
focus:ring-2 focus:ring-accent focus:ring-offset-2
311+
"
312+
/>
313+
<span className="text-sm text-text">
314+
{formatMessage({ id: 'theme.gradient.hoverGlow' })}
315+
</span>
316+
</label>
317+
318+
{/* Background Animation Checkbox */}
319+
<label className="flex items-center gap-3 cursor-pointer">
320+
<input
321+
type="checkbox"
322+
checked={enableBackgroundAnimation}
323+
onChange={(e) => setEnableBackgroundAnimation(e.target.checked)}
324+
className="
325+
w-4 h-4 rounded border-border text-accent
326+
focus:ring-2 focus:ring-accent focus:ring-offset-2
327+
"
328+
/>
329+
<span className="text-sm text-text">
330+
{formatMessage({ id: 'theme.gradient.bgAnimation' })}
331+
</span>
332+
</label>
333+
</div>
334+
</div>
335+
254336
{/* Theme Mode Selection */}
255337
<div>
256338
<h3 className="text-sm font-medium text-text mb-3">

ccw/frontend/src/hooks/useTheme.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@
44
// Convenient hook for theme management with multi-color scheme support
55

66
import { useCallback } from 'react';
7-
import { useAppStore, selectTheme, selectResolvedTheme, selectCustomHue, selectIsCustomTheme } from '../stores/appStore';
8-
import type { Theme, ColorScheme } from '../types/store';
7+
import {
8+
useAppStore,
9+
selectTheme,
10+
selectResolvedTheme,
11+
selectCustomHue,
12+
selectIsCustomTheme,
13+
selectGradientLevel,
14+
selectEnableHoverGlow,
15+
selectEnableBackgroundAnimation,
16+
} from '../stores/appStore';
17+
import type { Theme, ColorScheme, GradientLevel } from '../types/store';
918

1019
export interface UseThemeReturn {
1120
/** Current theme preference ('light', 'dark', 'system') */
@@ -20,6 +29,12 @@ export interface UseThemeReturn {
2029
customHue: number | null;
2130
/** Whether the current theme is a custom theme */
2231
isCustomTheme: boolean;
32+
/** Gradient level: 'off', 'standard', or 'enhanced' */
33+
gradientLevel: GradientLevel;
34+
/** Whether hover glow effects are enabled */
35+
enableHoverGlow: boolean;
36+
/** Whether background gradient animation is enabled */
37+
enableBackgroundAnimation: boolean;
2338
/** Set theme preference */
2439
setTheme: (theme: Theme) => void;
2540
/** Set color scheme */
@@ -28,6 +43,12 @@ export interface UseThemeReturn {
2843
setCustomHue: (hue: number | null) => void;
2944
/** Toggle between light and dark (ignores system) */
3045
toggleTheme: () => void;
46+
/** Set gradient level */
47+
setGradientLevel: (level: GradientLevel) => void;
48+
/** Set hover glow enabled */
49+
setEnableHoverGlow: (enabled: boolean) => void;
50+
/** Set background animation enabled */
51+
setEnableBackgroundAnimation: (enabled: boolean) => void;
3152
}
3253

3354
/**
@@ -54,10 +75,16 @@ export function useTheme(): UseThemeReturn {
5475
const colorScheme = useAppStore((state) => state.colorScheme);
5576
const customHue = useAppStore(selectCustomHue);
5677
const isCustomTheme = useAppStore(selectIsCustomTheme);
78+
const gradientLevel = useAppStore(selectGradientLevel);
79+
const enableHoverGlow = useAppStore(selectEnableHoverGlow);
80+
const enableBackgroundAnimation = useAppStore(selectEnableBackgroundAnimation);
5781
const setThemeAction = useAppStore((state) => state.setTheme);
5882
const setColorSchemeAction = useAppStore((state) => state.setColorScheme);
5983
const setCustomHueAction = useAppStore((state) => state.setCustomHue);
6084
const toggleThemeAction = useAppStore((state) => state.toggleTheme);
85+
const setGradientLevelAction = useAppStore((state) => state.setGradientLevel);
86+
const setEnableHoverGlowAction = useAppStore((state) => state.setEnableHoverGlow);
87+
const setEnableBackgroundAnimationAction = useAppStore((state) => state.setEnableBackgroundAnimation);
6188

6289
const setTheme = useCallback(
6390
(newTheme: Theme) => {
@@ -84,16 +111,43 @@ export function useTheme(): UseThemeReturn {
84111
toggleThemeAction();
85112
}, [toggleThemeAction]);
86113

114+
const setGradientLevel = useCallback(
115+
(level: GradientLevel) => {
116+
setGradientLevelAction(level);
117+
},
118+
[setGradientLevelAction]
119+
);
120+
121+
const setEnableHoverGlow = useCallback(
122+
(enabled: boolean) => {
123+
setEnableHoverGlowAction(enabled);
124+
},
125+
[setEnableHoverGlowAction]
126+
);
127+
128+
const setEnableBackgroundAnimation = useCallback(
129+
(enabled: boolean) => {
130+
setEnableBackgroundAnimationAction(enabled);
131+
},
132+
[setEnableBackgroundAnimationAction]
133+
);
134+
87135
return {
88136
theme,
89137
resolvedTheme,
90138
isDark: resolvedTheme === 'dark',
91139
colorScheme,
92140
customHue,
93141
isCustomTheme,
142+
gradientLevel,
143+
enableHoverGlow,
144+
enableBackgroundAnimation,
94145
setTheme,
95146
setColorScheme,
96147
setCustomHue,
97148
toggleTheme,
149+
setGradientLevel,
150+
setEnableHoverGlow,
151+
setEnableBackgroundAnimation,
98152
};
99153
}

ccw/frontend/src/index.css

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,3 +433,105 @@
433433
.animate-spin {
434434
animation: spin 1s linear infinite;
435435
}
436+
437+
/* ===========================
438+
Gradient Effects System
439+
Conditional styles based on data attributes
440+
=========================== */
441+
442+
/* Disable gradients when off */
443+
[data-gradient="off"] .bg-gradient-primary,
444+
[data-gradient="off"] .bg-gradient-brand,
445+
[data-gradient="off"] .bg-gradient-accent {
446+
background-image: none !important;
447+
background: inherit;
448+
}
449+
450+
[data-gradient="off"] .gradient-text {
451+
background-image: none !important;
452+
-webkit-background-clip: unset;
453+
background-clip: unset;
454+
-webkit-text-fill-color: inherit;
455+
}
456+
457+
/* Standard gradients (default) */
458+
[data-gradient="standard"] .bg-gradient-primary {
459+
background: linear-gradient(135deg, hsl(var(--primary)) 0%, hsl(var(--accent)) 100%);
460+
}
461+
462+
[data-gradient="standard"] .bg-gradient-brand {
463+
background: linear-gradient(135deg, hsl(var(--accent)) 0%, hsl(var(--primary)) 100%);
464+
}
465+
466+
[data-gradient="standard"] .bg-gradient-accent {
467+
background: linear-gradient(90deg, hsl(var(--accent)) 0%, hsl(var(--primary)) 100%);
468+
}
469+
470+
/* Enhanced gradients - more vibrant with multiple color stops */
471+
[data-gradient="enhanced"] .bg-gradient-primary {
472+
background: linear-gradient(135deg,
473+
hsl(var(--primary)) 0%,
474+
hsl(var(--accent)) 50%,
475+
hsl(var(--secondary)) 100%
476+
);
477+
}
478+
479+
[data-gradient="enhanced"] .bg-gradient-brand {
480+
background: linear-gradient(135deg,
481+
hsl(var(--accent)) 0%,
482+
hsl(var(--primary)) 40%,
483+
hsl(var(--secondary)) 100%
484+
);
485+
}
486+
487+
[data-gradient="enhanced"] .bg-gradient-accent {
488+
background: linear-gradient(90deg,
489+
hsl(var(--accent)) 0%,
490+
hsl(var(--primary)) 50%,
491+
hsl(var(--accent)) 100%
492+
);
493+
}
494+
495+
/* Hover glow effects - disabled when data-hover-glow="false" */
496+
.hover-glow,
497+
.hover-glow-primary {
498+
transition: box-shadow 0.3s ease;
499+
}
500+
501+
.hover-glow:hover {
502+
box-shadow: 0 0 20px hsla(var(--accent), 0.4);
503+
}
504+
505+
.hover-glow-primary:hover {
506+
box-shadow: 0 0 20px hsla(var(--primary), 0.4);
507+
}
508+
509+
[data-hover-glow="false"] .hover-glow:hover,
510+
[data-hover-glow="false"] .hover-glow-primary:hover {
511+
box-shadow: none;
512+
}
513+
514+
/* Background animation keyframes */
515+
@keyframes slow-gradient {
516+
0% {
517+
background-position: 0% 50%;
518+
}
519+
50% {
520+
background-position: 100% 50%;
521+
}
522+
100% {
523+
background-position: 0% 50%;
524+
}
525+
}
526+
527+
/* Animated background gradient class */
528+
.animate-slow-gradient {
529+
background-size: 200% 200%;
530+
animation: slow-gradient 15s ease infinite;
531+
}
532+
533+
/* Disable background animation when data-bg-animation="false" */
534+
[data-bg-animation="false"] .animate-slow-gradient {
535+
animation: none;
536+
background-size: 100% 100%;
537+
}

ccw/frontend/src/locales/en/theme.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,13 @@
2626
"preview.surface": "Card",
2727
"preview.accent": "Accent",
2828
"save": "Save Custom Theme",
29-
"reset": "Reset to Preset"
29+
"reset": "Reset to Preset",
30+
"gradient": {
31+
"title": "Gradient Effects",
32+
"off": "Off",
33+
"standard": "Standard",
34+
"enhanced": "Enhanced",
35+
"hoverGlow": "Enable hover glow effects",
36+
"bgAnimation": "Enable background gradient animation"
37+
}
3038
}

ccw/frontend/src/locales/zh/theme.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,13 @@
2626
"preview.surface": "卡片",
2727
"preview.accent": "强调色",
2828
"save": "保存自定义主题",
29-
"reset": "重置为预设"
29+
"reset": "重置为预设",
30+
"gradient": {
31+
"title": "渐变效果",
32+
"off": "关闭",
33+
"standard": "标准",
34+
"enhanced": "增强",
35+
"hoverGlow": "启用悬停光晕效果",
36+
"bgAnimation": "启用背景渐变动画"
37+
}
3038
}

0 commit comments

Comments
 (0)