diff --git a/.gitignore b/.gitignore index f25ae95..89c5664 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,4 @@ # React Router /.react-router/ /build/ - - +/docker/build/ diff --git a/app/app.css b/app/app.css index 75c6338..342b3a5 100644 --- a/app/app.css +++ b/app/app.css @@ -1,16 +1,105 @@ -@import 'tailwindcss'; +/* Dark mode is toggled by adding/removing `.dark` on (see ThemeModeProvider), + rather than following the OS preference, so it can stay in sync with the MUI theme. */ -@theme { - --font-sans: - 'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', - 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; +:root { + /* Water/brand blue, anchored on the PHLASK logo color (#10B6FF = brand-500). + Mirrored in app/theme/theme.ts (kept in sync manually). */ + --color-brand-50: #eafaff; + --color-brand-100: #d6f4ff; + --color-brand-300: #6fd6ff; + + /* Deep ocean navy, used for dark-mode surfaces */ + --color-navy-950: #071522; + --color-navy-900: #0a1929; + --color-navy-600: #1f4560; } html, body { - @apply bg-white dark:bg-gray-950; + background-color: var(--color-brand-50); + color: var(--color-navy-900); + color-scheme: light; +} + +html.dark, +html.dark body { + background-color: var(--color-navy-950); + color: var(--color-brand-50); +} + +html.dark { + color-scheme: dark; +} + +@keyframes wave-drift { + from { + transform: translateX(0); + } + to { + transform: translateX(-50%); + } +} + +@keyframes bubble-rise { + from { + transform: translateY(0) scale(1); + opacity: 0.5; + } + to { + transform: translateY(-140px) scale(1.15); + opacity: 0; + } +} + +/* Ambient per-type motion for resource-type chip icons (see chipColors.tsx) */ +@keyframes chip-icon-bob { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-2px); + } +} + +@keyframes chip-icon-pulse { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.18); + } +} + +@keyframes chip-icon-sway { + 0%, + 100% { + transform: rotate(-10deg); + } + 50% { + transform: rotate(10deg); + } +} - @media (prefers-color-scheme: dark) { - color-scheme: dark; +@keyframes chip-icon-wiggle { + 0%, + 100% { + transform: rotate(0deg); + } + 25% { + transform: rotate(-12deg); + } + 75% { + transform: rotate(12deg); } } + +/* Water-themed scrollbar */ +* { + scrollbar-color: var(--color-brand-300) transparent; +} + +html.dark * { + scrollbar-color: var(--color-navy-600) transparent; +} diff --git a/app/components/ThemeToggle.tsx b/app/components/ThemeToggle.tsx new file mode 100644 index 0000000..206b561 --- /dev/null +++ b/app/components/ThemeToggle.tsx @@ -0,0 +1,45 @@ +import { DarkMode, LightMode } from "@mui/icons-material"; +import { IconButton, Tooltip } from "@mui/material"; +import { useThemeMode } from "~/theme/ThemeModeProvider"; +import { brand, navy } from "~/theme/theme"; + +// Styling and icon visibility are driven purely by the `.dark` class (via +// theme.applyStyles) rather than the `mode` value from React state, so this +// can never mismatch between SSR and hydration. +export function ThemeToggle() { + const { toggleMode } = useThemeMode(); + + return ( + + ({ + bgcolor: brand[100], + color: brand[700], + transition: "transform 0.35s ease, background-color 0.2s ease", + "&:hover": { bgcolor: brand[200], transform: "rotate(-14deg)" }, + ...theme.applyStyles("dark", { + bgcolor: navy[800], + color: brand[300], + "&:hover": { bgcolor: navy[700] }, + }), + })} + > + ({ + ...theme.applyStyles("dark", { display: "none" }), + })} + /> + ({ + display: "none", + ...theme.applyStyles("dark", { display: "inline-block" }), + })} + /> + + + ); +} diff --git a/app/components/WaveDivider.tsx b/app/components/WaveDivider.tsx new file mode 100644 index 0000000..9618bfc --- /dev/null +++ b/app/components/WaveDivider.tsx @@ -0,0 +1,59 @@ +import { Box, type SxProps, type Theme } from "@mui/material"; + +// Two periods of the same wave back-to-back so a -50% translateX loops seamlessly. +const WAVE_PATH = + "M0,40 C150,90 350,0 600,40 C850,80 1050,0 1200,40 C1350,80 1550,0 1800,40 C1950,80 2150,0 2400,40 L2400,120 L0,120 Z"; + +type WaveDividerProps = { + color: string; + height?: number; + opacity?: number; + duration?: number; + reverse?: boolean; + sx?: SxProps; +}; + +export function WaveDivider({ + color, + height = 60, + opacity = 1, + duration = 18, + reverse = false, + sx, +}: WaveDividerProps) { + return ( + + + + ); +} diff --git a/app/root.tsx b/app/root.tsx index 9fc6636..e7f8594 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,3 +1,4 @@ +import { Box } from "@mui/material"; import { isRouteErrorResponse, Links, @@ -5,10 +6,20 @@ import { Outlet, Scripts, ScrollRestoration, + useLoaderData, + useRouteLoaderData, } from "react-router"; - import type { Route } from "./+types/root"; import "./app.css"; +import { ThemeModeProvider } from "./theme/ThemeModeProvider"; +import { getThemeMode } from "./theme/theme-cookie.server"; + +// The theme cookie (see theme-cookie.server.ts) lets the server render the +// right `dark` class on up front, so there's no flash of the wrong +// theme and no need for a blocking inline script before hydration. +export function loader({ request }: Route.LoaderArgs) { + return { themeMode: getThemeMode(request) }; +} export const links: Route.LinksFunction = () => [ { rel: "preconnect", href: "https://fonts.googleapis.com" }, @@ -24,8 +35,12 @@ export const links: Route.LinksFunction = () => [ ]; export function Layout({ children }: { children: React.ReactNode }) { + // useRouteLoaderData (not useLoaderData) because Layout also renders the + // ErrorBoundary path, where the root loader may not have run. + const themeMode = useRouteLoaderData("root")?.themeMode; + return ( - + @@ -42,7 +57,13 @@ export function Layout({ children }: { children: React.ReactNode }) { } export default function App() { - return ; + const { themeMode } = useLoaderData(); + + return ( + + + + ); } export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { @@ -62,14 +83,17 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { } return ( -
+

{message}

{details}

{stack && ( -
+        
           {stack}
-        
+
)} -
+ ); } diff --git a/app/theme/ThemeModeProvider.tsx b/app/theme/ThemeModeProvider.tsx new file mode 100644 index 0000000..37b77b7 --- /dev/null +++ b/app/theme/ThemeModeProvider.tsx @@ -0,0 +1,62 @@ +import CssBaseline from "@mui/material/CssBaseline"; +import { ThemeProvider } from "@mui/material/styles"; +import { + createContext, + type ReactNode, + useContext, + useEffect, + useMemo, + useState, +} from "react"; +import { THEME_COOKIE_NAME, type ThemeMode, theme } from "./theme"; + +type ThemeModeContextValue = { + mode: ThemeMode; + toggleMode: () => void; +}; + +const ThemeModeContext = createContext(null); + +export function ThemeModeProvider({ + children, + initialMode, +}: { + children: ReactNode; + initialMode: ThemeMode; +}) { + const [mode, setMode] = useState(initialMode); + + useEffect(() => { + document.documentElement.classList.toggle("dark", mode === "dark"); + // One year, readable by the root loader on the next request so SSR + // renders the right theme with no flash. Not httpOnly: it's a UI + // preference, not a secret, and needs to be writable from here. + // biome-ignore lint/suspicious/noDocumentCookie: Cookie Store API isn't supported in Safari/Firefox yet + document.cookie = `${THEME_COOKIE_NAME}=${mode}; path=/; max-age=31536000; samesite=lax`; + }, [mode]); + + const value = useMemo( + () => ({ + mode, + toggleMode: () => setMode((m) => (m === "dark" ? "light" : "dark")), + }), + [mode], + ); + + return ( + + + + {children} + + + ); +} + +export function useThemeMode(): ThemeModeContextValue { + const ctx = useContext(ThemeModeContext); + if (!ctx) { + throw new Error("useThemeMode must be used within a ThemeModeProvider"); + } + return ctx; +} diff --git a/app/theme/theme-cookie.server.ts b/app/theme/theme-cookie.server.ts new file mode 100644 index 0000000..bfa1122 --- /dev/null +++ b/app/theme/theme-cookie.server.ts @@ -0,0 +1,12 @@ +import { THEME_COOKIE_NAME, type ThemeMode } from "./theme"; + +// Plain, unsigned cookie: it only ever holds "light" | "dark", so there's +// nothing here worth signing/encoding, and keeping it plain lets the client +// write it with a bare `document.cookie =` assignment (see ThemeModeProvider). +export function getThemeMode(request: Request): ThemeMode { + const cookieHeader = request.headers.get("Cookie") ?? ""; + const match = cookieHeader.match( + new RegExp(`(?:^|;\\s*)${THEME_COOKIE_NAME}=(dark|light)`), + ); + return match?.[1] === "dark" ? "dark" : "light"; +} diff --git a/app/theme/theme.ts b/app/theme/theme.ts new file mode 100644 index 0000000..91efe20 --- /dev/null +++ b/app/theme/theme.ts @@ -0,0 +1,135 @@ +import { createTheme, type Theme } from "@mui/material/styles"; + +// Mirrors the brand-* / navy-* tokens in app/app.css (kept in sync manually — +// Tailwind's @theme values aren't importable into JS). +export const brand = { + 50: "#eafaff", + 100: "#d6f4ff", + 200: "#ade8ff", + 300: "#6fd6ff", + 400: "#38c1ff", + 500: "#10b6ff", + 600: "#0090de", + 700: "#0072b3", + 800: "#045c90", + 900: "#0a3a5c", +}; + +export const navy = { + 950: "#071522", + 900: "#0a1929", + 800: "#10263a", + 700: "#17344c", + 600: "#1f4560", +}; + +const teal = { + 300: "#5eead4", + 400: "#2dd4bf", + 500: "#14b8a6", + 600: "#0d9488", + 700: "#0f766e", +}; + +const fontFamily = [ + "Inter", + "ui-sans-serif", + "system-ui", + "sans-serif", + "Apple Color Emoji", + "Segoe UI Emoji", + "Segoe UI Symbol", + "Noto Color Emoji", +].join(","); + +const shape = { borderRadius: 14 }; + +const typography = { + fontFamily, + h4: { fontWeight: 700 }, + h5: { fontWeight: 700 }, + subtitle1: { fontWeight: 600 }, + button: { fontWeight: 600, textTransform: "none" as const }, +}; + +// Uses MUI's CSS-variables theming keyed off the same `.dark` class Tailwind +// toggles on (see ThemeModeProvider). Both light and dark rules are +// always present in the generated stylesheet — switching mode is a pure CSS +// selector match, not a JS-driven theme swap — so SSR and the first client +// render can never disagree about which theme object is "active" the way two +// separate light/dark Theme objects picked by JS state could. +export const theme: Theme = createTheme({ + cssVariables: { colorSchemeSelector: "class" }, + shape, + typography, + colorSchemes: { + light: { + palette: { + primary: { main: brand[500], light: brand[300], dark: brand[700] }, + secondary: { main: teal[600], light: teal[400], dark: teal[700] }, + background: { default: brand[50], paper: "#ffffff" }, + divider: brand[100], + text: { primary: navy[900], secondary: "#3d5a70" }, + }, + }, + dark: { + palette: { + primary: { main: brand[400], light: brand[200], dark: brand[600] }, + secondary: { main: teal[400], light: teal[300], dark: teal[600] }, + background: { default: navy[950], paper: navy[900] }, + divider: navy[700], + text: { primary: brand[50], secondary: "#9fc2d6" }, + }, + }, + }, + components: { + MuiPaper: { + styleOverrides: { + root: { backgroundImage: "none" }, + outlined: ({ theme }) => ({ + borderColor: brand[100], + boxShadow: + "0 1px 2px rgba(10,58,92,0.06), 0 8px 20px -10px rgba(10,58,92,0.25)", + ...theme.applyStyles("dark", { + borderColor: navy[700], + boxShadow: "none", + }), + }), + }, + }, + MuiButton: { + styleOverrides: { + root: { borderRadius: 999, paddingInline: 20 }, + contained: ({ theme }) => ({ + boxShadow: `0 6px 16px -4px ${brand[500]}66`, + "&:hover": { boxShadow: `0 8px 20px -4px ${brand[500]}88` }, + ...theme.applyStyles("dark", { + boxShadow: "0 6px 18px -4px #00000080", + "&:hover": { boxShadow: "0 8px 22px -4px #000000a0" }, + }), + }), + }, + }, + MuiChip: { styleOverrides: { root: { fontWeight: 600 } } }, + MuiTableHead: { + styleOverrides: { + root: ({ theme }) => ({ + "& .MuiTableCell-root": { + backgroundColor: brand[50], + ...theme.applyStyles("dark", { backgroundColor: navy[800] }), + }, + }), + }, + }, + MuiTextField: { + defaultProps: { variant: "outlined" }, + }, + MuiOutlinedInput: { + styleOverrides: { root: { borderRadius: 10 } }, + }, + }, +}); + +export type ThemeMode = "light" | "dark"; + +export const THEME_COOKIE_NAME = "phlask-theme-mode"; diff --git a/package.json b/package.json index ca30df3..798bbe5 100644 --- a/package.json +++ b/package.json @@ -33,12 +33,10 @@ "devDependencies": { "@biomejs/biome": "2.3.14", "@react-router/dev": "^7.13.0", - "@tailwindcss/vite": "^4.1.18", "@types/node": "^22.19.11", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "lefthook": "^2.1.1", - "tailwindcss": "^4.1.18", "typescript": "^5.9.3", "vite": "^7.3.1", "vite-tsconfig-paths": "^5.1.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f33c4e3..9890669 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,9 +66,6 @@ importers: '@react-router/dev': specifier: ^7.13.0 version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@22.19.11)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2)) - '@tailwindcss/vite': - specifier: ^4.1.18 - version: 4.1.18(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2)) '@types/node': specifier: ^22.19.11 version: 22.19.11 @@ -81,9 +78,6 @@ importers: lefthook: specifier: ^2.1.1 version: 2.1.1 - tailwindcss: - specifier: ^4.1.18 - version: 4.1.18 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -831,100 +825,6 @@ packages: resolution: {integrity: sha512-KaIHsO6LU1cuqpYMsprQhfedRJFuTAf4dr8/k4/4Po6urRVXIWzmdY/lHPnf8Mzw2Fh0ueQVQYdaXzOvCBGnGQ==} engines: {node: '>=20.0.0'} - '@tailwindcss/node@4.1.18': - resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} - - '@tailwindcss/oxide-android-arm64@4.1.18': - resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.1.18': - resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.1.18': - resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.1.18': - resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': - resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': - resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-arm64-musl@4.1.18': - resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-linux-x64-gnu@4.1.18': - resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-x64-musl@4.1.18': - resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-wasm32-wasi@4.1.18': - resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': - resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.1.18': - resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.1.18': - resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} - engines: {node: '>= 10'} - - '@tailwindcss/vite@4.1.18': - resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==} - peerDependencies: - vite: ^5.2.0 || ^6 || ^7 - '@tanstack/react-table@8.21.3': resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} engines: {node: '>=12'} @@ -1135,10 +1035,6 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - enhanced-resolve@5.19.0: - resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} - engines: {node: '>=10.13.0'} - error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -1243,9 +1139,6 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1452,9 +1345,6 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1748,13 +1638,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tailwindcss@4.1.18: - resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} - - tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} - engines: {node: '>=6'} - tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -2585,74 +2468,6 @@ snapshots: - bufferutil - utf-8-validate - '@tailwindcss/node@4.1.18': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.19.0 - jiti: 2.6.1 - lightningcss: 1.30.2 - magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.1.18 - - '@tailwindcss/oxide-android-arm64@4.1.18': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.1.18': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.1.18': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.1.18': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.1.18': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.1.18': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.1.18': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.1.18': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.1.18': - optional: true - - '@tailwindcss/oxide@4.1.18': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.18 - '@tailwindcss/oxide-darwin-arm64': 4.1.18 - '@tailwindcss/oxide-darwin-x64': 4.1.18 - '@tailwindcss/oxide-freebsd-x64': 4.1.18 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 - '@tailwindcss/oxide-linux-x64-musl': 4.1.18 - '@tailwindcss/oxide-wasm32-wasi': 4.1.18 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 - - '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2))': - dependencies: - '@tailwindcss/node': 4.1.18 - '@tailwindcss/oxide': 4.1.18 - tailwindcss: 4.1.18 - vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2) - '@tanstack/react-table@8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@tanstack/table-core': 8.21.3 @@ -2832,7 +2647,8 @@ snapshots: destroy@1.2.0: {} - detect-libc@2.1.2: {} + detect-libc@2.1.2: + optional: true dom-helpers@5.2.1: dependencies: @@ -2851,11 +2667,6 @@ snapshots: encodeurl@2.0.0: {} - enhanced-resolve@5.19.0: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.0 - error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -3000,8 +2811,6 @@ snapshots: gopd@1.2.0: {} - graceful-fs@4.2.11: {} - has-symbols@1.1.0: {} hasown@2.0.2: @@ -3043,7 +2852,8 @@ snapshots: isbot@5.1.35: {} - jiti@2.6.1: {} + jiti@2.6.1: + optional: true js-tokens@4.0.0: {} @@ -3144,6 +2954,7 @@ snapshots: lightningcss-linux-x64-musl: 1.30.2 lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + optional: true lines-and-columns@1.2.4: {} @@ -3157,10 +2968,6 @@ snapshots: dependencies: yallist: 3.1.1 - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - math-intrinsics@1.1.0: {} media-typer@0.3.0: {} @@ -3453,10 +3260,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tailwindcss@4.1.18: {} - - tapable@2.3.0: {} - tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) diff --git a/vite.config.ts b/vite.config.ts index 3f3f041..c4140c9 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,10 +1,9 @@ import { reactRouter } from "@react-router/dev/vite"; -import tailwindcss from "@tailwindcss/vite"; import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ - plugins: [tailwindcss(), reactRouter(), tsconfigPaths()], + plugins: [reactRouter(), tsconfigPaths()], server: { port: 5174, },