diff --git a/src/App.css b/src/App.css index b2c3a16..4bc0169 100644 --- a/src/App.css +++ b/src/App.css @@ -1,159 +1,42 @@ /* ======================================== - HiHala Dashboard - Mobile Excellence - Design System + Premium Mobile UX - ======================================== */ - -/* ======================================== - 1. CSS CUSTOM PROPERTIES (Design Tokens) + HiHala Dashboard - Minimalist Luxury ======================================== */ :root { - /* Colors - Refined palette */ - --bg: #f8fafc; - --bg-elevated: #ffffff; + --bg: #fafafa; --surface: #ffffff; - --surface-hover: #f1f5f9; - --border: #e2e8f0; - --border-light: #f1f5f9; - - /* Text hierarchy */ - --text-primary: #0f172a; - --text-secondary: #334155; - --text-muted: #64748b; - --text-subtle: #94a3b8; - - /* Brand colors */ + --border: #e5e5e5; + --text-primary: #171717; + --text-secondary: #525252; + --text-muted: #a3a3a3; + --accent: #2563eb; --primary: #2563eb; - --primary-hover: #1d4ed8; - --primary-light: #dbeafe; - --primary-subtle: #eff6ff; - - /* Semantic colors */ + --accent-light: #dbeafe; --success: #059669; --success-light: #d1fae5; --danger: #dc2626; --danger-light: #fee2e2; - --warning: #d97706; - --warning-light: #fef3c7; - - /* Chart colors */ - --chart-primary: #3b82f6; - --chart-secondary: #8b5cf6; - --chart-tertiary: #06b6d4; - --chart-quaternary: #f59e0b; - - /* Shadows - subtle depth */ - --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.04); - --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -1px rgba(0, 0, 0, 0.04); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.04); - --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.04), 0 0 0 1px rgba(0, 0, 0, 0.02); - - /* Radii */ - --radius-sm: 6px; - --radius-md: 10px; - --radius-lg: 14px; - --radius-xl: 18px; - --radius-full: 9999px; - - /* Spacing scale (4px base) */ - --space-1: 4px; - --space-2: 8px; - --space-3: 12px; - --space-4: 16px; - --space-5: 20px; - --space-6: 24px; - --space-8: 32px; - --space-10: 40px; - --space-12: 48px; - - /* Typography - Desktop */ - --text-xs: 0.75rem; /* 12px */ - --text-sm: 0.8125rem; /* 13px */ - --text-base: 0.9375rem; /* 15px */ - --text-lg: 1.0625rem; /* 17px */ - --text-xl: 1.25rem; /* 20px */ - --text-2xl: 1.5rem; /* 24px */ - --text-3xl: 1.875rem; /* 30px */ - - /* Touch targets */ - --touch-min: 44px; - --touch-comfortable: 48px; - - /* Transitions */ - --transition-fast: 150ms ease; - --transition-base: 200ms ease; - --transition-slow: 300ms ease; - --transition-spring: 400ms cubic-bezier(0.34, 1.56, 0.64, 1); - - /* Z-index layers */ - --z-base: 1; - --z-dropdown: 100; - --z-sticky: 200; - --z-overlay: 300; - --z-modal: 400; - --z-toast: 500; + --gold: #b8860b; + --shadow-sm: 0 1px 2px rgba(0,0,0,0.04); + --shadow: 0 4px 12px rgba(0,0,0,0.05); + --radius: 12px; } -/* Mobile-specific tokens */ -@media (max-width: 768px) { - :root { - --text-xs: 0.6875rem; /* 11px */ - --text-sm: 0.75rem; /* 12px */ - --text-base: 0.8125rem; /* 13px */ - --text-lg: 0.9375rem; /* 15px */ - --text-xl: 1.0625rem; /* 17px */ - --text-2xl: 1.25rem; /* 20px */ - --text-3xl: 1.5rem; /* 24px */ - } -} - -/* ======================================== - 2. RESET & BASE STYLES - ======================================== */ - -*, *::before, *::after { +* { margin: 0; padding: 0; box-sizing: border-box; } -html { - -webkit-text-size-adjust: 100%; - -webkit-tap-highlight-color: transparent; - scroll-behavior: smooth; -} - body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text-primary); line-height: 1.5; -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11'; - overflow-x: hidden; } -/* RTL Arabic font */ -[dir="rtl"] body { - font-family: 'Tajawal', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; -} - -/* Remove focus outline, add custom focus ring */ -:focus { - outline: none; -} - -:focus-visible { - outline: 2px solid var(--primary); - outline-offset: 2px; -} - -/* ======================================== - 3. LOADING & ERROR STATES - ======================================== */ - +/* Loading & Error */ .loading-container, .error-container { display: flex; @@ -161,17 +44,15 @@ body { align-items: center; justify-content: center; min-height: 100vh; - min-height: 100dvh; - gap: var(--space-4); - padding: var(--space-6); - text-align: center; + gap: 16px; + color: var(--text-secondary); } .loading-spinner { - width: 36px; - height: 36px; - border: 3px solid var(--border); - border-top-color: var(--primary); + width: 32px; + height: 32px; + border: 2px solid var(--border); + border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; } @@ -180,133 +61,127 @@ body { to { transform: rotate(360deg); } } -.loading-container p, -.error-container h2 { - font-size: var(--text-lg); - color: var(--text-secondary); - font-weight: 500; -} - -.error-container p { - font-size: var(--text-base); - color: var(--text-muted); - max-width: 320px; -} - .error-container button { - padding: var(--space-3) var(--space-5); + padding: 10px 20px; background: var(--text-primary); color: white; border: none; - border-radius: var(--radius-md); - font-size: var(--text-base); - font-weight: 600; + border-radius: 6px; cursor: pointer; - min-height: var(--touch-min); - transition: background var(--transition-fast); + font-weight: 500; + font-size: 0.875rem; } .error-container button:hover { background: var(--text-secondary); } -/* ======================================== - 4. EMPTY STATE - ======================================== */ - +/* Empty State */ .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; - padding: var(--space-12) var(--space-6); + padding: 48px 24px; text-align: center; + color: var(--text-secondary); } .empty-state-icon { font-size: 3rem; - margin-bottom: var(--space-4); - opacity: 0.6; + margin-bottom: 16px; + opacity: 0.5; } .empty-state-title { - font-size: var(--text-xl); + font-size: 1.125rem; font-weight: 600; color: var(--text-primary); - margin-bottom: var(--space-2); + margin-bottom: 8px; } .empty-state-message { - font-size: var(--text-base); + font-size: 0.875rem; color: var(--text-muted); - max-width: 280px; - margin-bottom: var(--space-5); + max-width: 300px; + margin-bottom: 20px; } .empty-state-action { - padding: var(--space-3) var(--space-5); + padding: 10px 20px; background: var(--primary); color: white; border: none; - border-radius: var(--radius-md); - font-size: var(--text-base); - font-weight: 600; + border-radius: 8px; + font-weight: 500; + font-size: 0.875rem; cursor: pointer; - min-height: var(--touch-min); - transition: all var(--transition-fast); + transition: all 0.2s; } .empty-state-action:hover { - background: var(--primary-hover); - transform: translateY(-1px); + background: #1d4ed8; } -/* ======================================== - 5. NAVIGATION - TOP BAR - ======================================== */ +/* Skeleton Loader */ +.skeleton { + background: linear-gradient(90deg, var(--border) 25%, var(--bg) 50%, var(--border) 75%); + background-size: 200% 100%; + animation: skeleton-loading 1.5s infinite; + border-radius: 4px; +} +@keyframes skeleton-loading { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +.skeleton-text { + height: 1em; + margin-bottom: 0.5em; +} + +.skeleton-text.lg { height: 2em; width: 60%; } +.skeleton-text.sm { height: 0.75em; width: 40%; } + +/* Navigation */ .nav-bar { background: var(--surface); border-bottom: 1px solid var(--border); + padding: 0 32px; + height: 64px; + display: flex; + align-items: center; + justify-content: center; position: sticky; top: 0; - z-index: var(--z-sticky); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - background: rgba(255, 255, 255, 0.9); + z-index: 100; } .nav-content { display: flex; align-items: center; justify-content: space-between; + width: 100%; max-width: 1400px; - margin: 0 auto; - padding: 0 var(--space-6); - height: 64px; } .nav-brand { display: flex; align-items: center; - gap: var(--space-2); + gap: 10px; } .nav-brand-icon { - width: 24px; - height: 24px; - color: var(--primary); + color: #3b82f6; } .nav-brand-text { font-family: 'DM Sans', 'Inter', -apple-system, sans-serif; - font-size: var(--text-xl); - font-weight: 700; - color: var(--text-primary); + font-size: 1.125rem; + font-weight: 600; + color: #1e3a5f; letter-spacing: -0.02em; - display: flex; - align-items: center; - gap: var(--space-1); } .data-source-select { @@ -314,62 +189,62 @@ body { -webkit-appearance: none; background: transparent; border: none; - color: var(--primary); + color: #3b82f6; font-family: inherit; font-size: inherit; - font-weight: 600; + font-weight: 500; cursor: pointer; - padding: var(--space-1) var(--space-5) var(--space-1) var(--space-2); - margin-left: var(--space-1); - border-radius: var(--radius-sm); - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%232563eb' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + padding: 2px 20px 2px 6px; + margin-left: 4px; + border-radius: 6px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%233b82f6' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 4px center; - transition: background-color var(--transition-fast); + transition: background-color 0.15s ease; } .data-source-select:hover { - background-color: var(--primary-subtle); + background-color: rgba(59, 130, 246, 0.08); +} + +.data-source-select:focus { + outline: none; + background-color: rgba(59, 130, 246, 0.12); } .data-source-select option { - color: var(--text-primary); + color: #1e3a5f; background: white; font-weight: 500; } .data-source-select option:disabled { - color: var(--text-subtle); + color: #94a3b8; } -/* Desktop nav links */ .nav-links { display: flex; - align-items: center; - gap: var(--space-2); + gap: 8px; } .nav-link { - display: flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-2) var(--space-4); color: var(--text-secondary); text-decoration: none; - font-size: var(--text-base); + padding: 10px 18px; + border-radius: 8px; + font-size: 0.875rem; font-weight: 600; - border-radius: var(--radius-md); + transition: all 0.2s; + display: flex; + align-items: center; + gap: 8px; background: var(--bg); border: 1px solid transparent; - transition: all var(--transition-fast); - min-height: var(--touch-min); } .nav-link svg { - width: 18px; - height: 18px; - opacity: 0.7; flex-shrink: 0; + opacity: 0.7; } .nav-link:hover { @@ -388,167 +263,223 @@ body { border-color: var(--primary); } -.nav-link.active svg { - opacity: 1; -} - -.nav-lang-toggle { +.nav-label-toggle { display: flex; align-items: center; - justify-content: center; - padding: var(--space-2) var(--space-3); - border-radius: var(--radius-md); - font-size: var(--text-base); + gap: 6px; + padding: 8px 14px; + border-radius: 8px; + font-size: 0.75rem; font-weight: 600; cursor: pointer; + transition: all 0.2s; background: var(--bg); border: 1px solid var(--border); color: var(--text-muted); - margin-inline-start: var(--space-2); - min-width: 50px; - min-height: var(--touch-min); - transition: all var(--transition-fast); + margin-left: 8px; } -.nav-lang-toggle:hover { +.nav-label-toggle:hover { background: var(--surface); color: var(--text-primary); border-color: var(--primary); } -/* ======================================== - 6. MOBILE BOTTOM NAVIGATION - ======================================== */ - -.mobile-nav { - display: none; - position: fixed; - bottom: 0; - left: 0; - right: 0; - background: var(--surface); - border-top: 1px solid var(--border); - padding: var(--space-2) var(--space-3); - padding-bottom: max(var(--space-2), env(safe-area-inset-bottom)); - justify-content: space-around; - align-items: center; - z-index: var(--z-sticky); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - background: rgba(255, 255, 255, 0.95); +.nav-label-toggle.active { + background: #10b981; + color: white; + border-color: #10b981; } -.mobile-nav-item { - display: flex; - flex-direction: column; - align-items: center; - gap: 2px; - padding: var(--space-2) var(--space-4); - border: none; - background: transparent; - color: var(--text-muted); - text-decoration: none; - font-size: var(--text-xs); - font-weight: 600; - border-radius: var(--radius-md); - transition: all var(--transition-fast); - cursor: pointer; - min-height: var(--touch-min); - position: relative; +.nav-label-toggle svg { + opacity: 0.8; } -.mobile-nav-item svg { - width: 22px; - height: 22px; - transition: transform var(--transition-spring); +.nav-link.active svg { + opacity: 1; } -.mobile-nav-item:active svg { - transform: scale(0.9); -} - -.mobile-nav-item.active { - color: var(--primary); -} - -.mobile-nav-item.active::before { - content: ''; - position: absolute; - top: 0; - left: 50%; - transform: translateX(-50%); - width: 24px; - height: 3px; - background: var(--primary); - border-radius: 0 0 3px 3px; -} - -/* ======================================== - 7. MAIN CONTENT LAYOUT - ======================================== */ - +/* Main Content */ .dashboard, .comparison { - padding: var(--space-6); + padding: 32px; max-width: 1400px; margin: 0 auto; - min-height: calc(100vh - 64px); + overflow-x: hidden; } -/* Page titles */ +/* Page Title */ .page-title { - margin-bottom: var(--space-6); + margin-bottom: 32px; } .page-title h1 { - font-size: var(--text-2xl); - font-weight: 700; + font-size: 1.5rem; + font-weight: 600; letter-spacing: -0.02em; - color: var(--text-primary); - margin-bottom: var(--space-1); + margin-bottom: 4px; } .page-title p { - font-size: var(--text-base); color: var(--text-muted); + font-size: 0.875rem; } -.page-title-with-actions { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: var(--space-4); - margin-bottom: var(--space-6); +/* Filters - now uses .controls for consistency */ + +/* Stats Grid */ +.stats-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 16px; + margin-bottom: 32px; } -.page-title-with-actions .page-title { - margin-bottom: 0; +.stat-card { + background: var(--surface); + padding: 24px; + border-radius: var(--radius); + border: 1px solid var(--border); } -.toggle-with-label { - display: flex; - align-items: center; - gap: var(--space-2); - margin-top: var(--space-1); -} - -.toggle-with-label .toggle-text { - font-size: var(--text-sm); +.stat-card h3 { + font-size: 0.75rem; font-weight: 500; color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 8px; } -/* ======================================== - 8. FILTER CONTROLS - ======================================== */ +.stat-value { + font-size: 1.75rem; + font-weight: 600; + color: var(--text-primary); + letter-spacing: -0.02em; +} +.stat-change { + font-size: 0.75rem; + font-weight: 500; + margin-top: 4px; +} + +.stat-change.positive { color: var(--success); } +.stat-change.negative { color: var(--danger); } + +/* Charts */ +.charts-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; + margin-bottom: 32px; +} + +.chart-card { + background: var(--surface); + padding: 24px; + border-radius: var(--radius); + border: 1px solid var(--border); + position: relative; +} + +.toggle-corner { + position: absolute; + top: 20px; + right: 20px; + z-index: 1; +} + +.chart-card.full-width { + grid-column: 1 / -1; +} + +.chart-card.half-width { + grid-column: span 1; +} + +.chart-card h2 { + font-size: 0.875rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 20px; +} + +.chart-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + gap: 12px; + flex-wrap: wrap; +} + +.chart-card-header h2 { + margin: 0; +} + +.chart-card-actions { + display: flex; + gap: 8px; + align-items: center; +} + +.chart-container { + height: 280px; + position: relative; +} + +/* Table */ +.table-container { + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; + font-size: 0.8125rem; +} + +table th { + font-weight: 500; + font-size: 0.6875rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 12px; + text-align: right; + border-bottom: 1px solid var(--border); +} + +table th:first-child { text-align: left; } + +table td { + padding: 12px; + text-align: right; + border-bottom: 1px solid var(--border); + color: var(--text-secondary); +} + +table td:first-child { text-align: left; } + +table tbody tr:hover { + background: var(--bg); +} + +.bold { font-weight: 600; color: var(--text-primary); } +.muted { color: var(--text-muted); } +.primary { color: var(--accent); } +.purple { color: #7c3aed; } +.positive { color: var(--success); } +.negative { color: var(--danger); } + +/* Comparison Page */ .controls { background: var(--surface); - padding: var(--space-5); - border-radius: var(--radius-lg); + padding: 24px; + border-radius: var(--radius); border: 1px solid var(--border); - margin-bottom: var(--space-6); - box-shadow: var(--shadow-card); + margin-bottom: 32px; } .controls-header { @@ -556,35 +487,36 @@ body { justify-content: space-between; align-items: center; cursor: pointer; - -webkit-tap-highlight-color: transparent; } .controls-header h3 { - font-size: var(--text-sm); - font-weight: 600; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.05em; margin: 0; } .controls-header-actions { display: flex; align-items: center; - gap: var(--space-2); + gap: 8px; +} + +.controls h3 { + font-size: 0.75rem; + font-weight: 500; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; } .controls-reset { background: transparent; border: 1px solid var(--border); color: var(--text-muted); - padding: var(--space-1) var(--space-3); - border-radius: var(--radius-sm); - font-size: var(--text-sm); + padding: 4px 12px; + border-radius: 6px; + font-size: 0.75rem; font-weight: 500; cursor: pointer; - min-height: 32px; - transition: all var(--transition-fast); + transition: all 0.2s; } .controls-reset:hover { @@ -597,20 +529,18 @@ body { background: none; border: none; color: var(--text-muted); - font-size: var(--text-sm); - font-weight: 500; + font-size: 0.75rem; cursor: pointer; - padding: var(--space-1) var(--space-2); - min-height: 32px; + padding: 4px 8px; } .controls-body { - margin-top: var(--space-4); + margin-top: 16px; } .control-row { display: flex; - gap: var(--space-4); + gap: 16px; flex-wrap: wrap; align-items: flex-end; } @@ -618,619 +548,121 @@ body { .control-group { display: flex; flex-direction: column; - gap: var(--space-1); - min-width: 150px; + gap: 6px; } .control-group label { - font-size: var(--text-xs); - font-weight: 600; - color: var(--text-secondary); + font-size: 0.6875rem; + font-weight: 500; + color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; } .control-group select, .control-group input[type="date"] { - padding: var(--space-3); + padding: 10px 14px; border: 1px solid var(--border); - border-radius: var(--radius-md); - font-size: var(--text-base); - font-weight: 500; + border-radius: 6px; + font-size: 0.875rem; background: var(--surface); color: var(--text-primary); - min-height: var(--touch-min); - transition: border-color var(--transition-fast); - cursor: pointer; + min-width: 150px; } .control-group select:focus, .control-group input[type="date"]:focus { - border-color: var(--primary); + outline: none; + border-color: var(--accent); } -/* ======================================== - 9. STAT CARDS - ======================================== */ - -.stats-grid { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: var(--space-4); - margin-bottom: var(--space-6); +.period-display { + background: var(--bg); + padding: 16px; + border-radius: 8px; + margin-top: 16px; + display: flex; + gap: 32px; } -.stat-card { - background: var(--surface); - padding: var(--space-5); - border-radius: var(--radius-lg); - border: 1px solid var(--border); - box-shadow: var(--shadow-card); - transition: all var(--transition-fast); -} - -.stat-card:hover { - box-shadow: var(--shadow-md); -} - -.stat-card h3 { - font-size: var(--text-xs); - font-weight: 600; +.period-box .label { + font-size: 0.6875rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; - margin-bottom: var(--space-2); + margin-bottom: 4px; } -.stat-value { - font-size: var(--text-3xl); - font-weight: 700; +.period-box .dates { + font-size: 0.875rem; color: var(--text-primary); - letter-spacing: -0.02em; - line-height: 1.2; -} - -.stat-change { - display: inline-flex; - align-items: center; - gap: 2px; - font-size: var(--text-sm); - font-weight: 600; - margin-top: var(--space-2); - padding: var(--space-1) var(--space-2); - border-radius: var(--radius-sm); -} - -.stat-change-arrow { - font-size: 0.9em; -} - -.stat-change-value { - margin: 0 2px; -} - -.stat-change-label { - opacity: 0.8; font-weight: 500; } -.stat-change.positive { - color: var(--success); - background: var(--success-light); -} - -.stat-change.negative { - color: var(--danger); - background: var(--danger-light); -} - -.stat-subtitle { - font-size: var(--text-sm); - color: var(--text-muted); - margin-top: var(--space-1); -} - -/* Stats carousel for mobile */ -.stats-carousel { - margin-bottom: var(--space-6); -} - -.stats-carousel .stat-card { - margin: 0; -} - -/* ======================================== - 10. CHART CARDS - ======================================== */ - -.charts-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: var(--space-4); - margin-bottom: var(--space-6); -} - -.chart-card { - background: var(--surface); - padding: var(--space-5); - border-radius: var(--radius-lg); - border: 1px solid var(--border); - box-shadow: var(--shadow-card); - position: relative; -} - -.chart-card.full-width { - grid-column: 1 / -1; -} - -.chart-card.half-width { - grid-column: span 1; -} - -.chart-card h2 { - font-size: var(--text-sm); - font-weight: 600; - color: var(--text-secondary); - margin-bottom: var(--space-4); -} - -.chart-card-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: var(--space-4); - gap: var(--space-3); - flex-wrap: wrap; -} - -.chart-card-header h2 { - margin: 0; -} - -.chart-card-actions { - display: flex; - gap: var(--space-2); - align-items: center; -} - -.chart-container { - height: 280px; - position: relative; -} - -/* Corner toggle in chart cards */ -.toggle-corner { - position: absolute; - top: var(--space-4); - right: var(--space-4); - left: auto; - z-index: 1; -} - -[dir="rtl"] .toggle-corner { - right: auto; - left: var(--space-4); -} - -/* ======================================== - 11. TOGGLE SWITCH (Daily/Weekly etc.) - ======================================== */ - -.toggle-switch { - display: inline-flex; - background: var(--bg); - padding: 3px; - border-radius: var(--radius-full); - border: 1px solid var(--border); - gap: 2px; -} - -.toggle-switch button { - border: none; - background: transparent; - color: var(--text-muted); - padding: var(--space-2) var(--space-3); - border-radius: var(--radius-full); - font-size: var(--text-sm); - font-weight: 600; - cursor: pointer; - min-height: 32px; - transition: all var(--transition-fast); -} - -.toggle-switch button:hover:not(.active) { - color: var(--text-secondary); -} - -.toggle-switch button.active { - background: var(--surface); - color: var(--text-primary); - box-shadow: var(--shadow-sm); -} - -/* Data Labels Toggle */ -.label-toggle { - display: inline-flex; - align-items: center; - gap: var(--space-1); - border: 2px solid var(--border); - background: var(--surface); - color: var(--text-muted); - padding: var(--space-2) var(--space-3); - border-radius: var(--radius-md); - font-size: var(--text-sm); - font-weight: 600; - cursor: pointer; - min-height: var(--touch-min); - transition: all var(--transition-fast); -} - -.label-toggle:hover { - border-color: var(--primary); - color: var(--text-primary); -} - -.label-toggle.active { - background: var(--primary); - border-color: var(--primary); - color: white; -} - -/* Chart metric selector pills */ -.chart-metric-selector { - display: inline-flex; - gap: 2px; - background: var(--bg); - padding: 3px; - border-radius: var(--radius-sm); - border: 1px solid var(--border); -} - -.chart-metric-selector button { - border: none; - background: transparent; - color: var(--text-muted); - padding: var(--space-1) var(--space-2); - border-radius: 4px; - font-size: var(--text-xs); - font-weight: 600; - cursor: pointer; - transition: all var(--transition-fast); -} - -.chart-metric-selector button:hover { - color: var(--text-secondary); -} - -.chart-metric-selector button.active { - background: var(--surface); - color: var(--text-primary); - box-shadow: var(--shadow-xs); -} - -/* ======================================== - 12. CAROUSEL - Touch-optimized - ======================================== */ - -.carousel { - outline: none; - touch-action: pan-y pinch-zoom; -} - -.carousel:focus-visible { - outline: 2px solid var(--primary); - outline-offset: 4px; - border-radius: var(--radius-md); -} - -.carousel-container { - position: relative; - overflow: hidden; -} - -.carousel-viewport { - overflow: hidden; - width: 100%; -} - -.carousel-track { - display: flex; - transition: transform 400ms cubic-bezier(0.25, 0.46, 0.45, 0.94); - will-change: transform; -} - -.carousel-slide { - min-width: 100%; - max-width: 100%; - flex-shrink: 0; - padding: 0 2px; - box-sizing: border-box; -} - -.carousel-slide .chart-card, -.carousel-slide .stat-card, -.carousel-slide .metric-card { - width: 100%; - margin: 0; - height: 100%; -} - -/* Carousel dots - pill navigation */ -.carousel-dots { - display: flex; - justify-content: center; - gap: var(--space-2); - margin-top: var(--space-4); - padding: var(--space-2) 0; - flex-wrap: wrap; -} - -.carousel-dot { - background: var(--bg); - border: 1px solid var(--border); - width: auto; - min-width: 32px; - height: 32px; - border-radius: var(--radius-full); - padding: 0 var(--space-3); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all var(--transition-fast); -} - -.carousel-dot .dot-label { - font-size: var(--text-xs); - font-weight: 600; - color: var(--text-muted); - white-space: nowrap; -} - -.carousel-dot:hover { - border-color: var(--primary); -} - -.carousel-dot.active { - background: var(--primary); - border-color: var(--primary); -} - -.carousel-dot.active .dot-label { - color: white; -} - -/* Labeled variant (larger pills) */ -.carousel-dots.labeled .carousel-dot { - min-width: auto; - height: auto; - padding: var(--space-2) var(--space-4); - min-height: 36px; -} - -/* Hide arrows on touch devices */ -.carousel-arrow { - display: none; -} - -/* Charts carousel */ -.charts-carousel { - margin-bottom: var(--space-6); -} - -.charts-carousel .chart-container { - height: 240px; -} - -/* Cards carousel */ -.cards-carousel { - margin-bottom: var(--space-6); -} - -/* Chart selectors inline (mobile) */ -.chart-selectors-inline { - display: flex; - gap: var(--space-2); - flex-wrap: wrap; - margin-bottom: var(--space-3); -} - -.chart-selectors-inline .chart-metric-selector { - flex-wrap: wrap; -} - -.chart-selectors-inline .chart-metric-selector button { - padding: var(--space-1) var(--space-2); - font-size: var(--text-xs); -} - -/* ======================================== - 13. TABLE STYLES - ======================================== */ - -.table-container { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - margin: 0 calc(var(--space-4) * -1); - padding: 0 var(--space-4); -} - -table { - width: 100%; - border-collapse: collapse; - font-size: var(--text-sm); -} - -table th { - font-weight: 600; - font-size: var(--text-xs); - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.05em; - padding: var(--space-3); - text-align: right; - border-bottom: 2px solid var(--border); - white-space: nowrap; -} - -table th:first-child { - text-align: left; -} - -table td { - padding: var(--space-3); - text-align: right; - border-bottom: 1px solid var(--border-light); - color: var(--text-secondary); -} - -table td:first-child { - text-align: left; -} - -table tbody tr:hover { - background: var(--surface-hover); -} - -table tbody tr:last-child td { - border-bottom: none; -} - -/* Table text variants */ -.bold { font-weight: 600; color: var(--text-primary); } -.muted { color: var(--text-muted); } -.primary { color: var(--primary); font-weight: 600; } -.purple { color: #7c3aed; font-weight: 600; } -.positive { color: var(--success); font-weight: 600; } -.negative { color: var(--danger); font-weight: 600; } - -/* ======================================== - 14. COMPARISON PAGE SPECIFIC - ======================================== */ - -/* Period Display Banner */ -.period-display-banner { - display: flex; - align-items: center; - justify-content: center; - gap: var(--space-6); - background: var(--bg); - padding: var(--space-5); - border-radius: var(--radius-lg); - margin-bottom: var(--space-6); -} - -.period-display-banner .period-box { - text-align: center; - min-width: 160px; -} - -.period-display-banner .period-label { - font-size: var(--text-xs); - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--text-muted); - margin-bottom: var(--space-1); -} - -.period-display-banner .period-value { - font-size: var(--text-2xl); - font-weight: 700; - margin-bottom: var(--space-1); -} - -.period-display-banner .period-box.prev .period-value { - color: var(--text-secondary); -} - -.period-display-banner .period-box.curr .period-value { - color: var(--text-primary); -} - -.period-display-banner .period-dates { - font-size: var(--text-sm); - color: var(--text-muted); -} - -.period-display-banner .period-vs { - font-size: var(--text-base); - font-weight: 600; - color: var(--text-subtle); - padding: 0 var(--space-2); -} - -/* Comparison metric cards */ +/* Comparison Metrics */ .comparison-grid { display: grid; grid-template-columns: repeat(4, 1fr); - gap: var(--space-4); - margin-bottom: var(--space-6); + gap: 16px; + margin-bottom: 32px; } .metric-card { background: var(--surface); - padding: var(--space-4); - border-radius: var(--radius-lg); + padding: 20px; + border-radius: var(--radius); border: 1px solid var(--border); - box-shadow: var(--shadow-card); } .metric-card h4 { - font-size: var(--text-xs); - font-weight: 600; + font-size: 0.75rem; + font-weight: 500; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; - margin-bottom: var(--space-3); + margin-bottom: 12px; } .metric-values { display: flex; align-items: center; justify-content: space-between; - gap: var(--space-3); + gap: 12px; } .metric-period { text-align: center; } -.metric-period .year, -.metric-period .label { - font-size: var(--text-xs); +.metric-period .year { + font-size: 0.625rem; color: var(--text-muted); text-transform: uppercase; margin-bottom: 2px; } .metric-period .value { - font-size: var(--text-lg); - font-weight: 700; + font-size: 1.125rem; + font-weight: 600; } -.metric-period.previous .value { - color: var(--text-muted); -} - -.metric-period.current .value { - color: var(--text-primary); -} +.metric-period.previous .value { color: var(--text-muted); } +.metric-period.current .value { color: var(--text-primary); } .metric-change { text-align: center; - padding: var(--space-1) var(--space-2); - border-radius: var(--radius-sm); - min-width: 60px; + padding: 6px 10px; + border-radius: 6px; + min-width: 70px; } .metric-change .pct { - font-size: var(--text-sm); - font-weight: 700; + font-size: 0.875rem; + font-weight: 600; } .metric-change .abs { - font-size: var(--text-xs); + font-size: 0.625rem; opacity: 0.8; } @@ -1245,226 +677,332 @@ table tbody tr:last-child td { } .metric-change.pending { - background: var(--bg); - color: var(--text-muted); + background: var(--muted-light, #f1f5f9); + color: var(--text-muted, #64748b); } .metric-change .pending-msg { - font-size: var(--text-xs); + font-size: 11px; font-style: italic; + text-align: center; + line-height: 1.3; } -/* Period display (legacy) */ -.period-display { - background: var(--bg); - padding: var(--space-4); - border-radius: var(--radius-md); - margin-top: var(--space-4); +/* Chart Header with Metric Selector */ +.chart-header { display: flex; - gap: var(--space-6); -} - -.period-box .label { - font-size: var(--text-xs); - font-weight: 600; - color: var(--text-muted); - text-transform: uppercase; - margin-bottom: var(--space-1); -} - -.period-box .dates { - font-size: var(--text-base); - font-weight: 600; - color: var(--text-primary); -} - -/* ======================================== - 15. CHART EXPORT - ======================================== */ - -.exportable-chart-wrapper, -.exportable-chart { - width: 100%; - position: relative; -} - -.chart-header-with-export { - display: flex; - align-items: center; justify-content: space-between; - margin-bottom: var(--space-3); - gap: var(--space-2); + align-items: center; + margin-bottom: 16px; + flex-wrap: wrap; + gap: 12px; } -.chart-header-with-export h2 { - font-size: var(--text-sm); - font-weight: 600; - color: var(--text-secondary); +.chart-header h2 { margin: 0; } -.chart-header-actions { +.chart-selectors { display: flex; - align-items: center; - gap: var(--space-2); - flex-wrap: wrap; - justify-content: flex-end; + gap: 8px; } -.chart-export-btn { - width: 36px; - height: 36px; +.chart-metric-selector { + display: flex; + gap: 4px; + background: var(--bg); + padding: 4px; + border-radius: 8px; +} + +/* Toggle switch style (Daily/Weekly) */ +.toggle-switch { + display: flex; + background: var(--border); + padding: 2px; + border-radius: 12px; + gap: 0; +} + +.toggle-switch button { + border: none; + background: transparent; + color: var(--text-muted); + padding: 3px 10px; + border-radius: 10px; + font-size: 0.625rem; + font-weight: 500; + cursor: pointer; + transition: all 0.25s ease; +} + +.toggle-switch button:hover:not(.active) { + color: var(--text-secondary); +} + +.toggle-switch button.active { + background: var(--surface); + color: var(--text-primary); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +/* Data Labels Toggle */ +.label-toggle { + display: flex; + align-items: center; + gap: 4px; + border: 2px solid var(--border); + background: var(--surface); + color: var(--text-muted); + padding: 8px 14px; + border-radius: 10px; + font-size: 0.8rem; + font-weight: 500; + cursor: pointer; + transition: all 0.25s ease; +} + +.label-toggle:hover { + border-color: var(--primary); + color: var(--text-primary); +} + +.label-toggle.active { + background: var(--primary); + border-color: var(--primary); + color: white; +} + +.chart-metric-selector button { + border: none; + background: transparent; + color: var(--text-muted); + padding: 6px 12px; + border-radius: 6px; + font-size: 0.75rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.chart-metric-selector button:hover { + color: var(--text-primary); + background: rgba(0,0,0,0.05); +} + +.chart-metric-selector button.active { + background: var(--surface); + color: var(--primary); + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + font-weight: 600; +} + +/* Charts Carousel */ +.charts-carousel { + margin-bottom: 32px; +} + +.chart-selectors-bar { + display: flex; + gap: 8px; + margin-bottom: 16px; + flex-wrap: wrap; +} + +/* Inline selectors inside chart cards (mobile) */ +.chart-selectors-inline { + display: flex; + gap: 6px; + flex-wrap: wrap; + justify-content: flex-start; + margin-bottom: 12px; +} + +.chart-selectors-inline .chart-metric-selector { + flex-wrap: wrap; +} + +.chart-selectors-inline .chart-metric-selector button { + padding: 4px 8px; + font-size: 0.6875rem; +} + +/* Carousel - elegant card slider */ +.carousel { + outline: none; +} + +.carousel:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 4px; + border-radius: 8px; +} + +.carousel-container { + position: relative; + margin: 0 -6px; +} + +.carousel-viewport { + overflow: hidden; + width: 100%; +} + +.carousel-track { + display: flex; + transition: transform 0.4s cubic-bezier(0.25, 0.1, 0.25, 1); +} + +.carousel-slide { + min-width: 100%; + max-width: 100%; + flex-shrink: 0; + padding: 0 6px; + box-sizing: border-box; + overflow: hidden; +} + +.carousel-slide .chart-section, +.carousel-slide .metric-card, +.carousel-slide .stat-card, +.carousel-slide .chart-card { + width: 100%; + margin: 0; + box-shadow: none; border: 1px solid var(--border); background: var(--surface); - border-radius: var(--radius-md); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - color: var(--text-muted); - transition: all var(--transition-fast); - flex-shrink: 0; } -.chart-export-btn:hover { +.carousel-slide .chart-section .chart-container, +.carousel-slide .chart-card .chart-container { + height: 240px; +} + +/* Hide arrows on mobile - use swipe */ +.carousel-arrow { + display: none; +} + +/* Dots / Pagination */ +.carousel-dots { + display: flex; + justify-content: center; + gap: 6px; + margin-top: 20px; + padding: 8px 0; +} + +.carousel-dot { + background: var(--border); + border: none; + width: 8px; + height: 8px; + border-radius: 4px; + padding: 0; + cursor: pointer; + transition: all 0.3s ease; +} + +.carousel-dot .dot-label { + display: none; +} + +.carousel-dot:hover { + background: var(--text-muted); +} + +.carousel-dot.active { + background: var(--primary); + width: 24px; +} + +/* Labeled dots variant (for named tabs) */ +.carousel-dots.labeled { + gap: 8px; +} + +.carousel-dots.labeled .carousel-dot { + width: auto; + height: auto; + padding: 8px 16px; + border-radius: 20px; background: var(--bg); - color: var(--text-primary); + border: 1px solid var(--border); +} + +.carousel-dots.labeled .carousel-dot .dot-label { + display: inline; + font-size: 0.75rem; + color: var(--text-muted); + font-weight: 500; +} + +.carousel-dots.labeled .carousel-dot:hover { + border-color: var(--primary); + background: var(--bg); +} + +.carousel-dots.labeled .carousel-dot.active { + width: auto; + background: var(--primary); border-color: var(--primary); } -.btn-export-all { - display: inline-flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-2) var(--space-4); - background: var(--primary); +.carousel-dots.labeled .carousel-dot.active .dot-label { color: white; - border: none; - border-radius: var(--radius-md); - font-size: var(--text-sm); - font-weight: 600; - cursor: pointer; - min-height: var(--touch-min); - transition: all var(--transition-fast); } -.btn-export-all:hover:not(:disabled) { - background: var(--primary-hover); -} - -.btn-export-all:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* ======================================== - 16. CHART SECTIONS (Used in Comparison) - ======================================== */ - +/* Chart Sections */ .chart-section { background: var(--surface); - padding: var(--space-5); - border-radius: var(--radius-lg); + padding: 20px; + border-radius: var(--radius); border: 1px solid var(--border); - margin-bottom: var(--space-4); - box-shadow: var(--shadow-card); + margin-bottom: 16px; +} + +.carousel-slide .chart-section { + margin-bottom: 0; + padding: 16px; + overflow: hidden; +} + +.carousel-slide .chart-section .chart-header { + flex-direction: row !important; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + gap: 8px; +} + +.carousel-slide .chart-section .chart-header h2 { + font-size: 0.8125rem; + white-space: nowrap; + margin: 0; +} + +.carousel-slide .chart-section .chart-header .chart-metric-selector { + flex-shrink: 0; +} + +.carousel-slide .chart-section .chart-container, +.carousel-slide .chart-card .chart-container { + width: 100%; + overflow: hidden; } .chart-section h2 { - font-size: var(--text-sm); + font-size: 0.875rem; font-weight: 600; - color: var(--text-secondary); - margin-bottom: var(--space-4); + margin-bottom: 20px; } .chart-section .chart-container { height: 300px; } -.chart-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: var(--space-3); - flex-wrap: wrap; - gap: var(--space-2); -} - -.chart-header h2 { - margin: 0; - font-size: var(--text-sm); - font-weight: 600; - color: var(--text-secondary); -} - -.chart-controls { - display: flex; - justify-content: space-between; - align-items: center; - gap: var(--space-2); - margin-bottom: var(--space-3); -} - -.chart-selectors { - display: flex; - gap: var(--space-2); -} - -/* ======================================== - 17. SLIDES BUILDER - ======================================== */ - -.slides-builder { - padding: var(--space-6); - max-width: 1400px; - margin: 0 auto; -} - -.slides-toolbar { - display: flex; - gap: var(--space-3); - margin-bottom: var(--space-6); - flex-wrap: wrap; -} - -.btn-primary, -.btn-secondary { - display: inline-flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-3) var(--space-4); - border-radius: var(--radius-md); - font-size: var(--text-base); - font-weight: 600; - cursor: pointer; - transition: all var(--transition-fast); - border: none; - min-height: var(--touch-min); -} - -.btn-primary { - background: var(--primary); - color: white; -} - -.btn-primary:hover { - background: var(--primary-hover); -} - -.btn-secondary { - background: var(--bg); - color: var(--text-primary); - border: 1px solid var(--border); -} - -.btn-secondary:hover { - background: var(--surface-hover); -} - -/* ======================================== - 18. VISIBILITY UTILITIES - ======================================== */ - +/* Desktop/Mobile visibility */ .mobile-only { display: none; } @@ -1473,10 +1011,31 @@ table tbody tr:last-child td { display: grid; } -/* ======================================== - 19. RESPONSIVE - TABLET (max-width: 1024px) - ======================================== */ +/* Removed old .filters-* styles - now using .controls-* for consistency */ +/* Stats Carousel (mobile) */ +.stats-carousel { + margin-bottom: 32px; +} + +/* Cards Carousel (mobile) */ +.cards-carousel { + margin-bottom: 32px; +} + +.cards-carousel .carousel-dots { + flex-wrap: wrap; +} + +.cards-carousel .carousel-dot { + padding: 6px 10px; +} + +.cards-carousel .dot-label { + font-size: 0.6875rem; +} + +/* Responsive */ @media (max-width: 1024px) { .stats-grid, .comparison-grid { @@ -1486,120 +1045,100 @@ table tbody tr:last-child td { .charts-grid { grid-template-columns: 1fr; } - - .chart-card.half-width { - grid-column: span 1; - } } -/* ======================================== - 20. RESPONSIVE - MOBILE (max-width: 768px) - ======================================== */ +/* Mobile Bottom Navigation */ +.mobile-nav { + display: none; +} @media (max-width: 768px) { - /* Show mobile nav, hide desktop nav links */ - .mobile-nav { - display: flex; - } - + /* Hide desktop nav links, show only brand */ .nav-links { display: none; } - /* Compact top nav */ .nav-bar { - padding: 0; + padding: 12px 16px; + height: auto; } .nav-content { - padding: 0 var(--space-4); - height: 52px; justify-content: center; } - .nav-brand-icon { - width: 20px; - height: 20px; + .nav-brand-text { + font-size: 1rem; } - .nav-brand-text { - font-size: var(--text-lg); + .nav-brand-icon { + width: 18px; + height: 18px; } .data-source-select { - font-size: var(--text-base); - padding: var(--space-1) var(--space-4) var(--space-1) var(--space-1); + font-size: 0.9rem; + padding: 2px 18px 2px 4px; } - /* Main content padding for bottom nav */ + /* Mobile Bottom Navigation */ + .mobile-nav { + display: flex; + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: var(--surface); + border-top: 1px solid var(--border); + padding: 8px 16px; + padding-bottom: max(8px, env(safe-area-inset-bottom)); + justify-content: space-around; + align-items: center; + z-index: 1000; + box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); + } + + .mobile-nav-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 8px 16px; + border: none; + background: transparent; + color: var(--text-muted); + text-decoration: none; + font-size: 0.65rem; + font-weight: 600; + border-radius: 12px; + transition: all 0.2s; + cursor: pointer; + } + + .mobile-nav-item:hover, + .mobile-nav-item.active { + color: var(--primary); + background: rgba(37, 99, 235, 0.08); + } + + .mobile-nav-item.active svg { + stroke-width: 2.5; + } + + /* Add padding to main content for bottom nav */ .dashboard, .comparison { - padding: var(--space-4); - padding-bottom: calc(80px + env(safe-area-inset-bottom)); - min-height: auto; + padding-bottom: 80px; } - /* Page title */ - .page-title h1 { - font-size: var(--text-xl); + .data-source-toggle { + margin-left: 8px; + padding: 2px; } - .page-title p { - font-size: var(--text-sm); - } - - .page-title-with-actions { - flex-direction: row; - align-items: flex-start; - margin-bottom: var(--space-4); - } - - .toggle-with-label { - margin-top: var(--space-1); - gap: var(--space-1); - } - - .toggle-with-label .toggle-text { - font-size: var(--text-xs); - } - - .page-title-with-actions .toggle-switch button { - padding: var(--space-1) var(--space-2); - font-size: var(--text-xs); - min-height: 28px; - } - - /* Controls - collapsible */ - .controls { - padding: var(--space-4); - margin-bottom: var(--space-4); - } - - .controls-toggle { - display: block; - } - - .controls.collapsed .controls-body { - display: none; - } - - .controls-body { - margin-top: var(--space-3); - } - - .control-row { - flex-direction: column; - gap: var(--space-3); - } - - .control-group { - width: 100%; - min-width: unset; - } - - .control-group select, - .control-group input[type="date"] { - width: 100%; + .data-source-toggle button { + padding: 4px 8px; + font-size: 0.6875rem; } /* Desktop/Mobile visibility switch */ @@ -1611,147 +1150,798 @@ table tbody tr:last-child td { display: none; } - /* Stats carousel improvements */ - .stats-carousel { - margin-bottom: var(--space-4); + /* Collapsible controls (Comparison) */ + .controls-toggle { + display: block; } - .stats-carousel .stat-card { - padding: var(--space-4); + .controls.collapsed .controls-body { + display: none; } - .stats-carousel .stat-card h3 { - font-size: var(--text-xs); + .controls.collapsed { + padding: 16px; } - .stats-carousel .stat-value { - font-size: var(--text-2xl); + /* Dashboard now uses .controls - removed duplicate .filters styles */ + + /* Chart header */ + .chart-header { + flex-direction: column; + align-items: flex-start; + gap: 12px; } - .stats-carousel .stat-change { - font-size: var(--text-xs); - padding: 2px var(--space-2); + .chart-selectors { + flex-wrap: wrap; + width: 100%; } - /* Charts carousel improvements */ - .charts-carousel { - margin-bottom: var(--space-4); + .chart-metric-selector { + flex-wrap: wrap; } - .charts-carousel .chart-card, - .charts-carousel .chart-section { - padding: var(--space-4); - overflow: hidden; + /* Chart selectors bar */ + .chart-selectors-bar { + justify-content: center; } - .charts-carousel .chart-card h2, - .charts-carousel .chart-section h2 { - font-size: var(--text-sm); - margin-bottom: var(--space-3); - padding-right: 100px; /* Space for corner toggle */ + /* Carousel - overlay arrows on mobile */ + .carousel-arrow { + width: 28px; + height: 28px; + font-size: 1rem; + opacity: 0.8; } - .charts-carousel .chart-container { - height: 220px; + .carousel-arrow.prev { + left: 4px; } - .charts-carousel .toggle-corner { - top: var(--space-3); - right: var(--space-3); + .carousel-arrow.next { + right: 4px; } - .charts-carousel .toggle-corner .toggle-switch button { - padding: var(--space-1) var(--space-2); - font-size: var(--text-xs); - min-height: 26px; - } - - /* Chart section header in carousel */ - .charts-carousel .chart-section .chart-header { - flex-direction: row; - flex-wrap: nowrap; - justify-content: space-between; - align-items: center; - gap: var(--space-2); - margin-bottom: var(--space-2); - } - - .charts-carousel .chart-section .chart-header h2 { - font-size: var(--text-sm); - white-space: nowrap; - margin: 0; - padding-right: 0; - } - - .charts-carousel .chart-section .toggle-switch button { - padding: var(--space-1) var(--space-2); - font-size: 10px; - min-height: 24px; - } - - /* Inline selectors in chart carousel */ - .chart-selectors-inline { - margin-bottom: var(--space-2); - } - - .chart-selectors-inline .chart-metric-selector button { - padding: 3px 6px; - font-size: 10px; - } - - /* Carousel dots */ .carousel-dots { - gap: var(--space-1); - margin-top: var(--space-3); + gap: 4px; + flex-wrap: wrap; } .carousel-dot { - min-width: auto; - height: 28px; - padding: 0 var(--space-2); + padding: 6px 10px; } .carousel-dot .dot-label { - font-size: 10px; + font-size: 0.6875rem; } - .carousel-dots.labeled .carousel-dot { - padding: var(--space-1) var(--space-3); + /* Control row */ + .control-row { + flex-direction: column; + align-items: stretch; + } + + .control-group { + width: 100%; + } + + .control-group select, + .control-group input[type="date"] { + width: 100%; + min-width: unset; + } + + /* Period display */ + .period-display { + flex-direction: column; + gap: 12px; + } + + /* Metric cards - keep horizontal layout on mobile */ + .metric-card { + padding: 16px; + } + + .metric-values { + flex-direction: row; + gap: 8px; + } + + .metric-period .value { + font-size: 1rem; + } + + .metric-change { + padding: 4px 8px; + min-width: 60px; + } + + .metric-change .pct { + font-size: 0.75rem; + } + + .metric-change .abs { + font-size: 0.5625rem; + } +} + +@media (max-width: 640px) { + .dashboard, .comparison { + padding: 12px; + } + + .stats-grid, + .comparison-grid { + grid-template-columns: 1fr; + } + + /* Page titles */ + .page-title { + margin-bottom: 20px; + } + + .page-title h1 { + font-size: 1.25rem; + } + + .page-title p { + font-size: 0.75rem; + } + + /* Stat cards */ + .stat-card { + padding: 16px; + } + + .stat-value { + font-size: 1.5rem; + } + + /* Chart section */ + .chart-section { + padding: 12px; + } + + .chart-section h2 { + font-size: 0.875rem; + margin-bottom: 12px; + } + + .chart-metric-selector button { + padding: 5px 8px; + font-size: 0.625rem; + } + + /* Controls */ + .controls { + padding: 12px; + } + + /* Carousel even tighter */ + .carousel-arrow { + width: 24px; + height: 24px; + font-size: 0.875rem; + } + + .carousel-arrow.prev { + left: 2px; + } + + .carousel-arrow.next { + right: 2px; + } +} + +@media (max-width: 400px) { + /* Very small screens */ + .nav-bar { + padding: 6px 8px; + } + + .nav-brand { + font-size: 0.875rem; + } + + .nav-link { + padding: 4px 6px; + font-size: 0.6875rem; + } + + .data-source-toggle button { + padding: 3px 6px; + font-size: 0.625rem; + } + + .carousel-dot { + padding: 4px 6px; + } + + .carousel-dot .dot-label { + font-size: 0.5625rem; + } + + .chart-selectors-bar { + gap: 4px; + } +} + +/* ========== Slides Builder ========== */ +.slides-builder { + padding: 24px; + max-width: 1400px; + margin: 0 auto; +} + +.slides-toolbar { + display: flex; + gap: 12px; + margin-bottom: 24px; + flex-wrap: wrap; +} + +.btn-primary, .btn-secondary { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 18px; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; + border: none; +} + +.btn-primary { + background: var(--accent); + color: white; +} + +.btn-primary:hover { + background: #2563eb; +} + +.btn-secondary { + background: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border); +} + +.btn-secondary:hover { + background: var(--bg-tertiary); +} + +.slides-workspace { + display: grid; + grid-template-columns: 280px 1fr; + gap: 24px; + min-height: 600px; +} + +.slides-list { + background: var(--bg-secondary); + border-radius: 12px; + padding: 16px; + border: 1px solid var(--border); +} + +.slides-list h3 { + font-size: 14px; + color: var(--text-secondary); + margin-bottom: 16px; + font-weight: 500; +} + +.empty-slides { + text-align: center; + padding: 40px 20px; + color: var(--text-secondary); +} + +.empty-slides button { + margin-top: 16px; + padding: 8px 16px; + background: var(--accent); + color: white; + border: none; + border-radius: 6px; + cursor: pointer; +} + +.slides-thumbnails { + display: flex; + flex-direction: column; + gap: 8px; +} + +.slide-thumbnail { + display: grid; + grid-template-columns: 32px 32px 1fr auto; + align-items: center; + gap: 8px; + padding: 12px; + background: var(--bg-primary); + border-radius: 8px; + cursor: pointer; + transition: all 0.15s ease; + border: 2px solid transparent; +} + +.slide-thumbnail:hover { + border-color: var(--border); +} + +.slide-thumbnail.active { + border-color: var(--accent); + background: rgba(59, 130, 246, 0.05); +} + +.slide-thumbnail .slide-number { + width: 24px; + height: 24px; + background: var(--bg-tertiary); + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); +} + +.slide-thumbnail .slide-icon { + font-size: 18px; +} + +.slide-thumbnail .slide-title-preview { + font-size: 13px; + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.slide-thumbnail .slide-actions { + display: flex; + gap: 4px; + opacity: 0; + transition: opacity 0.15s ease; +} + +.slide-thumbnail:hover .slide-actions { + opacity: 1; +} + +.slide-actions button { + width: 24px; + height: 24px; + border: none; + background: var(--bg-tertiary); + border-radius: 4px; + cursor: pointer; + font-size: 12px; + color: var(--text-secondary); +} + +.slide-actions button:hover { + background: var(--border); +} + +.slide-actions button.delete:hover { + background: #fee2e2; + color: #dc2626; +} + +/* Slide Editor */ +.slide-editor { + background: var(--bg-secondary); + border-radius: 12px; + padding: 24px; + border: 1px solid var(--border); +} + +.editor-section { + margin-bottom: 20px; +} + +.editor-section label { + display: block; + font-size: 13px; + font-weight: 500; + color: var(--text-secondary); + margin-bottom: 8px; +} + +.editor-section input[type="text"], +.editor-section input[type="date"], +.editor-section select { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--border); + border-radius: 8px; + font-size: 14px; + background: var(--bg-primary); + color: var(--text-primary); +} + +.editor-section input:focus, +.editor-section select:focus { + outline: none; + border-color: var(--accent); +} + +.editor-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} + +.chart-type-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; +} + +.chart-type-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 16px 12px; + border: 2px solid var(--border); + border-radius: 10px; + background: var(--bg-primary); + cursor: pointer; + transition: all 0.15s ease; +} + +.chart-type-btn:hover { + border-color: var(--accent); +} + +.chart-type-btn.active { + border-color: var(--accent); + background: rgba(59, 130, 246, 0.05); +} + +.chart-type-btn .chart-icon { + font-size: 24px; +} + +.chart-type-btn span:last-child { + font-size: 12px; + font-weight: 500; + color: var(--text-secondary); +} + +.slide-preview-box { + margin-top: 24px; + padding-top: 24px; + border-top: 1px solid var(--border); +} + +.slide-preview-box h4 { + font-size: 13px; + font-weight: 500; + color: var(--text-secondary); + margin-bottom: 16px; +} + +.preview-chart { + background: var(--bg-primary); + border-radius: 8px; + padding: 16px; + height: 200px; +} + +.preview-kpis { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} + +.preview-kpi { + background: var(--bg-primary); + border-radius: 8px; + padding: 16px; + text-align: center; +} + +.preview-kpi .kpi-value { + font-size: 1.5rem; + font-weight: 700; + color: var(--accent); +} + +.preview-kpi .kpi-label { + font-size: 12px; + color: var(--text-secondary); + margin-top: 4px; +} + +/* Preview Fullscreen */ +.preview-fullscreen { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); + z-index: 1000; + display: flex; + flex-direction: column; +} + +.preview-slide { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px; +} + +.preview-title { + color: #f8fafc; + font-size: 2.5rem; + font-weight: 600; + margin-bottom: 40px; + text-align: center; +} + +.preview-content { + width: 100%; + max-width: 900px; +} + +.preview-content .preview-chart { + height: 350px; + background: rgba(255, 255, 255, 0.03); + border-radius: 16px; + padding: 30px; +} + +.preview-content .preview-kpis .preview-kpi { + background: rgba(255, 255, 255, 0.05); + padding: 30px; +} + +.preview-content .preview-kpis .kpi-value { + font-size: 2.5rem; +} + +.preview-content .preview-kpis .kpi-label { + color: #94a3b8; +} + +.preview-footer { + color: #64748b; + font-size: 14px; + margin-top: 40px; +} + +.preview-controls { + display: flex; + justify-content: center; + gap: 12px; + padding: 20px; + background: rgba(0, 0, 0, 0.3); +} + +.preview-controls button { + padding: 10px 24px; + background: rgba(255, 255, 255, 0.1); + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 14px; +} + +.preview-controls button:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.2); +} + +.preview-controls button:disabled { + opacity: 0.3; + cursor: not-allowed; +} + +@media (max-width: 768px) { + .slides-workspace { + grid-template-columns: 1fr; + } + + .slides-list { + max-height: 200px; + overflow-y: auto; + } + + .editor-row { + grid-template-columns: 1fr; + } + + .chart-type-grid { + grid-template-columns: repeat(4, 1fr); + } + + .preview-slide { + padding: 30px; + } + + .preview-title { + font-size: 1.5rem; + } +} + +/* Chart Export Button */ +.exportable-chart { + position: relative; +} + +.chart-export-btn { + position: absolute; + top: 8px; + right: 8px; + z-index: 10; + width: 32px; + height: 32px; + border: none; + background: var(--bg-secondary); + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + opacity: 0; + transition: opacity 0.15s ease, background 0.15s ease; +} + +.exportable-chart:hover .chart-export-btn { + opacity: 1; +} + +.chart-export-btn:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + +.chart-canvas-wrapper { + width: 100%; + height: 100%; +} + +/* ======================================== + MOBILE EXCELLENCE ENHANCEMENTS + These rules ONLY apply to mobile (≤768px) + and do not affect desktop layout + ======================================== */ + +@media (max-width: 768px) { + /* Enhanced touch targets - minimum 44px */ + .mobile-nav-item { + min-height: 44px; + min-width: 60px; + } + + .carousel-dot { + min-height: 36px; + min-width: 36px; + } + + .toggle-switch button { + min-height: 32px; + padding: 6px 12px; + } + + .chart-metric-selector button { min-height: 28px; + padding: 4px 8px; } - /* Table responsive */ - .chart-card.full-width { - padding: var(--space-4); + /* Smoother carousel transitions */ + .carousel-track { + transition: transform 350ms cubic-bezier(0.25, 0.46, 0.45, 0.94); + will-change: transform; } - .chart-card.full-width h2 { - font-size: var(--text-sm); + /* Better visual feedback on touch */ + .carousel-dot:active, + .mobile-nav-item:active, + .toggle-switch button:active, + .stat-card:active { + transform: scale(0.96); + transition: transform 100ms ease; } - .table-container { - margin: 0 calc(var(--space-3) * -1); - padding: 0 var(--space-3); + /* Active indicator for bottom nav */ + .mobile-nav-item.active { + position: relative; } - table { - font-size: var(--text-xs); + .mobile-nav-item.active::after { + content: ''; + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 20px; + height: 3px; + background: var(--primary); + border-radius: 0 0 3px 3px; } - table th, - table td { - padding: var(--space-2) var(--space-2); + /* Improved stat cards in carousel */ + .stats-carousel .stat-card { + min-height: 100px; + display: flex; + flex-direction: column; + justify-content: center; } - table th { - font-size: 10px; + /* Better chart card spacing */ + .charts-carousel .chart-card { + padding: 16px; } - /* Comparison page mobile */ + .charts-carousel .chart-card h2 { + font-size: 0.875rem; + margin-bottom: 12px; + } + + /* Ensure toggle corner doesn't overlap title */ + .charts-carousel .chart-card h2 { + padding-right: 90px; + } + + .charts-carousel .toggle-corner { + top: 12px; + right: 12px; + } + + /* Improved carousel dot labels */ + .carousel-dots.labeled .carousel-dot { + padding: 6px 12px; + border-radius: 16px; + } + + .carousel-dots.labeled .carousel-dot.active { + background: var(--primary); + border-color: var(--primary); + } + + .carousel-dots.labeled .carousel-dot.active .dot-label { + color: white; + } + + /* Filters - more compact on mobile */ + .controls { + padding: 12px 14px; + margin-bottom: 16px; + } + + .controls h3 { + font-size: 0.6875rem; + } + + .control-group label { + font-size: 0.625rem; + } + + .control-group select, + .control-group input[type="date"] { + font-size: 0.8125rem; + padding: 10px 12px; + min-height: 44px; + } + + /* Period banner - stacked on mobile */ .period-display-banner { flex-direction: column; - gap: var(--space-3); - padding: var(--space-4); + gap: 16px; + padding: 16px; } .period-display-banner .period-box { @@ -1759,94 +1949,70 @@ table tbody tr:last-child td { } .period-display-banner .period-value { - font-size: var(--text-xl); + font-size: 1.25rem; } - .period-display { - flex-direction: column; - gap: var(--space-3); + .period-display-banner .period-dates { + font-size: 0.75rem; } - /* Metric cards - horizontal layout preserved */ - .metric-card { - padding: var(--space-3); + /* Table scroll hint - fade at edges */ + .table-container { + position: relative; } - .metric-card h4 { - font-size: 10px; - margin-bottom: var(--space-2); + .table-container::after { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 20px; + background: linear-gradient(to left, var(--surface), transparent); + pointer-events: none; } - .metric-values { - flex-direction: row; - gap: var(--space-2); - } - - .metric-period .value { - font-size: var(--text-base); - } - - .metric-period .year, - .metric-period .label { - font-size: 10px; - } - - .metric-change { - padding: var(--space-1); - min-width: 50px; - } - - .metric-change .pct { - font-size: var(--text-xs); - } - - .metric-change .abs { - font-size: 10px; - } - - /* Chart header actions mobile */ - .chart-header-actions { - gap: var(--space-1); - } - - .chart-header-actions .toggle-switch button, - .chart-header-actions .chart-metric-selector button { - padding: var(--space-1) var(--space-2); - font-size: 10px; - min-height: 26px; - } - - /* Export button mobile */ - .btn-export-all { - width: 100%; - justify-content: center; - font-size: var(--text-sm); - padding: var(--space-3) var(--space-4); - } - - .chart-export-btn { - width: 32px; - height: 32px; - } -} - -/* ======================================== - 21. RESPONSIVE - SMALL MOBILE (max-width: 375px) - ======================================== */ - -@media (max-width: 375px) { - .dashboard, - .comparison { - padding: var(--space-3); - padding-bottom: calc(80px + env(safe-area-inset-bottom)); + /* Page title adjustments */ + .page-title { + margin-bottom: 16px; } .page-title h1 { - font-size: var(--text-lg); + font-size: 1.25rem; + line-height: 1.3; + } + + .page-title p { + font-size: 0.8125rem; + line-height: 1.4; + } + + /* Compact header toggle */ + .page-title-with-actions .toggle-switch button { + padding: 4px 8px; + font-size: 0.625rem; + min-height: 26px; + } + + .toggle-with-label .toggle-text { + font-size: 0.625rem; + } +} + +/* Extra small screens (≤375px) */ +@media (max-width: 375px) { + .dashboard, + .comparison { + padding: 12px; + padding-bottom: 80px; + } + + .page-title h1 { + font-size: 1.125rem; } .stats-carousel .stat-value { - font-size: var(--text-xl); + font-size: 1.5rem; } .charts-carousel .chart-container { @@ -1854,234 +2020,36 @@ table tbody tr:last-child td { } .carousel-dot { - padding: 0 var(--space-1); - height: 26px; + min-height: 32px; + min-width: 32px; + padding: 4px 8px; } .carousel-dot .dot-label { - font-size: 9px; - } - - table th, - table td { - padding: var(--space-1); - font-size: 10px; - } - - table th { - font-size: 9px; - } - - .nav-brand-text { - font-size: var(--text-base); + font-size: 0.5625rem; } .mobile-nav-item { - padding: var(--space-2) var(--space-2); - font-size: 9px; + padding: 6px 10px; + font-size: 0.5625rem; } .mobile-nav-item svg { width: 20px; height: 20px; } -} - -/* ======================================== - 22. RTL SUPPORT - ======================================== */ - -[dir="rtl"] { - direction: rtl; -} - -/* Keep header LTR - brand stays on left */ -[dir="rtl"] .nav-bar { - direction: ltr; -} - -[dir="rtl"] .nav-links { - direction: rtl; -} - -/* Tables */ -[dir="rtl"] table { - text-align: right; -} - -[dir="rtl"] th, -[dir="rtl"] td { - text-align: right; -} - -[dir="rtl"] th:first-child, -[dir="rtl"] td:first-child { - text-align: right; -} - -[dir="rtl"] th:last-child, -[dir="rtl"] td:last-child { - text-align: left; -} - -/* Select dropdowns */ -[dir="rtl"] select { - text-align: right; - padding-right: var(--space-3); - padding-left: var(--space-8); - background-position: left var(--space-2) center; -} - -/* Page titles */ -[dir="rtl"] .page-title { - text-align: right; -} - -/* Stat cards */ -[dir="rtl"] .stat-card { - text-align: right; -} - -/* Chart cards */ -[dir="rtl"] .chart-card h2 { - text-align: right; -} - -/* Keep charts LTR (chart.js) */ -[dir="rtl"] canvas { - direction: ltr; -} - -/* Period display */ -[dir="rtl"] .period-display-banner { - flex-direction: row-reverse; -} - -/* Metric cards */ -[dir="rtl"] .metric-card { - text-align: right; -} - -[dir="rtl"] .metric-values { - flex-direction: row-reverse; -} - -/* Carousel */ -[dir="rtl"] .carousel-dots { - flex-direction: row-reverse; -} - -/* Toggle switches */ -[dir="rtl"] .toggle-switch { - flex-direction: row-reverse; -} - -/* Grids */ -[dir="rtl"] .stats-grid, -[dir="rtl"] .charts-grid, -[dir="rtl"] .comparison-grid { - direction: rtl; -} - -/* Mobile nav */ -[dir="rtl"] .mobile-nav { - flex-direction: row-reverse; -} - -/* Empty state */ -[dir="rtl"] .empty-state { - text-align: right; -} - -/* Slides */ -[dir="rtl"] .slides-toolbar { - flex-direction: row-reverse; -} - -/* ======================================== - 23. ANIMATIONS & MICRO-INTERACTIONS - ======================================== */ - -/* Skeleton loading */ -.skeleton { - background: linear-gradient(90deg, var(--border) 25%, var(--bg) 50%, var(--border) 75%); - background-size: 200% 100%; - animation: skeleton-loading 1.5s infinite; - border-radius: var(--radius-sm); -} - -@keyframes skeleton-loading { - 0% { background-position: 200% 0; } - 100% { background-position: -200% 0; } -} - -.skeleton-text { - height: 1em; - margin-bottom: 0.5em; -} - -.skeleton-text.lg { height: 2em; width: 60%; } -.skeleton-text.sm { height: 0.75em; width: 40%; } - -/* Smooth page transitions */ -.dashboard, -.comparison { - animation: fadeIn 200ms ease; -} - -@keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } -} - -/* Card hover lift */ -@media (hover: hover) { - .stat-card:hover, - .chart-card:hover, - .metric-card:hover { - transform: translateY(-2px); - } -} - -/* Touch feedback */ -@media (hover: none) { - .stat-card:active, - .carousel-dot:active, - .mobile-nav-item:active { - transform: scale(0.98); - transition: transform 50ms ease; - } -} - -/* Button press effect */ -button:active:not(:disabled) { - transform: scale(0.97); -} - -/* ======================================== - 24. PRINT STYLES - ======================================== */ - -@media print { - .nav-bar, - .mobile-nav, - .controls, - .carousel-dots, - .chart-export-btn, - .btn-export-all { - display: none !important; + + /* Smaller table text */ + table { + font-size: 0.6875rem; } - .dashboard, - .comparison { - padding: 0; - max-width: none; + table th { + font-size: 0.5625rem; + padding: 8px 4px; } - .chart-card, - .stat-card { - break-inside: avoid; - box-shadow: none; - border: 1px solid #ddd; + table td { + padding: 8px 4px; } } diff --git a/src/components/shared/Carousel.jsx b/src/components/shared/Carousel.jsx index 032ea59..4a9e963 100644 --- a/src/components/shared/Carousel.jsx +++ b/src/components/shared/Carousel.jsx @@ -15,10 +15,8 @@ function Carousel({ const [dragOffset, setDragOffset] = useState(0); const itemCount = React.Children.count(children); - // Threshold for swipe detection const SWIPE_THRESHOLD = 50; - const VELOCITY_THRESHOLD = 0.3; - + const handleTouchStart = useCallback((e) => { touchStartX.current = e.touches[0].clientX; touchStartY.current = e.touches[0].clientY; @@ -32,15 +30,14 @@ function Carousel({ const currentX = e.touches[0].clientX; const currentY = e.touches[0].clientY; const diffX = currentX - touchStartX.current; - const diffY = currentY - touchStartY.current; + const diffY = Math.abs(currentY - touchStartY.current); - // Only handle horizontal swipes - if (Math.abs(diffX) > Math.abs(diffY)) { - e.preventDefault(); - // Add resistance at edges + // Only handle horizontal swipes (prevent vertical scroll interference) + if (Math.abs(diffX) > diffY) { + // Add resistance at edges (rubber band effect) let offset = diffX; if ((activeIndex === 0 && diffX > 0) || (activeIndex === itemCount - 1 && diffX < 0)) { - offset = diffX * 0.3; // Rubber band effect + offset = diffX * 0.25; } setDragOffset(offset); } @@ -51,10 +48,9 @@ function Carousel({ const endX = e.changedTouches[0].clientX; const diff = touchStartX.current - endX; - const velocity = Math.abs(diff) / 200; // Rough velocity calc - // Determine if we should change slide - if (Math.abs(diff) > SWIPE_THRESHOLD || velocity > VELOCITY_THRESHOLD) { + // Change slide if swipe exceeds threshold + if (Math.abs(diff) > SWIPE_THRESHOLD) { if (diff > 0 && activeIndex < itemCount - 1) { setActiveIndex(activeIndex + 1); } else if (diff < 0 && activeIndex > 0) { @@ -77,9 +73,11 @@ function Carousel({ } }, [activeIndex, setActiveIndex, itemCount]); - // Calculate transform + // Calculate transform with drag offset const baseTransform = -(activeIndex * 100); - const dragPercentage = trackRef.current ? (dragOffset / trackRef.current.offsetWidth) * 100 : 0; + const dragPercentage = trackRef.current + ? (dragOffset / trackRef.current.offsetWidth) * 100 + : 0; const transform = baseTransform + dragPercentage; return ( @@ -97,7 +95,7 @@ function Carousel({ className="carousel-track" style={{ transform: `translateX(${transform}%)`, - transition: isDragging ? 'none' : 'transform 400ms cubic-bezier(0.25, 0.46, 0.45, 0.94)' + transition: isDragging ? 'none' : undefined }} onTouchStart={handleTouchStart} onTouchMove={handleTouchMove} @@ -109,7 +107,6 @@ function Carousel({ key={i} role="tabpanel" aria-hidden={activeIndex !== i} - aria-label={labels[i] || `Slide ${i + 1}`} > {child} @@ -127,7 +124,6 @@ function Carousel({ role="tab" aria-selected={activeIndex === i} aria-label={labels[i] || `Slide ${i + 1}`} - aria-controls={`slide-${i}`} > {showLabels && labels[i] && ( {labels[i]} diff --git a/src/components/shared/EmptyState.jsx b/src/components/shared/EmptyState.jsx index bc06edd..20dab2e 100644 --- a/src/components/shared/EmptyState.jsx +++ b/src/components/shared/EmptyState.jsx @@ -2,29 +2,18 @@ import React from 'react'; function EmptyState({ icon = '📊', - title, - message, - action = null, - actionLabel = 'Try Again', - className = '' + title = 'No data found', + message = 'Try adjusting your filters', + action, + actionLabel = 'Reset Filters' }) { return ( -
- - {title && ( -

{title}

- )} - {message && ( -

{message}

- )} +
+
{icon}
+

{title}

+

{message}

{action && ( - )} diff --git a/src/components/shared/FilterControls.jsx b/src/components/shared/FilterControls.jsx index 481b811..e2fbbd1 100644 --- a/src/components/shared/FilterControls.jsx +++ b/src/components/shared/FilterControls.jsx @@ -1,86 +1,33 @@ -import React, { useState, useEffect } from 'react'; -import { useLanguage } from '../../contexts/LanguageContext'; +import React, { useState } from 'react'; function FilterControls({ children, - title, + title = 'Filters', defaultExpanded = true, onReset = null, className = '' }) { - const { t } = useLanguage(); - const displayTitle = title || t('filters.title'); - - // Start collapsed on mobile - const [expanded, setExpanded] = useState(() => { - if (typeof window !== 'undefined') { - return window.innerWidth > 768 ? defaultExpanded : false; - } - return defaultExpanded; - }); - - // Handle resize - useEffect(() => { - const handleResize = () => { - // Auto-expand on desktop, keep user preference on mobile - if (window.innerWidth > 768) { - setExpanded(true); - } - }; - - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); - }, []); - - const toggleExpanded = () => { - setExpanded(!expanded); - }; + const [expanded, setExpanded] = useState(defaultExpanded); return (
-
{ - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - toggleExpanded(); - } - }} - > -

{displayTitle}

+
setExpanded(!expanded)}> +

{title}

{onReset && expanded && ( )} -
- -
+
{children}
@@ -90,7 +37,7 @@ function FilterControls({ function FilterGroup({ label, children }) { return (
- {label && } + {children}
); diff --git a/src/components/shared/StatCard.jsx b/src/components/shared/StatCard.jsx index 80d2e6a..a8ea393 100644 --- a/src/components/shared/StatCard.jsx +++ b/src/components/shared/StatCard.jsx @@ -1,20 +1,15 @@ import React from 'react'; -function StatCard({ title, value, change = null, changeLabel = 'YoY', subtitle = null }) { +function StatCard({ title, value, change = null, changeLabel = 'YoY' }) { const isPositive = change !== null && change >= 0; return (

{title}

{value}
- {subtitle && ( -
{subtitle}
- )} {change !== null && (
- {isPositive ? '↑' : '↓'} - {Math.abs(change).toFixed(1)}% - {changeLabel} + {isPositive ? '↑' : '↓'} {Math.abs(change).toFixed(1)}% {changeLabel}
)}