From 45000da741b5b46c36db3f1e429a850ac382f7eb Mon Sep 17 00:00:00 2001 From: Jai-Shankar1033 Date: Sun, 7 Jun 2026 13:13:40 +0530 Subject: [PATCH 1/2] fix: improve feature card readability and contrast (#319) --- apps/web/src/pages/LandingPage.css | 112 ++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 11 deletions(-) diff --git a/apps/web/src/pages/LandingPage.css b/apps/web/src/pages/LandingPage.css index 9d39a315..387ee469 100644 --- a/apps/web/src/pages/LandingPage.css +++ b/apps/web/src/pages/LandingPage.css @@ -73,36 +73,35 @@ gap: 1.75rem; padding: 4rem 0 5rem; } - .feature-card { + position: relative; + overflow: hidden; padding: 2.4rem; min-height: 140px; border-radius: var(--radius-xl); box-shadow: var(--shadow-lg); background: var(--bg-card); border: 1px solid var(--border); - transition: transform 0.35s ease, border-color 0.35s ease, box-shadow 0.35s ease; + transition: transform 0.35s ease, border-color 0.35s ease, box-shadow 0.35s ease, background 0.35s ease; } .feature-card:hover { - transform: translateY(-8px); - border-color: rgba(99, 102, 241, 0.4); - box-shadow: 0 26px 50px -18px rgba(0, 0, 0, 0.35); + transform: translateY(-6px); + border-color: rgba(99, 102, 241, 0.55); + box-shadow: 0 26px 50px -18px rgba(99, 102, 241, 0.3); } -.feature-icon { - font-size: 2.3rem; - margin-bottom: 1.4rem; -} + .feature-card h3 { font-size: 1.4rem; margin-bottom: 0.9rem; + color: var(--text-primary); } - .feature-card p { + font-size: 0.97rem; color: var(--text-secondary); - line-height: 1.7; + line-height: 1.75; } .footer { @@ -147,3 +146,94 @@ opacity: 0.6; } } + + +.dark .feature-card h3 { + color: #f1f5f9; +} + +.dark .feature-card p { + color: #cbd5e1; +} + + +:root:not(.dark) .feature-card h3 { + color: #0f172a; +} + +:root:not(.dark) .feature-card p { + color: #475569; +} + +:root:not(.dark) .feature-card { + box-shadow: 0 4px 24px rgba(99, 102, 241, 0.10), + 0 1.5px 6px rgba(0,0,0,0.07); + border-color: rgba(99, 102, 241, 0.18); +} + +:root:not(.dark) .feature-card:hover { + box-shadow: 0 8px 32px rgba(99, 102, 241, 0.18), + 0 2px 8px rgba(0,0,0,0.08); + border-color: rgba(99, 102, 241, 0.45); +} + + +.dark .feature-card { + border-color: rgba(255, 255, 255, 0.18); + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.35); +} + +.feature-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, #6366f1, #a855f7, #6366f1); + opacity: 0; + transition: opacity 0.35s ease; +} + +.feature-card:hover::before { + opacity: 1; +} + + +.feature-icon { + font-size: 2rem; + margin-bottom: 1.4rem; + display: inline-flex; + align-items: center; + justify-content: center; + width: 52px; + height: 52px; + border-radius: 14px; + background: rgba(99, 102, 241, 0.12); + border: 1px solid rgba(99, 102, 241, 0.2); +} + + +.dark .feature-icon { + background: rgba(99, 102, 241, 0.15); + border-color: rgba(99, 102, 241, 0.25); +} + + +:root:not(.dark) .feature-icon { + background: rgba(99, 102, 241, 0.08); + border-color: rgba(99, 102, 241, 0.15); +} + + +.dark .feature-card:hover { + background: rgba(99, 102, 241, 0.06); + box-shadow: 0 0 0 1px rgba(99, 102, 241, 0.2), + 0 26px 50px -18px rgba(99, 102, 241, 0.35); +} + +:root:not(.dark) .feature-card:hover { + background: rgba(255, 255, 255, 0.95); + box-shadow: 0 8px 32px rgba(99, 102, 241, 0.18), + 0 0 0 1px rgba(99, 102, 241, 0.15); +} \ No newline at end of file From 264d1aacb04630376ac82866ff2b7a116856c082 Mon Sep 17 00:00:00 2001 From: Jai-Shankar1033 Date: Thu, 11 Jun 2026 16:30:21 +0530 Subject: [PATCH 2/2] feat(web): add smooth theme transition animation --- apps/web/src/components/Navbar.css | 29 +++++++++++++++++++++- apps/web/src/components/Navbar.tsx | 39 +++++++++++++++++++++++++++--- apps/web/src/index.css | 35 ++++++++++++++++++++++++++- apps/web/src/lib/theme.tsx | 9 ++++++- 4 files changed, 105 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/Navbar.css b/apps/web/src/components/Navbar.css index 4e66a2f2..95a33fb0 100644 --- a/apps/web/src/components/Navbar.css +++ b/apps/web/src/components/Navbar.css @@ -34,7 +34,9 @@ align-items: center; justify-content: center; font-size: 1.25rem; - transition: transform 0.24s ease, background-color 0.24s ease, border-color 0.24s ease; + transition: transform 0.24s ease, background-color 0.24s ease, border-color 0.24s ease; + overflow: hidden; + position: relative; } .theme-toggle:hover { @@ -43,11 +45,36 @@ border-color: rgba(99, 102, 241, 0.3); } +.theme-toggle:active { + transform: scale(0.92); + background: rgba(99, 102, 241, 0.15); + border-color: rgba(99, 102, 241, 0.5); +} + +.theme-toggle.glow { + animation: toggle-glow 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + .theme-toggle:focus-visible { outline: 3px solid rgba(99, 102, 241, 0.24); outline-offset: 3px; } +.theme-toggle-icon { + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +.theme-toggle-icon.hiding { + animation: icon-hide 0.2s cubic-bezier(0.4, 0, 1, 1) forwards; +} + +.theme-toggle-icon.showing { + animation: icon-show 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; +} + @media (max-width: 860px) { .navbar { margin-top: 0.9rem; diff --git a/apps/web/src/components/Navbar.tsx b/apps/web/src/components/Navbar.tsx index debd63c7..7d4c5856 100644 --- a/apps/web/src/components/Navbar.tsx +++ b/apps/web/src/components/Navbar.tsx @@ -1,9 +1,31 @@ +import { useState } from 'react'; import { Link } from 'react-router-dom'; import { useTheme } from '../lib/theme'; import './Navbar.css'; +type IconState = 'idle' | 'hiding' | 'showing'; + export default function Navbar() { const { theme, toggleTheme } = useTheme(); + const [iconState, setIconState] = useState('idle'); + const [displayedTheme, setDisplayedTheme] = useState(theme); + const [glowing, setGlowing] = useState(false); + + const handleToggle = () => { + if (iconState !== 'idle') return; + setIconState('hiding'); + setGlowing(true); + }; + + const handleAnimationEnd = () => { + if (iconState === 'hiding') { + toggleTheme(); + setDisplayedTheme((t) => (t === 'dark' ? 'light' : 'dark')); + setIconState('showing'); + } else if (iconState === 'showing') { + setIconState('idle'); + } + }; return ( ); -} +} \ No newline at end of file diff --git a/apps/web/src/index.css b/apps/web/src/index.css index e8cde905..e0b65de7 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -56,6 +56,36 @@ html.dark { box-sizing: border-box; } + +html.theme-transitioning *, +html.theme-transitioning *::before, +html.theme-transitioning *::after { + transition: + background-color 0.35s cubic-bezier(0.4, 0, 0.2, 1), + background 0.35s cubic-bezier(0.4, 0, 0.2, 1), + color 0.35s cubic-bezier(0.4, 0, 0.2, 1), + border-color 0.35s cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 0.35s cubic-bezier(0.4, 0, 0.2, 1), + fill 0.35s cubic-bezier(0.4, 0, 0.2, 1), + stroke 0.35s cubic-bezier(0.4, 0, 0.2, 1) !important; +} + + +@keyframes icon-hide { + 0% { transform: scale(1) rotate(0deg); opacity: 1; } + 100% { transform: scale(0) rotate(-90deg); opacity: 0; } +} + +@keyframes icon-show { + 0% { transform: scale(0) rotate(90deg); opacity: 0; } + 100% { transform: scale(1) rotate(0deg); opacity: 1; } +} +@keyframes toggle-glow { + 0% { box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.55); } + 50% { box-shadow: 0 0 0 8px rgba(99, 102, 241, 0.18); } + 100% { box-shadow: 0 0 0 14px rgba(99, 102, 241, 0); } +} + body { font-family: 'Inter', sans-serif; background: @@ -191,7 +221,10 @@ html { } @media (prefers-reduced-motion: reduce) { - * { + *, + html.theme-transitioning *, + html.theme-transitioning *::before, + html.theme-transitioning *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; diff --git a/apps/web/src/lib/theme.tsx b/apps/web/src/lib/theme.tsx index 7beda8bd..095e3507 100644 --- a/apps/web/src/lib/theme.tsx +++ b/apps/web/src/lib/theme.tsx @@ -31,7 +31,14 @@ export function ThemeProvider({ children }: { children: ReactNode }) { localStorage.setItem('devcard-theme', theme); }, [theme]); - const toggleTheme = () => setTheme((t) => (t === 'dark' ? 'light' : 'dark')); +const toggleTheme = () => { + const root = document.documentElement; + root.classList.add('theme-transitioning'); + const cleanup = () => root.classList.remove('theme-transitioning'); + window.setTimeout(cleanup, 400); + root.addEventListener('transitionend', cleanup, { once: true }); + setTheme((t) => (t === 'dark' ? 'light' : 'dark')); +}; return (