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,
},