From c2a10ca0d42c97d947dfb72a93f19f89684c51bf Mon Sep 17 00:00:00 2001 From: Nayam Amarshe <25067102+NayamAmarshe@users.noreply.github.com> Date: Sun, 21 Apr 2024 19:34:59 +0530 Subject: [PATCH] Add toast and add shadcn theme vars --- electron/commands/batch-upscayl.ts | 10 +- package-lock.json | 88 +++++++- package.json | 1 + .../components/settings-tab/ResetSettings.tsx | 9 +- renderer/components/ui/toast.tsx | 127 ++++++++++++ renderer/components/ui/toaster.tsx | 33 +++ renderer/components/ui/use-toast.ts | 192 ++++++++++++++++++ .../upscayl-tab/config/LeftPaneImageSteps.tsx | 7 +- renderer/pages/_app.tsx | 2 + renderer/pages/index.tsx | 79 +++++-- renderer/styles/globals.css | 128 ++++++------ 11 files changed, 577 insertions(+), 99 deletions(-) create mode 100644 renderer/components/ui/toast.tsx create mode 100644 renderer/components/ui/toaster.tsx create mode 100644 renderer/components/ui/use-toast.ts diff --git a/electron/commands/batch-upscayl.ts b/electron/commands/batch-upscayl.ts index 9e5c70a..7ab88db 100644 --- a/electron/commands/batch-upscayl.ts +++ b/electron/commands/batch-upscayl.ts @@ -86,7 +86,6 @@ const batchUpscayl = async (event, payload: BatchUpscaylPayload) => { setStopped(false); let failed = false; - let isAlpha = false; const onData = (data: any) => { if (!mainWindow) return; @@ -95,14 +94,11 @@ const batchUpscayl = async (event, payload: BatchUpscaylPayload) => { COMMAND.FOLDER_UPSCAYL_PROGRESS, data.toString(), ); - if (data.includes("Error") || data.includes("failed")) { - logit("❌ INVALID GPU OR INVALID FILES IN FOLDER - FAILED"); + if ((data as string).includes("Error")) { + logit("❌ ", data); failed = true; upscayl.kill(); } - if (data.includes("has alpha channel")) { - isAlpha = true; - } }; const onError = (data: any) => { if (!mainWindow) return; @@ -116,7 +112,7 @@ const batchUpscayl = async (event, payload: BatchUpscaylPayload) => { mainWindow && mainWindow.webContents.send( COMMAND.UPSCAYL_ERROR, - "Error upscaling image. Error: " + data, + `Error upscaling image! ${data}`, ); return; }; diff --git a/package-lock.json b/package-lock.json index 1e25118..67fd64a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "upscayl", - "version": "2.10.0", + "version": "2.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "upscayl", - "version": "2.10.0", + "version": "2.11.0", "license": "AGPL-3.0", "dependencies": { "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-toast": "^1.1.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^1.0.0", @@ -1582,6 +1583,32 @@ } } }, + "node_modules/@radix-ui/react-collection": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", + "integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", @@ -1896,6 +1923,40 @@ } } }, + "node_modules/@radix-ui/react-toast": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.5.tgz", + "integrity": "sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-visually-hidden": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", @@ -2002,6 +2063,29 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz", + "integrity": "sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/rect": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz", diff --git a/package.json b/package.json index 75bfcf6..2f4b396 100644 --- a/package.json +++ b/package.json @@ -226,6 +226,7 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-toast": "^1.1.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^1.0.0", diff --git a/renderer/components/settings-tab/ResetSettings.tsx b/renderer/components/settings-tab/ResetSettings.tsx index d8685f0..9ecd28d 100644 --- a/renderer/components/settings-tab/ResetSettings.tsx +++ b/renderer/components/settings-tab/ResetSettings.tsx @@ -5,13 +5,12 @@ export function ResetSettings() {

RESET UPSCAYL

diff --git a/renderer/components/ui/toast.tsx b/renderer/components/ui/toast.tsx new file mode 100644 index 0000000..a822477 --- /dev/null +++ b/renderer/components/ui/toast.tsx @@ -0,0 +1,127 @@ +import * as React from "react" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const ToastProvider = ToastPrimitives.Provider + +const ToastViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName + +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", + { + variants: { + variant: { + default: "border bg-background text-foreground", + destructive: + "destructive group border-destructive bg-destructive text-destructive-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Toast = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, ...props }, ref) => { + return ( + + ) +}) +Toast.displayName = ToastPrimitives.Root.displayName + +const ToastAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastAction.displayName = ToastPrimitives.Action.displayName + +const ToastClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +ToastClose.displayName = ToastPrimitives.Close.displayName + +const ToastTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName + +const ToastDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName + +type ToastProps = React.ComponentPropsWithoutRef + +type ToastActionElement = React.ReactElement + +export { + type ToastProps, + type ToastActionElement, + ToastProvider, + ToastViewport, + Toast, + ToastTitle, + ToastDescription, + ToastClose, + ToastAction, +} diff --git a/renderer/components/ui/toaster.tsx b/renderer/components/ui/toaster.tsx new file mode 100644 index 0000000..a2209ba --- /dev/null +++ b/renderer/components/ui/toaster.tsx @@ -0,0 +1,33 @@ +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "@/components/ui/toast" +import { useToast } from "@/components/ui/use-toast" + +export function Toaster() { + const { toasts } = useToast() + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ) + })} + +
+ ) +} diff --git a/renderer/components/ui/use-toast.ts b/renderer/components/ui/use-toast.ts new file mode 100644 index 0000000..1671307 --- /dev/null +++ b/renderer/components/ui/use-toast.ts @@ -0,0 +1,192 @@ +// Inspired by react-hot-toast library +import * as React from "react" + +import type { + ToastActionElement, + ToastProps, +} from "@/components/ui/toast" + +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 1000000 + +type ToasterToast = ToastProps & { + id: string + title?: React.ReactNode + description?: React.ReactNode + action?: ToastActionElement +} + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER + return count.toString() +} + +type ActionType = typeof actionTypes + +type Action = + | { + type: ActionType["ADD_TOAST"] + toast: ToasterToast + } + | { + type: ActionType["UPDATE_TOAST"] + toast: Partial + } + | { + type: ActionType["DISMISS_TOAST"] + toastId?: ToasterToast["id"] + } + | { + type: ActionType["REMOVE_TOAST"] + toastId?: ToasterToast["id"] + } + +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map>() + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + } + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t + ), + } + + case "DISMISS_TOAST": { + const { toastId } = action + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId) + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t + ), + } + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + } + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + } + } +} + +const listeners: Array<(state: State) => void> = [] + +let memoryState: State = { toasts: [] } + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action) + listeners.forEach((listener) => { + listener(memoryState) + }) +} + +type Toast = Omit + +function toast({ ...props }: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }) + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss() + }, + }, + }) + + return { + id: id, + dismiss, + update, + } +} + +function useToast() { + const [state, setState] = React.useState(memoryState) + + React.useEffect(() => { + listeners.push(setState) + return () => { + const index = listeners.indexOf(setState) + if (index > -1) { + listeners.splice(index, 1) + } + } + }, [state]) + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + } +} + +export { useToast, toast } diff --git a/renderer/components/upscayl-tab/config/LeftPaneImageSteps.tsx b/renderer/components/upscayl-tab/config/LeftPaneImageSteps.tsx index b57dd58..70bacdb 100644 --- a/renderer/components/upscayl-tab/config/LeftPaneImageSteps.tsx +++ b/renderer/components/upscayl-tab/config/LeftPaneImageSteps.tsx @@ -16,6 +16,7 @@ import getModelScale from "@common/check-model-scale"; import COMMAND from "@common/commands"; import Select from "react-select"; import { cn } from "@/lib/utils"; +import { useToast } from "@/components/ui/use-toast"; interface IProps { selectImageHandler: () => Promise; @@ -70,6 +71,7 @@ function LeftPaneImageSteps({ const [targetHeight, setTargetHeight] = useState(null); const { logit } = useLog(); + const { toast } = useToast(); const outputHandler = async () => { var path = await window.electron.invoke(COMMAND.SELECT_FOLDER); @@ -320,7 +322,10 @@ function LeftPaneImageSteps({ className="btn btn-accent" onClick={ progress.length > 0 || !outputPath - ? () => alert("Please select an output folder first") + ? () => + toast({ + description: "Please select an output folder first", + }) : upscaylHandler } > diff --git a/renderer/pages/_app.tsx b/renderer/pages/_app.tsx index 5907a92..d4bb578 100644 --- a/renderer/pages/_app.tsx +++ b/renderer/pages/_app.tsx @@ -3,6 +3,7 @@ import Head from "next/head"; import { AppProps } from "next/app"; import { Provider } from "jotai"; import "react-tooltip/dist/react-tooltip.css"; +import { Toaster } from "@/components/ui/toaster"; const MyApp = ({ Component, pageProps }: AppProps) => { return ( @@ -12,6 +13,7 @@ const MyApp = ({ Component, pageProps }: AppProps) => { + ); diff --git a/renderer/pages/index.tsx b/renderer/pages/index.tsx index 502aac5..d93d519 100644 --- a/renderer/pages/index.tsx +++ b/renderer/pages/index.tsx @@ -42,6 +42,8 @@ import { newsAtom, showNewsModalAtom } from "@/atoms/newsAtom"; import matter from "gray-matter"; import { PanelLeftCloseIcon, PanelRightCloseIcon } from "lucide-react"; import { cn } from "@/lib/utils"; +import { useToast } from "@/components/ui/use-toast"; +import { ToastAction } from "@/components/ui/toast"; const Home = () => { const allowedFileTypes = ["png", "jpg", "jpeg", "webp"]; @@ -93,6 +95,7 @@ const Home = () => { const useCustomWidth = useAtomValue(useCustomWidthAtom); const { logit } = useLog(); + const { toast } = useToast(); const handleMouseMoveCompare = (e: React.MouseEvent) => { const { left, top, height, width } = @@ -117,23 +120,40 @@ const Home = () => { // ELECTRON EVENT LISTENERS useEffect(() => { const handleErrors = (data: string) => { - if (data.includes("invalid gpu")) { - alert( - "Error. Please make sure you have a Vulkan compatible GPU (Most modern GPUs support Vulkan). Upscayl does not work with CPU or iGPU sadly.", - ); + if (data.includes("Invalid GPU")) { + toast({ + title: "GPU Error", + description: `GPU error occurred. Please read the wiki for troubleshooting! ${data})`, + action: ( + + Troubleshoot + + ), + }); resetImagePaths(); - } else if (data.includes("failed")) { + } else if (data.includes("write") || data.includes("read")) { if (batchMode) return; - alert( - data.includes("encode") - ? `ENCODING ERROR: ${data}. For troubleshooting, please read the Upscayl Wiki.` - : `DECODING ERROR: ${data}. Additional Info: This image is possibly corrupt or not supported by Upscayl, or your GPU drivers are acting funny (PLEASE READ THE UPSCAYL WIKI). You could try converting the image into another format and upscaling again. Also make sure that the output path is correct and you have the proper write permissions for the directory. If not, then unfortunately there's not much we can do to help, sorry.`, - ); + toast({ + title: "Read/Write Error", + description: `Make sure that the path is correct and you have proper read/write permissions (${data})`, + action: ( + + Troubleshoot + + ), + }); + resetImagePaths(); + } else if (data.includes("tile size")) { + toast({ + title: "Error", + description: `The tile size is wrong. Please change the tile size in the settings or set to 0 (${data})`, + }); resetImagePaths(); } else if (data.includes("uncaughtException")) { - alert( - "Upscayl encountered an error. Possibly, the upscayl binary failed to execute the commands properly. Try checking the logs to see if you get any information. You can post an issue on Upscayl's GitHub repository for more help.", - ); + toast({ + title: "Exception Error", + description: `Upscayl encountered an error. Possibly, the upscayl binary failed to execute the commands properly. Try checking the logs to see if you get any information. You can post an issue on Upscayl's GitHub repository for more help.`, + }); resetImagePaths(); } }; @@ -148,7 +168,7 @@ const Home = () => { ); // LOG window.electron.on(COMMAND.LOG, (_, data: string) => { - logit(`🐞 BACKEND REPORTED: `, data); + logit(`𝌖 BACKEND REPORTED: `, data); }); // SCALING AND CONVERTING window.electron.on(COMMAND.SCALING_AND_CONVERTING, (_, data: string) => { @@ -156,7 +176,10 @@ const Home = () => { }); // UPSCAYL ERROR window.electron.on(COMMAND.UPSCAYL_ERROR, (_, data: string) => { - alert(data); + toast({ + title: "Error", + description: data, + }); resetImagePaths(); }); // UPSCAYL PROGRESS @@ -312,7 +335,11 @@ const Home = () => { const extension = path.toLocaleLowerCase().split(".").pop(); logit("🔤 Extension: ", extension); if (!allowedFileTypes.includes(extension.toLowerCase())) { - alert("Please select an image"); + toast({ + title: "Invalid Image", + description: + "Please select an image with a valid extension like PNG, JPG, JPEG, or WEBP.", + }); resetImagePaths(); } } else { @@ -397,7 +424,10 @@ const Home = () => { e.dataTransfer.files.length === 0 ) { logit("👎 No valid files dropped"); - alert("Please drag and drop an image"); + toast({ + title: "Invalid Image", + description: "Please drag and drop an image", + }); return; } const type = e.dataTransfer.items[0].type; @@ -409,7 +439,10 @@ const Home = () => { !allowedFileTypes.includes(extension.toLowerCase()) ) { logit("🚫 Invalid file dropped"); - alert("Please drag and drop an image"); + toast({ + title: "Invalid Image", + description: "Please drag and drop an image", + }); } else { logit("🖼 Setting image path: ", filePath); setImagePath(filePath); @@ -435,7 +468,10 @@ const Home = () => { !type.includes("image") && !allowedFileTypes.includes(extension.toLowerCase()) ) { - alert("Please drag and drop an image"); + toast({ + title: "Invalid Image", + description: "Please drag and drop an image", + }); } else { setImagePath(filePath); var dirname = filePath.match(/(.*)[\/\\]/)[1] || ""; @@ -501,7 +537,10 @@ const Home = () => { logit("🏁 UPSCAYL"); } } else { - alert(`Please select an image to upscale`); + toast({ + title: "No image selected", + description: "Please select an image to upscale", + }); logit("🚫 No valid image selected"); } }; diff --git a/renderer/styles/globals.css b/renderer/styles/globals.css index 472d006..7301bf1 100644 --- a/renderer/styles/globals.css +++ b/renderer/styles/globals.css @@ -239,79 +239,79 @@ } /* SHADCN CHANGES */ -/* @tailwind base; - @tailwind components; - @tailwind utilities; +@tailwind base; +@tailwind components; +@tailwind utilities; - @layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; - --radius: 0.5rem; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - } + --radius: 0.5rem; } - @layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - } - } */ + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +}