gbclient/a.sh

1131 lines
26 KiB
Bash
Raw Normal View History

#!/bin/bash
# Create the full directory structure
mkdir -p my-tauri-app/src/chat/{components,lib,providers,styles,types}
mkdir -p my-tauri-app/src/chat/components/{chat,projector,selector,ui}
mkdir -p my-tauri-app/src/styles
# Create all files with React+HTML equivalents
cat > my-tauri-app/src/chat/components/chat/chat-header.tsx << 'EOL'
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 || '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>
);
}
EOL
cat > my-tauri-app/src/chat/components/chat/chat-input.tsx << 'EOL'
import React from 'react';
import { EmojiPicker } from '../ui/emoji-picker';
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 [showEmoji, setShowEmoji] = React.useState(false);
const { sendActivity } = useChat();
const { playSound } = useSound();
const handleSend = () => {
if (!message.trim()) return;
playSound('send');
sendActivity({
type: 'message',
text: message.trim(),
});
setMessage('');
};
const handleEmojiSelect = (emoji: string) => {
playSound('click');
setMessage(prev => prev + emoji);
};
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" onClick={() => setShowEmoji(true)}>
<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}
/>
</>
);
}
EOL
cat > my-tauri-app/src/chat/components/chat/chat-window.tsx << 'EOL'
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: any) => {
if (activity.type === 'message') {
setMessages(prev => [...prev, activity as Message]);
}
});
return () => subscription.unsubscribe();
}, [line]);
return (
<div className="chat-window">
<ChatHeader />
<MessageList messages={messages} />
<ChatInput />
</div>
);
}
EOL
cat > my-tauri-app/src/chat/components/chat/message-list.tsx << 'EOL'
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<HTMLDivElement>(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>
);
}
EOL
cat > my-tauri-app/src/chat/components/chat-layout.tsx << 'EOL'
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>
);
}
EOL
cat > my-tauri-app/src/chat/components/projector/image-viewer.tsx << 'EOL'
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>
);
}
EOL
cat > my-tauri-app/src/chat/components/projector/markdown-viewer.tsx << 'EOL'
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>
);
}
EOL
cat > my-tauri-app/src/chat/components/projector/projector-view.tsx << 'EOL'
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<any>(null);
React.useEffect(() => {
if (!line) return;
const subscription = line.activity$
.subscribe((activity: any) => {
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>
);
}
EOL
cat > my-tauri-app/src/chat/components/projector/video-player.tsx << 'EOL'
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>
);
}
EOL
cat > my-tauri-app/src/chat/components/selector/person-selector.tsx << 'EOL'
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>
);
}
EOL
cat > my-tauri-app/src/chat/components/sound-initializer.tsx << 'EOL'
import React, { useEffect, useState } from 'react';
import { soundAssets } from '../../../public/sounds/manifest';
import { cacheAssets } from '../lib/asset-loader';
export function SoundInitializer({ children }: { children: React.ReactNode }) {
const [isReady, setIsReady] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const initializeSounds = async () => {
try {
await cacheAssets(Object.values(soundAssets));
setIsReady(true);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to initialize sounds');
}
};
initializeSounds();
}, []);
if (error) {
return (
<div className="error-container">
<p className="error-text">Error: {error}</p>
</div>
);
}
if (!isReady) {
return (
<div className="loading-container">
<p>Loading sounds...</p>
</div>
);
}
return <>{children}</>;
}
EOL
cat > my-tauri-app/src/chat/components/ui/emoji-picker.tsx << 'EOL'
import React from 'react';
import '../../styles/ui.css';
const EMOJI_CATEGORIES = {
"😀 🎮": ["😀", "😎", "🤖", "👾", "🎮", "✨", "🚀", "💫"],
"🌟 💫": ["⭐", "🌟", "💫", "✨", "⚡", "💥", "🔥", "🌈"],
"🤖 🎯": ["🤖", "🎯", "🎲", "🎮", "🕹️", "👾", "💻", "⌨️"]
};
export function EmojiPicker({ visible, onClose, onEmojiSelect }: {
visible: boolean;
onClose: () => void;
onEmojiSelect: (emoji: string) => void;
}) {
if (!visible) return null;
return (
<div className="emoji-picker-modal">
<div className="emoji-picker-header">
<h3>Select Emoji</h3>
<button onClick={onClose} className="close-button">
<svg viewBox="0 0 24 24">
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button>
</div>
<div className="emoji-picker-content">
{Object.entries(EMOJI_CATEGORIES).map(([category, emojis]) => (
<div key={category} className="emoji-category">
<h4 className="category-title">{category}</h4>
<div className="emoji-grid">
{emojis.map(emoji => (
<button
key={emoji}
className="emoji-button"
onClick={() => {
onEmojiSelect(emoji);
onClose();
}}
>
{emoji}
</button>
))}
</div>
</div>
))}
</div>
</div>
);
}
EOL
cat > my-tauri-app/src/chat/components/ui/settings-panel.tsx << 'EOL'
import React from 'react';
import { useSound } from '../../providers/sound-provider';
import '../../styles/ui.css';
export function SettingsPanel() {
const [theme, setTheme] = React.useState('dark');
const [sound, setSound] = React.useState(true);
const [powerMode, setPowerMode] = React.useState(false);
const { setEnabled, playSound } = useSound();
const handleSoundToggle = (value: boolean) => {
setSound(value);
setEnabled(value);
if (value) {
playSound('success');
}
};
const handleThemeChange = () => {
playSound('click');
setTheme(theme === 'dark' ? 'light' : 'dark');
};
const handlePowerMode = (value: boolean) => {
playSound(value ? 'success' : 'click');
setPowerMode(value);
};
return (
<div className="settings-panel">
<div className="settings-option">
{sound ? (
<svg className="icon" viewBox="0 0 24 24">
<path d="M3 15a1 1 0 011-1h2.83a1 1 0 00.8-.4l1.12-1.5a1 1 0 01.8-.4h3.55a1 1 0 00.8-.4l1.12-1.5a1 1 0 01.8-.4H19a1 1 0 011 1v6a1 1 0 01-1 1h-2.83a1 1 0 00-.8-.4l-1.12-1.5a1 1 0 00-.8-.4H9.72a1 1 0 01-.8-.4l-1.12-1.5a1 1 0 00-.8-.4H4a1 1 0 01-1-1v-2zM12 6a1 1 0 011-1h6a1 1 0 011 1v3"/>
</svg>
) : (
<svg className="icon" viewBox="0 0 24 24">
<path d="M3 15a1 1 0 011-1h2.83a1 1 0 00.8-.4l1.12-1.5a1 1 0 01.8-.4h3.55a1 1 0 00.8-.4l1.12-1.5a1 1 0 01.8-.4H19a1 1 0 011 1v6a1 1 0 01-1 1h-2.83a1 1 0 00-.8-.4l-1.12-1.5a1 1 0 00-.8-.4H9.72a1 1 0 01-.8-.4l-1.12-1.5a1 1 0 00-.8-.4H4a1 1 0 01-1-1v-2zM12 6a1 1 0 011-1h6a1 1 0 011 1v3M3 3l18 18"/>
</svg>
)}
<span className="option-text">Sound Effects</span>
<label className="switch">
<input
type="checkbox"
checked={sound}
onChange={(e) => handleSoundToggle(e.target.checked)}
/>
<span className="slider"></span>
</label>
</div>
</div>
);
}
EOL
cat > my-tauri-app/src/chat/index.tsx << 'EOL'
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';
export function Chat() {
return (
<SoundInitializer>
<SoundProvider>
<ChatProvider>
<ChatLayout />
</ChatProvider>
</SoundProvider>
</SoundInitializer>
);
}
EOL
cat > my-tauri-app/src/chat/lib/utils.ts << 'EOL'
export function formatTimestamp(date: Date): string {
return new Intl.DateTimeFormat('en-US', {
hour: '2-digit',
minute: '2-digit',
}).format(date);
}
export function cn(...classes: string[]): string {
return classes.filter(Boolean).join(' ');
}
export function generateId(): string {
return Math.random().toString(36).slice(2);
}
EOL
cat > my-tauri-app/src/chat/providers/chat-provider.tsx << 'EOL'
import React, { createContext, useContext, useState, useEffect } from 'react';
import { invoke } from '@tauri-apps/api/tauri';
import { User, ChatInstance } from '../types';
interface ChatContextType {
line: any;
user: User;
instance: ChatInstance | null;
sendActivity: (activity: any) => void;
selectedVoice: any;
setVoice: (voice: any) => void;
}
const ChatContext = createContext<ChatContextType | undefined>(undefined);
export function ChatProvider({ children }: { children: React.ReactNode }) {
const [line, setLine] = useState<any>(null);
const [instance, setInstance] = useState<ChatInstance | null>(null);
const [selectedVoice, setSelectedVoice] = useState(null);
const [user] = useState<User>({
id: `user_${Math.random().toString(36).slice(2)}`,
name: 'You'
});
useEffect(() => {
const initializeChat = async () => {
try {
const botId = window.location.pathname.split('/')[1] || 'default';
const instanceData = await invoke('get_chat_instance', { botId });
setInstance(instanceData as ChatInstance);
// Initialize DirectLine or other chat service
const directLine = {
activity$: { subscribe: () => {} },
postActivity: () => ({ subscribe: () => {} })
};
setLine(directLine);
} catch (error) {
console.error('Failed to initialize chat:', error);
}
};
initializeChat();
}, []);
const sendActivity = async (activity: any) => {
try {
await invoke('send_chat_activity', {
activity: {
...activity,
from: user,
timestamp: new Date().toISOString()
}
});
line?.postActivity(activity).subscribe();
} catch (error) {
console.error('Failed to send activity:', error);
}
};
const setVoice = (voice: any) => {
setSelectedVoice(voice);
};
return (
<ChatContext.Provider value={{ line, user, instance, sendActivity, selectedVoice, setVoice }}>
{children}
</ChatContext.Provider>
);
}
export function useChat() {
const context = useContext(ChatContext);
if (!context) {
throw new Error('useChat must be used within ChatProvider');
}
return context;
}
EOL
cat > my-tauri-app/src/chat/providers/sound-provider.tsx << 'EOL'
import React, { createContext, useContext, useCallback } from 'react';
import { invoke } from '@tauri-apps/api/tauri';
interface SoundContextType {
playSound: (sound: string) => void;
setEnabled: (enabled: boolean) => void;
}
const SoundContext = createContext<SoundContextType | undefined>(undefined);
export function SoundProvider({ children }: { children: React.ReactNode }) {
const [enabled, setEnabled] = React.useState(true);
const playSound = useCallback(async (sound: string) => {
if (!enabled) return;
try {
await invoke('play_sound', { sound });
} catch (error) {
console.error('Failed to play sound:', error);
}
}, [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;
}
EOL
cat > my-tauri-app/src/chat/types/index.ts << 'EOL'
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;
}
EOL
# Create CSS files
cat > my-tauri-app/src/styles/chat.css << 'EOL'
.chat-window {
display: flex;
flex-direction: column;
height: 100%;
background-color: #111;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: #111;
border-bottom: 1px solid #333;
}
.header-content {
display: flex;
flex-direction: column;
}
.header-title {
color: white;
font-size: 1.25rem;
font-weight: bold;
margin: 0;
}
.header-subtitle {
color: #00f3ff;
font-size: 0.875rem;
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, 243, 255, 0.1);
border: 1px solid #00f3ff;
}
.bot-message {
align-self: flex-start;
background-color: rgba(191, 0, 255, 0.1);
border: 1px solid #bf00ff;
}
.message-text {
color: white;
margin: 0;
}
.message-time {
color: #666;
font-size: 0.75rem;
margin-top: 0.25rem;
}
.input-container {
display: flex;
align-items: center;
padding: 1rem;
border-top: 1px solid #333;
background-color: #111;
}
.chat-input {
flex: 1;
min-height: 2.5rem;
max-height: 6rem;
padding: 0.5rem 1rem;
margin: 0 0.5rem;
background-color: #1a1a1a;
color: white;
border: 1px solid #333;
border-radius: 1.25rem;
resize: none;
}
.icon-button {
background: none;
border: none;
color: #00f3ff;
cursor: pointer;
padding: 0.5rem;
}
.send-button {
background-color: rgba(0, 243, 255, 0.1);
border: 1px solid #00f3ff;
border-radius: 50%;
padding: 0.5rem;
cursor: pointer;
}
.icon {
width: 1.5rem;
height: 1.5rem;
fill: currentColor;
}
EOL
cat > my-tauri-app/src/styles/layout.css << 'EOL'
.chat-layout {
display: flex;
height: 100vh;
background-color: #111;
}
.sidebar {
width: 18rem;
border-right: 1px solid #333;
}
.main-content {
flex: 1;
display: flex;
}
.projector {
width: 40%;
border-right: 1px solid #333;
}
.chat-area {
flex: 1;
}
EOL
cat > my-tauri-app/src/styles/projector.css << 'EOL'
.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;
}
EOL
cat > my-tauri-app/src/styles/selector.css << 'EOL'
.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;
}
EOL
cat > my-tauri-app/src/styles/ui.css << 'EOL'
.emoji-picker-modal {
position: fixed;
bottom: 5rem;
right: 2rem;
width: 20rem;
max-height: 25rem;
background-color: rgba(0, 0, 0, 0.95);
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: .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: .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;
}
EOL
echo "All files have been created with React + Tauri compatible versions!"