diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 2d79b87c..b8bb01d8 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -38,14 +38,12 @@
"lucide-react": "^1.6.0",
"monaco-editor": "^0.55.1",
"motion": "^12.38.0",
- "next-themes": "^0.4.6",
"radix-ui": "^1.4.3",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-is": "^19.2.4",
"react-use-measure": "^2.1.7",
"recharts": "^3.8.1",
- "sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss-animate": "^1.0.7",
"xterm-addon-search": "^0.13.0",
@@ -4896,16 +4894,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/next-themes": {
- "version": "0.4.6",
- "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
- "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
- "license": "MIT",
- "peerDependencies": {
- "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
- }
- },
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
@@ -5458,16 +5446,6 @@
"node": ">=8"
}
},
- "node_modules/sonner": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
- "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
- "license": "MIT",
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
- "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- }
- },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 310d1235..10cca7a0 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -40,14 +40,12 @@
"lucide-react": "^1.6.0",
"monaco-editor": "^0.55.1",
"motion": "^12.38.0",
- "next-themes": "^0.4.6",
"radix-ui": "^1.4.3",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-is": "^19.2.4",
"react-use-measure": "^2.1.7",
"recharts": "^3.8.1",
- "sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss-animate": "^1.0.7",
"xterm-addon-search": "^0.13.0",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 3bb1da70..fbcaa9a2 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -33,13 +33,13 @@ function AppContent() {
);
}
-import { Toaster } from 'sonner';
+import { ToastContainer } from './components/ui/toast';
function App() {
return (
-
+
);
}
diff --git a/frontend/src/components/ApiTokensSection.tsx b/frontend/src/components/ApiTokensSection.tsx
index b2271cf4..972e768f 100644
--- a/frontend/src/components/ApiTokensSection.tsx
+++ b/frontend/src/components/ApiTokensSection.tsx
@@ -6,7 +6,7 @@ import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { apiFetch } from '@/lib/api';
import { AdmiralGate } from './AdmiralGate';
import { TierBadge } from './TierBadge';
diff --git a/frontend/src/components/AppStoreView.tsx b/frontend/src/components/AppStoreView.tsx
index d23fde51..6a00f98a 100644
--- a/frontend/src/components/AppStoreView.tsx
+++ b/frontend/src/components/AppStoreView.tsx
@@ -7,7 +7,7 @@ import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFo
import { ScrollArea } from "@/components/ui/scroll-area";
import { Button } from "@/components/ui/button";
import { Search, Rocket, Loader2, Info, ExternalLink, Star } from "lucide-react";
-import { toast } from "sonner";
+import { toast } from "@/components/ui/toast-store";
import { apiFetch } from '@/lib/api';
import { useNodes } from '@/context/NodeContext';
import { useAuth } from '@/context/AuthContext';
diff --git a/frontend/src/components/AuditLogView.tsx b/frontend/src/components/AuditLogView.tsx
index d615c26f..4d1be187 100644
--- a/frontend/src/components/AuditLogView.tsx
+++ b/frontend/src/components/AuditLogView.tsx
@@ -8,7 +8,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import { ChevronLeft, ChevronRight, Search, ScrollText, RefreshCw, Download, ChevronDown } from 'lucide-react';
import { apiFetch } from '@/lib/api';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
interface AuditEntry {
id: number;
diff --git a/frontend/src/components/EditorLayout.tsx b/frontend/src/components/EditorLayout.tsx
index fc081f6f..7cd7d9f9 100644
--- a/frontend/src/components/EditorLayout.tsx
+++ b/frontend/src/components/EditorLayout.tsx
@@ -22,7 +22,7 @@ import { Plus, Trash2, Play, Square, Save, Terminal, RotateCw, CloudDownload, Pe
import type { LucideIcon } from 'lucide-react';
import { UserProfileDropdown } from './UserProfileDropdown';
import { apiFetch, fetchForNode } from '@/lib/api';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { Label } from './ui/label';
import { Command, CommandInput, CommandList, CommandItem } from './ui/command';
import { ScrollArea } from './ui/scroll-area';
diff --git a/frontend/src/components/FleetSnapshots.tsx b/frontend/src/components/FleetSnapshots.tsx
index fb96ce66..c134cfea 100644
--- a/frontend/src/components/FleetSnapshots.tsx
+++ b/frontend/src/components/FleetSnapshots.tsx
@@ -19,7 +19,7 @@ import {
} from '@/components/ui/alert-dialog';
import { apiFetch } from '@/lib/api';
import { useAuth } from '@/context/AuthContext';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
// --- Types ---
diff --git a/frontend/src/components/FleetView.tsx b/frontend/src/components/FleetView.tsx
index 27544638..2f4da2ab 100644
--- a/frontend/src/components/FleetView.tsx
+++ b/frontend/src/components/FleetView.tsx
@@ -17,7 +17,7 @@ import { apiFetch } from '@/lib/api';
import { useLicense } from '@/context/LicenseContext';
import { ProGate } from './ProGate';
import FleetSnapshots from './FleetSnapshots';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
// --- Types ---
diff --git a/frontend/src/components/HomeDashboard.tsx b/frontend/src/components/HomeDashboard.tsx
index ea09d1ef..3eb7c999 100644
--- a/frontend/src/components/HomeDashboard.tsx
+++ b/frontend/src/components/HomeDashboard.tsx
@@ -8,7 +8,7 @@ import { Input } from './ui/input';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from './ui/dialog';
import { Activity, Square, ArrowRight, Plus, Cpu, HardDrive, MemoryStick, Network } from 'lucide-react';
import { apiFetch } from '@/lib/api';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { Label } from './ui/label';
interface Stats {
diff --git a/frontend/src/components/NodeManager.tsx b/frontend/src/components/NodeManager.tsx
index 69524e80..238f91fc 100644
--- a/frontend/src/components/NodeManager.tsx
+++ b/frontend/src/components/NodeManager.tsx
@@ -2,7 +2,7 @@ import { useState } from 'react';
import { useNodes } from '@/context/NodeContext';
import type { Node } from '@/context/NodeContext';
import { apiFetch } from '@/lib/api';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogTrigger } from './ui/dialog';
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from './ui/alert-dialog';
import { Button } from './ui/button';
diff --git a/frontend/src/components/RegistriesSection.tsx b/frontend/src/components/RegistriesSection.tsx
index 7b1c1955..b1c4236d 100644
--- a/frontend/src/components/RegistriesSection.tsx
+++ b/frontend/src/components/RegistriesSection.tsx
@@ -6,7 +6,7 @@ import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { apiFetch } from '@/lib/api';
import { AdmiralGate } from './AdmiralGate';
import { TierBadge } from './TierBadge';
diff --git a/frontend/src/components/ResourcesView.tsx b/frontend/src/components/ResourcesView.tsx
index 94f327dc..2836585e 100644
--- a/frontend/src/components/ResourcesView.tsx
+++ b/frontend/src/components/ResourcesView.tsx
@@ -9,7 +9,7 @@ import { Skeleton } from "@/components/ui/skeleton";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { apiFetch } from '@/lib/api';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { Trash2, HardDrive, Network, PackageMinus, MonitorX, MoreVertical, AlertTriangle, ShieldCheck } from 'lucide-react';
import { useNodes } from '@/context/NodeContext';
import { useAuth } from '@/context/AuthContext';
diff --git a/frontend/src/components/SSOSection.tsx b/frontend/src/components/SSOSection.tsx
index 11ed457c..07b3467d 100644
--- a/frontend/src/components/SSOSection.tsx
+++ b/frontend/src/components/SSOSection.tsx
@@ -5,7 +5,7 @@ import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Badge } from '@/components/ui/badge';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { apiFetch } from '@/lib/api';
import { AdmiralGate } from './AdmiralGate';
import { TierBadge } from './TierBadge';
diff --git a/frontend/src/components/ScheduledOperationsView.tsx b/frontend/src/components/ScheduledOperationsView.tsx
index 035604b8..5130564e 100644
--- a/frontend/src/components/ScheduledOperationsView.tsx
+++ b/frontend/src/components/ScheduledOperationsView.tsx
@@ -12,7 +12,7 @@ import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import { Clock, Plus, Pencil, Trash2, History, RefreshCw, Play, ChevronLeft, ChevronRight, Download } from 'lucide-react';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { apiFetch } from '@/lib/api';
import cronstrue from 'cronstrue';
diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx
index 1d1292a7..0db67909 100644
--- a/frontend/src/components/SettingsModal.tsx
+++ b/frontend/src/components/SettingsModal.tsx
@@ -8,7 +8,7 @@ import {
import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { apiFetch } from '@/lib/api';
import {
Shield, Activity, Bell, Code, Server, Package,
diff --git a/frontend/src/components/StackAlertSheet.tsx b/frontend/src/components/StackAlertSheet.tsx
index 1dde86e6..887f3772 100644
--- a/frontend/src/components/StackAlertSheet.tsx
+++ b/frontend/src/components/StackAlertSheet.tsx
@@ -12,7 +12,7 @@ import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { Trash2, HelpCircle, AlertTriangle, Info, CheckCircle2, Loader2 } from 'lucide-react';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { apiFetch } from '@/lib/api';
import { useNodes } from '@/context/NodeContext';
import { useAuth } from '@/context/AuthContext';
diff --git a/frontend/src/components/settings/AppStoreSection.tsx b/frontend/src/components/settings/AppStoreSection.tsx
index 413eca89..bf078823 100644
--- a/frontend/src/components/settings/AppStoreSection.tsx
+++ b/frontend/src/components/settings/AppStoreSection.tsx
@@ -3,7 +3,7 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Skeleton } from '@/components/ui/skeleton';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { apiFetch } from '@/lib/api';
import { RefreshCw } from 'lucide-react';
import type { PatchableSettings } from './types';
diff --git a/frontend/src/components/settings/LicenseSection.tsx b/frontend/src/components/settings/LicenseSection.tsx
index 53c20d1b..e4faddce 100644
--- a/frontend/src/components/settings/LicenseSection.tsx
+++ b/frontend/src/components/settings/LicenseSection.tsx
@@ -3,7 +3,7 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { useLicense } from '@/context/LicenseContext';
import { TierBadge } from '@/components/TierBadge';
import {
diff --git a/frontend/src/components/settings/NotificationsSection.tsx b/frontend/src/components/settings/NotificationsSection.tsx
index 51eef9ea..febff745 100644
--- a/frontend/src/components/settings/NotificationsSection.tsx
+++ b/frontend/src/components/settings/NotificationsSection.tsx
@@ -7,7 +7,7 @@ import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { Badge } from '@/components/ui/badge';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { apiFetch } from '@/lib/api';
import { useNodes } from '@/context/NodeContext';
import { RefreshCw, Info } from 'lucide-react';
diff --git a/frontend/src/components/settings/UsersSection.tsx b/frontend/src/components/settings/UsersSection.tsx
index 84ee66a9..19375e19 100644
--- a/frontend/src/components/settings/UsersSection.tsx
+++ b/frontend/src/components/settings/UsersSection.tsx
@@ -9,7 +9,7 @@ import {
AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { apiFetch } from '@/lib/api';
import { useAuth, type UserRole } from '@/context/AuthContext';
import { useLicense } from '@/context/LicenseContext';
diff --git a/frontend/src/components/settings/WebhooksSection.tsx b/frontend/src/components/settings/WebhooksSection.tsx
index dd9c2577..6c85e1e4 100644
--- a/frontend/src/components/settings/WebhooksSection.tsx
+++ b/frontend/src/components/settings/WebhooksSection.tsx
@@ -6,7 +6,7 @@ import { Badge } from '@/components/ui/badge';
import { Switch } from '@/components/ui/switch';
import { Skeleton } from '@/components/ui/skeleton';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
-import { toast } from 'sonner';
+import { toast } from '@/components/ui/toast-store';
import { apiFetch } from '@/lib/api';
import { ProGate } from '@/components/ProGate';
import { TierBadge } from '@/components/TierBadge';
diff --git a/frontend/src/components/ui/sonner.tsx b/frontend/src/components/ui/sonner.tsx
deleted file mode 100644
index 1128edfc..00000000
--- a/frontend/src/components/ui/sonner.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { useTheme } from "next-themes"
-import { Toaster as Sonner } from "sonner"
-
-type ToasterProps = React.ComponentProps
-
-const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme()
-
- return (
-
- )
-}
-
-export { Toaster }
diff --git a/frontend/src/components/ui/toast-store.ts b/frontend/src/components/ui/toast-store.ts
new file mode 100644
index 00000000..63795724
--- /dev/null
+++ b/frontend/src/components/ui/toast-store.ts
@@ -0,0 +1,51 @@
+import { useSyncExternalStore } from 'react';
+
+export type ToastType = 'success' | 'error' | 'warning' | 'info';
+
+export interface Toast {
+ id: string;
+ type: ToastType;
+ message: string;
+ createdAt: number;
+}
+
+let toasts: Toast[] = [];
+const listeners: Set<() => void> = new Set();
+let idCounter = 0;
+
+function notify() {
+ listeners.forEach((fn) => fn());
+}
+
+function addToast(type: ToastType, message: string) {
+ const id = `toast-${++idCounter}-${Date.now()}`;
+ toasts = [...toasts, { id, type, message, createdAt: Date.now() }];
+ notify();
+}
+
+export function removeToast(id: string) {
+ toasts = toasts.filter((t) => t.id !== id);
+ notify();
+}
+
+function subscribe(callback: () => void) {
+ listeners.add(callback);
+ return () => {
+ listeners.delete(callback);
+ };
+}
+
+function getSnapshot() {
+ return toasts;
+}
+
+export function useToasts() {
+ return useSyncExternalStore(subscribe, getSnapshot);
+}
+
+export const toast = {
+ success: (message: string) => addToast('success', message),
+ error: (message: string) => addToast('error', message),
+ warning: (message: string) => addToast('warning', message),
+ info: (message: string) => addToast('info', message),
+};
diff --git a/frontend/src/components/ui/toast.tsx b/frontend/src/components/ui/toast.tsx
new file mode 100644
index 00000000..c83470b8
--- /dev/null
+++ b/frontend/src/components/ui/toast.tsx
@@ -0,0 +1,169 @@
+import React, { useEffect, useRef, useState, useCallback } from 'react';
+import { createPortal } from 'react-dom';
+import { AnimatePresence, motion } from 'motion/react';
+import { useToasts, removeToast, type ToastType } from './toast-store';
+
+/* ── Durations & Config ── */
+
+const DURATIONS: Record = {
+ success: 4000,
+ error: 6000,
+ warning: 5000,
+ info: 4000,
+};
+
+const MAX_VISIBLE = 5;
+
+/* ── SVG Icons (matching Sera UI exactly — h-6 w-6, stroke-based) ── */
+
+const InfoIcon: React.FC<{ className?: string }> = ({ className }) => (
+
+);
+
+const SuccessIcon: React.FC<{ className?: string }> = ({ className }) => (
+
+);
+
+const WarningIcon: React.FC<{ className?: string }> = ({ className }) => (
+
+);
+
+const ErrorIcon: React.FC<{ className?: string }> = ({ className }) => (
+
+);
+
+const CloseIcon: React.FC<{ className?: string }> = ({ className }) => (
+
+);
+
+/* ── Type config (matching Sera UI's notificationConfig) ── */
+
+const notificationConfig: Record = {
+ info: {
+ iconColor: 'text-blue-500 dark:text-blue-400',
+ icon: ,
+ gradient: 'from-blue-100/60 to-transparent dark:from-blue-900/20 dark:to-transparent',
+ },
+ success: {
+ iconColor: 'text-green-500 dark:text-green-400',
+ icon: ,
+ gradient: 'from-green-100/60 to-transparent dark:from-green-900/20 dark:to-transparent',
+ },
+ warning: {
+ iconColor: 'text-yellow-500 dark:text-yellow-400',
+ icon: ,
+ gradient: 'from-yellow-100/60 to-transparent dark:from-yellow-900/20 dark:to-transparent',
+ },
+ error: {
+ iconColor: 'text-red-500 dark:text-red-400',
+ icon: ,
+ gradient: 'from-red-100/60 to-transparent dark:from-red-900/20 dark:to-transparent',
+ },
+};
+
+/* ── ToastItem — faithful Sera UI Notification replica ── */
+
+function ToastItem({ id, type, message }: { id: string; type: ToastType; message: string }) {
+ const [hovered, setHovered] = useState(false);
+ const timerRef = useRef | null>(null);
+ const remainingRef = useRef(DURATIONS[type]);
+ const startRef = useRef(0);
+ const config = notificationConfig[type];
+ const duration = DURATIONS[type];
+
+ const dismiss = useCallback(() => {
+ removeToast(id);
+ }, [id]);
+
+ // Auto-dismiss timer with hover pause
+ useEffect(() => {
+ if (hovered) {
+ if (timerRef.current) {
+ clearTimeout(timerRef.current);
+ timerRef.current = null;
+ }
+ remainingRef.current -= Date.now() - startRef.current;
+ return;
+ }
+ startRef.current = Date.now();
+ timerRef.current = setTimeout(dismiss, remainingRef.current);
+ return () => {
+ if (timerRef.current) clearTimeout(timerRef.current);
+ };
+ }, [hovered, dismiss]);
+
+ return (
+ setHovered(true)}
+ onMouseLeave={() => setHovered(false)}
+ >
+ {/* Gradient overlay — exact Sera UI pattern */}
+
+
+ {/* Content */}
+
+
+ {config.icon}
+
+
+
+
+
+ {/* Progress bar — Sera UI style with Framer Motion */}
+
+
+
+
+ );
+}
+
+/* ── ToastContainer ── */
+
+export function ToastContainer() {
+ const toasts = useToasts();
+ const visible = toasts.slice(-MAX_VISIBLE);
+
+ return createPortal(
+
+
+ {visible.map((t) => (
+
+ ))}
+
+
,
+ document.body
+ );
+}