feat: scaffold tailwind + shadcn foundation, swap to next-themes

Installs Tailwind CSS v3, postcss, autoprefixer, next-themes, lucide-react,
clsx, tailwind-merge, class-variance-authority, sonner, and tailwindcss-animate.
Creates tailwind.config.ts, postcss.config.mjs, components.json, src/lib/utils.ts
(cn helper), replaces globals.css with CSS variable theme, adds
suppressHydrationWarning to html element, and replaces MUI ThemeProvider
with next-themes ThemeProvider + sonner Toaster. MUI remains installed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
fuomag9
2026-03-22 11:14:09 +01:00
parent 28034c69f1
commit 7aeaaded5e
10 changed files with 8593 additions and 215 deletions

View File

@@ -1,34 +1,59 @@
:root {
color-scheme: light dark;
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 4%;
--card: 0 0% 98%;
--card-foreground: 240 10% 4%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 4%;
--primary: 239 84% 67%;
--primary-foreground: 0 0% 100%;
--secondary: 192 91% 43%;
--secondary-foreground: 0 0% 100%;
--muted: 240 5% 96%;
--muted-foreground: 240 4% 46%;
--accent: 240 5% 96%;
--accent-foreground: 240 10% 4%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 100%;
--border: 240 6% 90%;
--input: 240 6% 90%;
--ring: 239 84% 67%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 4%;
--foreground: 240 5% 96%;
--card: 240 6% 10%;
--card-foreground: 240 5% 96%;
--popover: 240 6% 10%;
--popover-foreground: 240 5% 96%;
--primary: 239 84% 67%;
--primary-foreground: 0 0% 100%;
--secondary: 192 91% 43%;
--secondary-foreground: 0 0% 100%;
--muted: 240 4% 16%;
--muted-foreground: 240 5% 65%;
--accent: 240 4% 16%;
--accent-foreground: 240 5% 96%;
--destructive: 0 63% 31%;
--destructive-foreground: 0 0% 100%;
--border: 240 4% 16%;
--input: 240 4% 16%;
--ring: 239 84% 67%;
}
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
background: transparent;
color: #f5f7fb;
min-height: 100%;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
a {
color: inherit;
text-decoration: none;
}
button {
font: inherit;
}
input,
select,
textarea {
font: inherit;
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -4,7 +4,7 @@ import Providers from "./providers";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<html lang="en" suppressHydrationWarning>
<body>
<Providers>{children}</Providers>
</body>

View File

@@ -1,188 +1,14 @@
"use client";
import { ReactNode, useMemo } from "react";
import { CssBaseline, ThemeProvider, createTheme, responsiveFontSizes } from "@mui/material";
import { ReactNode } from "react";
import { ThemeProvider } from "next-themes";
import { Toaster } from "sonner";
export default function Providers({ children }: { children: ReactNode }) {
const theme = useMemo(
() =>
responsiveFontSizes(
createTheme({
palette: {
mode: "dark",
background: {
default: "#09090b", // Zinc-950
paper: "#18181b" // Zinc-900
},
primary: {
main: "#6366f1", // Indigo-500
light: "#818cf8",
dark: "#4f46e5",
contrastText: "#ffffff"
},
secondary: {
main: "#06b6d4", // Cyan-500
light: "#22d3ee",
dark: "#0891b2",
contrastText: "#ffffff"
},
error: {
main: "#ef4444", // Red-500
light: "#f87171",
dark: "#dc2626"
},
success: {
main: "#22c55e", // Green-500
light: "#4ade80",
dark: "#16a34a"
},
warning: {
main: "#f59e0b", // Amber-500
light: "#fbbf24",
dark: "#d97706"
},
info: {
main: "#3b82f6", // Blue-500
light: "#60a5fa",
dark: "#2563eb"
},
text: {
primary: "#f4f4f5", // Zinc-100
secondary: "#a1a1aa" // Zinc-400
}
},
typography: {
fontFamily: ['"Inter"', '"Segoe UI"', "Roboto", "sans-serif"].join(","),
h4: {
fontWeight: 700,
letterSpacing: "-0.02em"
},
h6: {
fontWeight: 600
},
button: {
fontWeight: 600,
textTransform: "none"
}
},
shape: {
borderRadius: 12
},
components: {
MuiCssBaseline: {
styleOverrides: {
body: {
backgroundColor: "#09090b",
backgroundImage:
"radial-gradient(circle at 50% 0%, rgba(99, 102, 241, 0.15), transparent 40%), radial-gradient(circle at 100% 0%, rgba(6, 182, 212, 0.1), transparent 30%)",
backgroundAttachment: "fixed"
}
}
},
MuiButton: {
defaultProps: {
disableElevation: true
},
styleOverrides: {
root: {
borderRadius: 8,
padding: "8px 16px",
transition: "all 0.2s ease-in-out"
},
contained: {
"&:hover": {
transform: "translateY(-1px)",
boxShadow: "0 4px 12px rgba(99, 102, 241, 0.3)"
}
}
}
},
MuiCard: {
styleOverrides: {
root: {
backgroundImage: "none",
backgroundColor: "rgba(24, 24, 27, 0.6)", // Zinc-900 / 60%
border: "1px solid rgba(255, 255, 255, 0.08)",
backdropFilter: "blur(12px)",
transition: "all 0.3s ease",
"&:hover": {
borderColor: "rgba(255, 255, 255, 0.15)",
boxShadow: "0 12px 32px rgba(0, 0, 0, 0.4)"
}
}
}
},
MuiPaper: {
styleOverrides: {
root: {
backgroundImage: "none"
}
}
},
MuiTableCell: {
styleOverrides: {
root: {
borderBottom: "1px solid rgba(255, 255, 255, 0.06)",
padding: "16px 24px"
},
head: {
fontWeight: 600,
backgroundColor: "rgba(24, 24, 27, 0.4)",
color: "#a1a1aa",
fontSize: "0.75rem",
textTransform: "uppercase",
letterSpacing: "0.05em"
}
}
},
MuiTableRow: {
styleOverrides: {
root: {
"&:hover": {
backgroundColor: "rgba(255, 255, 255, 0.02)"
}
}
}
},
MuiDialog: {
styleOverrides: {
paper: {
backgroundColor: "#18181b",
border: "1px solid rgba(255, 255, 255, 0.1)",
backgroundImage: "none",
boxShadow: "0 24px 48px rgba(0, 0, 0, 0.5)"
}
}
},
MuiTextField: {
styleOverrides: {
root: {
"& .MuiOutlinedInput-root": {
borderRadius: 10,
backgroundColor: "rgba(9, 9, 11, 0.5)",
"& fieldset": {
borderColor: "rgba(255, 255, 255, 0.08)"
},
"&:hover fieldset": {
borderColor: "rgba(255, 255, 255, 0.2)"
},
"&.Mui-focused fieldset": {
borderColor: "#6366f1"
}
}
}
}
}
}
})
),
[]
);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
<Toaster richColors position="bottom-right" />
</ThemeProvider>
);
}

20
components.json Normal file
View File

@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}

8429
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -26,28 +26,36 @@
"@mui/material": "^7.3.9",
"@mui/x-date-pickers": "^8.27.2",
"apexcharts": "^5.10.4",
"autoprefixer": "^10.4.27",
"bcryptjs": "^3.0.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"d3-geo": "^3.1.1",
"dayjs": "^1.11.20",
"drizzle-orm": "^0.45.1",
"lucide-react": "^0.577.0",
"maplibre-gl": "^5.20.1",
"maxmind": "^5.0.5",
"next": "^16.1.7",
"next-auth": "^5.0.0-beta.30",
"next-themes": "^0.4.6",
"node-forge": "^1.3.3",
"postcss": "^8.5.8",
"react": "^19.2.4",
"react-apexcharts": "^2.1.0",
"react-dom": "^19.2.4",
"react-map-gl": "^8.1.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^3.4.19",
"topojson-client": "^3.1.0"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.13",
"@types/bun": "latest",
"@eslint/js": "^10.0.1",
"better-sqlite3": "^12.8.0",
"@next/eslint-plugin-next": "^16.1.7",
"@playwright/test": "^1.58.2",
"@types/better-sqlite3": "^7.6.13",
"@types/bun": "latest",
"@types/d3-geo": "^3.1.0",
"@types/node": "^25.5.0",
"@types/node-forge": "^1.3.14",
@@ -55,8 +63,10 @@
"@types/react-dom": "^19.2.3",
"@types/topojson-client": "^3.1.5",
"@vitest/ui": "^4.1.0",
"better-sqlite3": "^12.8.0",
"drizzle-kit": "^0.31.10",
"eslint": "^10.0.3",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.9.3",
"typescript-eslint": "^8.57.1",
"vite-tsconfig-paths": "^6.1.1",

8
postcss.config.mjs Normal file
View File

@@ -0,0 +1,8 @@
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
export default config;

6
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

53
tailwind.config.ts Normal file
View File

@@ -0,0 +1,53 @@
import type { Config } from "tailwindcss";
import tailwindcssAnimate from "tailwindcss-animate";
const config: Config = {
darkMode: ["class"],
content: [
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
plugins: [tailwindcssAnimate],
};
export default config;

View File

@@ -24,7 +24,8 @@
],
"paths": {
"@/*": [
"./*"
"./*",
"src/*"
],
"@/src/*": [
"src/*"