@import "tailwindcss"; @plugin "@tailwindcss/typography"; @custom-variant dark (&:where(.dark, .dark *)); /* ───────────────────────────────────────────────────────────────────────── * Spacing scale: locked. * Card padding: p-6. Section gap: space-y-8. Form/control gap: gap-4. * Allowed Tailwind values: 1, 2, 3, 4, 6, 8, 10, 12. No 2.5 / 5. * Sidebar width is md:w-64 (256px); main offset matches md:pl-64. * ───────────────────────────────────────────────────────────────────────── */ :root { color-scheme: light; /* Sidebar column width (frame layout). Default for shells that don't override it; the dashboard shell (#dash-shell) sets it inline from user_preferences.ui_state (248px expanded / 64px collapsed rail) and the nav toggle updates it client-side. aside and
both read the variable so they stay in lockstep. */ --nav-w: 248px; /* Width the docked assistant panel claims from the page panel. Default is the plain frame gutter, so the layout is correct before any JS runs; the agent provider raises it while the panel is docked and clears it again. */ --agent-dock-w: 10px; /* Grayscale Chrome Palette: paper white + warm beige */ --background: 0 0% 100%; --foreground: 0 0% 9%; /* Warm-toned frame behind the floating page panel (frame layout) */ --frame: 40 18% 96%; --card: 0 0% 100%; --card-foreground: 0 0% 9%; --popover: 0 0% 100%; --popover-foreground: 0 0% 9%; --primary: 0 0% 9%; --primary-foreground: 0 0% 100%; --secondary: 40 11% 89%; /* Warm beige paper chip */ --secondary-foreground: 0 0% 9%; --muted: 40 8% 93%; --muted-foreground: 0 0% 40%; --accent: 40 8% 93%; --accent-foreground: 0 0% 9%; --destructive: 6 36% 48%; /* Muted terracotta */ --destructive-foreground: 0 0% 100%; --border: 45 5% 85%; --input: 45 5% 85%; --ring: 0 0% 9%; /* Radius */ --radius: 0.5rem; /* Success: Sage green */ --success: 155 25% 40%; --success-foreground: 0 0% 100%; /* Warning: Muted ochre */ --warning: 38 55% 50%; --warning-foreground: 0 0% 9%; /* Attn: darker ochre for the one-sentence attention line (.attn / AttnLine), derived from --warning but AA-safe as small text on the page background. */ --attn: 38 50% 34%; --warm-accent: 38 45% 52%; /* Chart colors */ --chart-1: 155 25% 40%; --chart-2: 6 36% 48%; --chart-3: 38 55% 50%; --chart-4: 0 0% 50%; /* Custom tokens */ --font-display: var(--font-hedvig-serif), Georgia, serif; --font-body: var(--font-geist-sans), system-ui, sans-serif; /* Shadows: barely-there; chrome relies on hairline borders */ --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.03), 0 1px 1px rgba(15, 23, 42, 0.02); --shadow-md: 0 4px 12px rgba(15, 23, 42, 0.03); --shadow-lg: 0 12px 40px rgba(15, 23, 42, 0.05); /* Animation */ --ease-out: cubic-bezier(0.25, 0.46, 0.45, 0.94); --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); --duration-fast: 150ms; --duration-base: 300ms; --duration-slow: 500ms; } .dark { color-scheme: dark; --background: 0 0% 7%; --foreground: 0 0% 87%; --frame: 0 0% 5%; --card: 0 0% 11%; --card-foreground: 0 0% 87%; --popover: 0 0% 11%; --popover-foreground: 0 0% 87%; --primary: 0 0% 87%; --primary-foreground: 0 0% 7%; --secondary: 0 0% 14%; --secondary-foreground: 0 0% 87%; --muted: 0 0% 16%; --muted-foreground: 0 0% 50%; --accent: 0 0% 16%; --accent-foreground: 0 0% 87%; --destructive: 6 32% 56%; --destructive-foreground: 0 0% 100%; --border: 45 4% 18%; --input: 45 4% 18%; --ring: 0 0% 87%; --success: 155 22% 48%; --success-foreground: 0 0% 100%; --warning: 38 50% 55%; --warning-foreground: 38 50% 90%; --attn: 38 45% 62%; --warm-accent: 38 42% 58%; --chart-1: 155 22% 48%; --chart-2: 6 32% 56%; --chart-3: 38 50% 55%; --chart-4: 0 0% 55%; --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.25); --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.35); --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.45); } @theme inline { --color-background: hsl(var(--background)); --color-foreground: hsl(var(--foreground)); --color-frame: hsl(var(--frame)); --color-card: hsl(var(--card)); --color-card-foreground: hsl(var(--card-foreground)); --color-popover: hsl(var(--popover)); --color-popover-foreground: hsl(var(--popover-foreground)); --color-primary: hsl(var(--primary)); --color-primary-foreground: hsl(var(--primary-foreground)); --color-secondary: hsl(var(--secondary)); --color-secondary-foreground: hsl(var(--secondary-foreground)); --color-muted: hsl(var(--muted)); --color-muted-foreground: hsl(var(--muted-foreground)); --color-accent: hsl(var(--accent)); --color-accent-foreground: hsl(var(--accent-foreground)); --color-destructive: hsl(var(--destructive)); --color-destructive-foreground: hsl(var(--destructive-foreground)); --color-border: hsl(var(--border)); --color-input: hsl(var(--input)); --color-ring: hsl(var(--ring)); --color-success: hsl(var(--success)); --color-success-foreground: hsl(var(--success-foreground)); --color-warning: hsl(var(--warning)); --color-warning-foreground: hsl(var(--warning-foreground)); --color-attn: hsl(var(--attn)); --color-warm-accent: hsl(var(--warm-accent)); --radius-lg: var(--radius); --radius-md: calc(var(--radius) - 2px); --radius-sm: calc(var(--radius) - 4px); --font-sans: var(--font-body); --font-mono: var(--font-geist-mono), monospace; --font-serif: var(--font-display); } * { border-color: hsl(var(--border)); } body { background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body); font-feature-settings: "cv02", "cv03", "cv04", "cv11"; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* Display typography - Fraunces */ .font-display { font-family: var(--font-display); font-optical-sizing: auto; } /* Layered so utility classes (e.g. font-sans on a pane title) can override: unlayered element rules would beat Tailwind's layered utilities. */ @layer base { h1, h2, h3 { font-family: var(--font-display); font-weight: 500; letter-spacing: -0.015em; font-optical-sizing: auto; } } /* Number styling - tabular for alignment */ .tabular-nums { font-variant-numeric: tabular-nums; } /* Text balance for better line wrapping on descriptions */ .text-balance { text-wrap: balance; } /* Safe area padding for mobile devices with notch/home indicator */ .safe-area-bottom { padding-bottom: max(0.5rem, env(safe-area-inset-bottom)); } /* Main content padding: clears bottom nav + safe area on mobile */ .safe-area-main-padding { padding-bottom: calc(5rem + env(safe-area-inset-bottom, 0px)); } /* Animation Keyframes */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideUp { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideInLeft { from { opacity: 0; transform: translateX(-8px); } to { opacity: 1; transform: translateX(0); } } @keyframes scaleIn { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } } @keyframes gentlePulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } } /* Three-dot "typing" indicator used by the agent chat pre-token bubble. Each dot rides the same wave but with staggered animation-delay so the bounce reads left-to-right. */ @keyframes typingDot { 0%, 60%, 100% { transform: translateY(0); opacity: 0.35; } 30% { transform: translateY(-3px); opacity: 1; } } .animate-typing-dot { animation: typingDot 1.1s ease-in-out infinite; } /* Marching-ants dashed border used by the import entity selector. */ @keyframes marchingAnts { to { stroke-dashoffset: -14; } } .animate-marching-ants { animation: marchingAnts 1.2s linear infinite; } @media (prefers-reduced-motion: reduce) { .animate-typing-dot { animation: gentlePulse 1.6s ease-in-out infinite; transform: none; } .animate-marching-ants { animation: none; } } /* ───────────────────────────────────────────────────────────────────────── * Enter/exit animation utilities (animate-in / animate-out vocabulary). * The codebase uses the shadcn/tailwindcss-animate classes but no animate * plugin was ever installed, so Tailwind v4 silently dropped them all: * every popover, dialog and slide-over appeared instantly. These implement * the exact subset in use, plugin-free. The duration and ease utilities * compose via Tailwind v4's --tw-duration and --tw-ease variables; the * global prefers-reduced-motion block collapses them like everything else. * ───────────────────────────────────────────────────────────────────────── */ @keyframes accEnter { from { opacity: var(--tw-enter-opacity, 1); transform: translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), 1); } } @keyframes accExit { to { opacity: var(--tw-exit-opacity, 1); transform: translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), 1); } } @utility animate-in { animation-name: accEnter; animation-duration: var(--tw-duration, 150ms); animation-timing-function: var(--tw-ease, cubic-bezier(0.32, 0.72, 0, 1)); animation-fill-mode: both; } @utility animate-out { animation-name: accExit; animation-duration: var(--tw-duration, 150ms); animation-timing-function: var(--tw-ease, cubic-bezier(0.32, 0.72, 0, 1)); animation-fill-mode: both; } @utility fade-in { --tw-enter-opacity: 0; } @utility fade-in-0 { --tw-enter-opacity: 0; } @utility fade-out { --tw-exit-opacity: 0; } @utility fade-out-0 { --tw-exit-opacity: 0; } @utility zoom-in-95 { --tw-enter-scale: 0.95; } @utility zoom-out-95 { --tw-exit-scale: 0.95; } @utility zoom-in-98 { --tw-enter-scale: 0.98; } @utility zoom-out-98 { --tw-exit-scale: 0.98; } @utility slide-in-from-top { --tw-enter-translate-y: -100%; } @utility slide-in-from-top-1 { --tw-enter-translate-y: -0.25rem; } @utility slide-in-from-top-2 { --tw-enter-translate-y: -0.5rem; } @utility slide-in-from-top-full { --tw-enter-translate-y: -100%; } @utility slide-in-from-bottom { --tw-enter-translate-y: 100%; } @utility slide-in-from-bottom-1 { --tw-enter-translate-y: 0.25rem; } @utility slide-in-from-bottom-2 { --tw-enter-translate-y: 0.5rem; } @utility slide-in-from-left { --tw-enter-translate-x: -100%; } @utility slide-in-from-left-1 { --tw-enter-translate-x: -0.25rem; } @utility slide-in-from-left-2 { --tw-enter-translate-x: -0.5rem; } @utility slide-in-from-right { --tw-enter-translate-x: 100%; } @utility slide-in-from-right-2 { --tw-enter-translate-x: 0.5rem; } @utility slide-in-from-right-full { --tw-enter-translate-x: 100%; } @utility slide-out-to-top { --tw-exit-translate-y: -100%; } @utility slide-out-to-bottom { --tw-exit-translate-y: 100%; } @utility slide-out-to-left { --tw-exit-translate-x: -100%; } @utility slide-out-to-right { --tw-exit-translate-x: 100%; } @utility slide-out-to-right-full { --tw-exit-translate-x: 100%; } /* Staggered entrance animation */ .stagger-enter > * { animation: slideUp var(--duration-slow) var(--ease-out) both; } .stagger-enter > *:nth-child(1) { animation-delay: 0ms; } .stagger-enter > *:nth-child(2) { animation-delay: 80ms; } .stagger-enter > *:nth-child(3) { animation-delay: 160ms; } .stagger-enter > *:nth-child(4) { animation-delay: 240ms; } .stagger-enter > *:nth-child(5) { animation-delay: 320ms; } .stagger-enter > *:nth-child(6) { animation-delay: 400ms; } .stagger-enter > *:nth-child(7) { animation-delay: 480ms; } .stagger-enter > *:nth-child(8) { animation-delay: 560ms; } .stagger-enter > *:nth-child(9) { animation-delay: 640ms; } .stagger-enter > *:nth-child(10) { animation-delay: 720ms; } /* Utility animations */ .animate-fade-in { animation: fadeIn var(--duration-base) var(--ease-out); } .animate-slide-up { animation: slideUp var(--duration-slow) var(--ease-out); } .animate-scale-in { animation: scaleIn var(--duration-base) var(--ease-out); } /* Soft focus states */ .focus-ring:focus-visible { outline: none; box-shadow: 0 0 0 2px hsl(var(--background)), 0 0 0 4px hsl(var(--primary) / 0.35); } /* Overlay-style scrollbars: invisible at rest, revealed only while the container is actually scrolling. ScrollbarReveal (mounted in the root layout) toggles .is-scrolling on the scrolled element; the thumb fades back out when scrolling stops. The gutter is reserved either way, so revealing never shifts layout. */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: transparent; border-radius: 3px; } .is-scrolling::-webkit-scrollbar-thumb { background: hsl(var(--muted-foreground) / 0.25); } @supports (scrollbar-color: auto) { /* Layered: the universal transition must not beat Tailwind's layered transition-* utilities (unlayered rules win over layers regardless of specificity: this silently killed every width/margin/color transition in the app). scrollbar-color still animates via the base default. */ @layer base { * { scrollbar-width: thin; scrollbar-color: transparent transparent; transition: scrollbar-color 300ms ease-out; } } .is-scrolling { scrollbar-color: hsl(var(--muted-foreground) / 0.3) transparent; } } /* Hover-reveal fallback for touch: coarse pointers have no hover, so the controls that desktop reveals on hover (row checkboxes, chevrons, quiet row actions) stay visible. Targets exactly the opacity-0 + group-hover:opacity-100 pairing and nothing else. */ @media (pointer: coarse) { .group .opacity-0.group-hover\:opacity-100 { opacity: 1; } } /* Always-visible thumb for modal scroll containers (DialogContent, SlideOverBody): inside an overlay the scrollbar is the only cue that content continues below the fold, so the at-rest auto-hide would read as "the dialog is cut off". */ .scrollbar-visible::-webkit-scrollbar-thumb { background: hsl(var(--muted-foreground) / 0.25); } @supports (scrollbar-color: auto) { .scrollbar-visible { scrollbar-color: hsl(var(--muted-foreground) / 0.3) transparent; } } /* Hide scrollbar utility */ .scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; } .scrollbar-hide::-webkit-scrollbar { display: none; } /* Hide number input spinners */ input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } input[type="number"] { -moz-appearance: textfield; } /* Selection color */ ::selection { background: hsl(var(--primary) / 0.15); color: hsl(var(--foreground)); } .dark ::selection { background: hsl(var(--primary) / 0.3); color: hsl(var(--foreground)); } /* Respect reduced motion preference */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } .stagger-enter > * { animation: none; opacity: 1; } }