Skip to content

Commit 826ee6e

Browse files
authored
feat(themes): add light theme preset SVGs, contrast up-arrow & fix hydration (JhaSourav07#944)
Fixes JhaSourav07#927 This PR introduces visual parity, high contrast navigation, and SSR hydration safety across CommitPulse: 1. ** Light Theme Preset SVGs**: - Added custom vector details for `Catppuccin Latte` (warm coffee latte cup with steam details). - Added custom astronomical solar-ray SVGs for the `Solarized Light` theme. - Restructured the `Gruvbox Light` lantern SVG to dynamically use the theme's background fill so that it displays perfectly in both dark and light modes. - Configured `Nord Light` to use the premium dynamic Nord snowflake vector. - Added a high-tech cyberpunk HUD neon triangle SVG for the `Aurora Cyberpunk` theme. 2. ** Contrast & Accessibility Improvements**: - Re-designed the "Return to Top" button (`ReturnToTop.tsx`) to feature **solid high-contrast background coloring** (pure white in light mode / solid black in dark mode) instead of transparent blends. - Added theme-matching emerald green icons, borders, and premium **neon emerald aura shadows** (`shadow-[0_4px_30px_rgba(16,185,129,0.3)]`) with scaling transitions on hover (`hover:scale-110 active:scale-95`). - Standardized text color variables across the customization studio and documentation pages to properly adapt under light and dark modes (fixing raw invisible text in light mode). 3. ** SSR Hydration Correction**: - Resolved the React/Next.js hydration mismatch warning in `Navbar` on load by wrapping dynamic theme toggle buttons under a client-side `mounted` check. --- ## Pillar - [x] 🎨 Pillar 1 — New Theme Design - [ ] 📐 Pillar 2 — Geometric SVG Improvement - [ ] 🕐 Pillar 3 — Timezone Logic Optimization - [x] 🛠️ Other (Bug fix, refactoring, docs) --- ## Visual Preview <!-- Drag and drop screenshots of the light theme quick preset buttons, the high-contrast up-arrow button, and the clean console without the Next.js hydration error. --> --- ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`). - [x] I have run `npm run format` and `npm run lint` locally and resolved all errors (CI will fail otherwise). - [x] My commits follow the Conventional Commits format (e.g., `feat(themes): ...`, `fix(calculate): ...`). - [x] I have updated `README.md` if I added a new theme or URL parameter. - [x] I have starred the repo. - [x] I have made sure that I have only one commit to merge in this PR. - [x] The SVG output matches the CommitPulse "premium quality" aesthetic standard (no raw elements, smooth animations, correct fonts). ## Visual Preview ### 1. Return-to-Top Button Redesign | Before (Mismatched Blue Gradient) | After (Sleek Glassmorphic & Neon Aura) | | --- | --- | | <img width="170" height="162" alt="image" src="https://github.com/user-attachments/assets/69512f2d-9fd9-4fcf-8d70-2b4f208b6c32" /> | <img width="169" height="160" alt="image" src="https://github.com/user-attachments/assets/9985bb84-fe63-468d-8ccf-82a912e588e3" /> | ### 2. Light Themes <img width="395" height="100" alt="image" src="https://github.com/user-attachments/assets/bb08d7ba-541f-4716-b358-cacb2339ac48" /> <img width="600" height="420" alt="image" src="https://github.com/user-attachments/assets/66ede947-25cc-47fa-8468-c63066334d73" /> <img width="600" height="420" alt="image" src="https://github.com/user-attachments/assets/f67a3bc4-3a6b-4980-9f85-2900b6c4ff6a" /> <img width="600" height="420" alt="image" src="https://github.com/user-attachments/assets/28f8a2ab-1750-419c-baa7-2d4960922248" /> <img width="600" height="420" alt="image" src="https://github.com/user-attachments/assets/71dae70b-5e8f-4247-9ccd-d635bc74c95c" />
2 parents 2401377 + 5bc2480 commit 826ee6e

9 files changed

Lines changed: 199 additions & 16 deletions

File tree

app/components/CustomizeCTA.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function CustomizeCTA() {
2222

2323
<div className="relative z-10 flex flex-col md:flex-row items-center justify-between gap-8 px-8 py-10">
2424
<div className="flex-1 text-center md:text-left">
25-
<p className="text-xs font-bold uppercase tracking-[0.25em] text-emerald-400 mb-3">
25+
<p className="text-xs font-bold uppercase tracking-[0.25em] text-emerald-600 dark:text-emerald-400 mb-3">
2626
Customization Studio
2727
</p>
2828
<h2 className="text-2xl md:text-3xl font-extrabold text-black dark:text-white tracking-tight mb-3 leading-snug">

app/components/navbar.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const NAV_LINKS = [
2828

2929
export default function Navbar() {
3030
const [open, setOpen] = useState(false);
31+
const [mounted, setMounted] = useState(false);
3132

3233
const [isDark, setIsDark] = useState(() => {
3334
if (typeof window === 'undefined') return true;
@@ -38,6 +39,11 @@ export default function Navbar() {
3839
const { shellRef, shellVars, handleMouseEnter, handleMouseMove, handleMouseLeave } =
3940
useGlowEffect();
4041

42+
useEffect(() => {
43+
// eslint-disable-next-line react-hooks/set-state-in-effect
44+
setMounted(true);
45+
}, []);
46+
4147
useEffect(() => {
4248
document.documentElement.classList.toggle('dark', isDark);
4349
localStorage.setItem('theme', isDark ? 'dark' : 'light');
@@ -130,7 +136,15 @@ export default function Navbar() {
130136
className="inline-flex h-10 w-10 items-center justify-center rounded-xl border border-white/15 bg-white/5 text-white transition hover:bg-white/10"
131137
aria-label="Toggle theme"
132138
>
133-
{isDark ? <Sun size={18} /> : <Moon size={18} />}
139+
{mounted ? (
140+
isDark ? (
141+
<Sun size={18} />
142+
) : (
143+
<Moon size={18} />
144+
)
145+
) : (
146+
<span className="w-[18px] h-[18px]" />
147+
)}
134148
</button>
135149

136150
{NAV_LINKS.map((link) => (

app/customize/components/ControlsPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export function ControlsPanel({
183183

184184
return (
185185
<div>
186-
<p className="text-xs font-bold uppercase tracking-[0.22em] text-emerald-400 mb-4">
186+
<p className="text-xs font-bold uppercase tracking-[0.22em] text-emerald-600 dark:text-emerald-400 mb-4">
187187
Controls
188188
</p>
189189

app/customize/components/ExportPanel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export function ExportPanel({
124124
<div className="bg-white/70 backdrop-blur-xl border border-black/10 dark:bg-black/35 dark:border-white/10 rounded-[1.75rem] p-6 shadow-[0_20px_60px_rgba(0,0,0,0.15)]">
125125
<div className="flex flex-col gap-4 mb-5 md:flex-row md:items-center md:justify-between">
126126
<div>
127-
<p className="text-xs font-bold uppercase tracking-[0.22em] text-emerald-400">
127+
<p className="text-xs font-bold uppercase tracking-[0.22em] text-emerald-600 dark:text-emerald-400">
128128
{formatLabel} Export Snippet
129129
</p>
130130
<p className="mt-1 text-[11px] text-gray-500 dark:text-white/25">
@@ -145,7 +145,7 @@ export function ExportPanel({
145145
aria-pressed={format === option.value}
146146
className={`rounded-lg px-3 py-1.5 text-xs font-bold transition-all ${
147147
format === option.value
148-
? 'bg-emerald-500/15 text-emerald-300 shadow-[0_0_24px_rgba(16,185,129,0.16)]'
148+
? 'bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 shadow-[0_0_24px_rgba(16,185,129,0.16)]'
149149
: 'text-gray-600 hover:text-black bg-gray-100/70 dark:bg-transparent dark:text-white/35 dark:hover:text-white'
150150
}`}
151151
>

app/customize/components/ThemeQuickPresets.tsx

Lines changed: 146 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ function IconSynthwave({ bg, text, accent }: IC): ReactElement {
309309
);
310310
}
311311

312-
function IconGruvbox({ text, accent }: IC): ReactElement {
312+
function IconGruvbox({ bg, text, accent }: IC): ReactElement {
313313
return (
314314
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" aria-hidden="true">
315315
{/* top handle */}
@@ -331,7 +331,7 @@ function IconGruvbox({ text, accent }: IC): ReactElement {
331331
{/* lantern body */}
332332
<rect x="8" y="11" width="12" height="11" rx="2.5" fill={text} opacity="0.82" />
333333
{/* glass chamber */}
334-
<rect x="10.2" y="13" width="7.6" height="6.8" rx="1.5" fill="#1d2021" opacity="0.9" />
334+
<rect x="10.2" y="13" width="7.6" height="6.8" rx="1.5" fill={bg} opacity="0.9" />
335335
{/* warm glow */}
336336
<ellipse cx="14" cy="16.5" rx="2.8" ry="3.2" fill={accent} opacity="0.95" />
337337
{/* flame */}
@@ -357,7 +357,145 @@ function IconHighcontrast({ text, accent }: IC): ReactElement {
357357
);
358358
}
359359

360-
const ICON_MAP: Record<ThemeKey, (c: IC) => ReactElement> = {
360+
function IconCatppuccinLatte({ text, accent }: IC): ReactElement {
361+
return (
362+
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" aria-hidden="true">
363+
{/* Cup body */}
364+
<path d="M6 10 C6 17, 7 20, 14 20 C21 20, 22 17, 22 10 Z" fill={accent} opacity="0.9" />
365+
{/* Cup handle */}
366+
<path
367+
d="M22 12 C24.5 12, 24.5 16, 22 16"
368+
stroke={accent}
369+
strokeWidth="2.2"
370+
strokeLinecap="round"
371+
fill="none"
372+
/>
373+
{/* Latte art / foam */}
374+
<ellipse cx="14" cy="10" rx="6.5" ry="1.8" fill={text} opacity="0.9" />
375+
{/* Steam lines */}
376+
<path
377+
d="M11 7 C11 5, 13 5, 13 3"
378+
stroke={text}
379+
strokeWidth="1.2"
380+
strokeLinecap="round"
381+
fill="none"
382+
opacity="0.6"
383+
/>
384+
<path
385+
d="M15 7 C15 5, 17 5, 17 3"
386+
stroke={text}
387+
strokeWidth="1.2"
388+
strokeLinecap="round"
389+
fill="none"
390+
opacity="0.6"
391+
/>
392+
</svg>
393+
);
394+
}
395+
396+
function IconSolarizedLight({ text, accent }: IC): ReactElement {
397+
return (
398+
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" aria-hidden="true">
399+
<circle cx="14" cy="14" r="5" stroke={accent} strokeWidth="2" opacity="0.95" />
400+
<circle cx="14" cy="14" r="2" fill={text} opacity="0.9" />
401+
{/* Solar/Astronomical clean geometric rays */}
402+
<line x1="14" y1="3" x2="14" y2="6" stroke={accent} strokeWidth="1.8" strokeLinecap="round" />
403+
<line
404+
x1="14"
405+
y1="22"
406+
x2="14"
407+
y2="25"
408+
stroke={accent}
409+
strokeWidth="1.8"
410+
strokeLinecap="round"
411+
/>
412+
<line x1="3" y1="14" x2="6" y2="14" stroke={accent} strokeWidth="1.8" strokeLinecap="round" />
413+
<line
414+
x1="22"
415+
y1="14"
416+
x2="25"
417+
y2="14"
418+
stroke={accent}
419+
strokeWidth="1.8"
420+
strokeLinecap="round"
421+
/>
422+
<line
423+
x1="6.2"
424+
y1="6.2"
425+
x2="8.3"
426+
y2="8.3"
427+
stroke={accent}
428+
strokeWidth="1.5"
429+
strokeLinecap="round"
430+
opacity="0.7"
431+
/>
432+
<line
433+
x1="19.7"
434+
y1="19.7"
435+
x2="21.8"
436+
y2="21.8"
437+
stroke={accent}
438+
strokeWidth="1.5"
439+
strokeLinecap="round"
440+
opacity="0.7"
441+
/>
442+
<line
443+
x1="19.7"
444+
y1="6.2"
445+
x2="17.6"
446+
y2="8.3"
447+
stroke={accent}
448+
strokeWidth="1.5"
449+
strokeLinecap="round"
450+
opacity="0.7"
451+
/>
452+
<line
453+
x1="6.2"
454+
y1="19.7"
455+
x2="8.3"
456+
y2="17.6"
457+
stroke={accent}
458+
strokeWidth="1.5"
459+
strokeLinecap="round"
460+
opacity="0.7"
461+
/>
462+
</svg>
463+
);
464+
}
465+
466+
function IconAuroraCyberpunk({ text, accent }: IC): ReactElement {
467+
return (
468+
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" aria-hidden="true">
469+
{/* Cyberpunk double triangles */}
470+
<polygon
471+
points="14,4 24,20 4,20"
472+
stroke={accent}
473+
strokeWidth="2.2"
474+
strokeLinejoin="round"
475+
opacity="0.9"
476+
/>
477+
<polygon
478+
points="14,10 20,20 8,20"
479+
stroke={text}
480+
strokeWidth="1.2"
481+
strokeLinejoin="round"
482+
opacity="0.7"
483+
/>
484+
<line
485+
x1="14"
486+
y1="4"
487+
x2="14"
488+
y2="20"
489+
stroke={accent}
490+
strokeWidth="1"
491+
strokeDasharray="2 2"
492+
opacity="0.5"
493+
/>
494+
</svg>
495+
);
496+
}
497+
498+
const ICON_MAP: Record<string, (c: IC) => ReactElement> = {
361499
dark: (c) => <IconDark {...c} />,
362500
light: (c) => <IconLight {...c} />,
363501
neon: (c) => <IconNeon {...c} />,
@@ -372,6 +510,11 @@ const ICON_MAP: Record<ThemeKey, (c: IC) => ReactElement> = {
372510
gruvbox: (c) => <IconGruvbox {...c} />,
373511
aurora_cyberpunk: (c) => <IconDark {...c} />,
374512
highcontrast: (c) => <IconHighcontrast {...c} />,
513+
aurora_cyberpunk: (c) => <IconAuroraCyberpunk {...c} />,
514+
catppuccin_latte: (c) => <IconCatppuccinLatte {...c} />,
515+
solarized_light: (c) => <IconSolarizedLight {...c} />,
516+
gruvbox_light: (c) => <IconGruvbox {...c} />,
517+
nord_light: (c) => <IconNord {...c} />,
375518
};
376519

377520
export function ThemeQuickPresets({ theme, onThemeChange }: ThemeQuickPresetsProps): ReactElement {

app/customize/page.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ export default function CustomizePage(): ReactElement {
227227
<div className="h-4 w-px bg-white/10" />
228228

229229
<div>
230-
<span className="text-xs font-bold uppercase tracking-[0.22em] text-emerald-400">
230+
<span className="text-xs font-bold uppercase tracking-[0.22em] text-emerald-600 dark:text-emerald-400">
231231
Customization Studio
232232
</span>
233233
</div>
@@ -316,7 +316,7 @@ export default function CustomizePage(): ReactElement {
316316
>
317317
{/* Live Preview */}
318318
<div className="bg-white/70 backdrop-blur-xl border border-black/10 dark:bg-black/35 dark:border-white/10 rounded-[1.75rem] p-6 shadow-[0_20px_60px_rgba(0,0,0,0.35)]">
319-
<p className="text-xs font-bold uppercase tracking-[0.22em] text-emerald-400 mb-5">
319+
<p className="text-xs font-bold uppercase tracking-[0.22em] text-emerald-600 dark:text-emerald-400 mb-5">
320320
Live Preview
321321
</p>
322322

@@ -404,7 +404,9 @@ export default function CustomizePage(): ReactElement {
404404
>
405405
<span className="text-purple-400">{decodeURIComponent(k)}</span>
406406
<span className="text-gray-400 dark:text-white/20">=</span>
407-
<span className="text-emerald-400">{decodeURIComponent(v)}</span>
407+
<span className="text-emerald-600 dark:text-emerald-400">
408+
{decodeURIComponent(v)}
409+
</span>
408410
</span>
409411
);
410412
}

app/documentation/page.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,26 @@ const themeDescriptions: Record<
165165
name: 'High Contrast',
166166
vibe: 'Ultra-high contrast dark theme with bold orange-red highlights for maximum visibility.',
167167
},
168+
169+
catppuccin_latte: {
170+
name: 'Catppuccin Latte',
171+
vibe: 'A cozy, warm light theme from the popular Catppuccin palette.',
172+
},
173+
174+
solarized_light: {
175+
name: 'Solarized Light',
176+
vibe: 'The classic Solarized light palette with excellent contrast and retro aesthetics.',
177+
},
178+
179+
gruvbox_light: {
180+
name: 'Gruvbox Light',
181+
vibe: 'Earthy, warm retro cream background with high-contrast charcoal text.',
182+
},
183+
184+
nord_light: {
185+
name: 'Nord Light',
186+
vibe: 'Ice-cold and minimal light gray palette with signature arctic-blue accents.',
187+
},
168188
};
169189

170190
const allthemes = Object.entries(themePalette).map(([slug, palette]) => ({
@@ -196,7 +216,7 @@ export default function DocumentationPage() {
196216

197217
<div className="relative mx-auto flex min-h-screen max-w-[1600px] flex-col px-6 pb-10 pt-6 md:px-8">
198218
<section className="mb-10 rounded-[2rem] border border-black/10 bg-white px-6 py-10 shadow-[0_20px_60px_rgba(0,0,0,0.06)] backdrop-blur dark:border-white/10 dark:bg-white/[0.03] dark:shadow-[0_30px_100px_rgba(0,0,0,0.45)] md:px-10">
199-
<div className="mb-6 inline-flex items-center rounded-full border border-emerald-400/20 bg-emerald-500/10 px-4 py-1 text-xs font-semibold uppercase tracking-[0.24em] text-emerald-300">
219+
<div className="mb-6 inline-flex items-center rounded-full border border-emerald-300/30 bg-emerald-50 dark:border-emerald-400/20 dark:bg-emerald-500/10 px-4 py-1 text-xs font-semibold uppercase tracking-[0.24em] text-emerald-700 dark:text-emerald-300">
200220
Documentation
201221
</div>
202222
<div className="grid gap-10 lg:grid-cols-[1.2fr_0.8fr] lg:items-end">
@@ -306,7 +326,7 @@ export default function DocumentationPage() {
306326
key={parameter.name}
307327
className="border-t border-black/10 align-top dark:border-white/8"
308328
>
309-
<td className="px-4 py-4 font-mono text-sm text-emerald-300">
329+
<td className="px-4 py-4 font-mono text-sm text-emerald-700 dark:text-emerald-300">
310330
{parameter.name}
311331
</td>
312332
<td className="px-4 py-4 text-sm text-gray-700 dark:text-white/70">
@@ -401,7 +421,7 @@ export default function DocumentationPage() {
401421
key={note}
402422
className="flex gap-3 rounded-[1.25rem] border border-black/10 bg-gray-50 px-4 py-4 dark:border-white/8 dark:bg-black/35"
403423
>
404-
<span className="mt-1 h-2.5 w-2.5 shrink-0 rounded-full bg-emerald-400 shadow-[0_0_18px_rgba(52,211,153,0.9)]" />
424+
<span className="mt-1 h-2.5 w-2.5 shrink-0 rounded-full bg-emerald-500 dark:bg-emerald-400 shadow-[0_0_18px_rgba(52,211,153,0.9)]" />
405425
<p className="text-sm leading-7 text-gray-600 dark:text-white/60">{note}</p>
406426
</div>
407427
))}
@@ -457,7 +477,7 @@ function Panel({
457477
}) {
458478
return (
459479
<section className="rounded-[2rem] border border-black/10 bg-white p-6 shadow-[0_20px_60px_rgba(0,0,0,0.06)] backdrop-blur dark:border-white/10 dark:bg-white/[0.03] dark:shadow-[0_30px_80px_rgba(0,0,0,0.32)] md:p-8">
460-
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-emerald-300">
480+
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-emerald-600 dark:text-emerald-300">
461481
{eyebrow}
462482
</p>
463483
<h2 className="mt-3 text-2xl font-bold tracking-tight text-black dark:text-white md:text-3xl">

components/ReturnToTop.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export default function ReturnToTop() {
4242
exit={{ opacity: 0, y: 20 }}
4343
transition={{ duration: 0.3 }}
4444
onClick={scrollToTop}
45-
className="fixed bottom-8 right-8 p-3 bg-gradient-to-r from-cyan-500 to-blue-500 hover:from-cyan-600 hover:to-blue-600 text-white rounded-full shadow-lg hover:shadow-xl transition-shadow duration-300 z-50 flex items-center justify-center"
45+
className="fixed bottom-8 right-8 p-3 rounded-full border border-emerald-500/20 bg-white text-emerald-600 hover:bg-emerald-500 hover:text-white hover:border-emerald-500 dark:border-emerald-400/20 dark:bg-black dark:text-emerald-400 dark:hover:bg-emerald-400 dark:hover:text-black dark:hover:border-emerald-400 hover:scale-110 active:scale-95 shadow-[0_4px_20px_rgba(16,185,129,0.15)] dark:shadow-[0_4px_30px_rgba(16,185,129,0.3)] transition-all duration-300 z-50 flex items-center justify-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#00ffaa] focus-visible:ring-offset-2"
4646
aria-label="Return to top"
4747
>
4848
<ChevronUp size={24} />

lib/svg/themes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export const themes: Record<string, BadgeTheme> = {
2525
gruvbox: makeTheme('282828', 'ebdbb2', 'fe8019'),
2626
aurora_cyberpunk: makeTheme('090B13', 'EAF2FF', '9D5CFF'),
2727
highcontrast: makeTheme('0a0a0a', '888888', 'ff4500'),
28+
catppuccin_latte: makeTheme('eff1f5', '4c4f69', '1e66f5'),
29+
solarized_light: makeTheme('fdf6e3', '586e75', '268bd2'),
30+
gruvbox_light: makeTheme('fbf1c7', '3c3836', 'd65d0e'),
31+
nord_light: makeTheme('eceff4', '2e3440', '5e81ac'),
2832
};
2933

3034
// Auto-theme pairs: the SVG switches between these two palettes

0 commit comments

Comments
 (0)