@tailwind base; @tailwind components; @tailwind utilities; @font-face { font-family: 'Manrope Variable'; font-style: normal; font-display: swap; font-weight: 200 800; src: url('@fontsource-variable/manrope/files/manrope-latin-wght-normal.woff2') format('woff2-variations'); } /* * Surfaces and neutrals. * * Accent variables are NOT defined here — they are written onto the root * element at runtime from the palette in @pig/core, so there is exactly one * definition of each colour and the CSS cannot drift from the TypeScript. */ :root { --bg: 0 0% 100%; --surface: 0 0% 100%; --surface-2: 240 5% 97%; --border: 240 6% 90%; --fg: 240 10% 4%; --muted: 240 4% 46%; --positive: 160 84% 32%; --warning: 32 95% 44%; --danger: 0 72% 45%; --info: 201 90% 40%; --shadow: 240 10% 4%; /* Safe-area insets, so layout can reference them even at zero. */ --safe-top: env(safe-area-inset-top, 0px); --safe-bottom: env(safe-area-inset-bottom, 0px); --safe-left: env(safe-area-inset-left, 0px); --safe-right: env(safe-area-inset-right, 0px); /* * The application header's height, including the notch it sits under. Both * side panes are sticky beneath it and subtract this from the viewport, so * it has to be one number rather than a magic constant in three files. */ --app-header-h: calc(3.5rem + var(--safe-top)); /* * The sidebar primitive's palette. * * Aliases, not values. shadcn's sidebar block wants its own `--sidebar-*` * scale; defining literal colours here would be a second palette to keep in * step with the first, and it would not follow the user's accent — which is * written onto this same element at runtime by lib/theme.tsx. Because these * are HSL channel triples pointing at other HSL channel triples, the dark * theme and every accent flow through for free and there is nothing to * duplicate under [data-theme='dark']. */ --sidebar-background: var(--surface); --sidebar-foreground: var(--fg); --sidebar-primary: var(--accent); --sidebar-primary-foreground: var(--accent-on); --sidebar-accent: var(--accent-subtle); --sidebar-accent-foreground: var(--accent-fg); --sidebar-border: var(--border); --sidebar-ring: var(--accent); } @media (min-width: 1024px) { :root { --app-header-h: calc(4rem + var(--safe-top)); } } :root[data-theme='dark'] { --bg: 240 10% 4%; --surface: 240 8% 7%; --surface-2: 240 6% 11%; --border: 240 5% 18%; --fg: 0 0% 98%; --muted: 240 5% 60%; --positive: 158 64% 52%; --warning: 38 92% 60%; --danger: 0 84% 65%; --info: 199 89% 60%; --shadow: 0 0% 0%; } @layer base { * { border-color: hsl(var(--border)); } html { /* Prevents iOS from silently enlarging text in landscape. */ -webkit-text-size-adjust: 100%; /* Keeps the background painted behind the rubber-band overscroll area. */ background-color: hsl(var(--bg)); } body { @apply bg-bg text-fg antialiased; font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11'; /* Stops the whole page bouncing on iOS while inner panes still scroll. */ overscroll-behavior-y: none; } ::selection { background: hsl(var(--accent-subtle)); color: hsl(var(--accent-fg)); } /* * Mobile Safari zooms the viewport when a focused input has a font size * below 16px, and never zooms back out. This is the fix — not * user-scalable=no, which breaks pinch-zoom for everyone. */ input, select, textarea { font-size: max(16px, 1rem); } /* A visible, consistent focus ring — keyboard users need it, and the default varies wildly between browsers. */ :focus-visible { @apply outline-none ring-2 ring-accent ring-offset-2 ring-offset-bg; } /* Respect a reduced-motion preference rather than animating regardless. */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } } @layer components { /* Horizontal scrollers (pipeline boards, wide tables) get momentum scrolling and snap on touch, without hijacking the page. */ .scroll-x { @apply overflow-x-auto; -webkit-overflow-scrolling: touch; scrollbar-width: thin; overscroll-behavior-x: contain; } .scroll-x::-webkit-scrollbar { height: 6px; } .scroll-x::-webkit-scrollbar-thumb { background-color: hsl(var(--border)); border-radius: 3px; } /* Touch targets. 44px is the documented iOS minimum and the difference between a usable and a frustrating phone experience. */ .tap { @apply min-h-[44px] min-w-[44px]; } .card { @apply rounded-2xl border border-border/90 bg-surface; box-shadow: 0 1px 2px hsl(var(--shadow) / 0.035), 0 12px 36px hsl(var(--shadow) / 0.025); } .app-canvas { background-color: hsl(var(--bg)); background-image: radial-gradient(circle at 78% -12%, hsl(var(--accent-subtle) / 0.7), transparent 34rem), linear-gradient(to bottom, hsl(var(--surface) / 0.25), transparent 28rem); background-attachment: fixed; } /* * The auth surface is intentionally monochrome even if a signed-in user has * selected another accent. It is public brand chrome, and forcing the local * dark token set keeps controls legible without changing the stored theme. */ .auth-shell { --bg: 240 10% 3%; --surface: 240 7% 6%; --surface-2: 240 5% 11%; --border: 240 4% 22%; --fg: 0 0% 98%; --muted: 240 5% 67%; --accent: 0 0% 98%; --accent-fg: 0 0% 98%; --accent-on: 240 6% 10%; --accent-subtle: 240 4% 16%; color-scheme: dark; } .auth-field-motion { animation: auth-field-drift 26s ease-in-out infinite alternate; will-change: transform; } .auth-visual { opacity: 0.52; } .auth-visual::after { position: absolute; inset: 0; content: ''; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.92' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.72'/%3E%3C/svg%3E"); background-size: 180px 180px; mix-blend-mode: soft-light; opacity: 0.14; } .auth-visual-vignette { background: linear-gradient(to bottom, hsl(var(--bg) / 0.18), hsl(var(--bg) / 0.72) 34%, hsl(var(--bg)) 100%), linear-gradient(to right, hsl(var(--bg) / 0.18), hsl(var(--bg) / 0.42)); } @media (min-width: 1024px) { .auth-visual { opacity: 1; } .auth-visual-vignette { background: linear-gradient(to right, transparent 48%, hsl(var(--bg) / 0.28) 78%, hsl(var(--bg) / 0.72)), linear-gradient(to bottom, hsl(var(--bg) / 0.06), transparent 18%, hsl(var(--bg) / 0.28)); } } @keyframes auth-field-drift { 0% { transform: translate3d(-1.4%, -0.8%, 0) scale(1.045); } 50% { transform: translate3d(0.8%, 0.6%, 0) scale(1.075); } 100% { transform: translate3d(-0.3%, 1.1%, 0) scale(1.055); } } @media (prefers-reduced-motion: reduce) { .auth-field-motion { animation: none; transform: scale(1.045); will-change: auto; } } /* Tabular figures keep money and percentages from jittering as they update, which matters on a dashboard that refreshes. */ .nums { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum'; } }