feat: add theme system from jarvis frontend

This commit is contained in:
Jason Woltje
2026-01-29 21:45:18 -06:00
parent 532f5a39a0
commit af8f5df111
6 changed files with 1929 additions and 18 deletions

View File

@@ -2,19 +2,747 @@
@tailwind components;
@tailwind utilities;
/* =============================================================================
DESIGN C: PROFESSIONAL/ENTERPRISE DESIGN SYSTEM
Philosophy: "Good design is as little design as possible." - Dieter Rams
============================================================================= */
/* -----------------------------------------------------------------------------
CSS Custom Properties - Light Theme (Default)
----------------------------------------------------------------------------- */
:root {
--foreground-rgb: 0, 0, 0;
--background-rgb: 255, 255, 255;
/* Base colors - increased contrast from surfaces */
--color-background: 245 247 250;
--color-foreground: 15 23 42;
/* Surface hierarchy (elevation levels) - improved contrast */
--surface-0: 255 255 255;
--surface-1: 250 251 252;
--surface-2: 241 245 249;
--surface-3: 226 232 240;
/* Text hierarchy */
--text-primary: 15 23 42;
--text-secondary: 51 65 85;
--text-tertiary: 71 85 105;
--text-muted: 100 116 139;
/* Border colors - stronger borders for light mode */
--border-default: 203 213 225;
--border-subtle: 226 232 240;
--border-strong: 148 163 184;
/* Brand accent - Indigo (professional, trustworthy) */
--accent-primary: 79 70 229;
--accent-primary-hover: 67 56 202;
--accent-primary-light: 238 242 255;
--accent-primary-muted: 199 210 254;
/* Semantic colors - Success (Emerald) */
--semantic-success: 16 185 129;
--semantic-success-light: 209 250 229;
--semantic-success-dark: 6 95 70;
/* Semantic colors - Warning (Amber) */
--semantic-warning: 245 158 11;
--semantic-warning-light: 254 243 199;
--semantic-warning-dark: 146 64 14;
/* Semantic colors - Error (Rose) */
--semantic-error: 244 63 94;
--semantic-error-light: 255 228 230;
--semantic-error-dark: 159 18 57;
/* Semantic colors - Info (Sky) */
--semantic-info: 14 165 233;
--semantic-info-light: 224 242 254;
--semantic-info-dark: 3 105 161;
/* Focus ring */
--focus-ring: 99 102 241;
--focus-ring-offset: 255 255 255;
/* Shadows - visible but subtle */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05), 0 1px 3px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.08), 0 2px 4px -2px rgb(0 0 0 / 0.06);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.08);
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-rgb: 0, 0, 0;
}
/* -----------------------------------------------------------------------------
CSS Custom Properties - Dark Theme
----------------------------------------------------------------------------- */
.dark {
--color-background: 3 7 18;
--color-foreground: 248 250 252;
/* Surface hierarchy (elevation levels) */
--surface-0: 15 23 42;
--surface-1: 30 41 59;
--surface-2: 51 65 85;
--surface-3: 71 85 105;
/* Text hierarchy */
--text-primary: 248 250 252;
--text-secondary: 203 213 225;
--text-tertiary: 148 163 184;
--text-muted: 100 116 139;
/* Border colors */
--border-default: 51 65 85;
--border-subtle: 30 41 59;
--border-strong: 71 85 105;
/* Brand accent adjustments for dark mode */
--accent-primary: 129 140 248;
--accent-primary-hover: 165 180 252;
--accent-primary-light: 30 27 75;
--accent-primary-muted: 55 48 163;
/* Semantic colors adjustments */
--semantic-success: 52 211 153;
--semantic-success-light: 6 78 59;
--semantic-success-dark: 167 243 208;
--semantic-warning: 251 191 36;
--semantic-warning-light: 120 53 15;
--semantic-warning-dark: 253 230 138;
--semantic-error: 251 113 133;
--semantic-error-light: 136 19 55;
--semantic-error-dark: 253 164 175;
--semantic-info: 56 189 248;
--semantic-info-light: 12 74 110;
--semantic-info-dark: 186 230 253;
/* Focus ring */
--focus-ring: 129 140 248;
--focus-ring-offset: 15 23 42;
/* Shadows - subtle glow in dark mode */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.3);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5), 0 4px 6px -4px rgb(0 0 0 / 0.4);
}
/* -----------------------------------------------------------------------------
Base Styles
----------------------------------------------------------------------------- */
* {
box-sizing: border-box;
}
html {
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
color: rgb(var(--foreground-rgb));
background: rgb(var(--background-rgb));
color: rgb(var(--text-primary));
background: rgb(var(--color-background));
font-size: 14px;
line-height: 1.5;
transition: background-color 0.15s ease, color 0.15s ease;
}
/* -----------------------------------------------------------------------------
Typography Utilities
----------------------------------------------------------------------------- */
@layer utilities {
.text-display {
font-size: 1.875rem;
line-height: 2.25rem;
font-weight: 600;
letter-spacing: -0.025em;
}
.text-heading-1 {
font-size: 1.5rem;
line-height: 2rem;
font-weight: 600;
letter-spacing: -0.025em;
}
.text-heading-2 {
font-size: 1.125rem;
line-height: 1.5rem;
font-weight: 600;
letter-spacing: -0.01em;
}
.text-body {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-caption {
font-size: 0.75rem;
line-height: 1rem;
}
.text-mono {
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
font-size: 0.8125rem;
line-height: 1.25rem;
}
/* Text color utilities */
.text-primary {
color: rgb(var(--text-primary));
}
.text-secondary {
color: rgb(var(--text-secondary));
}
.text-tertiary {
color: rgb(var(--text-tertiary));
}
.text-muted {
color: rgb(var(--text-muted));
}
}
/* -----------------------------------------------------------------------------
Surface & Card Utilities
----------------------------------------------------------------------------- */
@layer utilities {
.surface-0 {
background-color: rgb(var(--surface-0));
}
.surface-1 {
background-color: rgb(var(--surface-1));
}
.surface-2 {
background-color: rgb(var(--surface-2));
}
.surface-3 {
background-color: rgb(var(--surface-3));
}
.border-default {
border-color: rgb(var(--border-default));
}
.border-subtle {
border-color: rgb(var(--border-subtle));
}
.border-strong {
border-color: rgb(var(--border-strong));
}
}
/* -----------------------------------------------------------------------------
Focus States - Accessible & Visible
----------------------------------------------------------------------------- */
@layer base {
:focus-visible {
outline: 2px solid rgb(var(--focus-ring));
outline-offset: 2px;
}
/* Remove default focus for mouse users */
:focus:not(:focus-visible) {
outline: none;
}
}
/* -----------------------------------------------------------------------------
Scrollbar Styling - Minimal & Professional
----------------------------------------------------------------------------- */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgb(var(--text-muted) / 0.4);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: rgb(var(--text-muted) / 0.6);
}
/* Firefox */
* {
scrollbar-width: thin;
scrollbar-color: rgb(var(--text-muted) / 0.4) transparent;
}
/* -----------------------------------------------------------------------------
Button Component Styles
----------------------------------------------------------------------------- */
@layer components {
.btn {
@apply inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-all duration-150;
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2;
@apply disabled:opacity-50 disabled:cursor-not-allowed;
}
.btn-primary {
@apply btn px-4 py-2;
background-color: rgb(var(--accent-primary));
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: rgb(var(--accent-primary-hover));
}
.btn-secondary {
@apply btn px-4 py-2;
background-color: rgb(var(--surface-2));
color: rgb(var(--text-primary));
border: 1px solid rgb(var(--border-default));
}
.btn-secondary:hover:not(:disabled) {
background-color: rgb(var(--surface-3));
}
.btn-ghost {
@apply btn px-3 py-2;
background-color: transparent;
color: rgb(var(--text-secondary));
}
.btn-ghost:hover:not(:disabled) {
background-color: rgb(var(--surface-2));
color: rgb(var(--text-primary));
}
.btn-danger {
@apply btn px-4 py-2;
background-color: rgb(var(--semantic-error));
color: white;
}
.btn-danger:hover:not(:disabled) {
filter: brightness(0.9);
}
.btn-sm {
@apply px-3 py-1.5 text-xs;
}
.btn-lg {
@apply px-6 py-3 text-base;
}
}
/* -----------------------------------------------------------------------------
Input Component Styles
----------------------------------------------------------------------------- */
@layer components {
.input {
@apply w-full rounded-md px-3 py-2 text-sm transition-all duration-150;
@apply focus:outline-none focus:ring-2 focus:ring-offset-0;
background-color: rgb(var(--surface-0));
border: 1px solid rgb(var(--border-default));
color: rgb(var(--text-primary));
}
.input::placeholder {
color: rgb(var(--text-muted));
}
.input:focus {
border-color: rgb(var(--accent-primary));
box-shadow: 0 0 0 3px rgb(var(--accent-primary) / 0.1);
}
.input:disabled {
@apply opacity-50 cursor-not-allowed;
background-color: rgb(var(--surface-1));
}
.input-error {
border-color: rgb(var(--semantic-error));
}
.input-error:focus {
border-color: rgb(var(--semantic-error));
box-shadow: 0 0 0 3px rgb(var(--semantic-error) / 0.1);
}
}
/* -----------------------------------------------------------------------------
Card Component Styles
----------------------------------------------------------------------------- */
@layer components {
.card {
@apply rounded-lg p-4;
background-color: rgb(var(--surface-0));
border: 1px solid rgb(var(--border-default));
box-shadow: var(--shadow-sm);
}
.card-elevated {
@apply card;
box-shadow: var(--shadow-md);
}
.card-interactive {
@apply card transition-all duration-150;
}
.card-interactive:hover {
border-color: rgb(var(--border-strong));
box-shadow: var(--shadow-md);
}
}
/* -----------------------------------------------------------------------------
Badge Component Styles
----------------------------------------------------------------------------- */
@layer components {
.badge {
@apply inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium;
}
.badge-success {
background-color: rgb(var(--semantic-success-light));
color: rgb(var(--semantic-success-dark));
}
.badge-warning {
background-color: rgb(var(--semantic-warning-light));
color: rgb(var(--semantic-warning-dark));
}
.badge-error {
background-color: rgb(var(--semantic-error-light));
color: rgb(var(--semantic-error-dark));
}
.badge-info {
background-color: rgb(var(--semantic-info-light));
color: rgb(var(--semantic-info-dark));
}
.badge-neutral {
background-color: rgb(var(--surface-2));
color: rgb(var(--text-secondary));
}
.badge-primary {
background-color: rgb(var(--accent-primary-light));
color: rgb(var(--accent-primary));
}
}
/* -----------------------------------------------------------------------------
Status Indicator Styles
----------------------------------------------------------------------------- */
@layer components {
.status-dot {
@apply inline-block h-2 w-2 rounded-full;
}
.status-dot-success {
background-color: rgb(var(--semantic-success));
}
.status-dot-warning {
background-color: rgb(var(--semantic-warning));
}
.status-dot-error {
background-color: rgb(var(--semantic-error));
}
.status-dot-info {
background-color: rgb(var(--semantic-info));
}
.status-dot-neutral {
background-color: rgb(var(--text-muted));
}
/* Pulsing indicator for live/active status */
.status-dot-pulse {
@apply relative;
}
.status-dot-pulse::before {
content: "";
@apply absolute inset-0 rounded-full animate-ping;
background-color: inherit;
opacity: 0.5;
}
}
/* -----------------------------------------------------------------------------
Keyboard Shortcut Styling
----------------------------------------------------------------------------- */
@layer components {
.kbd {
@apply inline-flex items-center justify-center rounded px-1.5 py-0.5 text-xs font-medium;
background-color: rgb(var(--surface-2));
border: 1px solid rgb(var(--border-default));
color: rgb(var(--text-tertiary));
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
min-width: 1.5rem;
box-shadow: 0 1px 0 rgb(var(--border-strong));
}
.kbd-group {
@apply inline-flex items-center gap-1;
}
}
/* -----------------------------------------------------------------------------
Table Styles - Dense & Professional
----------------------------------------------------------------------------- */
@layer components {
.table-pro {
@apply w-full text-sm;
}
.table-pro thead {
@apply sticky top-0;
background-color: rgb(var(--surface-1));
border-bottom: 1px solid rgb(var(--border-default));
}
.table-pro th {
@apply px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider;
color: rgb(var(--text-tertiary));
}
.table-pro th.sortable {
@apply cursor-pointer select-none;
}
.table-pro th.sortable:hover {
color: rgb(var(--text-primary));
}
.table-pro tbody tr {
border-bottom: 1px solid rgb(var(--border-subtle));
transition: background-color 0.1s ease;
}
.table-pro tbody tr:hover {
background-color: rgb(var(--surface-1));
}
.table-pro td {
@apply px-4 py-3;
}
.table-pro-dense td {
@apply py-2;
}
}
/* -----------------------------------------------------------------------------
Skeleton Loading Styles
----------------------------------------------------------------------------- */
@layer components {
.skeleton {
@apply animate-pulse rounded;
background: linear-gradient(
90deg,
rgb(var(--surface-2)) 0%,
rgb(var(--surface-1)) 50%,
rgb(var(--surface-2)) 100%
);
background-size: 200% 100%;
}
.skeleton-text {
@apply skeleton h-4 w-full;
}
.skeleton-text-sm {
@apply skeleton h-3 w-3/4;
}
.skeleton-avatar {
@apply skeleton h-10 w-10 rounded-full;
}
.skeleton-card {
@apply skeleton h-32 w-full;
}
}
/* -----------------------------------------------------------------------------
Modal & Dialog Styles
----------------------------------------------------------------------------- */
@layer components {
.modal-backdrop {
@apply fixed inset-0 z-50 flex items-center justify-center p-4;
background-color: rgb(0 0 0 / 0.5);
backdrop-filter: blur(2px);
}
.modal-content {
@apply relative max-h-[90vh] w-full max-w-lg overflow-y-auto rounded-lg;
background-color: rgb(var(--surface-0));
border: 1px solid rgb(var(--border-default));
box-shadow: var(--shadow-lg);
}
.modal-header {
@apply flex items-center justify-between p-4 border-b;
border-color: rgb(var(--border-default));
}
.modal-body {
@apply p-4;
}
.modal-footer {
@apply flex items-center justify-end gap-3 p-4 border-t;
border-color: rgb(var(--border-default));
}
}
/* -----------------------------------------------------------------------------
Tooltip Styles
----------------------------------------------------------------------------- */
@layer components {
.tooltip {
@apply absolute z-50 rounded px-2 py-1 text-xs font-medium;
background-color: rgb(var(--text-primary));
color: rgb(var(--color-background));
box-shadow: var(--shadow-md);
}
.tooltip::before {
content: "";
@apply absolute;
border: 4px solid transparent;
}
.tooltip-top::before {
@apply left-1/2 top-full -translate-x-1/2;
border-top-color: rgb(var(--text-primary));
}
}
/* -----------------------------------------------------------------------------
Animations - Functional Only
----------------------------------------------------------------------------- */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.98);
}
to {
opacity: 1;
transform: scale(1);
}
}
.animate-fade-in {
animation: fadeIn 0.15s ease-out;
}
.animate-slide-in {
animation: slideIn 0.15s ease-out;
}
.animate-scale-in {
animation: scaleIn 0.15s ease-out;
}
/* Message animation - subtle for chat */
.message-animate {
animation: slideIn 0.2s ease-out;
}
/* Menu dropdown animation */
.animate-menu-enter {
animation: scaleIn 0.1s ease-out;
}
/* -----------------------------------------------------------------------------
Responsive Typography Adjustments
----------------------------------------------------------------------------- */
@media (max-width: 640px) {
.text-display {
font-size: 1.5rem;
line-height: 2rem;
}
.text-heading-1 {
font-size: 1.25rem;
line-height: 1.75rem;
}
}
/* -----------------------------------------------------------------------------
High Contrast Mode Support
----------------------------------------------------------------------------- */
@media (prefers-contrast: high) {
:root {
--border-default: 100 116 139;
--border-strong: 71 85 105;
}
.dark {
--border-default: 148 163 184;
--border-strong: 203 213 225;
}
}
/* -----------------------------------------------------------------------------
Reduced Motion Support
----------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* -----------------------------------------------------------------------------
Print Styles
----------------------------------------------------------------------------- */
@media print {
body {
background: white;
color: black;
}
.no-print {
display: none !important;
}
}

View File

@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import type { ReactNode } from "react";
import { AuthProvider } from "@/lib/auth/auth-context";
import { ErrorBoundary } from "@/components/error-boundary";
import { ThemeProvider } from "@/providers/ThemeProvider";
import "./globals.css";
export const metadata: Metadata = {
@@ -13,9 +14,11 @@ export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<ErrorBoundary>
<AuthProvider>{children}</AuthProvider>
</ErrorBoundary>
<ThemeProvider>
<ErrorBoundary>
<AuthProvider>{children}</AuthProvider>
</ErrorBoundary>
</ThemeProvider>
</body>
</html>
);