@tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { /* shadcn/ui variable format: H S% L% */ --background: 0 0% 100%; /* #ffffff */ --foreground: 224 39% 19%; /* #1e2844 */ --card: 0 0% 100%; --card-foreground: 224 39% 19%; --popover: 0 0% 100%; --popover-foreground: 224 39% 19%; --primary: 213 55% 56%; /* #3a7bc8 */ --primary-foreground: 0 0% 100%; --secondary: 224 39% 19%; /* #1e2844 */ --secondary-foreground: 0 0% 100%; --muted: 210 11% 96%; /* #f1f3f5 */ --muted-foreground: 228 10% 49%; /* #71768a */ --accent: 213 60% 95%; /* #eef3fb — soft brand-blue tint for hover/focus */ --accent-foreground: 224 39% 19%; /* dark navy text for contrast on light bg */ --destructive: 0 65% 51%; /* #d32f2f */ --destructive-foreground: 0 0% 100%; --border: 227 10% 82%; /* #cdcfd6 */ --input: 227 10% 82%; --ring: 213 55% 56%; /* #3a7bc8 focus ring */ --radius: 0.375rem; /* Sidebar (using dark navy) */ --sidebar-background: 224 39% 19%; --sidebar-foreground: 227 10% 82%; --sidebar-primary: 213 55% 56%; --sidebar-primary-foreground: 0 0% 100%; --sidebar-accent: 224 39% 15%; --sidebar-accent-foreground: 227 10% 82%; --sidebar-border: 226 18% 34%; --sidebar-ring: 213 55% 56%; /* Chart colors for Recharts */ --chart-1: 213 55% 56%; /* Brand blue */ --chart-2: 224 39% 19%; /* Dark navy */ --chart-3: 190 18% 60%; /* Teal */ --chart-4: 254 29% 50%; /* Purple */ --chart-5: 130 30% 76%; /* Mint */ --chart-6: 75 30% 82%; /* Sage */ } .dark { --background: 224 40% 12%; --foreground: 227 10% 91%; --card: 224 39% 19%; --card-foreground: 227 10% 91%; --popover: 224 39% 19%; --popover-foreground: 227 10% 91%; --primary: 213 52% 62%; --primary-foreground: 0 0% 100%; --secondary: 224 39% 22%; --secondary-foreground: 227 10% 82%; --muted: 224 39% 18%; --muted-foreground: 228 10% 49%; --accent: 224 39% 24%; /* subtle elevation above card for hover/focus */ --accent-foreground: 227 10% 91%; /* light text on dark accent */ --destructive: 0 72% 63%; --destructive-foreground: 0 0% 100%; --border: 224 35% 28%; --input: 224 35% 28%; --ring: 213 52% 62%; /* Sidebar stays dark navy in both modes */ --sidebar-background: 224 40% 10%; --sidebar-foreground: 227 10% 82%; --sidebar-primary: 213 52% 62%; --sidebar-primary-foreground: 0 0% 100%; --sidebar-accent: 224 40% 14%; --sidebar-accent-foreground: 227 10% 82%; --sidebar-border: 226 18% 28%; --sidebar-ring: 213 52% 62%; /* Chart colors (brightened for dark mode) */ --chart-1: 213 52% 62%; --chart-2: 227 10% 82%; --chart-3: 190 18% 55%; --chart-4: 254 29% 55%; --chart-5: 130 30% 70%; --chart-6: 75 30% 78%; } * { @apply border-border; } body { @apply bg-background text-foreground font-sans antialiased; /* Suppress iOS Safari's default black tap-highlight overlay so our * explicit `active:bg-accent` styles are the only press effect. * Without this, every tap on a mobile button/link flashes a muddy * dark rectangle on top of whatever active style we set. */ -webkit-tap-highlight-color: transparent; } /* Wave watermark - subtle background texture for auth pages */ .wave-watermark { background-image: repeating-linear-gradient( 135deg, transparent, transparent 10px, rgba(58, 123, 200, 0.03) 10px, rgba(58, 123, 200, 0.03) 20px ); } /* * No global focus ring. shadcn components opt in individually * (Button uses `focus-visible:ring-1`, DropdownMenuItem uses * `focus:bg-accent`, etc.) — that gives us a quiet, per-component * indicator without the chunky `ring-2 + ring-offset-2` artifact * the global rule was creating on every rounded element. * * Components that need a custom focus indicator (e.g. the global * search bar's wrapper-border swap) provide their own. Bare * focusable elements without explicit styles fall back to the * browser's native focus indicator, which keeps keyboard navigation * accessible without painting blue rings everywhere. */ /* Scrollbar styling */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { @apply bg-transparent; } ::-webkit-scrollbar-thumb { @apply bg-border rounded-full; } ::-webkit-scrollbar-thumb:hover { @apply bg-muted-foreground/30; } } /* ─── Form-factor shell visibility ────────────────────────────────────────── * Two shells (desktop + mobile) render to the DOM on every page; CSS hides * the inactive one. The data-form-factor body attribute is set server-side * from User-Agent (see src/lib/form-factor.ts). The media-query fallback * handles desktop browsers resized below lg (1024px), or stripped UAs. * * IMPORTANT: only `display: none` rules are emitted - we never set a positive * display, because the desktop shell uses Tailwind's `flex` class which would * be overridden by `display: block` (same specificity, later cascade). */ [data-shell='mobile'] { display: none; } @media (max-width: 1023.98px) { [data-shell='desktop'] { display: none; } [data-shell='mobile'] { display: block; } } body[data-form-factor='mobile'] [data-shell='desktop'] { display: none; } body[data-form-factor='mobile'] [data-shell='mobile'] { display: block; } /* * React Query Devtools floating button collides with the bottom tab bar's * "More" tab on mobile. The devtools panel itself remains accessible from * desktop where the toggle is positioned out of the way of any UI. */ @media (max-width: 1023.98px) { .tsqd-open-btn-container, .tsqd-parent-container { display: none !important; } } /* * Recharts focus-ring suppression. * * Recharts SVG surfaces become keyboard-focusable when a user clicks into * them (the library adds tabindex on chart sectors / paths). The global * `*:focus-visible` rule above paints a 4px brand-blue box-shadow ring, * which on a chart surface reads as a stray rectangle around the plot * area. Hover/tooltip already handles chart interactivity, so suppress * the ring entirely here. * * Lives OUTSIDE `@layer base` so Tailwind's PostCSS pipeline can't drop * it during purge (an earlier copy inside `@layer base` was being * silently removed at build time, leaving the ring intact). */ div.recharts-wrapper:focus, div.recharts-wrapper:focus-visible, svg.recharts-surface:focus, svg.recharts-surface:focus-visible, div.recharts-responsive-container:focus, div.recharts-responsive-container:focus-visible, .recharts-wrapper *:focus, .recharts-wrapper *:focus-visible { outline: none !important; box-shadow: none !important; --tw-ring-shadow: 0 0 #0000 !important; --tw-ring-offset-shadow: 0 0 #0000 !important; --tw-ring-color: transparent !important; --tw-ring-offset-color: transparent !important; } /* * Vaul drawer (bottom-direction) animation timing override. * * Vaul's defaults feel slightly snappy when the drawer is full-screen * (mobile search overlay) — the snap-on / snap-off reads as janky at * scale. We slow it down and use a softer easing curve (ease-out-quint) * which decelerates smoothly without the elastic kick. * * Scoped to mobile drawers via the data-vaul-drawer-direction attr so * the smaller MoreSheet drawer keeps its punchier default. * * The overlay's opacity transition is matched to the same duration so * the backdrop and drawer stay in sync. */ [data-vaul-drawer][data-vaul-drawer-direction='bottom'] { transition: transform 480ms cubic-bezier(0.22, 1, 0.36, 1) !important; } [data-vaul-drawer][data-vaul-drawer-direction='bottom'][data-state='closed'] { transition: transform 380ms cubic-bezier(0.4, 0, 0.2, 1) !important; } [data-vaul-overlay] { transition: opacity 480ms cubic-bezier(0.22, 1, 0.36, 1) !important; } [data-vaul-overlay][data-state='closed'] { transition: opacity 380ms cubic-bezier(0.4, 0, 0.2, 1) !important; } /* * GPU compositing hints for Vaul drawers. * * `will-change: transform` tells the browser to promote the drawer to * its own composite layer ahead of the animation, so the swipe-drag * transforms run on the GPU instead of triggering re-paints. Without * this, Safari sometimes defers layer creation until the first frame * of the drag, producing the visible "jump" reps were seeing when * flicking the drawer down to close. * * `backface-visibility: hidden` keeps the layer flat and prevents * sub-pixel jitter during the spring-physics close animation. * * `contain: layout style paint` isolates the drawer's render tree * from the rest of the document — repaints inside the drawer (e.g. * focus-state changes on a button) don't invalidate the parent. */ [data-vaul-drawer] { will-change: transform; backface-visibility: hidden; contain: layout style paint; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } [data-vaul-overlay] { will-change: opacity; backface-visibility: hidden; }