feat(deps): Tailwind 3 → 4 + swap tailwindcss-animate for tw-animate-css

Ran the official @tailwindcss/upgrade tool:
- tailwind.config.ts → @theme directive in globals.css
- @tailwind base/components/utilities → @import 'tailwindcss'
- postcss.config switched from tailwindcss + autoprefixer to
  @tailwindcss/postcss (autoprefixer baked in)
- focus-visible:outline-none → focus-visible:outline-hidden (the v3
  utility was a footgun — outline still showed in forced-colors mode)

Reverted the migration tool's over-zealous variant="outline" →
variant="outline-solid" rename on CVA prop values; that rename was
meant for the Tailwind `outline:` utility, not our Button/Badge
component variants.

Swapped tailwindcss-animate (v3-style JS plugin) for tw-animate-css
(v4-native @import). Same utility surface (animate-spin, animate-in,
etc.), one fewer JS plugin in the bundle.

Fixed the upgrade tool's malformed dark variant
(@custom-variant dark (&:is(class *)) — `class` was being parsed as
a tag) to canonical &:where(.dark, .dark *).

Verified: tsc 0 errors, eslint 0 errors (16 pre-existing warnings),
vitest 1315/1315, next build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 22:14:38 +02:00
parent 3147923d91
commit 0ab96d74a8
69 changed files with 561 additions and 753 deletions

View File

@@ -1,6 +1,183 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--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-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-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-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
--color-brand-50: #d8e5f4;
--color-brand-100: #b1cbe9;
--color-brand-200: #89b0de;
--color-brand-300: #6196d3;
--color-brand-400: #3a7bc8;
--color-brand-500: #2f6ab5;
--color-brand-600: #255a9e;
--color-brand-700: #1c4a87;
--color-brand: #3a7bc8;
--color-brand-dark: #1e2844;
--color-navy-50: #cdcfd6;
--color-navy-100: #9ea1af;
--color-navy-200: #71768a;
--color-navy-300: #474e66;
--color-navy-400: #1e2844;
--color-navy-500: #171f35;
--color-navy-600: #101625;
--color-navy: #1e2844;
--color-sage: #dae3c1;
--color-sage-light: #edf1e2;
--color-sage-dark: #b8c49e;
--color-mint: #add5b3;
--color-mint-light: #d6ead9;
--color-mint-dark: #7dba85;
--color-teal: #83aab1;
--color-teal-light: #b1cdd2;
--color-teal-dark: #5a8a92;
--color-purple: #685aa3;
--color-purple-light: #a49ac6;
--color-purple-dark: #4d4280;
--color-success: #2d8a4e;
--color-success-bg: #e8f5e9;
--color-success-border: #a5d6a7;
--color-warning: #e6a817;
--color-warning-bg: #fff8e1;
--color-warning-border: #ffe082;
--color-error: #d32f2f;
--color-error-bg: #ffebee;
--color-error-border: #ef9a9a;
--color-sidebar: #1e2844;
--color-sidebar-text: #cdcfd6;
--color-sidebar-hover: #171f35;
--color-sidebar-active: #3a7bc8;
--color-sidebar-divider: #474e66;
--font-sans: Inter, system-ui, -apple-system, Arial, sans-serif;
--font-mono: JetBrains Mono, ui-monospace, monospace;
--font-serif: Georgia, Times New Roman, serif;
--shadow-xs: 0 1px 2px 0 rgb(15 23 42 / 0.04);
--shadow-sm: 0 2px 4px -1px rgb(15 23 42 / 0.06);
--shadow: 0 1px 3px rgba(30, 40, 68, 0.1), 0 1px 2px rgba(30, 40, 68, 0.06);
--shadow-md: 0 4px 12px -2px rgb(15 23 42 / 0.08);
--shadow-lg: 0 12px 32px -8px rgb(15 23 42 / 0.12);
--shadow-xl: 0 20px 25px rgba(30, 40, 68, 0.1), 0 8px 10px rgba(30, 40, 68, 0.04);
--shadow-glow: 0 0 0 4px rgb(58 123 200 / 0.12);
--radius-sm: 0.375rem;
--radius: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.625rem;
--radius-xl: 0.875rem;
--background-image-gradient-brand: linear-gradient(135deg, #3a7bc8 0%, #2f6ab5 100%);
--background-image-gradient-brand-soft: linear-gradient(135deg, #d8e5f4 0%, #ffffff 100%);
--background-image-gradient-success: linear-gradient(135deg, #e8f5e9 0%, #ffffff 100%);
--background-image-gradient-warning: linear-gradient(135deg, #fef3c7 0%, #ffffff 100%);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
--width-sidebar: 256px;
--width-sidebar-collapsed: 64px;
--transition-duration-sidebar: 200ms;
--transition-duration-fast: 150ms;
--transition-duration-base: 200ms;
--transition-duration-slow: 300ms;
--spacing-safe: env(safe-area-inset-bottom);
--spacing-safe-top: env(safe-area-inset-top);
--spacing-safe-bottom: env(safe-area-inset-bottom);
--spacing-safe-left: env(safe-area-inset-left);
--spacing-safe-right: env(safe-area-inset-right);
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-badge-pop: badge-pop 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes badge-pop {
0% {
transform: scale(0.5);
opacity: 0;
}
60% {
transform: scale(1.18);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 1;
}
}
}
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
@layer base {
:root {