feat: Add ChatProvider for managing chat state and API interactions
Some checks failed
GBCI / build (push) Failing after 3m42s

- Implemented ChatProvider to manage chat context and user state.
- Added API fetching utility for chat instance and activity handling.
- Integrated chat service with methods for sending activities.
- Updated Tailwind CSS configuration to include additional content paths and custom utilities.
- Added client-side rendering directive to mode-toggle component.
- Created README.md for settings documentation.
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-06-21 14:30:11 -03:00
parent 1d7ac7c0a2
commit 22452abf36
29 changed files with 1233 additions and 1016 deletions

View file

@ -1,6 +1,6 @@
"use client";
import React, { createContext, useContext, useState, useEffect } from 'react';
import { User } from '../types';
const ChatContext = createContext(undefined);
@ -14,7 +14,7 @@ async function apiFetch(endpoint, options = {}) {
export function ChatProvider({ children }) {
const [line, setLine] = useState(null);
const [instance, setInstance] = useState(null);
const [user] = useState<User>({
const [user] = useState({
id: `user_${Math.random().toString(36).slice(2)}`,
name: 'You',
});

View file

@ -1,23 +0,0 @@
import React from 'react';
import { PersonSelector } from './selector/person-selector';
import { ProjectorView } from './projector/projector-view';
import { ChatWindow } from './chat/chat-window';
import '../styles/layout.css';
export function ChatLayout() {
return (
<div className="chat-layout">
<div className="sidebar">
<PersonSelector />
</div>
<div className="main-content">
<div className="projector">
<ProjectorView />
</div>
<div className="chat-area">
<ChatWindow />
</div>
</div>
</div>
);
}

View file

@ -1,20 +0,0 @@
import React from 'react';
import { useChat } from '../../providers/chat-provider';
import '../../styles/chat.css';
export function ChatHeader() {
const { instance } = useChat();
return (
<div className="chat-header">
<div className="header-content">
<h2 className="header-title">{instance?.name || 'Qwen Chat'}</h2>
<span className="header-subtitle">Online</span>
</div>
<button className="header-button">
<svg className="icon" viewBox="0 0 24 24">
<path d="M12 10a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-7 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm14 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4z" />
</svg>
</button>
</div>
);
}

View file

@ -1,67 +0,0 @@
import React from 'react';
import { useChat } from '../../providers/chat-provider';
import { useSound } from '../../providers/sound-provider';
import '../../styles/chat.css';
export function ChatInput() {
const [message, setMessage] = React.useState('');
const { sendActivity } = useChat();
const { playSound } = useSound();
const handleSend = () => {
if (!message.trim()) return;
playSound('send');
sendActivity({
type: 'message',
text: message.trim(),
});
setMessage('');
};
return (
<>
<div className="input-container">
<button className="icon-button" onClick={() => playSound('click')}>
<svg className="icon" viewBox="0 0 24 24">
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" />
</svg>
</button>
<button className="icon-button">
<svg className="icon" viewBox="0 0 24 24">
<path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zM8 14s1.5 2 4 2 4-2 4-2M9 9h.01M15 9h.01" />
</svg>
</button>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
className="chat-input"
placeholder="Type a message..."
/>
{message.trim().length > 0 ? (
<button className="send-button" onClick={handleSend}>
<svg className="icon" viewBox="0 0 24 24">
<path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z" />
</svg>
</button>
) : (
<button className="icon-button" onClick={() => playSound('click')}>
<svg className="icon" viewBox="0 0 24 24">
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
<path d="M19 10v2a7 7 0 0 1-14 0v-2M12 19v4" />
</svg>
</button>
)}
</div>
{/* <EmojiPicker
visible={showEmoji}
onClose={() => {
playSound('click');
setShowEmoji(false);
}}
onEmojiSelect={handleEmojiSelect}
/> */}
</>
);
}

View file

@ -1,31 +0,0 @@
"use client";
import React from 'react';
import { MessageList } from './message-list';
import { ChatInput } from './chat-input';
import { ChatHeader } from './chat-header';
import { useChat } from '../../providers/chat-provider';
import { Message } from '../../types';
import '../../styles/chat.css';
export function ChatWindow() {
const { line } = useChat();
const [messages, setMessages] = React.useState<Message[]>([]);
React.useEffect(() => {
if (!line) return;
const subscription = line.activity$.subscribe((activity) => {
if (activity.type === 'message') {
setMessages((prev) => [...prev, activity]);
}
});
return () => subscription.unsubscribe();
}, [line]);
return (
<div className="chat-window">
<ChatHeader />
<MessageList messages={messages} />
<ChatInput />
</div>
);
}

View file

@ -1,41 +0,0 @@
import React from 'react';
import { useChat } from '../../providers/chat-provider';
import { useSound } from '../../providers/sound-provider';
import { Message } from '../../types';
import '../../styles/chat.css';
interface MessageListProps {
messages: Message[];
}
export function MessageList({ messages }: MessageListProps) {
const scrollRef = React.useRef(null);
const { user } = useChat();
const { playSound } = useSound();
const prevMessagesLength = React.useRef(messages.length);
React.useEffect(() => {
if (messages.length > prevMessagesLength.current) {
const lastMessage = messages[messages.length - 1];
if (lastMessage.from.id !== user.id) {
playSound('receive');
}
scrollRef.current?.scrollIntoView({ behavior: 'smooth' });
}
prevMessagesLength.current = messages.length;
}, [messages]);
return (
<div className="message-list" ref={scrollRef}>
{messages.map((message, index) => (
<div
key={`${message.id}-${index}`}
className={`message-container ${message.from.id === user.id ? 'user-message' : 'bot-message'}`}
>
<p className="message-text">{message.text}</p>
<span className="message-time">{new Date(message.timestamp).toLocaleTimeString()}</span>
</div>
))}
</div>
);
}

View file

@ -1,14 +0,0 @@
import React from 'react';
import '../../styles/projector.css';
interface ImageViewerProps {
url: string;
}
export function ImageViewer({ url }: ImageViewerProps) {
return (
<div className="image-container">
<img src={url} className="projector-image" alt="Projected content" />
</div>
);
}

View file

@ -1,15 +0,0 @@
import React from 'react';
import ReactMarkdown from 'react-markdown';
import '../../styles/projector.css';
interface MarkdownViewerProps {
content: string;
}
export function MarkdownViewer({ content }: MarkdownViewerProps) {
return (
<div className="markdown-container">
<ReactMarkdown>{content}</ReactMarkdown>
</div>
);
}

View file

@ -1,38 +0,0 @@
"use client";
import React from 'react';
import { VideoPlayer } from './video-player';
import { ImageViewer } from './image-viewer';
import { MarkdownViewer } from './markdown-viewer';
import { useChat } from '../../providers/chat-provider';
import '../../styles/projector.css';
export function ProjectorView() {
const { line } = useChat();
const [content, setContent] = React.useState(null);
React.useEffect(() => {
if (!line) return;
const subscription = line.activity$.subscribe((activity) => {
if (activity.type === 'event' && activity.name === 'project') {
setContent(activity.value);
}
});
return () => subscription.unsubscribe();
}, [line]);
const renderContent = () => {
if (!content) return null;
switch (content.type) {
case 'video':
return <VideoPlayer url={content.url} />;
case 'image':
return <ImageViewer url={content.url} />;
case 'markdown':
return <MarkdownViewer content={content.text} />;
default:
return null;
}
};
return <div className="projector-container">{renderContent()}</div>;
}

View file

@ -1,14 +0,0 @@
import React from 'react';
import '../../styles/projector.css';
interface VideoPlayerProps {
url: string;
}
export function VideoPlayer({ url }: VideoPlayerProps) {
return (
<div className="video-container">
<video controls src={url} className="projector-video" />
</div>
);
}

View file

@ -1,41 +0,0 @@
"use client";
import React from 'react';
import { useChat } from '../../providers/chat-provider';
import '../../styles/selector.css';
export function PersonSelector() {
const [search, setSearch] = React.useState('');
const { instance } = useChat();
return (
<div className="selector-container">
<div className="selector-header">
{instance?.logo && <img src={instance.logo} className="selector-logo" alt="Logo" />}
</div>
<div className="search-container">
<svg className="search-icon" viewBox="0 0 24 24">
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
className="search-input"
placeholder="Search conversations..."
/>
</div>
<div className="selector-list">
{['FAQ', 'Support', 'Sales'].map((item) => (
<div key={item} className="selector-item">
<div className="selector-avatar">
<span>{item[0]}</span>
</div>
<div className="item-content">
<h3 className="item-title">{item}</h3>
<p className="item-subtitle">Start a conversation</p>
</div>
</div>
))}
</div>
</div>
);
}

View file

@ -1,29 +0,0 @@
"use client";
import React, { useEffect, useState } from 'react';
export function SoundInitializer({ children }) {
const [isReady, setIsReady] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const initializeSounds = async () => {
try {
// Simulate sound initialization
setIsReady(true);
} catch (err) {
setError(err.message || 'Failed to initialize sounds');
}
};
initializeSounds();
}, []);
if (error) {
return <div className="error-container"><p>Error: {error}</p></div>;
}
if (!isReady) {
return <div className="loading-container"><p>Loading sounds...</p></div>;
}
return <>{children}</>;
}

View file

@ -1,14 +0,0 @@
export function formatTimestamp(date) {
return new Intl.DateTimeFormat('en-US', {
hour: '2-digit',
minute: '2-digit',
}).format(date);
}
export function cn(...classes) {
return classes.filter(Boolean).join(' ');
}
export function generateId() {
return Math.random().toString(36).slice(2);
}

View file

@ -1,17 +1,354 @@
import React from 'react';
import { ChatProvider } from './providers/chat-provider';
import { ChatLayout } from './components/chat-layout';
import { SoundInitializer } from './components/sound-initializer';
import { SoundProvider } from './providers/sound-provider';
"use client";
import React, { useState, useRef, useEffect } from 'react';
import {
Send, Plus, Menu, Search, Archive, Trash2, Edit3,
MessageSquare, User, Bot, Copy, ThumbsUp, ThumbsDown,
RotateCcw, Share, MoreHorizontal, Image, Video, FileText,
Code, Table, Music, Mic, Settings, Zap, Brain, Sparkles,
BarChart2, Clock, Globe, Lock, Unlock, Book, Feather,
Camera, Film, Headphones, Download, Upload, Link, Mail,
AlertCircle, CheckCircle, HelpCircle, Info, Star, Heart,
Award, Gift, Coffee, ShoppingCart, CreditCard, Key,
Map, Navigation, Phone, Tag, Watch, Wifi,
Cloud, Sun, Moon, Umbrella, Droplet, Wind
} from 'lucide-react';
export default function Chat() {
return (
<SoundInitializer>
<SoundProvider>
<ChatProvider>
<ChatLayout />
</ChatProvider>
</SoundProvider>
</SoundInitializer>
const ChatPage = () => {
const [messages, setMessages] = useState([
{
id: 1,
type: 'assistant',
content: "Hello! I'm General Bots, a large language model by Pragmatismo. How can I help you today?",
timestamp: new Date().toISOString()
}
]);
const [input, setInput] = useState('');
const [isTyping, setIsTyping] = useState(false);
const [sidebarOpen, setSidebarOpen] = useState(true);
const [conversations, setConversations] = useState([
{ id: 1, title: 'Current Chat', timestamp: new Date(), active: true },
{ id: 2, title: 'Previous Conversation', timestamp: new Date(Date.now() - 86400000), active: false },
{ id: 3, title: 'Code Review Discussion', timestamp: new Date(Date.now() - 172800000), active: false },
{ id: 4, title: 'Project Planning', timestamp: new Date(Date.now() - 259200000), active: false },
]);
const [activeMode, setActiveMode] = useState('assistant');
const messagesEndRef = useRef(null);
const textareaRef = useRef(null);
// Mode buttons
const modeButtons = [
{ id: 'deep-think', icon: <Brain size={16} />, label: 'Deep Think' },
{ id: 'web', icon: <Globe size={16} />, label: 'Web' },
{ id: 'creative', icon: <Sparkles size={16} />, label: 'Creative' },
{ id: 'image', icon: <Image size={16} />, label: 'Image' },
{ id: 'video', icon: <Video size={16} />, label: 'Video' },
{ id: 'document', icon: <FileText size={16} />, label: 'Document' },
{ id: 'code', icon: <Code size={16} />, label: 'Code' },
{ id: 'data', icon: <Table size={16} />, label: 'Data' },
{ id: 'audio', icon: <Headphones size={16} />, label: 'Audio' },
{ id: 'analysis', icon: <BarChart2 size={16} />, label: 'Analysis' },
{ id: 'research', icon: <Book size={16} />, label: 'Research' },
{ id: 'writing', icon: <Feather size={16} />, label: 'Writing' },
];
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleSubmit = async () => {
if (!input.trim()) return;
const userMessage = {
id: Date.now(),
type: 'user',
content: input.trim(),
timestamp: new Date().toISOString()
};
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsTyping(true);
// Simulate assistant response
setTimeout(() => {
const assistantMessage = {
id: Date.now() + 1,
type: 'assistant',
content: `I understand you're asking about "${input.trim()}". This is a simulated response to demonstrate the chat interface. The actual implementation would connect to your chat provider and send real responses.`,
timestamp: new Date().toISOString()
};
setMessages(prev => [...prev, assistantMessage]);
setIsTyping(false);
}, 1500);
};
const handleKeyDown = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit();
}
};
const formatTimestamp = (timestamp) => {
const date = new Date(timestamp);
const now = new Date();
const diffInHours = (now - date) / (1000 * 60 * 60);
if (diffInHours < 24) {
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
} else if (diffInHours < 48) {
return 'Yesterday';
} else {
return date.toLocaleDateString();
}
};
const newChat = () => {
const newConv = {
id: Date.now(),
title: 'New Chat',
timestamp: new Date(),
active: true
};
setConversations(prev => [newConv, ...prev.map(c => ({ ...c, active: false }))]);
setMessages([{
id: Date.now(),
type: 'assistant',
content: "Hello! I'm General Bots, a large language model by Pragmatismo. How can I help you today?",
timestamp: new Date().toISOString()
}]);
};
const MessageActions = ({ message }) => (
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<button className="p-1.5 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors">
<Copy className="w-3.5 h-3.5" />
</button>
<button className="p-1.5 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors">
<ThumbsUp className="w-3.5 h-3.5" />
</button>
<button className="p-1.5 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors">
<ThumbsDown className="w-3.5 h-3.5" />
</button>
<button className="p-1.5 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors">
<Share className="w-3.5 h-3.5" />
</button>
</div>
);
}
// Auto-resize textarea
useEffect(() => {
const textarea = textareaRef.current;
if (textarea) {
textarea.style.height = 'auto';
textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;
}
}, [input]);
return (
<div className="flex min-h-[calc(100vh-95px)] bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-white">
{/* Sidebar */}
<div
className={`${
sidebarOpen ? 'w-80' : 'w-0'
} transition-all duration-300 ease-in-out bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex flex-col overflow-hidden`}
>
{sidebarOpen && (
<>
{/* Sidebar Header */}
<div className="p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
<button
onClick={newChat}
className="flex items-center gap-3 w-full p-3 rounded-xl border-2 border-dashed border-gray-300 dark:border-gray-600 hover:border-blue-400 dark:hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-all duration-200 group"
>
<Plus className="w-5 h-5 text-gray-600 dark:text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-400" />
<span className="font-medium text-gray-700 dark:text-gray-300 group-hover:text-blue-700 dark:group-hover:text-blue-300">
New Chat
</span>
</button>
</div>
{/* Conversations List */}
<div className="flex-1 overflow-hidden">
<div className="h-full overflow-y-auto px-3 py-2 space-y-1">
{conversations.map(conv => (
<div
key={conv.id}
className={`group relative flex items-center gap-3 p-3 rounded-xl cursor-pointer transition-all duration-200 ${
conv.active
? 'bg-blue-100 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-700'
: 'hover:bg-gray-100 dark:hover:bg-gray-700/50'
}`}
onClick={() => {
setConversations(prev => prev.map(c => ({ ...c, active: c.id === conv.id })));
}}
>
<MessageSquare className={`w-4 h-4 flex-shrink-0 ${
conv.active ? 'text-blue-600 dark:text-blue-400' : 'text-gray-500 dark:text-gray-400'
}`} />
<div className="flex-1 min-w-0">
<div className={`font-medium truncate ${
conv.active ? 'text-blue-900 dark:text-blue-100' : 'text-gray-900 dark:text-gray-100'
}`}>
{conv.title}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400 truncate">
{formatTimestamp(conv.timestamp)}
</div>
</div>
{conv.active && (
<div className="w-2 h-2 rounded-full bg-blue-500 flex-shrink-0"></div>
)}
</div>
))}
</div>
</div>
{/* Sidebar Footer */}
<div className="p-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0">
<div className="flex items-center gap-3 p-3 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-700/50 cursor-pointer transition-colors duration-200">
<User className="w-5 h-5 text-gray-600 dark:text-gray-400" />
<span className="font-medium text-gray-700 dark:text-gray-300">Account</span>
</div>
</div>
</>
)}
</div>
{/* Main Chat Area */}
<div className="flex-1 flex flex-col min-w-0 bg-white dark:bg-gray-900">
{/* Header */}
<div className="flex-shrink-0 p-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<button
onClick={() => setSidebarOpen(!sidebarOpen)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors duration-200"
>
<Menu className="w-5 h-5" />
</button>
<h1 className="text-xl font-semibold text-gray-900 dark:text-white">
{conversations.find(c => c.active)?.title || 'New Chat'}
</h1>
</div>
<button className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors duration-200">
<Search className="w-5 h-5" />
</button>
</div>
</div>
{/* Messages Container - This is the key fix */}
<div className="flex-1 overflow-hidden flex flex-col">
<div className="flex-1 overflow-y-auto px-4 py-6 space-y-6">
{messages.map(message => (
<div
key={message.id}
className={`group flex ${message.type === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div className={`max-w-[85%] md:max-w-[75%] ${
message.type === 'user'
? 'bg-blue-600 text-white'
: 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white'
} rounded-2xl px-4 py-3 shadow-sm`}>
<div className="flex items-start gap-3">
<div className="flex-shrink-0 mt-0.5">
{message.type === 'user' ? (
<User className="w-4 h-4" />
) : (
<Bot className="w-4 h-4" />
)}
</div>
<div className="flex-1 min-w-0">
<div className="whitespace-pre-wrap break-words leading-relaxed">
{message.content}
</div>
<div className={`mt-3 flex items-center justify-between text-xs ${
message.type === 'user'
? 'text-blue-100'
: 'text-gray-500 dark:text-gray-400'
}`}>
<span>{formatTimestamp(message.timestamp)}</span>
{message.type === 'assistant' && <MessageActions message={message} />}
</div>
</div>
</div>
</div>
</div>
))}
{isTyping && (
<div className="flex justify-start">
<div className="max-w-[85%] md:max-w-[75%] bg-gray-100 dark:bg-gray-800 rounded-2xl px-4 py-3 shadow-sm">
<div className="flex items-center gap-3">
<Bot className="w-4 h-4 text-gray-600 dark:text-gray-400" />
<div className="flex gap-1">
<div className="w-2 h-2 rounded-full bg-gray-400 animate-bounce"></div>
<div className="w-2 h-2 rounded-full bg-gray-400 animate-bounce" style={{ animationDelay: '0.2s' }}></div>
<div className="w-2 h-2 rounded-full bg-gray-400 animate-bounce" style={{ animationDelay: '0.4s' }}></div>
</div>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
</div>
{/* Mode Carousel */}
<div className="flex-shrink-0 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900">
<div className="overflow-x-auto px-4 py-3">
<div className="flex gap-2 min-w-max">
{modeButtons.map(button => (
<button
key={button.id}
onClick={() => setActiveMode(button.id)}
className={`flex items-center gap-2 px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 whitespace-nowrap ${
activeMode === button.id
? 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-700'
: 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700'
}`}
>
{button.icon}
<span>{button.label}</span>
</button>
))}
</div>
</div>
</div>
{/* Input Area */}
<div className="flex-shrink-0 p-4 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900">
<div className="relative max-w-4xl mx-auto">
<textarea
ref={textareaRef}
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Type your message..."
className="w-full p-4 pr-14 rounded-2xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent resize-none transition-all duration-200"
rows={1}
style={{ minHeight: '56px', maxHeight: '120px' }}
/>
<button
onClick={handleSubmit}
disabled={!input.trim()}
className={`absolute right-3 bottom-3 p-2.5 rounded-xl transition-all duration-200 ${
input.trim()
? 'bg-blue-600 hover:bg-blue-700 text-white shadow-lg hover:shadow-xl transform hover:scale-105'
: 'bg-gray-300 dark:bg-gray-600 text-gray-500 dark:text-gray-400 cursor-not-allowed'
}`}
>
<Send className="w-5 h-5" />
</button>
</div>
</div>
</div>
</div>
);
};
export default ChatPage;

View file

@ -1,28 +0,0 @@
"use client";
import React, { createContext, useCallback } from 'react';
const SoundContext = createContext(undefined);
export function SoundProvider({ children }) {
const [enabled, setEnabled] = React.useState(true);
const playSound = useCallback(() => {
if (!enabled) return;
const audio = new Audio();
audio.play().catch((err) => console.error('Failed to play sound:', err));
}, [enabled]);
return (
<SoundContext.Provider value={{ playSound, setEnabled }}>
{children}
</SoundContext.Provider>
);
}
export function useSound() {
const context = React.useContext(SoundContext);
if (!context) {
throw new Error('useSound must be used within SoundProvider');
}
return context;
}

View file

@ -1,110 +0,0 @@
.chat-window {
display: flex;
flex-direction: column;
height: 100%;
background-color: #f5f5f5;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: #fff;
border-bottom: 1px solid #ccc;
}
.header-content {
display: flex;
flex-direction: column;
}
.header-title {
font-size: 1.25rem;
font-weight: bold;
margin: 0;
}
.header-subtitle {
font-size: 0.875rem;
color: #007dff;
margin-top: 0.25rem;
}
.message-list {
flex: 1;
overflow-y: auto;
padding: 1rem;
}
.message-container {
max-width: 70%;
margin-bottom: 0.5rem;
padding: 0.75rem;
border-radius: 0.75rem;
}
.user-message {
align-self: flex-end;
background-color: rgba(0, 180, 255, 0.15);
border: 1px solid #00cfff;
}
.bot-message {
align-self: flex-start;
background-color: rgba(180, 0, 255, 0.1);
border: 1px solid #c400ff;
}
.message-text {
margin: 0;
}
.message-time {
font-size: 0.75rem;
color: #666;
margin-top: 0.25rem;
}
.input-container {
display: flex;
align-items: center;
padding: 1rem;
border-top: 1px solid #ccc;
background-color: #fff;
}
.chat-input {
flex: 1;
min-height: 2.5rem;
max-height: 6rem;
padding: 0.5rem 1rem;
margin: 0 0.5rem;
background-color: #f0f0f0;
color: #000;
border: 1px solid #ccc;
border-radius: 1.25rem;
resize: none;
}
.icon-button {
background: none;
border: none;
color: #007dff;
cursor: pointer;
padding: 0.5rem;
}
.send-button {
background-color: rgba(0, 180, 255, 0.15);
border: 1px solid #00cfff;
border-radius: 50%;
padding: 0.5rem;
cursor: pointer;
}
.icon {
width: 1.5rem;
height: 1.5rem;
fill: currentColor;
}

View file

@ -1,25 +0,0 @@
.chat-layout {
display: flex;
height: 100vh;
background-color: #111;
}
.sidebar {
width: 18rem;
border-right: 1px solid #333;
}
.main-content {
flex: 1;
display: flex;
flex-direction: column;
}
.projector {
height: 30%;
border-bottom: 1px solid #333;
}
.chat-area {
flex: 1;
}

View file

@ -1,45 +0,0 @@
.projector-container {
height: 100%;
padding: 1rem;
background-color: #111;
}
.image-container, .video-container {
width: 100%;
height: 100%;
background-color: #111;
border-radius: 0.5rem;
overflow: hidden;
}
.projector-image, .projector-video {
width: 100%;
height: 100%;
object-fit: contain;
}
.markdown-container {
height: 100%;
padding: 1rem;
color: white;
background-color: #111;
border-radius: 0.5rem;
overflow-y: auto;
}
.markdown-container h1,
.markdown-container h2,
.markdown-container h3 {
color: #00f3ff;
}
.markdown-container a {
color: #00f3ff;
text-decoration: none;
}
.markdown-container pre {
background-color: #1a1a1a;
padding: 1rem;
border-radius: 0.25rem;
}

View file

@ -1,87 +0,0 @@
.selector-container {
height: 100%;
background-color: #111;
}
.selector-header {
padding: 1rem;
border-bottom: 1px solid #333;
}
.selector-logo {
height: 2.5rem;
}
.search-container {
display: flex;
align-items: center;
padding: 0.75rem;
border-bottom: 1px solid #333;
}
.search-icon {
width: 1.25rem;
height: 1.25rem;
margin-right: 0.5rem;
color: #00f3ff;
}
.search-input {
flex: 1;
background: none;
border: none;
color: white;
font-size: 1rem;
}
.search-input::placeholder {
color: #666;
}
.selector-list {
height: calc(100% - 7rem);
overflow-y: auto;
}
.selector-item {
display: flex;
padding: 1rem;
border-bottom: 1px solid #333;
cursor: pointer;
}
.selector-item:hover {
background-color: #1a1a1a;
}
.selector-avatar {
width: 3rem;
height: 3rem;
border-radius: 50%;
background-color: #1a1a1a;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #00f3ff;
color: #00f3ff;
font-size: 1.25rem;
font-weight: bold;
}
.item-content {
margin-left: 1rem;
flex: 1;
}
.item-title {
color: white;
font-size: 1rem;
font-weight: bold;
margin: 0;
}
.item-subtitle {
color: #666;
font-size: 0.875rem;
margin-top: 0.25rem;
}

View file

@ -1,137 +0,0 @@
.emoji-picker-modal {
position: fixed;
bottom: 5rem;
right: 2rem;
width: 20rem;
max-height: 25rem;
background-color: whitesmoke;
border: 1px solid #00f3ff;
border-radius: 0.75rem;
box-shadow: 0 0 1rem rgba(0, 243, 255, 0.5);
z-index: 1000;
overflow: hidden;
}
.emoji-picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid rgba(0, 243, 255, 0.2);
}
.emoji-picker-header h3 {
color: #00f3ff;
margin: 0;
}
.close-button {
background: none;
border: none;
color: #00f3ff;
cursor: pointer;
}
.emoji-picker-content {
padding: 1rem;
overflow-y: auto;
max-height: calc(25rem - 3.5rem);
}
.emoji-category {
margin-bottom: 1.5rem;
}
.category-title {
color: #bf00ff;
font-size: 0.875rem;
margin-bottom: 0.75rem;
}
.emoji-grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 0.5rem;
}
.emoji-button {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
padding: 0.25rem;
border-radius: 0.25rem;
}
.emoji-button:hover {
background-color: rgba(0, 243, 255, 0.1);
}
.settings-panel {
background-color: #111;
padding: 1rem;
border-radius: 0.5rem;
border: 1px solid #333;
}
.settings-option {
display: flex;
align-items: center;
padding: 0.75rem;
margin-bottom: 0.5rem;
background-color: #1a1a1a;
border-radius: 0.5rem;
border: 1px solid #333;
}
.option-text {
flex: 1;
color: white;
margin-left: 0.75rem;
}
.switch {
position: relative;
display: inline-block;
width: 3rem;
height: 1.5rem;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #333;
transition: 0.4s;
border-radius: 1.5rem;
}
.slider:before {
position: absolute;
content: "";
height: 1.1rem;
width: 1.1rem;
left: 0.2rem;
bottom: 0.2rem;
background-color: #666;
transition: 0.4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: rgba(0, 243, 255, 0.2);
}
input:checked + .slider:before {
transform: translateX(1.5rem);
background-color: #00f3ff;
}

View file

@ -1,28 +0,0 @@
export interface Message {
id: string;
type: 'message' | 'event';
text?: string;
timestamp: string;
from: User;
attachments?: Attachment[];
}
export interface User {
id: string;
name: string;
avatar?: string;
}
export interface Attachment {
type: 'image' | 'video' | 'document';
url: string;
thumbnailUrl?: string;
}
export interface ChatInstance {
id: string;
name: string;
logo?: string;
botId: string;
webchatToken: string;
}

View file

@ -3,6 +3,7 @@
import { usePathname, useRouter } from 'next/navigation';
import { Button } from '../src/components/ui/button';
import Image from 'next/image';
import { useRef, useEffect } from 'react';
const examples = [
{ name: "Chat", href: "/chat" },
@ -21,30 +22,422 @@ const examples = [
export function Nav() {
const pathname = usePathname();
const router = useRouter();
const scrollContainerRef = useRef<HTMLDivElement>(null);
const navItemsRefs = useRef<(HTMLButtonElement | null)[]>([]);
// Enhanced URL matching to handle sub-routes
const isActive = (href: string) => {
if (href === '/') return pathname === href;
return pathname.startsWith(href);
};
useEffect(() => {
// Load GSAP from CDN
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js';
script.onload = () => {
const gsap = window.gsap;
// Animate navigation items on mount
gsap.fromTo('.nav-item',
{
opacity: 0,
y: -10,
scale: 0.9
},
{
opacity: 1,
y: 0,
scale: 1,
duration: 0.6,
stagger: 0.1,
ease: "back.out(1.7)"
}
);
// Add hover animations
navItemsRefs.current.forEach((item, index) => {
if (item) {
item.addEventListener('mouseenter', () => {
gsap.to(item, {
scale: 1.05,
duration: 0.3,
ease: "power2.out"
});
});
item.addEventListener('mouseleave', () => {
gsap.to(item, {
scale: 1,
duration: 0.3,
ease: "power2.out"
});
});
item.addEventListener('click', () => {
gsap.to(item, {
scale: 0.95,
duration: 0.1,
yoyo: true,
repeat: 1,
ease: "power2.inOut"
});
});
}
});
// Animate logo
gsap.fromTo('.logo',
{
opacity: 0,
x: -20,
rotate: -10
},
{
opacity: 1,
x: 0,
rotate: 0,
duration: 0.8,
ease: "elastic.out(1, 0.5)"
}
);
};
document.head.appendChild(script);
return () => {
if (document.head.contains(script)) {
document.head.removeChild(script);
}
};
}, []);
// Smooth scroll functions for mobile
const scrollLeft = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollBy({
left: -150,
behavior: 'smooth'
});
}
};
const scrollRight = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollBy({
left: 150,
behavior: 'smooth'
});
}
};
return (
<div className="examples-nav-container">
<div className="examples-nav-inner" style={{ display: 'flex', alignItems: 'center' }}>
<Image
src={"/images/generalbots-logo.svg"}
alt="Logo"
width={92}
height={56}
className="logo"
style={{ marginLeft: '10px' , marginRight: '10px' , marginTop: '3px'}} // Add some spacing between logo and buttons
/>
<div style={{ display: 'flex' }}>
{examples.map((example) => (
<Button
key={example.href}
onClick={() => router.push(example.href)}
className={`example-button ${pathname === example.href ? 'active' : ''}`}
<>
<div className="nav-container">
<div className="nav-inner">
<div className="nav-content">
<div className="logo-container">
<Image
src="/images/generalbots-logo.svg"
alt="Logo"
width={64}
height={24}
className="logo"
/>
</div>
<button className="scroll-btn scroll-left" onClick={scrollLeft} aria-label="Scroll left">
&#8249;
</button>
<div
ref={scrollContainerRef}
className="nav-scroll"
>
{example.name}
</Button>
))}
<div className="nav-items">
{examples.map((example, index) => {
const active = isActive(example.href);
return (
<button
key={example.href}
ref={el => navItemsRefs.current[index] = el}
onClick={() => router.push(example.href)}
className={`nav-item ${active ? 'active' : ''}`}
>
{example.name}
<div className="neon-glow"></div>
</button>
);
})}
</div>
</div>
<button className="scroll-btn scroll-right" onClick={scrollRight} aria-label="Scroll right">
&#8250;
</button>
</div>
</div>
</div>
</div>
<div className="nav-spacer"></div>
<style jsx>{`
.nav-container {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 50;
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%);
border-bottom: 1px solid #333;
height: 40px;
box-shadow: 0 2px 20px rgba(0, 255, 255, 0.1);
}
.nav-inner {
max-width: 100%;
margin: 0 auto;
padding: 0 16px;
height: 100%;
}
.nav-content {
display: flex;
align-items: center;
height: 100%;
gap: 8px;
}
.logo-container {
flex-shrink: 0;
padding: 4px;
border-radius: 6px;
background: rgba(0, 255, 255, 0.1);
box-shadow: 0 0 10px rgba(0, 255, 255, 0.3);
}
.logo {
height: 20px;
width: auto;
filter: drop-shadow(0 0 5px rgba(0, 255, 255, 0.5));
}
.scroll-btn {
display: none;
background: rgba(0, 255, 255, 0.2);
border: 1px solid rgba(0, 255, 255, 0.3);
color: #00ffff;
width: 32px;
height: 32px;
border-radius: 50%;
cursor: pointer;
font-size: 18px;
font-weight: bold;
transition: all 0.3s ease;
flex-shrink: 0;
z-index: 10;
}
.scroll-btn:hover {
background: rgba(0, 255, 255, 0.3);
box-shadow: 0 0 15px rgba(0, 255, 255, 0.5);
transform: scale(1.1);
}
.scroll-btn:active {
transform: scale(0.95);
}
.nav-scroll {
flex: 1;
overflow-x: auto;
overflow-y: hidden;
height: 100%;
-ms-overflow-style: none;
scrollbar-width: none;
scroll-behavior: smooth;
position: relative;
}
.nav-scroll::-webkit-scrollbar {
display: none;
}
.nav-items {
display: flex;
align-items: center;
height: 100%;
white-space: nowrap;
gap: 3px;
padding: 0 8px;
}
.nav-item {
position: relative;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(0, 255, 255, 0.2);
color: #e0e0e0;
font-size: 13px;
font-weight: 500;
padding: 6px 14px;
cursor: pointer;
border-radius: 6px;
height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
text-decoration: none;
white-space: nowrap;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
overflow: hidden;
}
.nav-item::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(0, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.nav-item:hover::before {
left: 100%;
}
.nav-item:hover {
background: rgba(0, 255, 255, 0.1);
border-color: rgba(0, 255, 255, 0.4);
color: #00ffff;
box-shadow: 0 0 15px rgba(0, 255, 255, 0.3);
text-shadow: 0 0 8px rgba(0, 255, 255, 0.6);
}
.nav-item.active {
background: linear-gradient(135deg, rgba(0, 255, 255, 0.2), rgba(0, 200, 255, 0.3));
border-color: #00ffff;
color: #ffffff;
box-shadow:
0 0 20px rgba(0, 255, 255, 0.4),
inset 0 0 10px rgba(0, 255, 255, 0.1);
text-shadow: 0 0 10px rgba(0, 255, 255, 0.8);
}
.nav-item.active:hover {
background: linear-gradient(135deg, rgba(0, 255, 255, 0.3), rgba(0, 200, 255, 0.4));
box-shadow:
0 0 25px rgba(0, 255, 255, 0.6),
inset 0 0 15px rgba(0, 255, 255, 0.2);
}
.neon-glow {
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, transparent, rgba(0, 255, 255, 0.3), transparent);
border-radius: 8px;
opacity: 0;
transition: opacity 0.3s ease;
z-index: -1;
}
.nav-item:hover .neon-glow,
.nav-item.active .neon-glow {
opacity: 1;
}
.nav-spacer {
height: 40px;
}
@media (max-width: 768px) {
.nav-container {
height: 36px;
}
.nav-spacer {
height: 36px;
}
.nav-inner {
padding: 0 8px;
}
.nav-content {
gap: 6px;
}
.scroll-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
font-size: 16px;
}
.logo {
height: 16px;
}
.nav-item {
font-size: 12px;
padding: 4px 12px;
height: 28px;
}
}
@media (max-width: 480px) {
.nav-container {
height: 32px;
}
.nav-spacer {
height: 32px;
}
.nav-inner {
padding: 0 6px;
}
.nav-content {
gap: 4px;
}
.scroll-btn {
width: 24px;
height: 24px;
font-size: 14px;
}
.logo {
height: 14px;
}
.nav-item {
font-size: 11px;
padding: 3px 10px;
height: 24px;
}
}
/* Touch-friendly scrolling for mobile */
@media (hover: none) and (pointer: coarse) {
.nav-scroll {
-webkit-overflow-scrolling: touch;
scroll-snap-type: x mandatory;
}
.nav-item {
scroll-snap-align: start;
}
}
`}</style>
</>
);
}

View file

@ -1,19 +1,21 @@
import { ThemeProvider } from '@/components/ui/theme-provider';
import { Nav } from './client-nav';
import './globals.css';
import './globals.css' // This path is correct if the file is in your src/app directory
import { ReactNode } from 'react'
import ModeToggle from '@/components/ui/mode-toggle';
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<body className='flex flex-col min-h-screen'>
<Nav />
{/* <ThemeProvider attribute="class" defaultTheme="system" enableSystem> */}
{/* <ModeToggle /> */}
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<ModeToggle />
{children}
{/* </ThemeProvider> */}
</ThemeProvider>
</body>
</html>
)

14
app/settings/README.md Normal file
View file

@ -0,0 +1,14 @@
https://whoapi.com/domain-availability-api-pricing/
- **Ports Used**:
Main website: (https://www.pragmatismo.com.br).
Webmail (Stalwart): (https://mail.pragmatismo.com.br).
Database (PostgreSQL): .
SSO (Zitadel): (https://sso.pragmatismo.com.br).
Storage (MinIO): (https://drive.pragmatismo.com.br).
ALM (Forgejo): (https://alm.pragmatismo.com.br).
BotServer : (https://gb.pragmatismo.com.br).
Meeting: (https://call.pragmatismo.com.br).
IMAP: 993.
SMTP: 465.

View file

@ -9,17 +9,7 @@ export default function SettingsProfilePage() {
<div>
<h2 className="text-lg font-medium">Profile</h2>
<p className="text-sm text-gray-500">
- **Ports Used**:
Main website: (https://www.pragmatismo.com.br).
Webmail (Stalwart): (https://mail.pragmatismo.com.br).
Database (PostgreSQL): .
SSO (Zitadel): (https://sso.pragmatismo.com.br).
Storage (MinIO): (https://drive.pragmatismo.com.br).
ALM (Forgejo): (https://alm.pragmatismo.com.br).
BotServer : (https://gb.pragmatismo.com.br).
Meeting: (https://call.pragmatismo.com.br).
IMAP: 993.
SMTP: 465.
</p>
</div>
<div className="border-t border-gray-200 my-4" />

View file

@ -12,7 +12,6 @@
"@hookform/resolvers": "^3.9.1",
"@livekit/components-react": "^2.9.3",
"@livekit/components-styles": "^1.1.5",
"@next/font": "^14.2.15",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
"@radix-ui/react-aspect-ratio": "^1.1.2",
@ -50,6 +49,7 @@
"clsx": "2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^2.30.0",
"gsap": "^3.13.0",
"jotai": "^2.12.2",
"livekit-client": "^2.11.3",
"lucide-react": "0.454.0",
@ -81,7 +81,7 @@
"postcss": "8.4.23",
"postcss-load-config": "6.0.1",
"react-test-renderer": "18.2.0",
"tailwindcss": "3.1.8",
"tailwindcss": "3.3.0",
"typescript": "5.6.2"
}
}

336
pnpm-lock.yaml generated
View file

@ -10,13 +10,13 @@ importers:
dependencies:
'@hookform/resolvers':
specifier: ^3.9.1
version: 3.9.1(react-hook-form@7.53.2(react@18.3.1))
version: 3.9.1(react-hook-form@7.58.1(react@18.3.1))
'@livekit/components-react':
specifier: ^2.9.3
version: 2.9.3(livekit-client@2.11.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tslib@2.8.1)
'@next/font':
specifier: ^14.2.15
version: 14.2.15(next@15.3.1(@babel/core@7.18.6)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
'@livekit/components-styles':
specifier: ^1.1.5
version: 1.1.6
'@radix-ui/react-accordion':
specifier: ^1.2.3
version: 1.2.7(@types/react-dom@18.3.1)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -128,6 +128,9 @@ importers:
date-fns:
specifier: ^2.30.0
version: 2.30.0
gsap:
specifier: ^3.13.0
version: 3.13.0
jotai:
specifier: ^2.12.2
version: 2.12.3(@types/react@18.3.1)(react@18.3.1)
@ -139,7 +142,7 @@ importers:
version: 0.454.0(react@18.3.1)
nativewind:
specifier: 2.0.10
version: 2.0.10(react@18.3.1)(tailwindcss@3.1.8(postcss@8.4.35))
version: 2.0.10(react@18.3.1)(tailwindcss@3.3.0(postcss@8.4.35))
next:
specifier: ^15.2.4
version: 15.3.1(@babel/core@7.18.6)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -159,8 +162,8 @@ importers:
specifier: 18.3.1
version: 18.3.1(react@18.3.1)
react-hook-form:
specifier: ^7.53.2
version: 7.53.2(react@18.3.1)
specifier: ^7.56.1
version: 7.58.1(react@18.3.1)
react-markdown:
specifier: 10.1.0
version: 10.1.0(@types/react@18.3.1)(react@18.3.1)
@ -172,7 +175,7 @@ importers:
version: 3.0.2
tailwindcss-animate:
specifier: 1.0.7
version: 1.0.7(tailwindcss@3.1.8(postcss@8.4.35))
version: 1.0.7(tailwindcss@3.3.0(postcss@8.4.35))
uuid:
specifier: 11.0.3
version: 11.0.3
@ -214,8 +217,8 @@ importers:
specifier: 18.2.0
version: 18.2.0(react@18.3.1)
tailwindcss:
specifier: 3.1.8
version: 3.1.8(postcss@8.4.35)
specifier: 3.3.0
version: 3.3.0(postcss@8.4.35)
typescript:
specifier: 5.6.2
version: 5.6.2
@ -1189,6 +1192,10 @@ packages:
cpu: [x64]
os: [win32]
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
'@istanbuljs/load-nyc-config@1.1.0':
resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
engines: {node: '>=8'}
@ -1304,6 +1311,10 @@ packages:
'@livekit/krisp-noise-filter':
optional: true
'@livekit/components-styles@1.1.6':
resolution: {integrity: sha512-V6zfuREC2ksW8z6T6WSbEvdLB5ICVikGz1GtLr59UcxHDyAsKDbuDHAyl3bF3xBqPKYmY3GWF3Qk39rnScyOtA==}
engines: {node: '>=18'}
'@livekit/mutex@1.1.1':
resolution: {integrity: sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==}
@ -1313,11 +1324,6 @@ packages:
'@next/env@15.3.1':
resolution: {integrity: sha512-cwK27QdzrMblHSn9DZRV+DQscHXRuJv6MydlJRpFSqJWZrTYMLzKDeyueJNN9MGd8NNiUKzDQADAf+dMLXX7YQ==}
'@next/font@14.2.15':
resolution: {integrity: sha512-QopYhBmCDDrNDynbi+ZD1hDZXmQXVFo7TmAFp4DQgO/kogz1OLbQ92hPigJbj572eZ3GaaVxNIyYVn3/eAsehg==}
peerDependencies:
next: '*'
'@next/swc-darwin-arm64@15.3.1':
resolution: {integrity: sha512-hjDw4f4/nla+6wysBL07z52Gs55Gttp5Bsk5/8AncQLJoisvTBP0pRIBK/B16/KqQyH+uN4Ww8KkcAqJODYH3w==}
engines: {node: '>= 10'}
@ -1381,6 +1387,10 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@radix-ui/number@1.1.1':
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
@ -2454,18 +2464,6 @@ packages:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
acorn-node@1.8.2:
resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==}
acorn-walk@7.2.0:
resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==}
engines: {node: '>=0.4.0'}
acorn@7.4.1:
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
engines: {node: '>=0.4.0'}
hasBin: true
acorn@8.14.1:
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
engines: {node: '>=0.4.0'}
@ -2506,6 +2504,10 @@ packages:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-regex@6.1.0:
resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
engines: {node: '>=12'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@ -2514,6 +2516,13 @@ packages:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
engines: {node: '>=10'}
ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
@ -2656,6 +2665,9 @@ packages:
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
@ -2794,6 +2806,10 @@ packages:
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
commander@6.2.1:
resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==}
engines: {node: '>= 6'}
@ -2902,9 +2918,6 @@ packages:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
defined@1.0.1:
resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==}
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@ -2920,11 +2933,6 @@ packages:
detect-node-es@1.1.0:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
detective@5.2.1:
resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==}
engines: {node: '>=0.8.0'}
hasBin: true
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
@ -2951,6 +2959,9 @@ packages:
domutils@2.8.0:
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
editions@2.3.1:
resolution: {integrity: sha512-ptGvkwTvGdGfC0hfhKg0MT+TRLRKGtUiWGBInxOm5pz7ssADezahjCUaYuZ8Dr+C05FW0AECIIPt4WBxVINEhA==}
engines: {node: '>=0.8'}
@ -2965,6 +2976,9 @@ packages:
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
enhanced-resolve@5.18.1:
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
engines: {node: '>=10.13.0'}
@ -3230,6 +3244,10 @@ packages:
resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
@ -3278,6 +3296,10 @@ packages:
glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
@ -3296,6 +3318,9 @@ packages:
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
gsap@3.13.0:
resolution: {integrity: sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@ -3456,6 +3481,9 @@ packages:
resolution: {integrity: sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==}
engines: {node: '>=0.12'}
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
jest-changed-files@29.7.0:
resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -3589,6 +3617,10 @@ packages:
node-notifier:
optional: true
jiti@1.21.7:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true
jiti@2.4.2:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
@ -3755,6 +3787,9 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@ -3933,9 +3968,17 @@ packages:
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
minipass@7.1.2:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
@ -3948,6 +3991,9 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@ -4079,6 +4125,9 @@ packages:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@ -4112,6 +4161,10 @@ packages:
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
path-scurry@1.11.1:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
@ -4207,6 +4260,12 @@ packages:
peerDependencies:
postcss: ^8.2.14
postcss-nested@6.0.0:
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.2.14
postcss-selector-parser@6.1.2:
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
engines: {node: '>=4'}
@ -4281,8 +4340,8 @@ packages:
react: '>= 16.8.6'
react-dom: '>= 16.8.6'
react-hook-form@7.53.2:
resolution: {integrity: sha512-YVel6fW5sOeedd1524pltpHX+jgU2u3DSDtXEaBORNdqiNrsX/nUI/iGXONegttg0mJVnfrIkiV0cmTU6Oo2xw==}
react-hook-form@7.58.1:
resolution: {integrity: sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA==}
engines: {node: '>=18.0.0'}
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
@ -4551,6 +4610,10 @@ packages:
signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
simple-lru-cache@0.0.2:
resolution: {integrity: sha512-uEv/AFO0ADI7d99OHDmh1QfYzQk/izT1vCmu/riQfh7qjBVUUgRT87E5s5h7CxWCA/+YoZerykpEthzVrW3LIw==}
@ -4627,6 +4690,10 @@ packages:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
string-width@5.1.2:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
stringify-entities@4.0.4:
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
@ -4634,6 +4701,10 @@ packages:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-ansi@7.1.0:
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
engines: {node: '>=12'}
strip-bom@4.0.0:
resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==}
engines: {node: '>=8'}
@ -4668,6 +4739,11 @@ packages:
stylis@4.2.0:
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
@ -4692,8 +4768,8 @@ packages:
peerDependencies:
tailwindcss: '>=3.0.0 || insiders'
tailwindcss@3.1.8:
resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==}
tailwindcss@3.3.0:
resolution: {integrity: sha512-hOXlFx+YcklJ8kXiCAfk/FMyr4Pm9ck477G0m/us2344Vuj355IpoEDB5UmGAsSpTBmr+4ZhjzW04JuFXkb/fw==}
engines: {node: '>=12.13.0'}
hasBin: true
peerDependencies:
@ -4735,6 +4811,13 @@ packages:
resolution: {integrity: sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==}
engines: {node: '>=0.8'}
thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
tmpl@1.0.5:
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
@ -4758,6 +4841,9 @@ packages:
ts-debounce@4.0.0:
resolution: {integrity: sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==}
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
@ -5008,6 +5094,10 @@ packages:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
wrap-ansi@8.1.0:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@ -5033,10 +5123,6 @@ packages:
'@angular/common': '>= 5.0.0'
'@angular/core': '>= 5.0.0'
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@ -6073,9 +6159,9 @@ snapshots:
'@floating-ui/utils@0.2.9': {}
'@hookform/resolvers@3.9.1(react-hook-form@7.53.2(react@18.3.1))':
'@hookform/resolvers@3.9.1(react-hook-form@7.58.1(react@18.3.1))':
dependencies:
react-hook-form: 7.53.2(react@18.3.1)
react-hook-form: 7.58.1(react@18.3.1)
'@img/sharp-darwin-arm64@0.34.1':
optionalDependencies:
@ -6155,6 +6241,15 @@ snapshots:
'@img/sharp-win32-x64@0.34.1':
optional: true
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
string-width-cjs: string-width@4.2.3
strip-ansi: 7.1.0
strip-ansi-cjs: strip-ansi@6.0.1
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
'@istanbuljs/load-nyc-config@1.1.0':
dependencies:
camelcase: 5.3.1
@ -6367,6 +6462,8 @@ snapshots:
tslib: 2.8.1
usehooks-ts: 3.1.1(react@18.3.1)
'@livekit/components-styles@1.1.6': {}
'@livekit/mutex@1.1.1': {}
'@livekit/protocol@1.36.1':
@ -6375,10 +6472,6 @@ snapshots:
'@next/env@15.3.1': {}
'@next/font@14.2.15(next@15.3.1(@babel/core@7.18.6)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))':
dependencies:
next: 15.3.1(@babel/core@7.18.6)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@next/swc-darwin-arm64@15.3.1':
optional: true
@ -6418,6 +6511,9 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1
'@pkgjs/parseargs@0.11.0':
optional: true
'@radix-ui/number@1.1.1': {}
'@radix-ui/primitive@1.1.2': {}
@ -7522,16 +7618,6 @@ snapshots:
dependencies:
event-target-shim: 5.0.1
acorn-node@1.8.2:
dependencies:
acorn: 7.4.1
acorn-walk: 7.2.0
xtend: 4.0.2
acorn-walk@7.2.0: {}
acorn@7.4.1: {}
acorn@8.14.1: {}
adaptivecards@2.11.1: {}
@ -7566,12 +7652,18 @@ snapshots:
ansi-regex@5.0.1: {}
ansi-regex@6.1.0: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
ansi-styles@5.2.0: {}
ansi-styles@6.2.1: {}
any-promise@1.3.0: {}
anymatch@3.1.3:
dependencies:
normalize-path: 3.0.0
@ -7873,6 +7965,10 @@ snapshots:
balanced-match: 1.0.2
concat-map: 0.0.1
brace-expansion@2.0.2:
dependencies:
balanced-match: 1.0.2
braces@3.0.3:
dependencies:
fill-range: 7.1.1
@ -8004,6 +8100,8 @@ snapshots:
commander@2.20.3: {}
commander@4.1.1: {}
commander@6.2.1: {}
compute-scroll-into-view@1.0.17: {}
@ -8107,8 +8205,6 @@ snapshots:
deepmerge@4.3.1: {}
defined@1.0.1: {}
dequal@2.0.3: {}
detect-libc@2.0.3: {}
@ -8117,12 +8213,6 @@ snapshots:
detect-node-es@1.1.0: {}
detective@5.2.1:
dependencies:
acorn-node: 1.8.2
defined: 1.0.1
minimist: 1.2.8
devlop@1.1.0:
dependencies:
dequal: 2.0.3
@ -8151,6 +8241,8 @@ snapshots:
domelementtype: 2.3.0
domhandler: 4.3.1
eastasianwidth@0.2.0: {}
editions@2.3.1:
dependencies:
errlop: 2.2.0
@ -8162,6 +8254,8 @@ snapshots:
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
enhanced-resolve@5.18.1:
dependencies:
graceful-fs: 4.2.11
@ -8394,6 +8488,11 @@ snapshots:
locate-path: 7.2.0
path-exists: 5.0.0
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
signal-exit: 4.1.0
fraction.js@4.3.7: {}
fs-readdir-recursive@1.1.0: {}
@ -8425,6 +8524,15 @@ snapshots:
glob-to-regexp@0.4.1: {}
glob@10.4.5:
dependencies:
foreground-child: 3.3.1
jackspeak: 3.4.3
minimatch: 9.0.5
minipass: 7.1.2
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
glob@7.2.3:
dependencies:
fs.realpath: 1.0.0
@ -8451,6 +8559,8 @@ snapshots:
graceful-fs@4.2.11: {}
gsap@3.13.0: {}
has-flag@4.0.0: {}
hasown@2.0.2:
@ -8629,6 +8739,12 @@ snapshots:
editions: 2.3.1
textextensions: 2.6.0
jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
jest-changed-files@29.7.0:
dependencies:
execa: 5.1.1
@ -8943,6 +9059,8 @@ snapshots:
- supports-color
- ts-node
jiti@1.21.7: {}
jiti@2.4.2: {}
jotai@2.12.3(@types/react@18.3.1)(react@18.3.1):
@ -9062,6 +9180,8 @@ snapshots:
dependencies:
js-tokens: 4.0.0
lru-cache@10.4.3: {}
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@ -9391,8 +9511,14 @@ snapshots:
dependencies:
brace-expansion: 1.1.11
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.2
minimist@1.2.8: {}
minipass@7.1.2: {}
mkdirp@0.5.6:
dependencies:
minimist: 1.2.8
@ -9401,9 +9527,15 @@ snapshots:
ms@2.1.3: {}
mz@2.7.0:
dependencies:
any-promise: 1.3.0
object-assign: 4.1.1
thenify-all: 1.6.0
nanoid@3.3.11: {}
nativewind@2.0.10(react@18.3.1)(tailwindcss@3.1.8(postcss@8.4.35)):
nativewind@2.0.10(react@18.3.1)(tailwindcss@3.3.0(postcss@8.4.35)):
dependencies:
'@babel/generator': 7.27.0
'@babel/helper-module-imports': 7.18.6
@ -9417,7 +9549,7 @@ snapshots:
postcss-css-variables: 0.18.0(postcss@8.4.35)
postcss-nested: 5.0.6(postcss@8.4.35)
react-is: 18.3.1
tailwindcss: 3.1.8(postcss@8.4.35)
tailwindcss: 3.3.0(postcss@8.4.35)
use-sync-external-store: 1.5.0(react@18.3.1)
transitivePeerDependencies:
- react
@ -9536,6 +9668,8 @@ snapshots:
p-try@2.2.0: {}
package-json-from-dist@1.0.1: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@ -9569,6 +9703,11 @@ snapshots:
path-parse@1.0.7: {}
path-scurry@1.11.1:
dependencies:
lru-cache: 10.4.3
minipass: 7.1.2
path-type@4.0.0: {}
path-type@6.0.0: {}
@ -9637,6 +9776,11 @@ snapshots:
postcss: 8.4.35
postcss-selector-parser: 6.1.2
postcss-nested@6.0.0(postcss@8.4.35):
dependencies:
postcss: 8.4.35
postcss-selector-parser: 6.1.2
postcss-selector-parser@6.1.2:
dependencies:
cssesc: 3.0.0
@ -9731,7 +9875,7 @@ snapshots:
- '@babel/core'
- supports-color
react-hook-form@7.53.2(react@18.3.1):
react-hook-form@7.58.1(react@18.3.1):
dependencies:
react: 18.3.1
@ -10065,6 +10209,8 @@ snapshots:
signal-exit@3.0.7: {}
signal-exit@4.1.0: {}
simple-lru-cache@0.0.2: {}
simple-swizzle@0.2.2:
@ -10133,6 +10279,12 @@ snapshots:
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
string-width@5.1.2:
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 9.2.2
strip-ansi: 7.1.0
stringify-entities@4.0.4:
dependencies:
character-entities-html4: 2.1.0
@ -10142,6 +10294,10 @@ snapshots:
dependencies:
ansi-regex: 5.0.1
strip-ansi@7.1.0:
dependencies:
ansi-regex: 6.1.0
strip-bom@4.0.0: {}
strip-final-newline@2.0.0: {}
@ -10166,6 +10322,16 @@ snapshots:
stylis@4.2.0: {}
sucrase@3.35.0:
dependencies:
'@jridgewell/gen-mapping': 0.3.8
commander: 4.1.1
glob: 10.4.5
lines-and-columns: 1.2.4
mz: 2.7.0
pirates: 4.0.7
ts-interface-checker: 0.1.13
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
@ -10180,22 +10346,23 @@ snapshots:
tailwind-merge@3.0.2: {}
tailwindcss-animate@1.0.7(tailwindcss@3.1.8(postcss@8.4.35)):
tailwindcss-animate@1.0.7(tailwindcss@3.3.0(postcss@8.4.35)):
dependencies:
tailwindcss: 3.1.8(postcss@8.4.35)
tailwindcss: 3.3.0(postcss@8.4.35)
tailwindcss@3.1.8(postcss@8.4.35):
tailwindcss@3.3.0(postcss@8.4.35):
dependencies:
arg: 5.0.2
chokidar: 3.6.0
color-name: 1.1.4
detective: 5.2.1
didyoumean: 1.2.2
dlv: 1.1.3
fast-glob: 3.3.3
glob-parent: 6.0.2
is-glob: 4.0.3
jiti: 1.21.7
lilconfig: 2.1.0
micromatch: 4.0.8
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.1.1
@ -10203,11 +10370,12 @@ snapshots:
postcss-import: 14.1.0(postcss@8.4.35)
postcss-js: 4.0.1(postcss@8.4.35)
postcss-load-config: 3.1.4(postcss@8.4.35)
postcss-nested: 5.0.6(postcss@8.4.35)
postcss-nested: 6.0.0(postcss@8.4.35)
postcss-selector-parser: 6.1.2
postcss-value-parser: 4.2.0
quick-lru: 5.1.1
resolve: 1.22.10
sucrase: 3.35.0
transitivePeerDependencies:
- ts-node
@ -10241,6 +10409,14 @@ snapshots:
textextensions@2.6.0: {}
thenify-all@1.6.0:
dependencies:
thenify: 3.3.1
thenify@3.3.1:
dependencies:
any-promise: 1.3.0
tmpl@1.0.5: {}
to-fast-properties@2.0.0: {}
@ -10257,6 +10433,8 @@ snapshots:
ts-debounce@4.0.0: {}
ts-interface-checker@0.1.13: {}
tslib@1.14.1: {}
tslib@2.4.0: {}
@ -10500,6 +10678,12 @@ snapshots:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@8.1.0:
dependencies:
ansi-styles: 6.2.1
string-width: 5.1.2
strip-ansi: 7.1.0
wrappy@1.0.2: {}
write-file-atomic@4.0.2:
@ -10515,8 +10699,6 @@ snapshots:
'@angular/core': 19.2.4(rxjs@7.8.2)(zone.js@0.15.0)
tslib: 1.14.1
xtend@4.0.2: {}
y18n@5.0.8: {}
yallist@3.1.1: {}

View file

@ -1,3 +1,5 @@
"use client";
import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"

View file

@ -1,81 +1,185 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
}
},
keyframes: {
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
}
}
darkMode: ["class"],
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
// HSL-based colors from the second config
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
},
// Custom colors from the first config
gray: {
50: '#f9fafb',
100: '#f3f4f6',
200: '#e5e7eb',
300: '#d1d5db',
400: '#9ca3af',
500: '#6b7280',
600: '#4b5563',
700: '#374151',
800: '#1f2937',
900: '#111827',
},
blue: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
}
},
borderRadius: {
// From second config
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
// From first config
'xl': '0.75rem',
'2xl': '1rem',
},
animation: {
// From first config
'bounce': 'bounce 1s infinite',
// From second config
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
},
keyframes: {
// From second config
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
}
},
transitionDuration: {
// From first config
'200': '200ms',
'300': '300ms',
},
maxWidth: {
// From first config
'4xl': '56rem',
'85%': '85%',
'75%': '75%',
},
spacing: {
// From first config
'0.5': '0.125rem',
'1.5': '0.375rem',
'2.5': '0.625rem',
'3.5': '0.875rem',
'14': '3.5rem',
'80': '20rem',
}
},
plugins: [require("tailwindcss-animate")],
},
plugins: [
// From second config
require("tailwindcss-animate"),
// From first config
function({ addUtilities }) {
const newUtilities = {
'.scrollbar-thin': {
'scrollbar-width': 'thin',
},
'.scrollbar-thumb-gray-400': {
'scrollbar-color': '#9ca3af transparent',
},
'.scrollbar-thumb-gray-600': {
'scrollbar-color': '#4b5563 transparent',
},
'.scrollbar-track-transparent': {
'scrollbar-track-color': 'transparent',
},
// WebKit scrollbar styles
'.scrollbar-thin::-webkit-scrollbar': {
width: '6px',
height: '6px',
},
'.scrollbar-thin::-webkit-scrollbar-track': {
background: 'transparent',
},
'.scrollbar-thin::-webkit-scrollbar-thumb': {
background: '#9ca3af',
'border-radius': '3px',
},
'.scrollbar-thin::-webkit-scrollbar-thumb:hover': {
background: '#6b7280',
},
'.dark .scrollbar-thin::-webkit-scrollbar-thumb': {
background: '#4b5563',
},
'.dark .scrollbar-thin::-webkit-scrollbar-thumb:hover': {
background: '#374151',
},
}
addUtilities(newUtilities)
}
],
}