diff --git a/.mimocode/.cron-lock b/.mimocode/.cron-lock index 2c7e60e..f5c1c5a 100644 --- a/.mimocode/.cron-lock +++ b/.mimocode/.cron-lock @@ -1 +1 @@ -{"pid":864463,"startedAt":1783550197179} \ No newline at end of file +{"pid":233628,"startedAt":1783590885727} \ No newline at end of file diff --git a/apps/frontend/src/App.jsx b/apps/frontend/src/App.jsx index 768b5b6..5e55fe1 100644 --- a/apps/frontend/src/App.jsx +++ b/apps/frontend/src/App.jsx @@ -24,11 +24,38 @@ function App() { height: window.innerHeight }); + // Theme: 'auto' | 'light' | 'dark' + const [theme, setThemeState] = useState(() => { + return localStorage.getItem('ff-theme') || 'auto'; + }); + // Device detection const isMobile = screenSize.width <= 768; const isTablet = screenSize.width > 768 && screenSize.width <= 1024; const isDesktop = screenSize.width > 1024; + // Apply theme to document + useEffect(() => { + const mq = window.matchMedia('(prefers-color-scheme: dark)'); + + function applyTheme() { + const isDark = theme === 'dark' || (theme === 'auto' && mq.matches); + document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light'); + } + + applyTheme(); + + if (theme === 'auto') { + mq.addEventListener('change', applyTheme); + return () => mq.removeEventListener('change', applyTheme); + } + }, [theme]); + + function setTheme(newTheme) { + setThemeState(newTheme); + localStorage.setItem('ff-theme', newTheme); + } + useEffect(() => { // Set initial screen size const handleResize = () => { @@ -131,6 +158,8 @@ function App() { onShowSaved={() => setShowSaved(true)} onLogout={handleLogout} onAdminClick={handleAdminClick} + theme={theme} + onThemeChange={setTheme} /> ); break; diff --git a/apps/frontend/src/components/BottomNav.css b/apps/frontend/src/components/BottomNav.css index c9b7ba0..4b28750 100644 --- a/apps/frontend/src/components/BottomNav.css +++ b/apps/frontend/src/components/BottomNav.css @@ -65,9 +65,9 @@ height: 3.5rem; border-radius: var(--radius-full); /* Stronger, brighter blue to make scan button easy to find */ - background: #2b5bb5; - color: #ffffff; - box-shadow: 0 8px 26px rgba(43, 91, 181, 0.32); + background: var(--fab-bg); + color: var(--fab-text); + box-shadow: 0 8px 26px var(--fab-shadow); margin-top: -1.5rem; transition: transform 0.12s, box-shadow 160ms ease; } @@ -78,12 +78,12 @@ .nav-fab:hover { transform: translateY(-2px); - box-shadow: 0 12px 36px rgba(43, 91, 181, 0.36); + box-shadow: 0 12px 36px var(--fab-shadow); } .nav-fab:focus { outline: none; - box-shadow: 0 0 0 6px rgba(43,91,181,0.12), 0 8px 26px rgba(43, 91, 181, 0.32); + box-shadow: 0 0 0 6px rgba(43,91,181,0.12), 0 8px 26px var(--fab-shadow); } .nav-badge { diff --git a/apps/frontend/src/components/LoginModal.css b/apps/frontend/src/components/LoginModal.css index f62452e..6872d14 100644 --- a/apps/frontend/src/components/LoginModal.css +++ b/apps/frontend/src/components/LoginModal.css @@ -118,7 +118,7 @@ .modal-error { font-size: 0.82rem; - color: #b91c1c; + color: var(--error); margin: 0.25rem 0 0.75rem; } @@ -149,7 +149,7 @@ .modal-submit { background: var(--primary); border: none; - color: #fff; + color: var(--on-primary); padding: 0.55rem 1.35rem; border-radius: 999px; cursor: pointer; diff --git a/apps/frontend/src/components/PharmacyList.css b/apps/frontend/src/components/PharmacyList.css index 3f4cd9b..1176a2c 100644 --- a/apps/frontend/src/components/PharmacyList.css +++ b/apps/frontend/src/components/PharmacyList.css @@ -139,8 +139,8 @@ } .stock.low-stock { - background: rgba(180, 83, 9, 0.12); - color: #b45309; + background: var(--warning-container); + color: var(--warning); } .stock.out-of-stock { diff --git a/apps/frontend/src/components/SavedNotifications.css b/apps/frontend/src/components/SavedNotifications.css index 4c21c66..a7d95ac 100644 --- a/apps/frontend/src/components/SavedNotifications.css +++ b/apps/frontend/src/components/SavedNotifications.css @@ -73,7 +73,7 @@ } .saved-notifications-error { - color: #b91c1c; + color: var(--error); } .saved-notifications-list { @@ -155,8 +155,8 @@ } .saved-notifications-remove:hover:not(:disabled) { - border-color: #b91c1c; - color: #b91c1c; + border-color: var(--error); + color: var(--error); background: rgba(185, 28, 28, 0.06); } diff --git a/apps/frontend/src/index.css b/apps/frontend/src/index.css index 813aa39..0556329 100644 --- a/apps/frontend/src/index.css +++ b/apps/frontend/src/index.css @@ -71,6 +71,101 @@ --primary-shadow: rgba(127, 191, 143, 0.12); --primary-light: #eaf7ec; --primary-faint: rgba(127, 191, 143, 0.06); + --fab-bg: #2b5bb5; + --fab-text: #ffffff; + --fab-shadow: rgba(43, 91, 181, 0.32); + --warning: #b45309; + --warning-container: rgba(180, 83, 9, 0.12); + --suggestion-1: #ffffff; + --suggestion-2: #f0f7ff; + --suggestion-3: #e0f0ff; + --suggestion-4: #d4ebff; + --suggestion-border-1: #e2e8f0; + --suggestion-border-2: #dbeafe; + --suggestion-border-3: #bfdbfe; + --suggestion-border-4: #93c5fd; + --suggestion-text: #1e293b; + --suggestion-icon-bg: rgba(59, 130, 246, 0.08); + --suggestion-icon-color: #3b82f6; +} + +/* ── Dark mode ── */ +[data-theme="dark"] { + --primary: #8ed4a0; + --on-primary: #003912; + --primary-container: #1a5c2a; + --on-primary-container: #c2ecd0; + --secondary: #b8c9ff; + --on-secondary: #1a3066; + --secondary-container: #2a4080; + --on-secondary-container: #d9e2ff; + --tertiary: #dac4ff; + --on-tertiary: #3b2a5a; + --tertiary-container: #524070; + --on-tertiary-container: #efe7ff; + --surface: #111417; + --on-surface: #e1e3e4; + --on-surface-variant: #c0c9bb; + --surface-container-lowest: #0c0e10; + --surface-container-low: #191c1d; + --surface-container: #1e2122; + --surface-container-high: #282b2c; + --surface-container-highest: #333637; + --error: #ffb4ab; + --on-error: #690005; + --error-container: #93000a; + --on-error-container: #ffdad6; + --outline: #8a9486; + --outline-variant: #41493e; + --on-background: #e1e3e4; + --inverse-surface: #e1e3e4; + --inverse-on-surface: #2e3132; + --inverse-primary: #2e7d32; + --surface-tint: #8ed4a0; + --surface-dim: #111417; + --surface-bright: #373a3b; + --surface-variant: #41493e; + --primary-fixed: #acf4a4; + --primary-fixed-dim: #91d78a; + --secondary-fixed: #d9e2ff; + --secondary-fixed-dim: #b0c6ff; + --tertiary-fixed: #e9ddff; + --tertiary-fixed-dim: #cfbcff; + + --shadow-soft: 0 4px 20px rgba(0, 0, 0, 0.32); + + --text-main: var(--on-surface); + --text-muted: var(--on-surface-variant); + --surface-muted: var(--surface-container-low); + --surface-card: var(--surface-container-lowest); + --border: var(--outline-variant); + --border-strong: var(--outline); + --glass-bg: var(--surface-container); + --glass-border: var(--outline-variant); + --glass-shadow: var(--shadow-soft); + --accent: var(--primary); + --accent-warm: #f5a97a; + --primary-hover: #a0dfb0; + --primary-ring: rgba(142, 212, 160, 0.25); + --primary-shadow: rgba(142, 212, 160, 0.15); + --primary-light: #0d2b12; + --primary-faint: rgba(142, 212, 160, 0.08); + --fab-bg: #5b8bd5; + --fab-text: #ffffff; + --fab-shadow: rgba(91, 139, 213, 0.35); + --warning: #f59e0b; + --warning-container: rgba(245, 158, 11, 0.15); + --suggestion-1: #1e2122; + --suggestion-2: #1a2332; + --suggestion-3: #1a2a3a; + --suggestion-4: #1a3040; + --suggestion-border-1: #333637; + --suggestion-border-2: #2a4080; + --suggestion-border-3: #2a4a8a; + --suggestion-border-4: #3060a0; + --suggestion-text: #e1e3e4; + --suggestion-icon-bg: rgba(96, 165, 250, 0.12); + --suggestion-icon-color: #60a5fa; } * { @@ -79,6 +174,14 @@ box-sizing: border-box; } +html { + color-scheme: light; +} + +[data-theme="dark"] { + color-scheme: dark; +} + html, body { height: 100%; overflow: hidden; @@ -117,6 +220,14 @@ body::after { z-index: -1; } +[data-theme="dark"] body::before { + opacity: 0.15; +} + +[data-theme="dark"] body::after { + background-color: rgba(0, 0, 0, 0.55); +} + #root { height: 100dvh; overflow: hidden; diff --git a/apps/frontend/src/views/HomeView.css b/apps/frontend/src/views/HomeView.css index df64682..d4befc2 100644 --- a/apps/frontend/src/views/HomeView.css +++ b/apps/frontend/src/views/HomeView.css @@ -192,9 +192,9 @@ .home-card--scan { /* Keep the scan button vivid and easy to find (intentionally prominent) */ - background: #2b5bb5; /* vivid blue to stand out */ - color: #ffffff; - box-shadow: 0 8px 28px rgba(43, 91, 181, 0.28); + background: var(--fab-bg); + color: var(--fab-text); + box-shadow: 0 8px 28px var(--fab-shadow); transform: translateY(0); } @@ -204,7 +204,7 @@ .home-card--scan:focus { outline: none; - box-shadow: 0 0 0 6px rgba(43,91,181,0.12), 0 8px 28px rgba(43,91,181,0.28); + box-shadow: 0 0 0 6px rgba(43,91,181,0.12), 0 8px 28px var(--fab-shadow); } .home-card-icon { diff --git a/apps/frontend/src/views/ProfileView.css b/apps/frontend/src/views/ProfileView.css index 70c53b1..1f81b26 100644 --- a/apps/frontend/src/views/ProfileView.css +++ b/apps/frontend/src/views/ProfileView.css @@ -845,3 +845,63 @@ align-self: flex-end; } } + +/* Theme Toggle */ +.menu-item-theme-label { + font-size: 0.8rem; + font-weight: 500; + color: var(--on-surface-variant); +} + +.profile-theme-options { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.profile-theme-btn { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.85rem 1rem; + background: var(--surface); + border: 2px solid var(--outline-variant); + border-radius: var(--radius); + cursor: pointer; + color: var(--on-surface-variant); + font-family: inherit; + text-align: left; + transition: border-color 0.15s, color 0.15s, background 0.15s; +} + +.profile-theme-btn:hover { + border-color: var(--primary); + color: var(--on-surface); +} + +.profile-theme-btn--active { + border-color: var(--primary); + background: var(--primary-faint); + color: var(--primary); +} + +.profile-theme-btn--active .profile-theme-btn-label { + color: var(--primary); + font-weight: 700; +} + +.profile-theme-btn-label { + flex: 1; + font-size: 1rem; + font-weight: 600; + color: var(--on-surface); +} + +.profile-theme-btn-desc { + font-size: 0.8rem; + color: var(--on-surface-variant); +} + +.profile-modal-theme { + max-width: 24rem; +} diff --git a/apps/frontend/src/views/ProfileView.jsx b/apps/frontend/src/views/ProfileView.jsx index 12f1151..3f49d82 100644 --- a/apps/frontend/src/views/ProfileView.jsx +++ b/apps/frontend/src/views/ProfileView.jsx @@ -34,7 +34,7 @@ function resolveAvatarUrl(url) { return url; } -function ProfileView({ currentUser, onProfileSaved, onLogout, onAdminClick }) { +function ProfileView({ currentUser, onProfileSaved, onLogout, onAdminClick, theme, onThemeChange }) { const [firstName, setFirstName] = useState(currentUser?.first_name || ''); const [lastName, setLastName] = useState(currentUser?.last_name || ''); const [avatarUrl, setAvatarUrl] = useState(resolveAvatarUrl(currentUser?.avatar_url)); @@ -57,6 +57,9 @@ function ProfileView({ currentUser, onProfileSaved, onLogout, onAdminClick }) { const [configSaving, setConfigSaving] = useState(false); const [configFeedback, setConfigFeedback] = useState(null); + // Theme modal state + const [showTheme, setShowTheme] = useState(false); + // Addresses modal state const [showAddresses, setShowAddresses] = useState(false); const [addresses, setAddresses] = useState([]); @@ -410,6 +413,21 @@ function ProfileView({ currentUser, onProfileSaved, onLogout, onAdminClick }) { + + {searchHistory.length > 0 && (
Tus búsquedas recientes:
@@ -718,6 +736,44 @@ function ProfileView({ currentUser, onProfileSaved, onLogout, onAdminClick }) {Elige cómo se ve la aplicación. En modo automático, se adapta al tema de tu dispositivo.
+