王晓东 vor 1 Monat
Commit
d8059ac227

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 328 - 0
App.tsx

@@ -0,0 +1,328 @@
+
+
+import * as React from 'react';
+import { Sidebar } from './components/Sidebar';
+import { PageBuilder } from './components/PageBuilder';
+import AIAssistant from './components/AIAssistant';
+import PageAnalytics from './components/PageAnalytics';
+import InteractionAnalytics from './components/InteractionAnalytics';
+import ShortLinks from './components/ShortLinks';
+import EnterpriseHosting from './components/EnterpriseHosting';
+import SEOServices from './components/SEOServices';
+import VideoCreator from './components/VideoCreator';
+import NewsCreator from './components/NewsCreator';
+import { ContentScheduler } from './components/ContentScheduler';
+import { CRM } from './components/CRM';
+import { GreenPage, AIGCSettings, FormSubmission } from './types';
+import { createNewPage } from './services/mockDataService';
+import PageManagementModal from './components/PageManagementModal';
+import ShareModal from './components/ShareModal';
+import Showcase from './components/Showcase';
+import Auth from './components/Auth';
+import { authService } from './services/authService';
+import { LanguageProvider, useTranslation } from './hooks/useI18n';
+
+const MainApp = () => {
+    const { t } = useTranslation();
+    const [currentUser, setCurrentUser] = React.useState<string | null>(() => authService.getCurrentUser());
+    const [pages, setPages] = React.useState<GreenPage[]>([]);
+    const [activePageId, setActivePageId] = React.useState<string | null>(null);
+    const [activeNavKey, setActiveNavKey] = React.useState('Page.Link');
+    const [isPageManagerOpen, setIsPageManagerOpen] = React.useState(false);
+    const [isShareModalOpen, setIsShareModalOpen] = React.useState(false);
+    const [theme, setTheme] = React.useState<'light' | 'dark'>('dark');
+    
+    // Load user pages on login/app load, and handle multi-tab logout
+    React.useEffect(() => {
+        const loadUserData = () => {
+            const user = authService.getCurrentUser();
+            if (user) {
+                if (user !== currentUser) {
+                    setCurrentUser(user);
+                }
+                const userPages = authService.getUserPages();
+                if (userPages) {
+                    setPages(userPages);
+                    if (userPages.length > 0) {
+                        // Ensure activePageId is valid, otherwise default to the first page
+                        const currentActive = userPages.find(p => p.id === activePageId);
+                        if (!currentActive) {
+                            setActivePageId(userPages[0].id);
+                        }
+                    } else {
+                        setActivePageId(null);
+                    }
+                }
+            } else {
+                setCurrentUser(null);
+                setPages([]);
+                setActivePageId(null);
+            }
+        };
+
+        loadUserData();
+
+        // Listen for storage changes to handle login/logout from other tabs
+        window.addEventListener('storage', loadUserData);
+        return () => {
+            window.removeEventListener('storage', loadUserData);
+        };
+    }, [currentUser, activePageId]);
+
+    // Save pages whenever they change
+    React.useEffect(() => {
+        if (currentUser && pages.length > 0) {
+            authService.saveUserPages(pages);
+        }
+    }, [pages, currentUser]);
+
+
+    React.useEffect(() => {
+        if (theme === 'dark') {
+            document.documentElement.classList.add('dark');
+        } else {
+            document.documentElement.classList.remove('dark');
+        }
+    }, [theme]);
+
+    const activePage = React.useMemo(() => pages.find(p => p.id === activePageId), [pages, activePageId]);
+    
+    const handleAuthSuccess = () => {
+        const user = authService.getCurrentUser();
+        setCurrentUser(user);
+    };
+
+    const handleLogout = () => {
+        authService.logout();
+        setCurrentUser(null);
+    };
+
+    const handleUpdateActivePage = (updates: Partial<GreenPage>) => {
+        setPages(currentPages => 
+            currentPages.map(page =>
+                page.id === activePageId ? { ...page, ...updates } : page
+            )
+        );
+    };
+    
+    const handleUpdatePage = (id: string, newName: string) => {
+        setPages(currentPages =>
+            currentPages.map(page =>
+                page.id === id ? { ...page, name: newName, slug: newName.toLowerCase().replace(/\s+/g, '-') } : page
+            )
+        );
+    };
+
+    const handleCreatePage = (name: string) => {
+        if (!name.trim()) return;
+        const newPage = createNewPage(name);
+        setPages(prev => [...prev, newPage]);
+        setActivePageId(newPage.id);
+        setActiveNavKey('Page.Link');
+        setIsPageManagerOpen(false);
+    };
+
+    const handleImportShowcasePage = (pageData: GreenPage) => {
+        // This is a deep copy to prevent state mutation issues
+        const newPage: GreenPage = JSON.parse(JSON.stringify(pageData));
+
+        // Make it unique for the user's list
+        newPage.id = `page_${Date.now()}_${Math.random()}`;
+        newPage.name = `${pageData.name} (Copy)`;
+        newPage.slug = `${pageData.slug}-copy-${Math.floor(Math.random() * 1000)}`;
+
+        setPages(prev => [...prev, newPage]);
+        setActivePageId(newPage.id);
+        setActiveNavKey('Page.Link'); // Switch to the page builder
+        alert(t('app.import_success', { name: pageData.name }));
+    };
+
+    const handleVideosGenerated = (newVideos: any[]) => {
+        if (!activePage) return;
+        const updatedAIGCSettings = {
+            ...activePage.aigcSettings,
+            videos: [...activePage.aigcSettings.videos, ...newVideos]
+        };
+        handleUpdateActivePage({ aigcSettings: updatedAIGCSettings });
+        alert(t('app.videos_generated_success', { count: newVideos.length }));
+        setActiveNavKey('AIGC.Scheduler');
+    };
+
+    const handleScheduleUpdate = (newSchedule: any[]) => {
+        if (!activePage) return;
+        const updatedAIGCSettings = { ...activePage.aigcSettings, schedule: newSchedule };
+        handleUpdateActivePage({ aigcSettings: updatedAIGCSettings });
+    };
+
+    const handleUpdateAIGCSettings = (newSettings: AIGCSettings) => {
+        handleUpdateActivePage({ aigcSettings: newSettings });
+    };
+
+    const handleFormSubmission = (submission: FormSubmission) => {
+        if (!activePage) return;
+        const updatedAnalyticsData = {
+            ...activePage.analyticsData,
+            formSubmissions: [...(activePage.analyticsData.formSubmissions || []), submission]
+        };
+        handleUpdateActivePage({ analyticsData: updatedAnalyticsData });
+        alert(t('app.form_submission_success'));
+    };
+
+    const renderContent = () => {
+        if (activeNavKey.startsWith('Showcase.')) {
+            const tab = (activeNavKey.split('.')[1] || 'Personal').toLowerCase();
+            return <Showcase initialTab={tab as any} onImportPage={handleImportShowcasePage} />;
+        }
+        
+        if (!activePage) {
+            return (
+                <div className="p-8 text-center text-gray-500 dark:text-gray-400 flex flex-col items-center justify-center h-full">
+                    <h2 className="text-2xl font-bold mb-2 text-gray-800 dark:text-gray-200">{t('app.welcome')}</h2>
+                    <p className="mb-6">{t('app.no_pages_prompt')}</p>
+                    <button onClick={() => setIsPageManagerOpen(true)} className="bg-brand-primary text-white font-bold py-2 px-5 rounded-md hover:bg-brand-secondary transition-colors">
+                        {t('app.create_first_page')}
+                    </button>
+                </div>
+            );
+        }
+
+        if (activeNavKey.startsWith('Page.')) {
+            const tab = (activeNavKey.split('.')[1] || '').toLowerCase();
+            return <PageBuilder
+                key={activePage.id}
+                pageSettings={activePage.pageSettings}
+                onUpdate={(newSettings) => handleUpdateActivePage({ pageSettings: newSettings })}
+                aigcVideos={activePage.aigcSettings.videos}
+                aigcArticles={activePage.aigcSettings.articles}
+                initialTab={tab as any || 'link'}
+                onOpenShareModal={() => setIsShareModalOpen(true)}
+                onFormSubmit={handleFormSubmission}
+            />
+        }
+        
+        if (activeNavKey.startsWith('AI Assistant.')) {
+            const tab = (activeNavKey.split('.')[1] || 'Persona').toLowerCase();
+            return <AIAssistant
+                key={activePage.id}
+                aiAssistantSettings={activePage.aiAssistantSettings}
+                onUpdateSettings={(newSettings) => handleUpdateActivePage({ aiAssistantSettings: newSettings })}
+                initialTab={tab as any}
+            />
+        }
+
+        if (activeNavKey.startsWith('SEO.')) {
+            const tab = (activeNavKey.split('.')[1] || 'ShortLinks');
+            switch (tab) {
+                case 'ShortLinks':
+                    return <ShortLinks
+                        key={activePage.id}
+                        slug={activePage.slug}
+                        onUpdateSlug={(slug) => handleUpdateActivePage({ slug })}
+                    />
+                case 'Hosting':
+                    return <EnterpriseHosting key={activePage.id} />
+                case 'Services':
+                    return <SEOServices key={activePage.id} />
+                default:
+                    return <ShortLinks key={activePage.id} slug={activePage.slug} onUpdateSlug={(slug) => handleUpdateActivePage({ slug })}/>
+            }
+        }
+
+        switch (activeNavKey) {
+            case 'Analytics.Page':
+                return <PageAnalytics key={activePage.id} analyticsData={activePage.analyticsData} />
+            case 'Analytics.Interactions':
+                return <InteractionAnalytics
+                    key={activePage.id}
+                    analyticsData={activePage.analyticsData}
+                    onUpdateConversations={(conversations) => handleUpdateActivePage({ analyticsData: { ...activePage.analyticsData, conversations } })}
+                />
+            case 'Analytics.CRM':
+                return <CRM key={activePage.id} analyticsData={activePage.analyticsData} />
+            case 'AIGC.Creator':
+                 return <VideoCreator
+                    key={activePage.id}
+                    aigcSettings={activePage.aigcSettings}
+                    onUpdateAIGCSettings={handleUpdateAIGCSettings}
+                    onVideosGenerated={handleVideosGenerated}
+                />
+            case 'AIGC.News':
+                return <NewsCreator
+                    key={activePage.id}
+                    aigcSettings={activePage.aigcSettings}
+                    onUpdateAIGCSettings={handleUpdateAIGCSettings}
+                />
+            case 'AIGC.Scheduler':
+                return <ContentScheduler
+                    key={activePage.id}
+                    videos={activePage.aigcSettings.videos}
+                    schedule={activePage.aigcSettings.schedule}
+                    onScheduleUpdate={handleScheduleUpdate}
+                />
+            default:
+                return <PageBuilder
+                    key={activePage.id}
+                    pageSettings={activePage.pageSettings}
+                    onUpdate={(newSettings) => handleUpdateActivePage({ pageSettings: newSettings })}
+                    aigcVideos={activePage.aigcSettings.videos}
+                    aigcArticles={activePage.aigcSettings.articles}
+                    initialTab="link"
+                    onOpenShareModal={() => setIsShareModalOpen(true)}
+                    onFormSubmit={handleFormSubmission}
+                />
+        }
+    };
+
+    if (!currentUser) {
+        return <Auth onAuthSuccess={handleAuthSuccess} />;
+    }
+
+    return (
+        <div className="flex h-screen bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
+            <Sidebar
+                activePageName={activePage ? activePage.name : t('app.no_page_selected')}
+                activePageThemeColor={activePage ? activePage.themeColor : 'from-gray-500 to-gray-700'}
+                onOpenPageManager={() => setIsPageManagerOpen(true)}
+                activeNavKey={activeNavKey}
+                setActiveNavKey={setActiveNavKey}
+                theme={theme}
+                setTheme={setTheme}
+                currentUser={currentUser}
+                onLogout={handleLogout}
+            />
+            <main className="flex-1 overflow-y-auto">
+                {renderContent()}
+            </main>
+            {isPageManagerOpen && (
+                <PageManagementModal
+                    pages={pages}
+                    activePageId={activePageId || ''}
+                    onSelectPage={(id) => {
+                        setActivePageId(id);
+                        setIsPageManagerOpen(false);
+                    }}
+                    onCreatePage={handleCreatePage}
+                    onUpdatePage={handleUpdatePage}
+                    onClose={() => setIsPageManagerOpen(false)}
+                />
+            )}
+            {isShareModalOpen && activePage && (
+                <ShareModal
+                    pageSlug={activePage.slug}
+                    onClose={() => setIsShareModalOpen(false)}
+                />
+            )}
+        </div>
+    );
+};
+
+const App = () => {
+    return (
+        <LanguageProvider>
+            <MainApp />
+        </LanguageProvider>
+    );
+};
+
+
+export default App;

+ 20 - 0
README.md

@@ -0,0 +1,20 @@
+<div align="center">
+<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
+</div>
+
+# Run and deploy your AI Studio app
+
+This contains everything you need to run your app locally.
+
+View your app in AI Studio: https://ai.studio/apps/drive/18AewSlCKc3Kdp0ZxRVdndKKE_HnszGzG
+
+## Run Locally
+
+**Prerequisites:**  Node.js
+
+
+1. Install dependencies:
+   `npm install`
+2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
+3. Run the app:
+   `npm run dev`

+ 484 - 0
components/AIAssistant.tsx

@@ -0,0 +1,484 @@
+
+
+import * as React from 'react';
+import { ChatMessage, AIAssistantSettings } from '../types';
+import { sendChatMessage, createAIChatSession } from '../services/geminiService';
+import { Icon } from './ui/Icon';
+import { Tabs } from './ui/Tabs';
+import { useTranslation } from '../hooks/useI18n';
+
+
+const ChatWindow: React.FC = () => {
+    const { t } = useTranslation();
+    const [messages, setMessages] = React.useState<ChatMessage[]>([]);
+    const [userInput, setUserInput] = React.useState('');
+    const [isLoading, setIsLoading] = React.useState(false);
+    const messagesEndRef = React.useRef<HTMLDivElement>(null);
+
+    React.useEffect(() => {
+        messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+    }, [messages, isLoading]);
+
+    const handleSendMessage = React.useCallback(async () => {
+        if (!userInput.trim()) return;
+
+        const userMessage: ChatMessage = { id: `msg-user-${Date.now()}`, sender: 'user', text: userInput };
+        setMessages(prev => [...prev, userMessage]);
+        const messageToSend = userInput;
+        setUserInput('');
+        setIsLoading(true);
+
+        const aiResponse = await sendChatMessage(messageToSend);
+        setMessages(prev => [...prev, aiResponse]);
+        setIsLoading(false);
+    }, [userInput]);
+
+    return (
+        <div className="flex flex-col h-full bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm">
+            <div className="p-4 border-b border-gray-200 dark:border-gray-700">
+                <h3 className="font-semibold text-lg text-gray-900 dark:text-white">{t('ai_assistant.test_title')}</h3>
+            </div>
+            <div className="flex-1 p-4 overflow-y-auto space-y-4">
+                {messages.map(msg => (
+                    <div key={msg.id} className={`flex items-start gap-3 ${msg.sender === 'user' ? 'justify-end' : ''}`}>
+                        {msg.sender === 'ai' && (
+                            <div className="w-8 h-8 rounded-full bg-brand-primary/20 text-brand-primary flex items-center justify-center flex-shrink-0">
+                                <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /></Icon>
+                            </div>
+                        )}
+                        <div className={`max-w-md p-3 rounded-lg ${msg.sender === 'user' ? 'bg-blue-600 text-white rounded-br-none' : 'bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-bl-none'}`}>
+                            <p className="text-sm">{msg.text}</p>
+                        </div>
+                         {msg.sender === 'user' && (
+                            <div className="w-8 h-8 rounded-full bg-gray-500 dark:bg-gray-600 flex items-center justify-center flex-shrink-0 text-white">
+                                <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></Icon>
+                            </div>
+                        )}
+                    </div>
+                ))}
+                {isLoading && (
+                     <div className="flex items-start gap-3">
+                         <div className="w-8 h-8 rounded-full bg-brand-primary/20 text-brand-primary flex items-center justify-center flex-shrink-0">
+                            <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /></Icon>
+                         </div>
+                         <div className="max-w-md p-3 rounded-lg bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-bl-none">
+                            <div className="flex items-center space-x-1">
+                                <span className="w-2 h-2 bg-gray-400 dark:bg-gray-400 rounded-full animate-pulse delay-0"></span>
+                                <span className="w-2 h-2 bg-gray-400 dark:bg-gray-400 rounded-full animate-pulse delay-150"></span>
+                                <span className="w-2 h-2 bg-gray-400 dark:bg-gray-400 rounded-full animate-pulse delay-300"></span>
+                            </div>
+                        </div>
+                     </div>
+                )}
+                <div ref={messagesEndRef} />
+            </div>
+            <div className="p-4 border-t border-gray-200 dark:border-gray-700 flex items-center gap-3">
+                <input
+                    type="text"
+                    value={userInput}
+                    onChange={(e) => setUserInput(e.target.value)}
+                    onKeyPress={(e) => e.key === 'Enter' && !isLoading && handleSendMessage()}
+                    placeholder="Type your message..."
+                    className="flex-1 bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white p-2 rounded-md border-2 border-gray-300 dark:border-gray-600 focus:outline-none focus:border-brand-primary focus:ring-1 focus:ring-brand-primary/50"
+                    disabled={isLoading}
+                />
+                <button onClick={handleSendMessage} disabled={isLoading} className="px-4 py-2 bg-brand-primary text-white font-semibold rounded-md hover:bg-brand-secondary disabled:bg-gray-500 transition-colors">
+                    Send
+                </button>
+            </div>
+        </div>
+    );
+};
+
+const sectionClasses = "bg-white dark:bg-gray-800/50 p-6 rounded-lg border border-gray-200 dark:border-gray-700/50";
+const labelClasses = "block text-lg font-semibold text-gray-900 dark:text-white";
+const descriptionClasses = "text-sm text-gray-500 dark:text-gray-400 mt-1 mb-4";
+const inputBaseClasses = "w-full text-gray-900 dark:text-white p-3 rounded-md border focus:outline-none focus:border-brand-primary focus:ring-0 transition-colors";
+const inputBgClasses = "bg-gray-100/50 dark:bg-black/20 border-gray-300 dark:border-white/10";
+
+
+const tagColors = [
+    { bg: 'bg-red-500', text: 'text-white', closeButton: 'text-red-100 hover:text-white' },
+    { bg: 'bg-blue-600', text: 'text-white', closeButton: 'text-blue-100 hover:text-white' },
+    { bg: 'bg-green-600', text: 'text-white', closeButton: 'text-green-100 hover:text-white' },
+    { bg: 'bg-yellow-500', text: 'text-black', closeButton: 'text-yellow-800 hover:text-black' },
+    { bg: 'bg-purple-600', text: 'text-white', closeButton: 'text-purple-100 hover:text-white' },
+    { bg: 'bg-indigo-600', text: 'text-white', closeButton: 'text-indigo-100 hover:text-white' },
+    { bg: 'bg-pink-600', text: 'text-white', closeButton: 'text-pink-100 hover:text-white' },
+    { bg: 'bg-teal-500', text: 'text-white', closeButton: 'text-teal-100 hover:text-white' },
+];
+
+const getColorForKeyword = (keyword: string) => {
+    let hash = 0;
+    for (let i = 0; i < keyword.length; i++) {
+        hash = keyword.charCodeAt(i) + ((hash << 5) - hash);
+        hash = hash & hash;
+    }
+    const index = Math.abs(hash % tagColors.length);
+    return tagColors[index];
+};
+
+
+const KeywordInput: React.FC<{
+    label: string;
+    description: string;
+    keywords: string[];
+    onAdd: (keyword: string) => void;
+    onRemove: (keyword: string) => void;
+}> = ({ label, description, keywords, onAdd, onRemove }) => {
+    const { t } = useTranslation();
+    const [newKeyword, setNewKeyword] = React.useState('');
+
+    const handleAdd = () => {
+        if (newKeyword.trim() && !keywords.includes(newKeyword.trim())) {
+            onAdd(newKeyword.trim());
+            setNewKeyword('');
+        }
+    };
+
+    return (
+        <div className={sectionClasses}>
+            <label className={labelClasses}>{label}</label>
+            <p className={descriptionClasses}>{description}</p>
+            <div className="flex items-center gap-2">
+                <input
+                    type="text"
+                    value={newKeyword}
+                    onChange={(e) => setNewKeyword(e.target.value)}
+                    onKeyPress={(e) => e.key === 'Enter' && handleAdd()}
+                    className={`${inputBaseClasses} ${inputBgClasses}`}
+                    placeholder={`${t('add')} a keyword...`}
+                />
+                <button onClick={handleAdd} className="px-4 py-2 bg-gray-200 dark:bg-gray-600 text-sm font-semibold rounded-md hover:bg-gray-300 dark:hover:bg-gray-500">{t('add')}</button>
+            </div>
+            <div className="flex flex-wrap gap-3 mt-4 min-h-[3rem]">
+                {keywords.map(kw => {
+                    const color = getColorForKeyword(kw);
+                    return (
+                        <div key={kw} className={`flex items-center gap-2 ${color.bg} ${color.text} text-base font-bold px-4 py-2 rounded-full`}>
+                            <span>{kw}</span>
+                            <button onClick={() => onRemove(kw)} className={`${color.closeButton} text-lg font-bold leading-none transition-colors`}>×</button>
+                        </div>
+                    );
+                })}
+            </div>
+        </div>
+    );
+};
+
+const KnowledgeBaseModal: React.FC<{
+    onClose: () => void;
+    onAddFiles: (files: File[]) => void;
+    onAddUrl: (url: string) => void;
+}> = ({ onClose, onAddFiles, onAddUrl }) => {
+    const { t } = useTranslation();
+    const [activeTab, setActiveTab] = React.useState<'files' | 'web'>('files');
+    const [newUrl, setNewUrl] = React.useState('');
+
+    const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        if (e.target.files) {
+            onAddFiles(Array.from(e.target.files));
+            onClose();
+        }
+    };
+
+    const handleAddUrl = () => {
+        if (newUrl.trim()) {
+            try {
+                new URL(newUrl.trim());
+                onAddUrl(newUrl.trim());
+                onClose();
+            } catch (_) {
+                alert('Please enter a valid URL.');
+            }
+        }
+    };
+
+    return (
+        <div className="fixed inset-0 bg-black/70 z-50 flex items-center justify-center backdrop-blur-sm" onClick={onClose}>
+            <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl w-full max-w-2xl border border-gray-200 dark:border-gray-700 flex flex-col" onClick={e => e.stopPropagation()}>
+                <div className="p-6 border-b border-gray-200 dark:border-gray-700">
+                    <h2 className="text-2xl font-bold text-gray-900 dark:text-white">{t('ai_assistant.add_knowledge')}</h2>
+                </div>
+                <div className="p-6">
+                    <Tabs tabs={['files', 'web']} activeTab={activeTab} onTabClick={(t) => setActiveTab(t as 'files' | 'web')} />
+                </div>
+                <div className="p-6 pt-0 flex-1">
+                    {activeTab === 'files' ? (
+                        <div className="mt-1 flex justify-center px-6 pt-10 pb-10 border-2 border-gray-300 dark:border-gray-600 border-dashed rounded-md h-full">
+                            <div className="space-y-1 text-center">
+                                <Icon className="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500"><path d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></Icon>
+                                <div className="flex text-sm text-gray-500 dark:text-gray-400">
+                                    <label htmlFor="file-upload" className="relative cursor-pointer bg-white dark:bg-gray-800 rounded-md font-medium text-brand-primary hover:text-brand-secondary focus-within:outline-none">
+                                        <span>Upload files</span>
+                                        <input id="file-upload" name="file-upload" type="file" multiple className="sr-only" onChange={handleFileChange} />
+                                    </label>
+                                    <p className="pl-1">or drag and drop</p>
+                                </div>
+                                <p className="text-xs text-gray-400 dark:text-gray-500">PDF, TXT, DOCX up to 10MB</p>
+                            </div>
+                        </div>
+                    ) : (
+                         <div>
+                            <label htmlFor="url-input" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Add from Web</label>
+                            <div className="flex items-center gap-2">
+                                <input id="url-input" type="url" value={newUrl} onChange={e => setNewUrl(e.target.value)} onKeyPress={e => e.key === 'Enter' && handleAddUrl()}
+                                    className={`${inputBaseClasses} ${inputBgClasses}`}
+                                    placeholder="https://example.com/about" />
+                                <button onClick={handleAddUrl} className="px-4 py-2 bg-gray-200 dark:bg-gray-600 text-sm font-semibold rounded-md hover:bg-gray-300 dark:hover:bg-gray-500">{t('add')} URL</button>
+                            </div>
+                        </div>
+                    )}
+                </div>
+            </div>
+        </div>
+    );
+};
+
+interface EditorProps {
+    settings: AIAssistantSettings;
+    updateSettings: (updates: Partial<AIAssistantSettings>) => void;
+}
+
+const PersonaEditor: React.FC<EditorProps> = ({ settings, updateSettings }) => {
+    const { t } = useTranslation();
+    return (
+        <div className="space-y-8">
+            <div className={sectionClasses}>
+                <label htmlFor="persona" className={labelClasses}>{t('ai_assistant.system_instruction')}</label>
+                 <p className={descriptionClasses}>{t('ai_assistant.system_instruction_desc')}</p>
+                <textarea id="persona" rows={6} value={settings.persona} onChange={e => updateSettings({ persona: e.target.value })}
+                    className={`${inputBaseClasses} ${inputBgClasses}`}
+                    placeholder="e.g., You are a witty pirate captain who gives business advice."/>
+            </div>
+
+             <div className={sectionClasses}>
+                <div className="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6">
+                    <div>
+                        <label htmlFor="voice" className={labelClasses}>{t('ai_assistant.voice')}</label>
+                         <p className={`${descriptionClasses} mb-2`}>{t('ai_assistant.voice_desc')}</p>
+                        <div className="flex items-center gap-2">
+                            <select id="voice" value={settings.voiceId} onChange={e => updateSettings({ voiceId: e.target.value })}
+                                className={`${inputBaseClasses} ${inputBgClasses}`}>
+                                <option value="vo1">Alloy</option><option value="vo2">Echo</option><option value="vo3">Fable</option>
+                            </select>
+                            <button className="p-3 rounded-md bg-gray-200 dark:bg-gray-600 hover:bg-gray-300 dark:hover:bg-gray-500">
+                                <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.108 12 5v14c0 .892-1.077 1.337-1.707.707L5.586 15z" /></Icon>
+                            </button>
+                        </div>
+                    </div>
+                    <div>
+                        <label className={labelClasses}>{t('ai_assistant.clone')}</label>
+                        <p className={`${descriptionClasses} mb-2`}>{t('ai_assistant.clone_desc')}</p>
+                        <button disabled className="w-full p-3 rounded-md bg-gray-200 dark:bg-gray-600 text-gray-500 dark:text-gray-400 cursor-not-allowed flex items-center justify-center gap-2">
+                            {t('ai_assistant.clone_voice')} <span className="text-xs bg-yellow-400 text-yellow-900 px-2 py-0.5 rounded-full font-bold">{t('pro')}</span>
+                        </button>
+                    </div>
+                     <div>
+                        <label htmlFor="language" className={labelClasses}>{t('ai_assistant.language')}</label>
+                        <p className={`${descriptionClasses} mb-2`}>{t('ai_assistant.language_desc')}</p>
+                        <select id="language" value={settings.language} onChange={e => updateSettings({ language: e.target.value })}
+                            className={`${inputBaseClasses} ${inputBgClasses}`}>
+                            <option>English</option>
+                            <option>Japanese</option>
+                            <option>Chinese</option>
+                            <option>Korean</option>
+                        </select>
+                    </div>
+                    <div>
+                        <label className={labelClasses}>{t('ai_assistant.conversation_style')}</label>
+                        <p className={`${descriptionClasses} mb-2`}>{t('ai_assistant.conversation_style_desc')}</p>
+                        <div className="flex items-center gap-1 bg-gray-100/50 dark:bg-black/20 p-1 rounded-lg border border-gray-300 dark:border-white/10">
+                            {(['friendly', 'professional', 'witty'] as const).map(style => (
+                                <button key={style} onClick={() => updateSettings({ conversationStyle: style })}
+                                    className={`flex-1 px-3 py-2 text-sm rounded-md capitalize transition-colors ${settings.conversationStyle === style ? 'bg-brand-primary text-white shadow' : 'hover:bg-gray-200 dark:hover:bg-gray-600'}`}>
+                                    {style}
+                                </button>
+                            ))}
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+const KnowledgeEditor: React.FC<EditorProps & { onOpenModal: () => void }> = ({ settings, updateSettings, onOpenModal }) => {
+    const { t } = useTranslation();
+    const handleRemoveFile = (fileName: string) => {
+        updateSettings({ knowledgeBaseFiles: settings.knowledgeBaseFiles.filter(f => f.name !== fileName) });
+    };
+
+    const handleRemoveUrl = (urlToRemove: string) => {
+        updateSettings({ knowledgeBaseUrls: settings.knowledgeBaseUrls.filter(url => url !== urlToRemove) });
+    };
+    
+    const hasContent = settings.knowledgeBaseFiles.length > 0 || settings.knowledgeBaseUrls.length > 0;
+
+    return (
+        <div className="space-y-8">
+            <div>
+                <button onClick={onOpenModal} className="w-full flex items-center justify-center gap-2 px-4 py-3 bg-brand-primary text-white font-semibold rounded-md hover:bg-brand-secondary transition-colors">
+                    <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" /></Icon>
+                    {t('ai_assistant.add_knowledge')}
+                </button>
+            </div>
+            <div className={sectionClasses}>
+                {!hasContent ? (
+                    <div className="text-center py-10">
+                        <Icon className="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500"><path strokeLinecap="round" strokeLinejoin="round" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7l8-4 8 4m-8-4v14" /></Icon>
+                        <h3 className="mt-2 text-sm font-medium text-gray-900 dark:text-white">{t('ai_assistant.no_knowledge')}</h3>
+                        <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">{t('ai_assistant.no_knowledge_desc')}</p>
+                    </div>
+                ) : (
+                    <div className="space-y-3">
+                         {settings.knowledgeBaseUrls.map(url => (
+                            <div key={url} className="flex items-center justify-between px-4 py-4 bg-gray-100 dark:bg-gray-900/50 rounded-lg">
+                                <div className="flex items-center gap-3 min-w-0">
+                                    <Icon className="h-6 w-6 text-gray-500 dark:text-gray-400 flex-shrink-0"><path strokeLinecap="round" strokeLinejoin="round" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" /></Icon>
+                                    <a href={url} target="_blank" rel="noopener noreferrer" className="text-sm font-medium text-gray-900 dark:text-white truncate hover:underline">{url}</a>
+                                </div>
+                                <button onClick={() => handleRemoveUrl(url)} className="text-gray-400 dark:text-gray-500 hover:text-white p-1 rounded-full">
+                                    <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /></Icon>
+                                </button>
+                            </div>
+                        ))}
+                        {(settings.knowledgeBaseFiles.length > 0 && settings.knowledgeBaseUrls.length > 0) && (
+                            <hr className="border-gray-200 dark:border-gray-700" />
+                        )}
+                        {settings.knowledgeBaseFiles.map(file => (
+                            <div key={file.name} className="flex items-center justify-between px-4 py-4 bg-gray-100 dark:bg-gray-900/50 rounded-lg">
+                                <div className="flex items-center gap-3 min-w-0">
+                                    <Icon className="h-6 w-6 text-gray-500 dark:text-gray-400 flex-shrink-0"><path strokeLinecap="round" strokeLinejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></Icon>
+                                    <span className="text-sm font-medium text-gray-900 dark:text-white truncate">{file.name}</span>
+                                    <span className="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0">({(file.size / 1024).toFixed(1)} KB)</span>
+                                </div>
+                                <button onClick={() => handleRemoveFile(file.name)} className="text-gray-400 dark:text-gray-500 hover:text-white p-1 rounded-full">
+                                    <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /></Icon>
+                                </button>
+                            </div>
+                        ))}
+                    </div>
+                )}
+            </div>
+        </div>
+    );
+};
+
+
+const SensitivityEditor: React.FC<EditorProps> = ({ settings, updateSettings }) => {
+    const { t } = useTranslation();
+    return (
+        <div className="space-y-8">
+            <KeywordInput
+                label={t('ai_assistant.forbidden_user')}
+                description={t('ai_assistant.forbidden_user_desc')}
+                keywords={settings.forbiddenUserKeywords}
+                onAdd={(kw) => updateSettings({ forbiddenUserKeywords: [...settings.forbiddenUserKeywords, kw] })}
+                onRemove={(kw) => updateSettings({ forbiddenUserKeywords: settings.forbiddenUserKeywords.filter(k => k !== kw) })}
+            />
+             <KeywordInput
+                label={t('ai_assistant.forbidden_ai')}
+                description={t('ai_assistant.forbidden_ai_desc')}
+                keywords={settings.forbiddenAIKeywords}
+                onAdd={(kw) => updateSettings({ forbiddenAIKeywords: [...settings.forbiddenAIKeywords, kw] })}
+                onRemove={(kw) => updateSettings({ forbiddenAIKeywords: settings.forbiddenAIKeywords.filter(k => k !== kw) })}
+            />
+        </div>
+    );
+};
+
+interface AIAssistantProps {
+    aiAssistantSettings: AIAssistantSettings;
+    onUpdateSettings: (newSettings: AIAssistantSettings) => void;
+    initialTab: 'persona' | 'knowledge' | 'sensitivity';
+}
+
+const AIAssistant: React.FC<AIAssistantProps> = ({ aiAssistantSettings, onUpdateSettings, initialTab }) => {
+    const { t } = useTranslation();
+    const [isKnowledgeModalOpen, setIsKnowledgeModalOpen] = React.useState(false);
+
+    React.useEffect(() => {
+        createAIChatSession(aiAssistantSettings.persona);
+    }, [aiAssistantSettings.persona]);
+
+    const updateSettings = (updates: Partial<AIAssistantSettings>) => {
+        onUpdateSettings({ ...aiAssistantSettings, ...updates });
+    };
+
+    const handleAddFiles = (files: File[]) => {
+        const newFiles = files.map(file => ({
+            name: file.name,
+            size: file.size,
+            type: file.type,
+        }));
+        updateSettings({ knowledgeBaseFiles: [...aiAssistantSettings.knowledgeBaseFiles, ...newFiles] });
+    };
+
+    const handleAddUrl = (newUrl: string) => {
+        if (!aiAssistantSettings.knowledgeBaseUrls.includes(newUrl)) {
+            updateSettings({ knowledgeBaseUrls: [...aiAssistantSettings.knowledgeBaseUrls, newUrl] });
+        }
+    };
+    
+    const handleSaveChanges = () => {
+        createAIChatSession(aiAssistantSettings.persona);
+        alert(t('ai_assistant.save_success'));
+    };
+    
+    const renderEditor = () => {
+        switch (initialTab) {
+            case 'persona':
+                return <PersonaEditor settings={aiAssistantSettings} updateSettings={updateSettings} />;
+            case 'knowledge':
+                return <KnowledgeEditor settings={aiAssistantSettings} updateSettings={updateSettings} onOpenModal={() => setIsKnowledgeModalOpen(true)} />;
+            case 'sensitivity':
+                 return <SensitivityEditor settings={aiAssistantSettings} updateSettings={updateSettings} />;
+            default:
+                return <PersonaEditor settings={aiAssistantSettings} updateSettings={updateSettings} />;
+        }
+    };
+    
+    const pageTitles = {
+        persona: t('nav.ai_assistant.persona'),
+        knowledge: t('nav.ai_assistant.knowledge'),
+        sensitivity: t('nav.ai_assistant.sensitivity')
+    };
+
+    return (
+        <>
+        {isKnowledgeModalOpen && (
+            <KnowledgeBaseModal 
+                onClose={() => setIsKnowledgeModalOpen(false)}
+                onAddFiles={handleAddFiles}
+                onAddUrl={handleAddUrl}
+            />
+        )}
+        <div className="flex h-full p-8 gap-8">
+            <div className="flex-1 flex flex-col min-w-0">
+                <header className="flex justify-between items-center flex-shrink-0 mb-8">
+                    <div>
+                        <h2 className="text-3xl font-bold text-gray-900 dark:text-white">{t('ai_assistant.title')} / {pageTitles[initialTab]}</h2>
+                        <p className="text-gray-500 dark:text-gray-400 mt-1">{t('ai_assistant.subtitle')}</p>
+                    </div>
+                    {initialTab === 'persona' && (
+                        <button onClick={handleSaveChanges} className="bg-brand-primary text-white font-bold py-2 px-5 rounded-md hover:bg-brand-secondary transition-colors">
+                            {t('ai_assistant.save_changes')}
+                        </button>
+                    )}
+                </header>
+                <div className="overflow-y-auto flex-1 -mr-4 pr-4">
+                    {renderEditor()}
+                </div>
+            </div>
+            <aside className="w-[450px] flex-shrink-0">
+                <div className="sticky top-8">
+                     <ChatWindow />
+                </div>
+            </aside>
+        </div>
+        </>
+    );
+};
+
+export default AIAssistant;

+ 62 - 0
components/AIGC.tsx

@@ -0,0 +1,62 @@
+import * as React from 'react';
+import VideoCreator from './VideoCreator';
+import { ContentScheduler } from './ContentScheduler';
+import { Tabs } from './ui/Tabs';
+import { AIGCSettings, AIGCVideo, ScheduledPost } from '../types';
+import { useTranslation } from '../hooks/useI18n';
+
+interface AIGCProps {
+    aigcSettings: AIGCSettings;
+    onVideosGenerated: (videos: AIGCVideo[]) => void;
+    onScheduleUpdate: (schedule: ScheduledPost[]) => void;
+    // FIX: Add onUpdateAIGCSettings to props to be passed down to VideoCreator.
+    onUpdateAIGCSettings: (newSettings: AIGCSettings) => void;
+}
+
+const AIGC: React.FC<AIGCProps> = ({ aigcSettings, onVideosGenerated, onScheduleUpdate, onUpdateAIGCSettings }) => {
+    const { t } = useTranslation();
+    const [activeTab, setActiveTab] = React.useState<'creator' | 'scheduler'>('creator');
+
+    const renderContent = () => {
+        switch (activeTab) {
+            case 'creator':
+                // FIX: Pass the correct props to VideoCreator as its interface has changed.
+                return <VideoCreator
+                    aigcSettings={aigcSettings}
+                    onUpdateAIGCSettings={onUpdateAIGCSettings}
+                    onVideosGenerated={onVideosGenerated}
+                />
+            case 'scheduler':
+                return <ContentScheduler
+                    videos={aigcSettings.videos}
+                    schedule={aigcSettings.schedule}
+                    onScheduleUpdate={onScheduleUpdate}
+                />
+            default:
+                return null
+        }
+    };
+    
+    const tabLabels = {
+        creator: t('nav.aigc.creator'),
+        scheduler: t('nav.aigc.scheduler'),
+    };
+
+    return (
+        <div className='h-full flex flex-col'>
+            <div className='p-6 border-b border-gray-200 dark:border-gray-700'>
+                 <Tabs
+                    tabs={Object.keys(tabLabels) as Array<keyof typeof tabLabels>}
+                    activeTab={activeTab}
+                    labels={tabLabels}
+                    onTabClick={(tab) => setActiveTab(tab)}
+                />
+            </div>
+            <div className='flex-1 overflow-hidden'>
+                {renderContent()}
+            </div>
+        </div>
+    );
+};
+
+export default AIGC;

+ 58 - 0
components/AnalyticsDashboard.tsx

@@ -0,0 +1,58 @@
+import * as React from 'react';
+import PageAnalytics from './PageAnalytics';
+import InteractionAnalytics from './InteractionAnalytics';
+// FIX: Changed to a named import as CRM is not a default export.
+import { CRM } from './CRM';
+import { Tabs } from './ui/Tabs';
+import { AnalyticsData, Conversation } from '../types';
+import { useTranslation } from '../hooks/useI18n';
+
+interface AnalyticsDashboardProps {
+    analyticsData: AnalyticsData;
+    onUpdateConversations: (conversations: Conversation[]) => void;
+}
+
+const AnalyticsDashboard: React.FC<AnalyticsDashboardProps> = ({ analyticsData, onUpdateConversations }) => {
+    const { t } = useTranslation();
+    const [activeTab, setActiveTab] = React.useState<'page' | 'interactions' | 'crm'>('page');
+
+    const renderContent = () => {
+        switch (activeTab) {
+            case 'page':
+                return <PageAnalytics analyticsData={analyticsData} />
+            case 'interactions':
+                return <InteractionAnalytics
+                    analyticsData={analyticsData}
+                    onUpdateConversations={onUpdateConversations}
+                />
+            case 'crm':
+                 return <CRM analyticsData={analyticsData} />
+            default:
+                return null
+        }
+    };
+    
+    const tabLabels = {
+        page: t('nav.analytics.page'),
+        interactions: t('nav.analytics.interactions'),
+        crm: t('nav.analytics.crm'),
+    };
+
+    return (
+        <div>
+             <div className='p-6 border-b border-gray-200 dark:border-gray-700'>
+                <Tabs
+                    tabs={Object.keys(tabLabels)}
+                    activeTab={activeTab}
+                    // FIX: Ensure the tab type is correctly handled by casting it to the specific string literal union type expected by setActiveTab.
+                    onTabClick={(tab) => setActiveTab(tab as 'page' | 'interactions' | 'crm')}
+                />
+            </div>
+            <div className='p-6'>
+                {renderContent()}
+            </div>
+        </div>
+    );
+};
+
+export default AnalyticsDashboard;

+ 98 - 0
components/Auth.tsx

@@ -0,0 +1,98 @@
+import * as React from 'react';
+import { authService } from '../services/authService';
+import { Icon } from './ui/Icon';
+import { useTranslation } from '../hooks/useI18n';
+
+interface AuthProps {
+    onAuthSuccess: () => void;
+}
+
+const Auth: React.FC<AuthProps> = ({ onAuthSuccess }) => {
+    const { t } = useTranslation();
+    const [isLogin, setIsLogin] = React.useState(true);
+    const [email, setEmail] = React.useState('');
+    const [password, setPassword] = React.useState('');
+    const [error, setError] = React.useState('');
+    const [isLoading, setIsLoading] = React.useState(false);
+
+    const handleSubmit = async (e: React.FormEvent) => {
+        e.preventDefault();
+        setError('');
+        setIsLoading(true);
+
+        const action = isLogin ? authService.login : authService.register;
+        const result = action(email, password);
+
+        if (result.success) {
+            onAuthSuccess();
+        } else {
+            setError(result.message);
+        }
+        setIsLoading(false);
+    };
+    
+    return (
+        <div className="w-full h-screen flex items-center justify-center bg-gray-100 dark:bg-gray-900 p-4">
+            <div className="w-full max-w-sm mx-auto bg-white dark:bg-gray-800 rounded-2xl shadow-xl p-8 border border-gray-200 dark:border-gray-700">
+                <div className="text-center mb-8">
+                     <div className="inline-flex items-center justify-center gap-3 mb-4">
+                        <div className="bg-gradient-to-r from-green-400 to-blue-500 p-2 rounded-lg">
+                            <Icon className="h-8 w-8 text-white">
+                                <path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
+                            </Icon>
+                        </div>
+                        <h1 className="text-3xl font-bold text-gray-900 dark:text-white">GreenPage AI</h1>
+                    </div>
+                    <h2 className="text-2xl font-semibold text-gray-800 dark:text-gray-200">{isLogin ? t('auth.welcome_back') : t('auth.create_account')}</h2>
+                    <p className="text-gray-500 dark:text-gray-400 mt-1">
+                        {isLogin ? t('auth.signin_prompt') : t('auth.signup_prompt')}
+                    </p>
+                </div>
+                <form onSubmit={handleSubmit} className="space-y-6">
+                    <div>
+                        <label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300">{t('auth.email')}</label>
+                        <input
+                            id="email"
+                            type="email"
+                            value={email}
+                            onChange={(e) => setEmail(e.target.value)}
+                            required
+                            className="mt-1 block w-full px-3 py-2 bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-brand-primary focus:border-brand-primary sm:text-sm"
+                        />
+                    </div>
+                    <div>
+                        <label htmlFor="password"  className="block text-sm font-medium text-gray-700 dark:text-gray-300">{t('auth.password')}</label>
+                        <input
+                            id="password"
+                            type="password"
+                            value={password}
+                            onChange={(e) => setPassword(e.target.value)}
+                            required
+                            className="mt-1 block w-full px-3 py-2 bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-brand-primary focus:border-brand-primary sm:text-sm"
+                        />
+                    </div>
+                    {error && <p className="text-sm text-red-500">{error}</p>}
+                    <div>
+                        <button
+                            type="submit"
+                            disabled={isLoading}
+                            className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-brand-primary hover:bg-brand-secondary focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-brand-primary disabled:bg-gray-500"
+                        >
+                            {isLoading ? t('auth.processing') : (isLogin ? t('auth.signin') : t('auth.signup'))}
+                        </button>
+                    </div>
+                </form>
+                <div className="mt-6 text-center">
+                    <p className="text-sm text-gray-600 dark:text-gray-400">
+                        {isLogin ? t('auth.no_account') : t('auth.has_account')}
+                        <button onClick={() => { setIsLogin(!isLogin); setError(''); }} className="ml-1 font-medium text-brand-primary hover:text-brand-secondary">
+                            {isLogin ? t('auth.signup') : t('auth.signin')}
+                        </button>
+                    </p>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default Auth;

+ 106 - 0
components/CRM.tsx

@@ -0,0 +1,106 @@
+import * as React from 'react';
+import { AnalyticsData, Visitor, Conversation } from '../types';
+import { format, parseISO } from 'date-fns';
+import { Icon } from './ui/Icon';
+import { useTranslation } from '../hooks/useI18n';
+
+interface CRMProps {
+    analyticsData: AnalyticsData;
+}
+
+export const CRM: React.FC<CRMProps> = ({ analyticsData }) => {
+    const { t } = useTranslation();
+    const [sortConfig, setSortConfig] = React.useState<{ key: keyof Visitor; direction: 'asc' | 'desc' } | null>({ key: 'lastSeen', direction: 'desc' });
+
+    const visitors = React.useMemo((): Visitor[] => {
+        const visitorMap = new Map<string, { lastSeen: Date; visits: Set<string>; conversationCount: number }>();
+        analyticsData.conversations.forEach(conv => {
+            if (!visitorMap.has(conv.visitorId)) {
+                visitorMap.set(conv.visitorId, { lastSeen: new Date(0), visits: new Set(), conversationCount: 0 });
+            }
+            const data = visitorMap.get(conv.visitorId)!;
+            const convDate = new Date(conv.timestamp);
+            if (convDate > data.lastSeen) {
+                data.lastSeen = convDate;
+            }
+            data.visits.add(conv.timestamp);
+            data.conversationCount += 1;
+        });
+
+        const visitorList = Array.from(visitorMap.entries()).map(([id, data]) => ({
+            id,
+            lastSeen: data.lastSeen.toISOString(),
+            visitCount: data.visits.size,
+            conversationCount: data.conversationCount,
+        }));
+        
+        return visitorList;
+    }, [analyticsData.conversations]);
+    
+    const sortedVisitors = React.useMemo(() => {
+        const sortableItems = [...visitors];
+        if (sortConfig !== null) {
+            sortableItems.sort((a, b) => {
+                if (a[sortConfig.key] < b[sortConfig.key]) {
+                    return sortConfig.direction === 'asc' ? -1 : 1;
+                }
+                if (a[sortConfig.key] > b[sortConfig.key]) {
+                    return sortConfig.direction === 'asc' ? 1 : -1;
+                }
+                return 0;
+            });
+        }
+        return sortableItems;
+    }, [visitors, sortConfig]);
+    
+    const requestSort = (key: keyof Visitor) => {
+        let direction: 'asc' | 'desc' = 'asc';
+        if (sortConfig && sortConfig.key === key && sortConfig.direction === 'asc') {
+            direction = 'desc';
+        }
+        setSortConfig({ key, direction });
+    };
+
+    const SortableHeader: React.FC<{ sortKey: keyof Visitor; children?: React.ReactNode }> = ({ sortKey, children }) => {
+        const isSorted = sortConfig?.key === sortKey;
+        const directionIcon = sortConfig?.direction === 'asc' ? '▲' : '▼';
+        return (
+            // FIX: The className was truncated. It has been completed.
+            <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer" onClick={() => requestSort(sortKey)}>
+                <div className="flex items-center">
+                    {children}
+                    {isSorted && <span className="ml-2">{directionIcon}</span>}
+                </div>
+            </th>
+        );
+    };
+    
+    // FIX: The component was not returning any JSX, causing a type error. A return statement with a table has been added.
+    return (
+        <div className="p-6 bg-white dark:bg-gray-800 rounded-lg">
+            <h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">{t('analytics.crm.title')}</h2>
+            <div className="overflow-x-auto">
+                <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
+                    <thead className="bg-gray-50 dark:bg-gray-700">
+                        <tr>
+                            <SortableHeader sortKey="id">{t('analytics.crm.visitor_id')}</SortableHeader>
+                            <SortableHeader sortKey="lastSeen">{t('analytics.crm.last_seen')}</SortableHeader>
+                            <SortableHeader sortKey="visitCount">{t('analytics.crm.visits')}</SortableHeader>
+                            <SortableHeader sortKey="conversationCount">{t('analytics.interactions.conversations')}</SortableHeader>
+                        </tr>
+                    </thead>
+                    <tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
+                        {sortedVisitors.map((visitor) => (
+                            <tr key={visitor.id}>
+                                <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-brand-primary">{visitor.id}</td>
+                                <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{format(parseISO(visitor.lastSeen), 'MMM d, yyyy h:mm a')}</td>
+                                <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{visitor.visitCount}</td>
+                                <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{visitor.conversationCount}</td>
+                            </tr>
+                        ))}
+                    </tbody>
+                </table>
+            </div>
+        </div>
+    );
+};

+ 123 - 0
components/ChatWidget.tsx

@@ -0,0 +1,123 @@
+import * as React from 'react';
+import { ChatWidgetSettings, ChatMessage } from '../types';
+import { sendChatMessage } from '../services/geminiService';
+import { Icon } from './ui/Icon';
+
+interface ChatWidgetProps {
+    settings: ChatWidgetSettings;
+}
+
+const ChatWidget: React.FC<ChatWidgetProps> = ({ settings }) => {
+    const [isOpen, setIsOpen] = React.useState(false);
+    const [messages, setMessages] = React.useState<ChatMessage[]>([]);
+    const [userInput, setUserInput] = React.useState('');
+    const [isLoading, setIsLoading] = React.useState(false);
+    const messagesEndRef = React.useRef<HTMLDivElement>(null);
+
+    React.useEffect(() => {
+        if (isOpen && messages.length === 0) {
+            setMessages([{ id: `msg-ai-${Date.now()}`, sender: 'ai', text: 'Hello! How can I help you today?' }]);
+        }
+    }, [isOpen, messages.length]);
+
+    React.useEffect(() => {
+        if (messagesEndRef.current) {
+            messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
+        }
+    }, [messages, isLoading]);
+
+    const handleSendMessage = React.useCallback(async () => {
+        if (!userInput.trim()) return;
+
+        const userMessage: ChatMessage = { id: `msg-user-${Date.now()}`, sender: 'user', text: userInput };
+        setMessages(prev => [...prev, userMessage]);
+        const messageToSend = userInput;
+        setUserInput('');
+        setIsLoading(true);
+
+        const aiResponse = await sendChatMessage(messageToSend);
+        setMessages(prev => [...prev, aiResponse]);
+        setIsLoading(false);
+    }, [userInput]);
+
+    return (
+        <>
+            <div
+                className={`absolute bottom-24 right-5 w-full max-w-sm h-[70vh] max-h-[600px] bg-white dark:bg-gray-800 rounded-2xl shadow-2xl flex flex-col transition-all duration-300 ease-in-out z-40 ${isOpen ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10 pointer-events-none'}`}
+                style={{ backgroundColor: settings.panelBackgroundColor }}
+            >
+                <div className="flex-shrink-0 p-4 flex justify-between items-center rounded-t-2xl" style={{ backgroundColor: settings.headerBackgroundColor, color: settings.headerTextColor }}>
+                    <h3 className="font-bold text-lg">AI Assistant</h3>
+                    <button onClick={() => setIsOpen(false)} style={{ color: settings.headerTextColor }}>
+                        <Icon className="h-6 w-6"><path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" /></Icon>
+                    </button>
+                </div>
+                <div className="flex-1 p-4 overflow-y-auto space-y-4">
+                    {messages.map(msg => (
+                        <div key={msg.id} className={`flex items-start gap-3 ${msg.sender === 'user' ? 'justify-end' : ''}`}>
+                            {msg.sender === 'ai' && (
+                                <div className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0" style={{ backgroundColor: settings.aiMessageBackgroundColor, color: settings.aiMessageTextColor }}>
+                                    <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /></Icon>
+                                </div>
+                            )}
+                            <div className="max-w-xs p-3 rounded-lg text-sm"
+                                style={{
+                                    backgroundColor: msg.sender === 'user' ? settings.userMessageBackgroundColor : settings.aiMessageBackgroundColor,
+                                    color: msg.sender === 'user' ? settings.userMessageTextColor : settings.aiMessageTextColor,
+                                    borderRadius: msg.sender === 'user' ? '1rem 1rem 0.25rem 1rem' : '1rem 1rem 1rem 0.25rem'
+                                }}
+                            >
+                                <p>{msg.text}</p>
+                            </div>
+                        </div>
+                    ))}
+                    {isLoading && (
+                         <div className="flex items-start gap-3">
+                             <div className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0" style={{ backgroundColor: settings.aiMessageBackgroundColor }}>
+                                <Icon className="h-5 w-5" style={{ color: settings.aiMessageTextColor }}><path d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /></Icon>
+                             </div>
+                             <div className="max-w-xs p-3 rounded-lg" style={{ backgroundColor: settings.aiMessageBackgroundColor }}>
+                                <div className="flex items-center space-x-1">
+                                    <span className="w-2 h-2 rounded-full animate-pulse delay-0" style={{ backgroundColor: settings.aiMessageTextColor }}></span>
+                                    <span className="w-2 h-2 rounded-full animate-pulse delay-150" style={{ backgroundColor: settings.aiMessageTextColor }}></span>
+                                    <span className="w-2 h-2 rounded-full animate-pulse delay-300" style={{ backgroundColor: settings.aiMessageTextColor }}></span>
+                                </div>
+                            </div>
+                         </div>
+                    )}
+                    <div ref={messagesEndRef} />
+                </div>
+                <div className="flex-shrink-0 p-4 border-t border-gray-200 dark:border-gray-700 flex items-center gap-3">
+                    <input
+                        type="text"
+                        value={userInput}
+                        onChange={e => setUserInput(e.target.value)}
+                        onKeyPress={e => e.key === 'Enter' && !isLoading && handleSendMessage()}
+                        placeholder="Ask me anything..."
+                        className="flex-1 bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white p-2 rounded-md border border-gray-300 dark:border-gray-600 focus:ring-2 focus:ring-brand-primary focus:outline-none"
+                        disabled={isLoading}
+                    />
+                    <button onClick={handleSendMessage} disabled={isLoading} className="p-2 rounded-full text-white disabled:opacity-50" style={{ backgroundColor: settings.headerBackgroundColor }}>
+                        <Icon className="h-6 w-6"><path strokeLinecap="round" strokeLinejoin="round" d="M5 10l7-7m0 0l7 7m-7-7v18" /></Icon>
+                    </button>
+                </div>
+            </div>
+
+            <button
+                onClick={() => setIsOpen(!isOpen)}
+                className={`absolute bottom-5 right-5 h-16 w-16 rounded-full shadow-lg flex items-center justify-center text-white transition-transform transform hover:scale-110 z-40 ${isOpen ? 'animate-none' : 'animate-breathing'}`}
+                style={{ backgroundColor: settings.iconColor }}
+                aria-label="Open AI Assistant"
+            >
+                <Icon className="h-8 w-8 transition-transform duration-300" style={{ transform: isOpen ? 'rotate(180deg) scale(0.75)' : 'rotate(0) scale(1)'}}>
+                    {isOpen 
+                        ? <path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
+                        : <path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
+                    }
+                </Icon>
+            </button>
+        </>
+    );
+};
+
+export default ChatWidget;

+ 365 - 0
components/ContentScheduler.tsx

@@ -0,0 +1,365 @@
+import * as React from 'react';
+import { AIGCVideo, ScheduledPost, SocialAccount } from '../types';
+import { Icon } from './ui/Icon';
+import { format, startOfMonth, endOfMonth, startOfWeek, endOfWeek, eachDayOfInterval, addMonths, subMonths, addWeeks, subWeeks, addDays, subDays, isSameMonth, isToday, parseISO, isPast, getDay, isSameDay } from 'date-fns';
+import { useTranslation } from '../hooks/useI18n';
+
+const initialSocialAccounts: SocialAccount[] = [
+    {
+        id: 'tw',
+        platform: 'Twitter',
+        username: 'greenpage_ai',
+        icon: <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 fill-current text-[#1DA1F2]">
+            <title>Twitter</title>
+            <path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.223.085a4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z" />
+        </svg>
+    },
+    {
+        id: 'fb',
+        platform: 'Facebook',
+        username: 'GreenPageApp',
+        icon: <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 fill-current text-[#1877F2]">
+            <title>Facebook</title>
+            <path d="M22.675 0H1.325C.593 0 0 .593 0 1.325v21.351C0 23.407.593 24 1.325 24H12.82v-9.294H9.692v-3.622h3.128V8.413c0-3.1 1.893-4.788 4.659-4.788 1.325 0 2.463.099 2.795.143v3.24l-1.918.001c-1.504 0-1.795.715-1.795 1.763v2.313h3.587l-.467 3.622h-3.12V24h6.116c.732 0 1.325-.593 1.325-1.325V1.325C24 .593 23.407 0 22.675 0z" />
+        </svg>
+    },
+    {
+        id: 'ig',
+        platform: 'Instagram',
+        username: 'greenpage.ai',
+        icon: <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 fill-current">
+            <title>Instagram</title>
+            <defs>
+                <radialGradient id="ig-grad" gradientUnits="userSpaceOnUse" r="150%" cx="30%" cy="107%">
+                    <stop stopColor="#fdf497" offset="0" />
+                    <stop stopColor="#fdf497" offset="0.05" />
+                    <stop stopColor="#fd5949" offset="0.45" />
+                    <stop stopColor="#d6249f" offset="0.6" />
+                    <stop stopColor="#285AEB" offset="0.9" />
+                </radialGradient>
+            </defs>
+            <path fill="url(#ig-grad)" d="M12 0C8.74 0 8.333.015 7.053.072 5.775.132 4.905.333 4.14.63c-.784.3-.986.623-1.77.933-.448.174-.9.34-1.356.59-.446.243-.7.4-.933.71-.24.3-.466.737-.62 1.14-.156.4-.3 1.07-.343 1.76-.05.8-.07 1.37-.07 4.23s.02 3.43.07 4.23c.044.68.2 1.36.343 1.76.155.4.38.84.62 1.14.233.3.486.467.933.71.457.25.908.417 1.356.59.783.31 1.2.63 1.77.933.765.3 1.635.5 2.913.56.05.002.1.003.15.003.25 0 .5 0 .75-.004 1.28-.056 2.15-.26 2.913-.56.784-.3 1.2-.623 1.77-.933.448-.174-.9.34 1.356.59.446-.243-.7-.4.933-.71.24-.3.466.737-.62-1.14.156-.4.3-1.07.343-1.76.05-.8.07-1.37.07-4.23s-.02-3.43-.07-4.23c-.044-.68-.2-1.36-.343-1.76-.155-.4-.38-.84-.62-1.14-.233-.3-.486-.467-.933-.71-.457-.25-.908.417-1.356.59-.783-.31-1.2-.63-1.77-.933-.765-.3-1.635-.5-2.913-.56-.25-.003-.5-.004-.75-.004zm0 2.16c3.203 0 3.585.016 4.85.07 1.17.055 1.805.248 2.227.415.562.217.96.477 1.382.896.413.42.67.824.896 1.383.167.422.36 1.057.413 2.227.055 1.265.07 1.646.07 4.85s-.015 3.585-.07 4.85c-.055 1.17-.248 1.805-.413 2.227-.228.562-.483-.96-.896 1.383-.42.413-.824-.67-1.382-.896-.422-.167-1.057.36-2.227-.413-1.265.055-1.646.07-4.85.07s-3.585-.015-4.85-.07c-1.17-.055-1.805-.248-2.227-.413-.562-.228-.96-.483-1.382-.896-.413-.42-.67-.824-.896-1.383-.167-.422-.36-1.057-.413-2.227-.055-1.265-.07-1.646-.07-4.85s.015-3.585.07-4.85c.055-1.17.248 1.805.413-2.227.228.562.483.96.896-1.382.42-.413.824-.67 1.382-.896.422-.167 1.057.36 2.227-.413 1.265-.055 1.646-.07 4.85-.07zm0 3.27c-3.405 0-6.167 2.76-6.167 6.167s2.762 6.167 6.167 6.167 6.167-2.76 6.167-6.167-2.762-6.167-6.167-6.167zm0 10.167c-2.209 0-4-1.79-4-4s1.791-4 4-4 4 1.79 4 4-1.791 4-4 4zm4.865-9.865c0 .765-.623 1.385-1.39 1.385s-1.39-.62-1.39-1.385.623-1.385 1.39-1.385 1.39.62 1.39 1.385z" />
+        </svg>
+    },
+];
+
+const CheckCircleIcon: React.FC<{ className?: string }> = ({ className }) => (
+    <Icon className={className || "h-4 w-4 text-green-400"}><path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></Icon>
+);
+
+
+// --- MODAL COMPONENTS ---
+
+const SchedulePostModal: React.FC<{
+    video: AIGCVideo | null;
+    post: ScheduledPost | null;
+    date: string;
+    accounts: SocialAccount[];
+    onClose: () => void;
+    onSchedule: (post: Omit<ScheduledPost, 'id' | 'status'>) => void;
+    onUpdate: (post: ScheduledPost) => void;
+    onDelete: (postId: string) => void;
+}> = ({ video, post, date, accounts, onClose, onSchedule, onUpdate, onDelete }) => {
+    const { t, dateLocale } = useTranslation();
+    const [time, setTime] = React.useState(post ? post.time : '10:00');
+    const [caption, setCaption] = React.useState(post ? post.caption : (video ? `Check out our new video: ${video.title}!` : ""));
+    const [selectedAccounts, setSelectedAccounts] = React.useState<string[]>(post ? post.socialAccountIds : []);
+
+    const handleAccountToggle = (accountId: string) => {
+        setSelectedAccounts(prev =>
+            prev.includes(accountId) ? prev.filter(id => id !== accountId) : [...prev, accountId]
+        );
+    };
+
+    const handleSubmit = () => {
+        if (selectedAccounts.length === 0 || !time) {
+            alert('Please select at least one social account and set a time.');
+            return;
+        }
+        if (post) {
+            onUpdate({ ...post, time, caption, socialAccountIds: selectedAccounts });
+        } else if (video) {
+            onSchedule({ videoId: video.id, date, time, caption, socialAccountIds: selectedAccounts });
+        }
+        onClose();
+    };
+
+    if (!video) return null;
+
+    return (
+        <div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50" onClick={() => onClose()}>
+            <div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md" onClick={e => e.stopPropagation()}>
+                <div className="p-6 border-b border-gray-200 dark:border-gray-700">
+                    <h3 className="text-lg font-bold text-gray-900 dark:text-white">{post ? t('aigc.scheduler.title') : t('aigc.scheduler.title')}</h3>
+                    <p className="text-sm text-gray-500 dark:text-gray-400">{`${t('for')} ${format(parseISO(date), 'MMMM d, yyyy', { locale: dateLocale })}`}</p>
+                </div>
+                <div className="p-6 space-y-4">
+                    <div className="flex items-center gap-4">
+                        <img src={video.thumbnailUrl} alt={video.title} className="w-32 h-20 rounded object-cover" />
+                        <p className="font-semibold text-gray-900 dark:text-white">{video.title}</p>
+                    </div>
+                     <div>
+                        <label htmlFor="time" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('aigc.scheduler.time')}</label>
+                        <input type="time" id="time" value={time} onChange={e => setTime(e.target.value)} className="w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600"/>
+                    </div>
+                     <div>
+                        <label htmlFor="caption" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('aigc.scheduler.caption')}</label>
+                        <textarea id="caption" rows={3} value={caption} onChange={e => setCaption(e.target.value)} className="w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600" />
+                    </div>
+                    <div>
+                        <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{t('aigc.scheduler.post_to')}</label>
+                        <div className="space-y-2">
+                            {accounts.map(acc => (
+                                <label key={acc.id} className="flex items-center gap-3 p-2 bg-gray-100 dark:bg-gray-700 rounded-md cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600">
+                                    <input type="checkbox" className="h-4 w-4 rounded bg-gray-200 dark:bg-gray-900 border-gray-300 dark:border-gray-600 text-brand-primary focus:ring-brand-secondary" checked={selectedAccounts.includes(acc.id)} onChange={() => handleAccountToggle(acc.id)} />
+                                    {acc.icon}
+                                    <span className="font-semibold">{acc.platform}</span>
+                                    <span className="text-gray-500 dark:text-gray-400 text-sm ml-auto">@{acc.username}</span>
+                                </label>
+                            ))}
+                        </div>
+                    </div>
+                </div>
+                <div className={`p-4 bg-gray-50 dark:bg-gray-900/50 flex ${post ? 'justify-between' : 'justify-end'} gap-3`}>
+                   {post && <button onClick={() => { onDelete(post.id); onClose(); }} className="px-4 py-2 rounded-md text-sm font-semibold text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/50">{t('delete')}</button>}
+                   <div className="flex gap-3">
+                       <button onClick={onClose} className="px-4 py-2 rounded-md text-sm font-semibold text-gray-700 dark:text-gray-300 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600">{t('cancel')}</button>
+                       <button onClick={handleSubmit} className="px-4 py-2 rounded-md text-sm font-semibold text-white bg-brand-primary hover:bg-brand-secondary">{post ? t('update') : t('aigc.scheduler.schedule')}</button>
+                   </div>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+const VideoLibrarySidebar: React.FC<{
+    videos: AIGCVideo[];
+    onDragStart: (e: React.DragEvent, videoId: string) => void;
+    activeFilter: 'all' | 'scheduled' | 'unscheduled';
+    onFilterChange: (filter: 'all' | 'scheduled' | 'unscheduled') => void;
+}> = ({ videos, onDragStart, activeFilter, onFilterChange }) => {
+    const { t } = useTranslation();
+    return (
+        <aside className="w-80 bg-white dark:bg-gray-800 p-4 flex flex-col border-r border-gray-200 dark:border-gray-700">
+            <h3 className="text-lg font-bold text-gray-900 dark:text-white mb-4">{t('aigc.scheduler.video_library')}</h3>
+            <div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-900 p-1 rounded-lg mb-4">
+                {(['all', 'scheduled', 'unscheduled'] as const).map(filter => (
+                    <button 
+                        key={filter} 
+                        onClick={() => onFilterChange(filter)}
+                        className={`flex-1 px-3 py-1 text-sm rounded-md capitalize transition-colors ${activeFilter === filter ? 'bg-brand-primary text-white shadow' : 'hover:bg-gray-200 dark:hover:bg-gray-600'}`}>
+                        {t(filter)}
+                    </button>
+                ))}
+            </div>
+            <div className="flex-1 overflow-y-auto pr-2 -mr-4 space-y-3">
+                {videos.map(video => (
+                    <div key={video.id} draggable onDragStart={e => onDragStart(e, video.id)} className="flex items-center gap-3 p-2 bg-gray-50 dark:bg-gray-900/50 rounded-lg cursor-grab active:cursor-grabbing">
+                        <img src={video.thumbnailUrl} alt={video.title} className="w-24 h-16 rounded object-cover" />
+                        <p className="flex-1 font-semibold text-sm text-gray-900 dark:text-white truncate">{video.title}</p>
+                    </div>
+                ))}
+                {videos.length === 0 && <p className="text-center text-sm text-gray-400 dark:text-gray-500 py-10">{t('aigc.scheduler.no_videos_filter')}</p>}
+            </div>
+        </aside>
+    );
+};
+
+// --- CALENDAR COMPONENTS ---
+
+const CalendarHeader: React.FC<{ view: string; currentDate: Date; onPrev: () => void; onNext: () => void; onViewChange: (view: 'month' | 'week' | 'day') => void; }> = ({ view, currentDate, onPrev, onNext, onViewChange }) => {
+    const { dateLocale } = useTranslation();
+    const formatString = view === 'month' ? 'MMMM yyyy' : (view === 'week' ? "MMM d, yyyy" : 'MMMM d, yyyy');
+    return (
+        <div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
+            <div>
+                <h2 className="text-xl font-bold text-gray-900 dark:text-white">{format(currentDate, formatString, { locale: dateLocale })}</h2>
+                 {view === 'week' && <p className="text-sm text-gray-500 dark:text-gray-400">{`Week of ${format(startOfWeek(currentDate, { locale: dateLocale }), 'MMM d')} - ${format(endOfWeek(currentDate, { locale: dateLocale }), 'MMM d')}`}</p>}
+            </div>
+            <div className="flex items-center gap-4">
+                <div className="flex items-center">
+                    <button onClick={onPrev} className="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"><Icon className="h-5 w-5"><path d="M15 19l-7-7 7-7" /></Icon></button>
+                    <button onClick={onNext} className="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"><Icon className="h-5 w-5"><path d="M9 5l7 7-7 7" /></Icon></button>
+                </div>
+                <div className="flex items-center gap-1 bg-gray-200 dark:bg-gray-700 p-1 rounded-lg">
+                    {(['month', 'week', 'day'] as const).map(v => (
+                        <button key={v} onClick={() => onViewChange(v)} className={`px-3 py-1 text-sm rounded-md capitalize ${view === v ? 'bg-brand-primary text-white' : 'hover:bg-gray-300 dark:hover:bg-gray-600'}`}>{v}</button>
+                    ))}
+                </div>
+            </div>
+        </div>
+    );
+};
+
+const ScheduledPostItem: React.FC<{ post: ScheduledPost; video: AIGCVideo | undefined; onClick: (e: React.MouseEvent) => void; }> = ({ post, video, onClick }) => {
+    if (!video) return null;
+    const postDate = parseISO(`${post.date}T${post.time}`);
+    const isDistributed = post.status === 'distributed' || isPast(postDate);
+    return (
+        <button onClick={onClick} className={`w-full text-left p-2 rounded-md mb-1 ${isDistributed ? 'bg-green-500/10 hover:bg-green-500/20' : 'bg-blue-500/10 hover:bg-blue-500/20'}`}>
+            <div className="flex items-center gap-2">
+                {isDistributed ? <CheckCircleIcon className="h-4 w-4 text-green-400" /> : <div className="h-4 w-4" />}
+                <p className={`text-xs font-semibold ${isDistributed ? 'text-green-600 dark:text-green-400' : 'text-blue-600 dark:text-blue-400'}`}>{post.time}</p>
+            </div>
+            <p className="text-sm font-semibold truncate mt-1 text-gray-800 dark:text-gray-200">{video.title}</p>
+        </button>
+    );
+};
+
+const Calendar: React.FC<{ 
+    view: 'month' | 'week' | 'day'; 
+    currentDate: Date;
+    schedule: ScheduledPost[];
+    videos: AIGCVideo[];
+    onDateSelect: (date: string) => void;
+    onPostSelect: (post: ScheduledPost) => void;
+    onDrop: (e: React.DragEvent, date: string) => void;
+}> = ({ view, currentDate, schedule, videos, onDateSelect, onPostSelect, onDrop }) => {
+    const { dateLocale } = useTranslation();
+    const [dragOverDate, setDragOverDate] = React.useState<string | null>(null);
+    
+    const renderMonthView = () => {
+        const monthStart = startOfMonth(currentDate);
+        const monthEnd = endOfMonth(monthStart);
+        const days = eachDayOfInterval({ start: startOfWeek(monthStart, { locale: dateLocale }), end: endOfWeek(monthEnd, { locale: dateLocale }) });
+
+        const weekdays = eachDayOfInterval({
+            start: startOfWeek(currentDate, { locale: dateLocale }),
+            end: endOfWeek(currentDate, { locale: dateLocale }),
+        });
+
+        return (
+            <>
+                <div className="grid grid-cols-7 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-700">
+                    {weekdays.map(day => <div key={day.toString()} className="py-2">{format(day, 'E', { locale: dateLocale })}</div>)}
+                </div>
+                <div className="grid grid-cols-7 flex-1">
+                    {days.map(day => {
+                        const dayStr = format(day, 'yyyy-MM-dd');
+                        const isDragOver = dragOverDate === dayStr;
+                        const postsForDay = schedule.filter(p => isSameDay(parseISO(p.date), day)).sort((a,b) => a.time.localeCompare(b.time));
+                        return (
+                            <div 
+                                key={day.toString()} 
+                                onDragEnter={(e) => { e.preventDefault(); setDragOverDate(dayStr); }}
+                                onDragLeave={(e) => { e.preventDefault(); setDragOverDate(null); }}
+                                onDragOver={e => e.preventDefault()} 
+                                onDrop={e => { onDrop(e, dayStr); setDragOverDate(null); }}
+                                onClick={() => onDateSelect(dayStr)}
+                                className={`relative border-r border-b border-gray-200 dark:border-gray-700 p-2 transition-all duration-200 ${!isSameMonth(day, currentDate) ? 'bg-gray-50 dark:bg-gray-900/50' : ''} ${isDragOver ? 'bg-brand-primary/20 border-2 border-brand-primary' : ''}`}
+                            >
+                                <time dateTime={dayStr} className={`text-sm font-semibold ${isToday(day) ? 'bg-brand-primary text-white rounded-full h-6 w-6 flex items-center justify-center' : ''}`}>{format(day, 'd')}</time>
+                                <div className="mt-2">{postsForDay.map(post => <ScheduledPostItem key={post.id} post={post} video={videos.find(v => v.id === post.videoId)} onClick={e => { e.stopPropagation(); onPostSelect(post); }} />)}</div>
+                            </div>
+                        );
+                    })}
+                </div>
+            </>
+        );
+    };
+    
+    // Day and Week views could be implemented similarly if needed
+    
+    return (
+        <div className="flex-1 flex flex-col overflow-y-auto">
+            {renderMonthView()}
+        </div>
+    );
+};
+
+
+// --- MAIN COMPONENT ---
+interface ContentSchedulerProps {
+    videos: AIGCVideo[];
+    schedule: ScheduledPost[];
+    onScheduleUpdate: (newSchedule: ScheduledPost[]) => void;
+}
+
+export const ContentScheduler: React.FC<ContentSchedulerProps> = ({ videos, schedule, onScheduleUpdate }) => {
+    const [currentDate, setCurrentDate] = React.useState(new Date());
+    const [view, setView] = React.useState<'month' | 'week' | 'day'>('month');
+    const [isModalOpen, setIsModalOpen] = React.useState(false);
+    const [modalData, setModalData] = React.useState<{ videoId: string | null; postId: string | null; date: string }>({ videoId: null, postId: null, date: '' });
+    const [videoFilter, setVideoFilter] = React.useState<'all' | 'scheduled' | 'unscheduled'>('all');
+
+    const scheduledVideoIds = React.useMemo(() => new Set(schedule.map(p => p.videoId)), [schedule]);
+
+    const filteredVideos = React.useMemo(() => {
+        if (videoFilter === 'scheduled') {
+            return videos.filter(v => scheduledVideoIds.has(v.id));
+        }
+        if (videoFilter === 'unscheduled') {
+            return videos.filter(v => !scheduledVideoIds.has(v.id));
+        }
+        return videos;
+    }, [videos, videoFilter, scheduledVideoIds]);
+
+    const handleSchedule = (newPostData: Omit<ScheduledPost, 'id' | 'status'>) => {
+        const newPost: ScheduledPost = { ...newPostData, id: `post-${Date.now()}`, status: 'scheduled' };
+        onScheduleUpdate([...schedule, newPost]);
+    };
+
+    const handleUpdate = (updatedPost: ScheduledPost) => onScheduleUpdate(schedule.map(p => p.id === updatedPost.id ? updatedPost : p));
+    const handleDelete = (postId: string) => onScheduleUpdate(schedule.filter(p => p.id !== postId));
+
+    const handleDragStart = (e: React.DragEvent, videoId: string) => e.dataTransfer.setData("videoId", videoId);
+    const handleDrop = (e: React.DragEvent, date: string) => {
+        const videoId = e.dataTransfer.getData("videoId");
+        if(videoId) {
+            setModalData({ videoId, postId: null, date });
+            setIsModalOpen(true);
+        }
+    };
+    
+    const changeDate = (direction: 'prev' | 'next') => {
+        const op = direction === 'prev' ? 'sub' : 'add';
+        const unit = view === 'month' ? 'Months' : view === 'week' ? 'Weeks' : 'Days';
+        const fn = {
+            sub: { Months: subMonths, Weeks: subWeeks, Days: subDays },
+            add: { Months: addMonths, Weeks: addWeeks, Days: addDays }
+        }[op][unit];
+        setCurrentDate(fn(currentDate, 1));
+    };
+
+    const modalPost = modalData.postId ? schedule.find(p => p.id === modalData.postId) : null;
+    const modalVideo = videos.find(v => v.id === (modalPost ? modalPost.videoId : modalData.videoId));
+    
+    return (
+        <div className="flex h-full">
+            {isModalOpen && <SchedulePostModal
+                video={modalVideo || null}
+                post={modalPost || null}
+                date={modalData.date}
+                accounts={initialSocialAccounts}
+                onClose={() => setIsModalOpen(false)}
+                onSchedule={handleSchedule}
+                onUpdate={handleUpdate}
+                onDelete={handleDelete}
+            />}
+            <VideoLibrarySidebar 
+                videos={filteredVideos} 
+                onDragStart={handleDragStart} 
+                activeFilter={videoFilter}
+                onFilterChange={setVideoFilter}
+            />
+            <main className="flex-1 flex flex-col bg-white dark:bg-gray-800/50">
+                <CalendarHeader 
+                    view={view}
+                    currentDate={currentDate}
+                    onPrev={() => changeDate('prev')}
+                    onNext={() => changeDate('next')}
+                    onViewChange={setView}
+                />
+                <Calendar
+                    view={view}
+                    currentDate={currentDate}
+                    schedule={schedule}
+                    videos={videos}
+                    onDateSelect={(date) => { setModalData({ videoId: null, postId: null, date }); setIsModalOpen(true); }}
+                    onPostSelect={post => { setModalData({ videoId: null, postId: post.id, date: post.date }); setIsModalOpen(true); }}
+                    onDrop={handleDrop}
+                />
+            </main>
+        </div>
+    );
+};

+ 458 - 0
components/DesignEditor.tsx

@@ -0,0 +1,458 @@
+import * as React from 'react';
+import { DesignSettings, ThemeName, ButtonStyle, ButtonShape, FontFamily, BackgroundType, BannerType, MediaSource, SideNavSettings, BannerSettings } from '../types';
+import { Icon } from './ui/Icon';
+import { useTranslation } from '../hooks/useI18n';
+
+interface AccordionProps {
+  title: string;
+  children?: React.ReactNode;
+  defaultOpen?: boolean;
+}
+
+const Accordion: React.FC<AccordionProps> = ({ title, children, defaultOpen = true }) => {
+    const [isOpen, setIsOpen] = React.useState(defaultOpen);
+    return (
+        <div className="border-b border-gray-200 dark:border-gray-700">
+            <button onMouseDown={(e) => e.preventDefault()} onClick={() => setIsOpen(!isOpen)} className="w-full flex justify-between items-center py-4 text-left">
+                <h3 className="text-lg font-semibold text-gray-900 dark:text-white">{title}</h3>
+                <Icon className={`h-5 w-5 transition-transform text-gray-500 dark:text-gray-400 ${isOpen ? 'rotate-180' : ''}`}><path d="M19 9l-7 7-7-7" /></Icon>
+            </button>
+            {isOpen && <div className="pb-6 space-y-4">{children}</div>}
+        </div>
+    );
+};
+
+const ColorPalettePicker: React.FC<{ 
+    label: string; 
+    color: string; 
+    onChange: (color: string) => void;
+    themeColors?: string[];
+}> = ({ label, color, onChange, themeColors = ['#ffffff', '#f3f4f6', '#d1d5db', '#6b7280', '#374151', '#111827', '#10b981', '#3b82f6', '#ef4444'] }) => (
+    <div className="flex items-center justify-between">
+        <label className="text-sm text-gray-600 dark:text-gray-300">{label}</label>
+        <div className="flex items-center gap-2">
+            <div className="flex gap-1">
+                {themeColors.map(c => (
+                     <button key={c} onMouseDown={(e) => e.preventDefault()} onClick={() => onChange(c)} className={`h-6 w-6 rounded-full border border-gray-300 dark:border-gray-600 ${color.toLowerCase() === c.toLowerCase() ? 'ring-2 ring-brand-primary ring-offset-2 ring-offset-gray-50 dark:ring-offset-gray-900' : ''}`} style={{backgroundColor: c}} />
+                ))}
+            </div>
+            <input type="color" value={color} onChange={e => onChange(e.target.value)} className="w-8 h-8 rounded border-none cursor-pointer bg-transparent" />
+        </div>
+    </div>
+);
+
+const predefinedGradients = [
+    { name: 'Abyss', value: 'bg-gradient-to-br from-gray-800 to-gray-900' },
+    { name: 'Emerald', value: 'bg-gradient-to-br from-green-500 to-teal-600' },
+    { name: 'Sunset', value: 'bg-gradient-to-br from-red-500 to-yellow-500' },
+    { name: 'Violet', value: 'bg-gradient-to-br from-purple-600 to-indigo-700' },
+];
+
+const predefinedBackgrounds: MediaSource[] = [
+    { type: 'url', value: 'https://picsum.photos/seed/bg-beach/1200/800' },
+    { type: 'url', value: 'https://picsum.photos/seed/bg-music/1200/800' },
+    { type: 'url', value: 'https://picsum.photos/seed/bg-city/1200/800' },
+    { type: 'url', value: 'https://picsum.photos/seed/bg-dock/1200/800' },
+];
+
+const getImageUrl = (source: MediaSource): string => {
+    if (source.type === 'url') return source.value;
+    if (source.type === 'file') return source.value.previewUrl;
+    return ''; // AIGC not applicable for backgrounds
+}
+
+const themes: { name: string; value: ThemeName }[] = [
+    { name: 'Light', value: 'light' }, { name: 'Dark', value: 'dark' }, { name: 'Synthwave', value: 'synthwave' }, { name: 'Retro', value: 'retro' },
+];
+
+const fontSizes = [
+    { name: 'Small', value: 'text-sm' }, { name: 'Base', value: 'text-base' }, { name: 'Large', value: 'text-lg' },
+];
+
+const designTemplates: { name: string, settings: Partial<DesignSettings> & { sideNavSettings: Partial<SideNavSettings>, bannerSettings: Partial<BannerSettings> } }[] = [
+    { name: 'Midnight Emerald', settings: { theme: 'dark', fontColor: '#A7F3D0', backgroundType: 'gradient', backgroundValue: 'bg-gradient-to-br from-emerald-900 via-gray-900 to-black', buttonStyle: 'outline', buttonShape: 'rounded', fontFamily: 'sans', sideNavSettings: { backgroundColor: '#064e3b', textColor: '#a7f3d0', activeLinkColor: '#10b981', hoverBackgroundColor: '#047857', hoverTextColor: '#ffffff', fontFamily: 'sans', fontSize: '14px' }, bannerSettings: { type: 'image', value: '', imageSource: { type: 'url', value: 'https://picsum.photos/seed/emerald-city/1200/300' }, height: 300, width: 'contained' } } },
+    { name: 'Aspen Light', settings: { theme: 'light', fontColor: '#374151', backgroundType: 'color', backgroundValue: '#f9fafb', buttonStyle: 'filled', buttonShape: 'pill', fontFamily: 'sans', sideNavSettings: { backgroundColor: '#ffffff', textColor: '#4b5563', activeLinkColor: '#d1d5db', hoverBackgroundColor: '#f3f4f6', hoverTextColor: '#111827', fontFamily: 'sans', fontSize: '14px' }, bannerSettings: { type: 'image', value: '', imageSource: { type: 'url', value: 'https://picsum.photos/seed/aspen/1200/300' }, height: 300, width: 'contained' } } },
+    { name: 'Sakura Dream', settings: { theme: 'dark', fontColor: '#fbcfe8', backgroundType: 'gradient', backgroundValue: 'bg-gradient-to-br from-gray-900 via-fuchsia-900/50 to-gray-900', buttonStyle: 'outline', buttonShape: 'pill', fontFamily: 'serif', sideNavSettings: { backgroundColor: '#581c87', textColor: '#fbcfe8', activeLinkColor: '#c026d3', hoverBackgroundColor: '#7e22ce', hoverTextColor: '#ffffff', fontFamily: 'serif', fontSize: '14px' }, bannerSettings: { type: 'image', value: '', imageSource: { type: 'url', value: 'https://picsum.photos/seed/sakura/1200/300' }, height: 300, width: 'contained' } } },
+    { name: 'Golden Sands', settings: { theme: 'light', fontColor: '#78350f', backgroundType: 'gradient', backgroundValue: 'bg-gradient-to-br from-amber-100 to-yellow-200', buttonStyle: 'filled', buttonShape: 'rounded', fontFamily: 'serif', sideNavSettings: { backgroundColor: '#fef3c7', textColor: '#92400e', activeLinkColor: '#f59e0b', hoverBackgroundColor: '#fcd34d', hoverTextColor: '#78350f', fontFamily: 'serif', fontSize: '14px' }, bannerSettings: { type: 'image', value: '', imageSource: { type: 'url', value: 'https://picsum.photos/seed/sands/1200/300' }, height: 300, width: 'contained' } } },
+    { name: 'Neo Tokyo', settings: { theme: 'dark', fontColor: '#a5b4fc', backgroundType: 'gradient', backgroundValue: 'bg-gradient-to-br from-black via-indigo-900 to-fuchsia-800', buttonStyle: 'outline', buttonShape: 'square', fontFamily: 'mono', sideNavSettings: { backgroundColor: '#1e1b4b', textColor: '#c7d2fe', activeLinkColor: '#4f46e5', hoverBackgroundColor: '#3730a3', hoverTextColor: '#ffffff', fontFamily: 'mono', fontSize: '14px' }, bannerSettings: { type: 'image', value: '', imageSource: { type: 'url', value: 'https://picsum.photos/seed/tokyo-night/1200/300' }, height: 300, width: 'contained' } } },
+    { name: 'Arctic Dawn', settings: { theme: 'light', fontColor: '#1e3a8a', backgroundType: 'gradient', backgroundValue: 'bg-gradient-to-br from-blue-100 via-white to-blue-50', buttonStyle: 'filled', buttonShape: 'pill', fontFamily: 'sans', sideNavSettings: { backgroundColor: '#eff6ff', textColor: '#1e3a8a', activeLinkColor: '#60a5fa', hoverBackgroundColor: '#dbeafe', hoverTextColor: '#1e293b', fontFamily: 'sans', fontSize: '14px' }, bannerSettings: { type: 'image', value: '', imageSource: { type: 'url', value: 'https://picsum.photos/seed/arctic/1200/300' }, height: 300, width: 'contained' } } },
+    { name: 'Mahogany', settings: { theme: 'dark', fontColor: '#fed7aa', backgroundType: 'color', backgroundValue: '#292524', buttonStyle: 'filled', buttonShape: 'square', fontFamily: 'serif', sideNavSettings: { backgroundColor: '#44403c', textColor: '#fed7aa', activeLinkColor: '#f97316', hoverBackgroundColor: '#9a3412', hoverTextColor: '#ffffff', fontFamily: 'serif', fontSize: '14px' }, bannerSettings: { type: 'image', value: '', imageSource: { type: 'url', value: 'https://picsum.photos/seed/mahogany/1200/300' }, height: 300, width: 'contained' } } },
+    { name: 'Oceanic Deep', settings: { theme: 'dark', fontColor: '#67e8f9', backgroundType: 'gradient', backgroundValue: 'bg-gradient-to-tr from-gray-900 to-teal-900', buttonStyle: 'outline', buttonShape: 'rounded', fontFamily: 'sans', sideNavSettings: { backgroundColor: '#134e4a', textColor: '#99f6e4', activeLinkColor: '#0d9488', hoverBackgroundColor: '#115e59', hoverTextColor: '#ccfbf1', fontFamily: 'sans', fontSize: '14px' }, bannerSettings: { type: 'image', value: '', imageSource: { type: 'url', value: 'https://picsum.photos/seed/oceanic/1200/300' }, height: 300, width: 'contained' } } },
+    { name: 'Scarlet & Slate', settings: { theme: 'dark', fontColor: '#f1f5f9', backgroundType: 'color', backgroundValue: '#1e293b', buttonStyle: 'filled', buttonShape: 'square', fontFamily: 'sans', sideNavSettings: { backgroundColor: '#0f172a', textColor: '#cbd5e1', activeLinkColor: '#dc2626', hoverBackgroundColor: '#475569', hoverTextColor: '#ffffff', fontFamily: 'sans', fontSize: '14px' }, bannerSettings: { type: 'image', value: '', imageSource: { type: 'url', value: 'https://picsum.photos/seed/scarlet-slate/1200/300' }, height: 300, width: 'contained' } } },
+    { name: 'Minimalist White', settings: { theme: 'light', fontColor: '#334155', backgroundType: 'color', backgroundValue: '#ffffff', buttonStyle: 'outline', buttonShape: 'square', fontFamily: 'sans', sideNavSettings: { backgroundColor: '#f1f5f9', textColor: '#475569', activeLinkColor: '#94a3b8', hoverBackgroundColor: '#e2e8f0', hoverTextColor: '#0f172a', fontFamily: 'sans', fontSize: '14px' }, bannerSettings: { type: 'image', value: '', imageSource: { type: 'url', value: 'https://picsum.photos/seed/minimal-white/1200/300' }, height: 300, width: 'contained' } } },
+];
+
+const extendedDesignTemplates: { name: string, settings: Partial<DesignSettings> & { sideNavSettings: Partial<SideNavSettings>, bannerSettings: Partial<BannerSettings> } }[] = [
+    { name: 'Cyberpunk Sunset', settings: { theme: 'dark' as ThemeName, fontColor: '#f472b6', backgroundType: 'gradient' as BackgroundType, backgroundValue: 'bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-900', buttonStyle: 'outline' as ButtonStyle, buttonShape: 'square' as ButtonShape, fontFamily: 'mono' as FontFamily, sideNavSettings: { backgroundColor: '#2b1c58', textColor: '#f472b6', activeLinkColor: '#a855f7', hoverBackgroundColor: '#4c1d95', hoverTextColor: '#f5d0fe', fontFamily: 'mono' as FontFamily, fontSize: '14px' }, bannerSettings: { type: 'image' as BannerType, value: '', imageSource: { type: 'url' as const, value: 'https://picsum.photos/seed/cyberpunk/1200/300' }, height: 300, width: 'contained' as 'contained' } } },
+    { name: 'Rustic Charm', settings: { theme: 'light' as ThemeName, fontColor: '#3f3f46', backgroundType: 'color' as BackgroundType, backgroundValue: '#f5f5f4', buttonStyle: 'filled' as ButtonStyle, buttonShape: 'rounded' as ButtonShape, fontFamily: 'serif' as FontFamily, sideNavSettings: { backgroundColor: '#e7e5e4', textColor: '#57534e', activeLinkColor: '#a8a29e', hoverBackgroundColor: '#d6d3d1', hoverTextColor: '#1c1917', fontFamily: 'serif' as FontFamily, fontSize: '14px' }, bannerSettings: { type: 'image' as BannerType, value: '', imageSource: { type: 'url' as const, value: 'https://picsum.photos/seed/rustic/1200/300' }, height: 300, width: 'contained' as 'contained' } } },
+    { name: 'Ocean Breeze', settings: { theme: 'light' as ThemeName, fontColor: '#0c4a6e', backgroundType: 'gradient' as BackgroundType, backgroundValue: 'bg-gradient-to-br from-sky-200 to-blue-200', buttonStyle: 'filled' as ButtonStyle, buttonShape: 'pill' as ButtonShape, fontFamily: 'sans' as FontFamily, sideNavSettings: { backgroundColor: '#e0f2fe', textColor: '#075985', activeLinkColor: '#38bdf8', hoverBackgroundColor: '#bae6fd', hoverTextColor: '#0c4a6e', fontFamily: 'sans' as FontFamily, fontSize: '14px' }, bannerSettings: { type: 'image' as BannerType, value: '', imageSource: { type: 'url' as const, value: 'https://picsum.photos/seed/breeze/1200/300' }, height: 300, width: 'contained' as 'contained' } } },
+    { name: 'Vintage Noir', settings: { theme: 'dark' as ThemeName, fontColor: '#d6d3d1', backgroundType: 'color' as BackgroundType, backgroundValue: '#1c1917', buttonStyle: 'outline' as ButtonStyle, buttonShape: 'square' as ButtonShape, fontFamily: 'serif' as FontFamily, sideNavSettings: { backgroundColor: '#292524', textColor: '#a8a29e', activeLinkColor: '#facc15', hoverBackgroundColor: '#44403c', hoverTextColor: '#fafafa', fontFamily: 'serif' as FontFamily, fontSize: '14px' }, bannerSettings: { type: 'image' as BannerType, value: '', imageSource: { type: 'url' as const, value: 'https://picsum.photos/seed/noir/1200/300' }, height: 300, width: 'contained' as 'contained' } } },
+    { name: 'Jungle Expedition', settings: { theme: 'light' as ThemeName, fontColor: '#1a2e05', backgroundType: 'gradient' as BackgroundType, backgroundValue: 'bg-gradient-to-br from-lime-100 via-green-100 to-lime-200', buttonStyle: 'filled' as ButtonStyle, buttonShape: 'pill' as ButtonShape, fontFamily: 'sans' as FontFamily, sideNavSettings: { backgroundColor: '#dcfce7', textColor: '#166534', activeLinkColor: '#4ade80', hoverBackgroundColor: '#bbf7d0', hoverTextColor: '#14532d', fontFamily: 'sans' as FontFamily, fontSize: '14px' }, bannerSettings: { type: 'image' as BannerType, value: '', imageSource: { type: 'url' as const, value: 'https://picsum.photos/seed/jungle/1200/300' }, height: 300, width: 'contained' as 'contained' } } },
+    { name: 'Cosmic Rift', settings: { theme: 'dark' as ThemeName, fontColor: '#e9d5ff', backgroundType: 'gradient' as BackgroundType, backgroundValue: 'bg-[radial-gradient(ellipse_at_bottom,_var(--tw-gradient-stops))] from-gray-700 via-gray-900 to-black', buttonStyle: 'outline' as ButtonStyle, buttonShape: 'pill' as ButtonShape, fontFamily: 'mono' as FontFamily, sideNavSettings: { backgroundColor: '#1e293b', textColor: '#93c5fd', activeLinkColor: '#60a5fa', hoverBackgroundColor: '#334155', hoverTextColor: '#dbeafe', fontFamily: 'mono' as FontFamily, fontSize: '14px' }, bannerSettings: { type: 'image' as BannerType, value: '', imageSource: { type: 'url' as const, value: 'https://picsum.photos/seed/cosmic/1200/300' }, height: 300, width: 'contained' as 'contained' } } },
+    { name: 'Autumn Glow', settings: { theme: 'light' as ThemeName, fontColor: '#422006', backgroundType: 'color' as BackgroundType, backgroundValue: '#fff7ed', buttonStyle: 'filled' as ButtonStyle, buttonShape: 'rounded' as ButtonShape, fontFamily: 'serif' as FontFamily, sideNavSettings: { backgroundColor: '#fed7aa', textColor: '#9a3412', activeLinkColor: '#fb923c', hoverBackgroundColor: '#fdba74', hoverTextColor: '#7c2d12', fontFamily: 'serif' as FontFamily, fontSize: '14px' }, bannerSettings: { type: 'image' as BannerType, value: '', imageSource: { type: 'url' as const, value: 'https://picsum.photos/seed/autumn/1200/300' }, height: 300, width: 'contained' as 'contained' } } },
+    { name: 'Concrete Jungle', settings: { theme: 'dark' as ThemeName, fontColor: '#e5e7eb', backgroundType: 'color' as BackgroundType, backgroundValue: '#27272a', buttonStyle: 'outline' as ButtonStyle, buttonShape: 'square' as ButtonShape, fontFamily: 'sans' as FontFamily, sideNavSettings: { backgroundColor: '#3f3f46', textColor: '#d4d4d8', activeLinkColor: '#a1a1aa', hoverBackgroundColor: '#52525b', hoverTextColor: '#fafafa', fontFamily: 'sans' as FontFamily, fontSize: '14px' }, bannerSettings: { type: 'image' as BannerType, value: '', imageSource: { type: 'url' as const, value: 'https://picsum.photos/seed/concrete/1200/300' }, height: 300, width: 'contained' as 'contained' } } },
+    { name: 'Pastel Playground', settings: { theme: 'light' as ThemeName, fontColor: '#57534e', backgroundType: 'color' as BackgroundType, backgroundValue: '#fdf2f8', buttonStyle: 'filled' as ButtonStyle, buttonShape: 'pill' as ButtonShape, fontFamily: 'sans' as FontFamily, sideNavSettings: { backgroundColor: '#fce7f3', textColor: '#9d174d', activeLinkColor: '#f472b6', hoverBackgroundColor: '#fbcfe8', hoverTextColor: '#831843', fontFamily: 'sans' as FontFamily, fontSize: '14px' }, bannerSettings: { type: 'image' as BannerType, value: '', imageSource: { type: 'url' as const, value: 'https://picsum.photos/seed/pastel/1200/300' }, height: 300, width: 'contained' as 'contained' } } },
+    { name: 'Deep Space', settings: { theme: 'dark' as ThemeName, fontColor: '#bfdbfe', backgroundType: 'gradient' as BackgroundType, backgroundValue: 'bg-gradient-to-tl from-gray-900 via-slate-900 to-blue-900', buttonStyle: 'outline' as ButtonStyle, buttonShape: 'rounded' as ButtonShape, fontFamily: 'mono' as FontFamily, sideNavSettings: { backgroundColor: '#0f172a', textColor: '#60a5fa', activeLinkColor: '#3b82f6', hoverBackgroundColor: '#1e293b', hoverTextColor: '#93c5fd', fontFamily: 'mono' as FontFamily, fontSize: '14px' }, bannerSettings: { type: 'image' as BannerType, value: '', imageSource: { type: 'url' as const, value: 'https://picsum.photos/seed/space/1200/300' }, height: 300, width: 'contained' as 'contained' } } },
+].concat(Array.from({ length: 20 }).map((_, i) => ({
+    name: `Pro Theme ${i + 1}`,
+    settings: {
+        theme: (['dark', 'light'] as const)[i % 2],
+        fontColor: i % 2 === 0 ? '#e0e7ff' : '#1e3a8a',
+        backgroundType: 'gradient' as const,
+        backgroundValue: "bg-gradient-to-br from-slate-800 to-indigo-900",
+        buttonStyle: (['filled', 'outline'] as const)[i % 2],
+        buttonShape: (['rounded', 'pill', 'square'] as const)[i % 3],
+        fontFamily: (['sans', 'serif', 'mono'] as const)[i % 3],
+        sideNavSettings: {
+            backgroundColor: i % 2 === 0 ? '#1e293b' : '#eef2ff',
+            textColor: i % 2 === 0 ? '#94a3b8' : '#312e81',
+            activeLinkColor: i % 2 === 0 ? '#4f46e5' : '#818cf8',
+            hoverBackgroundColor: i % 2 === 0 ? '#334155' : '#c7d2fe',
+            hoverTextColor: i % 2 === 0 ? '#e0e7ff' : '#1e1b4b',
+            fontFamily: (['sans', 'serif', 'mono'] as const)[i % 3],
+            fontSize: '14px',
+        },
+        bannerSettings: { type: 'image' as const, value: '', imageSource: { type: 'url' as const, value: `https://picsum.photos/seed/pro-theme-${i + 1}/1200/300` }, height: 300, width: 'contained' as const },
+    }
+})));
+
+interface DesignEditorProps {
+    design: DesignSettings;
+    setDesign: (newDesign: DesignSettings) => void;
+}
+
+const DesignEditor: React.FC<DesignEditorProps> = ({ design, setDesign }) => {
+    const { t } = useTranslation();
+    const [isThemeModalOpen, setIsThemeModalOpen] = React.useState(false);
+
+    const updateDesign = (key: keyof DesignSettings, value: any) => {
+        setDesign({ ...design, [key]: value });
+    };
+
+    const updateNested = (parentKey: keyof DesignSettings, childKey: string, value: any) => {
+        updateDesign(parentKey, { ...(design[parentKey] as any), [childKey]: value });
+    };
+
+    const applyThemeTemplate = (templateSettings: any) => {
+        const newSettings = JSON.parse(JSON.stringify(templateSettings));
+        const fullNewDesign = {
+            ...design,
+            ...newSettings,
+            bannerSettings: {
+                ...design.bannerSettings,
+                ...newSettings.bannerSettings,
+            },
+            sideNavSettings: {
+                ...design.sideNavSettings,
+                ...newSettings.sideNavSettings,
+            },
+        };
+        
+        if (!fullNewDesign.bannerSettings.height) fullNewDesign.bannerSettings.height = 300;
+        if (!fullNewDesign.bannerSettings.width) fullNewDesign.bannerSettings.width = 'contained';
+
+        setDesign(fullNewDesign);
+    };
+
+    const handleUploadImage = () => {
+        const newImage: MediaSource = { 
+            type: 'file', 
+            value: { 
+                name: `user_upload_${Date.now()}.jpg`, 
+                size: 123456, 
+                previewUrl: `https://picsum.photos/seed/user-bg-${Date.now()}/400/300` 
+            }
+        };
+        const updatedImages = [...(design.userBackgroundImages || []), newImage];
+        setDesign({ ...design, userBackgroundImages: updatedImages });
+    };
+
+
+    return (
+        <div className="space-y-1">
+            <Accordion title={t('design_editor.templates')} defaultOpen={true}>
+                 <div className="grid grid-cols-2 gap-3">
+                    {designTemplates.map(template => (
+                        <button key={template.name} onMouseDown={(e) => e.preventDefault()} onClick={() => applyThemeTemplate(template.settings)} className="p-2 rounded-lg border-2 border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-brand-primary transition-colors text-left">
+                           <div className="h-12 w-full rounded-md mb-2 bg-cover bg-center" style={{backgroundImage: `url(${(template.settings.bannerSettings as any)?.imageSource?.value})`}} />
+                           <p className="font-semibold text-sm">{template.name}</p>
+                        </button>
+                    ))}
+                    <button onClick={() => setIsThemeModalOpen(true)} className="col-span-2 p-4 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600 hover:border-brand-primary hover:bg-gray-100 dark:hover:bg-gray-900/50 transition-colors flex items-center justify-center">
+                        <span className="font-semibold text-gray-500 dark:text-gray-300">{t('design_editor.more_themes')}</span>
+                    </button>
+                </div>
+            </Accordion>
+            
+            <Accordion title={t('design_editor.profile')}>
+                <div className="space-y-2 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
+                    <h4 className="font-semibold mb-2">{t('design_editor.avatar')}</h4>
+                    <div className="flex items-center gap-2">
+                        <label className="text-xs text-gray-500 dark:text-gray-400">{t('design_editor.source')}:</label>
+                        <button onMouseDown={(e) => e.preventDefault()} onClick={() => {
+                            const newSource: MediaSource = { type: 'url', value: '' };
+                            updateDesign('avatarSource', newSource);
+                        }} className={`px-3 py-1 text-xs rounded-md ${design.avatarSource?.type === 'url' ? 'bg-brand-primary text-white' : 'bg-gray-200 dark:bg-gray-600'}`}>URL</button>
+                        <button onMouseDown={(e) => e.preventDefault()} onClick={() => {
+                            const newSource: MediaSource = { type: 'file', value: { name: 'avatar.jpg', size: 12345, previewUrl: `https://api.dicebear.com/8.x/adventurer/svg?seed=NewAvatar` } };
+                            updateDesign('avatarSource', newSource);
+                        }} className={`px-3 py-1 text-xs rounded-md ${design.avatarSource?.type === 'file' ? 'bg-brand-primary text-white' : 'bg-gray-200 dark:bg-gray-600'}`}>{t('link_editor.upload')}</button>
+                    </div>
+                    {design.avatarSource?.type === 'url' ?
+                        <input type="url" placeholder="Enter image URL..." value={design.avatarSource.value} onChange={e => {
+                            const newSource: MediaSource = { type: 'url', value: e.target.value };
+                            updateDesign('avatarSource', newSource);
+                        }} className="w-full bg-gray-200 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600" />
+                        : 
+                        design.avatarSource?.type === 'file' ?
+                        <div className="text-sm p-2 bg-gray-200 dark:bg-gray-700 rounded-md">Simulated Upload: {design.avatarSource.value.name}</div>
+                        : <div className="text-sm p-2 text-gray-500">No avatar set.</div>
+                    }
+                </div>
+            </Accordion>
+
+            <Accordion title={t('design_editor.appearance')}>
+                <>
+                    <div className="grid grid-cols-2 gap-4">
+                        {themes.map(th => <button key={th.value} onMouseDown={(e) => e.preventDefault()} onClick={() => updateDesign('theme', th.value)} className={`p-4 rounded-lg border-2 transition-colors ${design.theme === th.value ? 'border-brand-primary bg-gray-200 dark:bg-gray-700' : 'border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-brand-primary'}`}><p className="font-semibold">{th.name}</p></button>)}
+                        <button onMouseDown={(e) => e.preventDefault()} onClick={() => updateDesign('theme', 'custom')} className={`p-4 rounded-lg border-2 transition-colors ${design.theme === 'custom' ? 'border-brand-primary bg-gray-200 dark:bg-gray-700' : 'border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-brand-primary'}`}><p className="font-semibold">{t('design_editor.custom')}</p></button>
+                    </div>
+                    {design.theme === 'custom' && (
+                        <div className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg space-y-3 mt-4">
+                            <ColorPalettePicker label={t('design_editor.background_color')} color={design.customThemeColors.background} onChange={c => updateNested('customThemeColors', 'background', c)} />
+                            <ColorPalettePicker label={t('design_editor.text_color')} color={design.customThemeColors.text} onChange={c => updateNested('customThemeColors', 'text', c)} />
+                            <ColorPalettePicker label={t('design_editor.button_color')} color={design.customThemeColors.button} onChange={c => updateNested('customThemeColors', 'button', c)} />
+                            <ColorPalettePicker label={t('design_editor.button_text')} color={design.customThemeColors.buttonText} onChange={c => updateNested('customThemeColors', 'buttonText', c)} />
+                        </div>
+                    )}
+                </>
+            </Accordion>
+            
+            <Accordion title={t('design_editor.typography')}>
+                <div className="space-y-4">
+                    <ColorPalettePicker label={t('design_editor.font_color')} color={design.fontColor} onChange={c => updateDesign('fontColor', c)} />
+                    <div>
+                         <label className="text-sm text-gray-600 dark:text-gray-300">{t('design_editor.font_size')}</label>
+                         <select value={design.fontSize} onChange={e => updateDesign('fontSize', e.target.value)} className="w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600 mt-1">
+                            {fontSizes.map(fs => <option key={fs.value} value={fs.value}>{fs.name}</option>)}
+                         </select>
+                    </div>
+                    <div>
+                         <label className="text-sm text-gray-600 dark:text-gray-300">{t('design_editor.font_family')}</label>
+                         <select value={design.fontFamily} onChange={e => updateDesign('fontFamily', e.target.value as FontFamily)} className="w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600 mt-1">
+                            <option value="sans">Sans-Serif</option><option value="serif">Serif</option><option value="mono">Monospaced</option>
+                         </select>
+                    </div>
+                </div>
+            </Accordion>
+
+            <Accordion title={t('design_editor.buttons')}>
+                <div className="space-y-4">
+                    <div>
+                        <h4 className="text-sm font-semibold mb-2">{t('design_editor.button_style')}</h4>
+                        <div className="flex gap-2">
+                           {(['filled', 'outline'] as ButtonStyle[]).map(style => <button key={style} onMouseDown={(e) => e.preventDefault()} onClick={() => updateDesign('buttonStyle', style)} className={`px-4 py-2 text-sm rounded-md capitalize flex-1 border-2 ${design.buttonStyle === style ? 'bg-brand-primary border-brand-primary text-white' : 'bg-transparent border-gray-300 dark:border-gray-600'}`}>{style}</button>)}
+                        </div>
+                    </div>
+                    <div>
+                        <h4 className="text-sm font-semibold mb-2">{t('design_editor.button_shape')}</h4>
+                        <div className="flex gap-2">
+                           {(['rounded', 'pill', 'square'] as ButtonShape[]).map(shape => <button key={shape} onMouseDown={(e) => e.preventDefault()} onClick={() => updateDesign('buttonShape', shape)} className={`px-4 py-2 text-sm flex-1 border-2 ${design.buttonShape === shape ? 'bg-brand-primary border-brand-primary text-white' : 'bg-transparent border-gray-300 dark:border-gray-600'} ${shape === 'rounded' ? 'rounded-md' : shape === 'pill' ? 'rounded-full' : ''}`}>{shape}</button>)}
+                        </div>
+                    </div>
+                </div>
+            </Accordion>
+            
+            <Accordion title={t('design_editor.background')}>
+                 <div>
+                    <div className="flex gap-2 mb-4">
+                        {(['color', 'gradient', 'image'] as BackgroundType[]).map(type => 
+                            <button key={type} onMouseDown={(e) => e.preventDefault()} onClick={() => {
+                                let newValue = design.backgroundValue;
+                                if (type === 'color' && design.backgroundType !== 'color') newValue = '#1f2937';
+                                if (type === 'gradient' && design.backgroundType !== 'gradient') newValue = predefinedGradients[0].value;
+                                if (type === 'image' && design.backgroundType !== 'image') {
+                                    const allImages = [...predefinedBackgrounds, ...(design.userBackgroundImages || [])];
+                                    newValue = allImages.length > 0 ? getImageUrl(allImages[0]) : '';
+                                };
+                                setDesign({ ...design, backgroundType: type, backgroundValue: newValue });
+                            }} 
+                            className={`px-4 py-2 text-sm rounded-md capitalize flex-1 border-2 ${design.backgroundType === type ? 'bg-brand-primary border-brand-primary text-white' : 'bg-transparent border-gray-300 dark:border-gray-600'}`}>{type}</button>
+                        )}
+                    </div>
+                    {design.backgroundType === 'color' && <ColorPalettePicker label={t('design_editor.background_color')} color={design.backgroundValue} onChange={c => updateDesign('backgroundValue', c)} />}
+                    {design.backgroundType === 'gradient' && (<div className="grid grid-cols-2 gap-4">{predefinedGradients.map(bg => <button key={bg.name} onMouseDown={(e) => e.preventDefault()} onClick={() => updateDesign('backgroundValue', bg.value)} className={`h-16 rounded-lg ${bg.value} flex items-center justify-center text-white font-semibold transition-all ${design.backgroundValue === bg.value ? 'ring-2 ring-brand-primary ring-offset-2 ring-offset-gray-50 dark:ring-offset-gray-900' : ''}`}>{bg.name}</button>)}</div>)}
+                    {design.backgroundType === 'image' && (
+                        <div className="space-y-4">
+                            <div className="grid grid-cols-2 gap-3">
+                                {[...predefinedBackgrounds, ...(design.userBackgroundImages || [])].map(bg => {
+                                    const url = getImageUrl(bg);
+                                    const isSelected = design.backgroundValue === url;
+                                    return (
+                                        <button key={url} onMouseDown={(e) => e.preventDefault()} onClick={() => updateDesign('backgroundValue', url)} className={`relative group rounded-lg overflow-hidden border-2 transition-all h-24 ${isSelected ? 'border-brand-primary' : 'border-transparent'}`}>
+                                            <img src={url} className="w-full h-full object-cover" alt="Background option"/>
+                                            {isSelected && <div className="absolute inset-0 bg-black/50 flex items-center justify-center"><Icon className="h-6 w-6 text-white"><path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7"/></Icon></div>}
+                                        </button>
+                                    )
+                                })}
+                            </div>
+                            <button onClick={handleUploadImage} className="w-full p-3 bg-gray-200 dark:bg-gray-700 rounded-lg text-sm font-semibold hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">
+                                {t('design_editor.upload_image')}
+                            </button>
+                        </div>
+                    )}
+                 </div>
+            </Accordion>
+            
+            <Accordion title={t('design_editor.enterprise_layout')} defaultOpen={false}>
+                <div className="space-y-6">
+                    <div>
+                        <h4 className="font-semibold mb-3">{t('design_editor.banner')}</h4>
+                        <div className="space-y-4 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
+                             <div>
+                                <label className="text-sm text-gray-600 dark:text-gray-300 block mb-2">{t('design_editor.banner_type')}</label>
+                                <div className="flex gap-2">
+                                     {(['color', 'gradient', 'image', 'none'] as BannerType[]).map(type => 
+                                        <button key={type} onMouseDown={(e) => e.preventDefault()} onClick={() => {
+                                            let newSettings = { ...design.bannerSettings, type };
+                                            if (type === 'color' && design.bannerSettings.type !== 'color') newSettings.value = '#374151';
+                                            if (type === 'gradient' && design.bannerSettings.type !== 'gradient') newSettings.value = predefinedGradients[0].value;
+                                            if (type === 'image' && design.bannerSettings.type !== 'image') {
+                                                if (!newSettings.imageSource) newSettings.imageSource = { type: 'url', value: '' };
+                                            }
+                                            updateDesign('bannerSettings', newSettings);
+                                        }} className={`px-4 py-2 text-sm rounded-md capitalize flex-1 border-2 ${design.bannerSettings.type === type ? 'bg-brand-primary border-brand-primary text-white' : 'bg-transparent border-gray-300 dark:border-gray-600'}`}>{type}</button>
+                                     )}
+                                </div>
+                            </div>
+                            {design.bannerSettings.type !== 'none' && (
+                                <>
+                                    <div>
+                                        <label className="text-sm text-gray-600 dark:text-gray-300 block mb-2">{t('design_editor.banner_width')}</label>
+                                        <div className="flex gap-2">
+                                            <button onMouseDown={(e) => e.preventDefault()} onClick={() => updateNested('bannerSettings', 'width', 'contained')} className={`px-4 py-2 text-sm rounded-md capitalize flex-1 border-2 ${design.bannerSettings.width === 'contained' ? 'bg-brand-primary border-brand-primary text-white' : 'bg-transparent border-gray-300 dark:border-gray-600'}`}>{t('design_editor.contained')}</button>
+                                            <button onMouseDown={(e) => e.preventDefault()} onClick={() => updateNested('bannerSettings', 'width', 'full')} className={`px-4 py-2 text-sm rounded-md capitalize flex-1 border-2 ${design.bannerSettings.width === 'full' ? 'bg-brand-primary border-brand-primary text-white' : 'bg-transparent border-gray-300 dark:border-gray-600'}`}>{t('design_editor.full_width')}</button>
+                                        </div>
+                                    </div>
+                                    <div>
+                                        <label htmlFor="bannerHeight" className="text-sm text-gray-600 dark:text-gray-300 block mb-1">{t('design_editor.banner_height')}</label>
+                                        <input id="bannerHeight" type="number" value={design.bannerSettings.height} onChange={e => updateNested('bannerSettings', 'height', parseInt(e.target.value, 10) || 0)} className="w-full bg-gray-200 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600"/>
+                                    </div>
+                                    {design.bannerSettings.type === 'color' && <ColorPalettePicker label={t('design_editor.banner_color')} color={design.bannerSettings.value} onChange={c => updateNested('bannerSettings', 'value', c)} />}
+                                    {design.bannerSettings.type === 'gradient' && (<div className="grid grid-cols-2 gap-4">{predefinedGradients.map(bg => <button key={bg.name} onMouseDown={(e) => e.preventDefault()} onClick={() => updateNested('bannerSettings', 'value', bg.value)} className={`h-16 rounded-lg ${bg.value} flex items-center justify-center text-white font-semibold transition-all ${design.bannerSettings.value === bg.value ? 'ring-2 ring-brand-primary ring-offset-2 ring-offset-gray-50 dark:ring-offset-gray-900' : ''}`}>{bg.name}</button>)}</div>)}
+                                    {design.bannerSettings.type === 'image' && (
+                                        <div className="space-y-2">
+                                            <div className="flex items-center gap-2">
+                                                <label className="text-xs text-gray-500 dark:text-gray-400">{t('design_editor.source')}:</label>
+                                                <button onMouseDown={(e) => e.preventDefault()} onClick={() => {
+                                                    const newSource: MediaSource = { type: 'url', value: '' };
+                                                    updateNested('bannerSettings', 'imageSource', newSource);
+                                                }} className={`px-3 py-1 text-xs rounded-md ${design.bannerSettings.imageSource?.type === 'url' ? 'bg-brand-primary text-white' : 'bg-gray-200 dark:bg-gray-600'}`}>URL</button>
+                                                <button onMouseDown={(e) => e.preventDefault()} onClick={() => {
+                                                    const newSource: MediaSource = { type: 'file', value: { name: 'banner.jpg', size: 12345, previewUrl: `https://picsum.photos/seed/upload-${Date.now()}/1200/300` } };
+                                                    updateNested('bannerSettings', 'imageSource', newSource);
+                                                }} className={`px-3 py-1 text-xs rounded-md ${design.bannerSettings.imageSource?.type === 'file' ? 'bg-brand-primary text-white' : 'bg-gray-200 dark:bg-gray-600'}`}>{t('link_editor.upload')}</button>
+                                            </div>
+                                            {design.bannerSettings.imageSource?.type === 'url' ?
+                                                <input type="url" placeholder="Enter image URL..." value={design.bannerSettings.imageSource.value} onChange={e => {
+                                                    const newSource: MediaSource = { type: 'url', value: e.target.value };
+                                                    updateNested('bannerSettings', 'imageSource', newSource);
+                                                }} className="w-full bg-gray-200 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600" />
+                                                : 
+                                                design.bannerSettings.imageSource?.type === 'file' ?
+                                                <div className="text-sm p-2 bg-gray-200 dark:bg-gray-700 rounded-md">Simulated Upload: {design.bannerSettings.imageSource.value.name}</div>
+                                                : null
+                                            }
+                                        </div>
+                                    )}
+                                </>
+                            )}
+                        </div>
+                    </div>
+                    <div>
+                        <h4 className="font-semibold mb-3">{t('design_editor.side_nav')}</h4>
+                        <div className="space-y-3 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
+                             <div className="grid grid-cols-2 gap-4">
+                                <div>
+                                     <label className="text-sm text-gray-600 dark:text-gray-300">{t('design_editor.font_family')}</label>
+                                     <select value={design.sideNavSettings.fontFamily} onChange={e => updateNested('sideNavSettings', 'fontFamily', e.target.value as FontFamily)} className="w-full bg-gray-200 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600 mt-1">
+                                        <option value="sans">Sans-Serif</option><option value="serif">Serif</option><option value="mono">Monospaced</option>
+                                     </select>
+                                </div>
+                                <div>
+                                     <label className="text-sm text-gray-600 dark:text-gray-300">{t('design_editor.font_size')}</label>
+                                      <input type="text" placeholder="e.g., 14px" value={design.sideNavSettings.fontSize} onChange={e => updateNested('sideNavSettings', 'fontSize', e.target.value)} className="w-full bg-gray-200 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600 mt-1" />
+                                </div>
+                             </div>
+                             <div>
+                                <label className="text-sm text-gray-600 dark:text-gray-300 block mb-2">{t('design_editor.position')}</label>
+                                <div className="flex gap-2">
+                                    {(['normal', 'top', 'center'] as const).map(style => 
+                                        <button key={style} onMouseDown={(e) => e.preventDefault()} onClick={() => updateNested('sideNavSettings', 'navFloatStyle', style)} 
+                                            className={`px-4 py-2 text-sm rounded-md capitalize flex-1 border-2 ${(design.sideNavSettings.navFloatStyle || 'normal') === style ? 'bg-brand-primary border-brand-primary text-white' : 'bg-transparent border-gray-300 dark:border-gray-600'}`}>
+                                            {style === 'top' ? t('design_editor.float_top') : style === 'center' ? t('design_editor.float_center') : t('design_editor.normal')}
+                                        </button>
+                                    )}
+                                </div>
+                            </div>
+                             <div>
+                                <label className="text-sm text-gray-600 dark:text-gray-300 block mb-2">{t('design_editor.background_style')}</label>
+                                <div className="flex gap-2">
+                                    {(['compact', 'full'] as const).map(style => 
+                                        <button key={style} onMouseDown={(e) => e.preventDefault()} onClick={() => updateNested('sideNavSettings', 'navBackgroundStyle', style)} 
+                                            className={`px-4 py-2 text-sm rounded-md capitalize flex-1 border-2 ${(design.sideNavSettings.navBackgroundStyle || 'compact') === style ? 'bg-brand-primary border-brand-primary text-white' : 'bg-transparent border-gray-300 dark:border-gray-600'}`}>
+                                            {style === 'full' ? t('design_editor.full_height') : t('design_editor.compact')}
+                                        </button>
+                                    )}
+                                </div>
+                            </div>
+                            <ColorPalettePicker label={t('design_editor.background_color')} color={design.sideNavSettings.backgroundColor} onChange={c => updateNested('sideNavSettings', 'backgroundColor', c)} />
+                            <ColorPalettePicker label={t('design_editor.text_color')} color={design.sideNavSettings.textColor} onChange={c => updateNested('sideNavSettings', 'textColor', c)} />
+                            <ColorPalettePicker label={t('design_editor.active_link')} color={design.sideNavSettings.activeLinkColor} onChange={c => updateNested('sideNavSettings', 'activeLinkColor', c)} />
+                            <ColorPalettePicker label={t('design_editor.hover_bg')} color={design.sideNavSettings.hoverBackgroundColor} onChange={c => updateNested('sideNavSettings', 'hoverBackgroundColor', c)} />
+                            <ColorPalettePicker label={t('design_editor.hover_text')} color={design.sideNavSettings.hoverTextColor} onChange={c => updateNested('sideNavSettings', 'hoverTextColor', c)} />
+                        </div>
+                    </div>
+                    <div>
+                        <h4 className="font-semibold mb-3">{t('design_editor.chat_widget')}</h4>
+                        <div className="space-y-3 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
+                            <ColorPalettePicker label={t('design_editor.icon_color')} color={design.chatWidgetSettings.iconColor} onChange={c => updateNested('chatWidgetSettings', 'iconColor', c)} />
+                            <ColorPalettePicker label={t('design_editor.header_bg')} color={design.chatWidgetSettings.headerBackgroundColor} onChange={c => updateNested('chatWidgetSettings', 'headerBackgroundColor', c)} />
+                            <ColorPalettePicker label={t('design_editor.header_text')} color={design.chatWidgetSettings.headerTextColor} onChange={c => updateNested('chatWidgetSettings', 'headerTextColor', c)} />
+                             <ColorPalettePicker label={t('design_editor.panel_bg')} color={design.chatWidgetSettings.panelBackgroundColor} onChange={c => updateNested('chatWidgetSettings', 'panelBackgroundColor', c)} />
+                            <ColorPalettePicker label={t('design_editor.user_message')} color={design.chatWidgetSettings.userMessageBackgroundColor} onChange={c => updateNested('chatWidgetSettings', 'userMessageBackgroundColor', c)} />
+                            <ColorPalettePicker label={t('design_editor.user_text')} color={design.chatWidgetSettings.userMessageTextColor} onChange={c => updateNested('chatWidgetSettings', 'userMessageTextColor', c)} />
+                             <ColorPalettePicker label={t('design_editor.ai_message')} color={design.chatWidgetSettings.aiMessageBackgroundColor} onChange={c => updateNested('chatWidgetSettings', 'aiMessageBackgroundColor', c)} />
+                            <ColorPalettePicker label={t('design_editor.ai_text')} color={design.chatWidgetSettings.aiMessageTextColor} onChange={c => updateNested('chatWidgetSettings', 'aiMessageTextColor', c)} />
+                        </div>
+                    </div>
+                </div>
+            </Accordion>
+
+            {isThemeModalOpen && (
+                 <div className="fixed inset-0 bg-black/70 z-50 flex items-center justify-center backdrop-blur-sm" onClick={() => setIsThemeModalOpen(false)}>
+                    <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl w-full max-w-5xl h-[90vh] p-8 border border-gray-200 dark:border-gray-700 flex flex-col" onClick={e => e.stopPropagation()}>
+                        <div className="flex justify-between items-center mb-6 flex-shrink-0">
+                            <h2 className="text-3xl font-bold text-gray-900 dark:text-white">{t('design_editor.more_themes')}</h2>
+                            <button onClick={() => setIsThemeModalOpen(false)} className="text-gray-400 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">
+                                <Icon className="h-8 w-8"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /></Icon>
+                            </button>
+                        </div>
+                        <div className="flex-1 overflow-y-auto pr-4 -mr-4">
+                            <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
+                                {extendedDesignTemplates.map(template => (
+                                    <button key={template.name} onMouseDown={(e) => e.preventDefault()} onClick={() => { applyThemeTemplate(template.settings); setIsThemeModalOpen(false); }} className="p-2 rounded-lg border-2 border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-brand-primary transition-colors text-left">
+                                        <div className="h-20 w-full rounded-md mb-2 bg-cover bg-center" style={{backgroundImage: `url(${(template.settings.bannerSettings as any)?.imageSource?.value})`}} />
+                                        <p className="font-semibold text-sm">{template.name}</p>
+                                    </button>
+                                ))}
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            )}
+        </div>
+    );
+};
+
+export default DesignEditor;

+ 60 - 0
components/EnterpriseHosting.tsx

@@ -0,0 +1,60 @@
+import * as React from 'react';
+import { Icon } from './ui/Icon';
+import { useTranslation } from '../hooks/useI18n';
+
+const EnterpriseHosting: React.FC = () => {
+    const { t } = useTranslation();
+    const [customDomain, setCustomDomain] = React.useState('');
+
+    return (
+        <div className="p-8 max-w-4xl mx-auto">
+            <header className="mb-8">
+                <h2 className="text-3xl font-bold text-gray-900 dark:text-white">{t('seo.hosting.title')}</h2>
+                <p className="text-gray-500 dark:text-gray-400 mt-1">{t('seo.hosting.subtitle')}</p>
+            </header>
+            <div className="space-y-8">
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm">
+                    <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">{t('seo.hosting.custom_domain')}</h3>
+                    <p className="text-gray-500 dark:text-gray-400 mb-2">{t('seo.hosting.custom_domain_desc')}</p>
+                    <div className="flex items-center bg-gray-100 dark:bg-gray-700 rounded-md">
+                        <input
+                            type="text"
+                            value={customDomain}
+                            onChange={(e) => setCustomDomain(e.target.value)}
+                            placeholder="your-domain.com"
+                            className="w-full bg-transparent p-3 text-gray-900 dark:text-white focus:outline-none rounded-md"
+                        />
+                    </div>
+                    <button className="mt-4 w-full md:w-auto bg-brand-primary text-white font-semibold py-2 px-5 rounded-md hover:bg-brand-secondary transition-colors flex items-center justify-center gap-2">
+                        {t('seo.hosting.connect_domain')}
+                        <span className="text-xs bg-yellow-400 text-yellow-900 px-2 py-0.5 rounded-full font-bold">{t('pro')}</span>
+                    </button>
+                    <p className="text-xs text-gray-400 dark:text-gray-500 mt-4">{t('seo.hosting.dns_note')}</p>
+                </div>
+
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm">
+                    <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">{t('seo.hosting.subscription')}</h3>
+                    <p className="text-gray-500 dark:text-gray-400 mb-4">{t('seo.hosting.subscription_desc')}</p>
+                    <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
+                        {['Starter', 'Business', 'Enterprise'].map((plan, index) => (
+                            <div key={plan} className={`p-6 rounded-lg border-2 ${index === 1 ? 'border-brand-primary bg-brand-primary/5' : 'border-gray-200 dark:border-gray-700'}`}>
+                                <h4 className="text-xl font-bold text-gray-900 dark:text-white">{plan}</h4>
+                                <p className="text-3xl font-bold text-gray-900 dark:text-white my-4">${[29, 79, 199][index]}<span className="text-base font-normal text-gray-500 dark:text-gray-400">/mo</span></p>
+                                <ul className="space-y-2 text-sm text-gray-600 dark:text-gray-300">
+                                    <li className="flex items-center gap-2"><Icon className="h-4 w-4 text-brand-primary"><path d="M5 13l4 4L19 7"/></Icon> Custom Domain</li>
+                                    <li className="flex items-center gap-2"><Icon className="h-4 w-4 text-brand-primary"><path d="M5 13l4 4L19 7"/></Icon> {['10k', '50k', 'Unlimited'][index]} Clicks/mo</li>
+                                    <li className="flex items-center gap-2"><Icon className="h-4 w-4 text-brand-primary"><path d="M5 13l4 4L19 7"/></Icon> Managed Hosting</li>
+                                </ul>
+                                <button className={`mt-6 w-full py-2 rounded-md font-semibold ${index === 1 ? 'bg-brand-primary text-white' : 'bg-gray-200 dark:bg-gray-600'}`}>
+                                    {index === 1 ? t('seo.hosting.current_plan') : t('seo.hosting.select_plan')}
+                                </button>
+                            </div>
+                        ))}
+                    </div>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default EnterpriseHosting;

+ 96 - 0
components/EnterpriseNav.tsx

@@ -0,0 +1,96 @@
+import * as React from 'react';
+import { Block, HeaderBlock, SideNavSettings } from '../types';
+import { Icon } from './ui/Icon';
+
+const getFontFamilyValue = (font: SideNavSettings['fontFamily']) => {
+    switch (font) {
+        case 'serif': return 'serif';
+        case 'mono': return 'monospace';
+        case 'sans': default: return 'sans-serif';
+    }
+}
+
+var EnterpriseNav: React.FC<{ blocks: Block[]; deviceView: 'pc' | 'mobile'; scrollContainerRef: React.RefObject<HTMLDivElement>, settings: SideNavSettings }> = function(props) {
+    var blocks = props.blocks, deviceView = props.deviceView, scrollContainerRef = props.scrollContainerRef, settings = props.settings;
+    var isNavOpenState = React.useState(false);
+    var isNavOpen = isNavOpenState[0];
+    var setIsNavOpen = isNavOpenState[1];
+    
+    var headers = blocks.filter(function(b) { return b.type === 'header'; }) as HeaderBlock[];
+
+    var handleScrollTo = function(blockId: string) {
+        if (scrollContainerRef.current) {
+            var element = scrollContainerRef.current.querySelector('#block-' + blockId);
+            if (element) {
+                element.scrollIntoView({ behavior: 'smooth', block: 'start' });
+            }
+        }
+        setIsNavOpen(false);
+    };
+
+    var handleBackToTop = function() {
+        if (scrollContainerRef.current) {
+            scrollContainerRef.current.scrollTo({ top: 0, behavior: 'smooth' });
+        }
+        setIsNavOpen(false);
+    };
+
+    var navLinks = [
+        React.createElement(
+          "button",
+          {
+            key: "back-to-top",
+            onClick: handleBackToTop,
+            className: "block w-full text-left p-2 rounded-md transition-colors font-semibold",
+            style: { color: settings.textColor },
+            onMouseOver: (e: any) => { e.currentTarget.style.backgroundColor = settings.hoverBackgroundColor; e.currentTarget.style.color = settings.hoverTextColor; },
+            onMouseOut: (e: any) => { e.currentTarget.style.backgroundColor = 'transparent'; e.currentTarget.style.color = settings.textColor; },
+          } as any,
+          "↑ Back to Top"
+        )
+    ].concat(headers.map(function (header) {
+        return React.createElement(
+          "button",
+          {
+            key: header.id,
+            onClick: function () { return handleScrollTo(header.id); },
+            className: "block w-full text-left p-2 rounded-md transition-colors",
+            style: { color: settings.textColor },
+            onMouseOver: (e: any) => { e.currentTarget.style.backgroundColor = settings.hoverBackgroundColor; e.currentTarget.style.color = settings.hoverTextColor; },
+            onMouseOut: (e: any) => { e.currentTarget.style.backgroundColor = 'transparent'; e.currentTarget.style.color = settings.textColor; },
+          } as any,
+          header.text
+        );
+      }));
+
+    var navElement = React.createElement("nav", { className: "space-y-2" }, ...navLinks);
+
+    if (deviceView === 'pc') {
+        var navBackgroundStyle = settings.navBackgroundStyle || 'compact';
+        var asideStyle: React.CSSProperties = {
+            fontFamily: getFontFamilyValue(settings.fontFamily),
+            fontSize: settings.fontSize,
+        };
+        var asideClasses = "p-4";
+        if (navBackgroundStyle === 'compact') {
+            asideClasses += ' rounded-lg';
+            asideStyle.backgroundColor = settings.backgroundColor;
+        }
+        return React.createElement("aside", {
+            className: asideClasses,
+            style: asideStyle
+        }, navElement);
+    }
+    
+    var mobileNavContent = React.createElement("div", { className: "p-4" }, navElement);
+
+    return React.createElement(React.Fragment, null,
+        React.createElement("button", { onClick: function() { return setIsNavOpen(true); }, className: "absolute top-4 left-4 z-30 p-2 bg-black/50 rounded-full" } as any,
+             React.createElement(Icon, { className: "h-6 w-6 text-white", children: React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4 6h16M4 12h16M4 18h16" }) })
+        ),
+        isNavOpen && React.createElement("div", { className: "absolute inset-0 bg-black/50 z-40", onClick: function() { return setIsNavOpen(false); } } as any),
+        React.createElement("div", { className: "absolute top-0 left-0 h-full w-64 bg-white dark:bg-gray-800 shadow-lg z-50 transition-transform duration-300 " + (isNavOpen ? "translate-x-0" : "-translate-x-full") } as any, mobileNavContent)
+    );
+};
+
+export default EnterpriseNav;

+ 217 - 0
components/InteractionAnalytics.tsx

@@ -0,0 +1,217 @@
+import * as React from 'react';
+import { Conversation, AnalyticsData, ChatMessage } from '../types';
+import { subDays, format, eachDayOfInterval, startOfDay } from 'date-fns';
+import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
+import { Icon } from './ui/Icon';
+import { useTranslation } from '../hooks/useI18n';
+
+const STOP_WORDS_LIST = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now'];
+const STOP_WORDS = new Set(STOP_WORDS_LIST);
+
+const EditResponseModal: React.FC<{
+    message: ChatMessage;
+    onClose: () => void;
+    onSave: (newText: string) => void;
+}> = ({ message, onClose, onSave }) => {
+    const { t } = useTranslation();
+    const [text, setText] = React.useState(message.text);
+    return (
+        <div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50" onClick={onClose}>
+            <div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md" onClick={e => e.stopPropagation()}>
+                <div className="p-6 border-b border-gray-200 dark:border-gray-700">
+                    <h3 className="text-lg font-bold text-gray-900 dark:text-white">{t('analytics.interactions.edit_response_title')}</h3>
+                </div>
+                <div className="p-6">
+                    <textarea value={text} onChange={e => setText(e.target.value)} rows={5} className="w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600" />
+                </div>
+                <div className="p-4 bg-gray-50 dark:bg-gray-900/50 flex justify-end gap-3">
+                    <button onClick={onClose} className="px-4 py-2 rounded-md text-sm font-semibold text-gray-700 dark:text-gray-300 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600">{t('cancel')}</button>
+                    <button onClick={() => onSave(text)} className="px-4 py-2 rounded-md text-sm font-semibold text-white bg-brand-primary hover:bg-brand-secondary">{t('analytics.interactions.save_and_update')}</button>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+interface InteractionAnalyticsProps {
+    analyticsData: AnalyticsData;
+    onUpdateConversations: (conversations: Conversation[]) => void;
+}
+
+const InteractionAnalytics: React.FC<InteractionAnalyticsProps> = ({ analyticsData, onUpdateConversations }) => {
+    // FIX: Destructure 'language' from useTranslation hook to make it available for 'toLocaleString'.
+    const { t, dateLocale, language } = useTranslation();
+    const { conversations } = analyticsData;
+    const [dateFilter, setDateFilter] = React.useState<string>('28');
+    const [statusFilter, setStatusFilter] = React.useState<'all' | 'open' | 'resolved'>('all');
+    const [searchTerm, setSearchTerm] = React.useState('');
+    const [isDarkMode, setIsDarkMode] = React.useState(document.documentElement.classList.contains('dark'));
+    const [editingMessage, setEditingMessage] = React.useState<{convId: string, msg: ChatMessage} | null>(null);
+
+    React.useEffect(() => {
+        const observer = new MutationObserver(() => setIsDarkMode(document.documentElement.classList.contains('dark')));
+        observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
+        return () => observer.disconnect();
+    }, []);
+
+    const chartColors = {
+        grid: isDarkMode ? '#374151' : '#e5e7eb',
+        text: isDarkMode ? '#9CA3AF' : '#6b7280',
+        tooltipBg: isDarkMode ? '#1F2937' : '#FFFFFF',
+        tooltipBorder: isDarkMode ? '#4B5563' : '#d1d5db',
+        bar1: isDarkMode ? '#10b981' : '#10b981',
+        bar2: isDarkMode ? '#3b82f6' : '#3b82f6',
+    };
+
+    const filteredConversations = React.useMemo(() => {
+        const dateLimit = subDays(new Date(), parseInt(dateFilter, 10));
+        return conversations.filter(c => {
+            const conversationDate = new Date(c.timestamp);
+            if (conversationDate < dateLimit) return false;
+            if (statusFilter !== 'all' && c.status !== statusFilter) return false;
+            if (searchTerm && !c.interactions.some(i => i.text.toLowerCase().includes(searchTerm.toLowerCase()))) return false;
+            return true;
+        });
+    }, [conversations, dateFilter, statusFilter, searchTerm]);
+
+    const analyticsSummary = React.useMemo(() => {
+        const total = filteredConversations.length;
+        if (total === 0) return { total: 0, resolved: 0, resolutionRate: '0%', avgInteractions: 0, avgResponseTime: 'N/A', conversationsByDay: [], commonTopics: [], visitorBreakdown: [] };
+
+        const resolved = filteredConversations.filter(c => c.status === 'resolved').length;
+        const resolutionRate = `${((resolved / total) * 100).toFixed(0)}%`;
+        const totalInteractions = filteredConversations.reduce((sum, c) => sum + c.interactions.length, 0);
+        const avgInteractions = parseFloat((totalInteractions / total).toFixed(1));
+        const responseTimes = filteredConversations.map(c => c.firstResponseTime || 0).filter(t => t > 0);
+        const avgResponseTimeSec = responseTimes.length > 0 ? responseTimes.reduce((sum, t) => sum + t, 0) / responseTimes.length : 0;
+        const avgResponseTime = avgResponseTimeSec > 0 ? `${Math.round(avgResponseTimeSec)}s` : 'N/A';
+        
+        const daysInRange = eachDayOfInterval({ start: subDays(new Date(), parseInt(dateFilter) - 1), end: new Date() });
+        const conversationsByDay = daysInRange.map(day => ({
+            date: format(day, 'MMM d', { locale: dateLocale }),
+            conversations: filteredConversations.filter(c => format(startOfDay(new Date(c.timestamp)), 'MMM d', { locale: dateLocale }) === format(day, 'MMM d', { locale: dateLocale })).length
+        }));
+
+        const wordCounts: { [key: string]: number } = {};
+        filteredConversations.forEach(c => c.interactions.forEach(i => { if(i.sender === 'user') { const words = i.text.toLowerCase().replace(/[^\w\s]/g, '').split(/\s+/); words.forEach(word => { if (word && !STOP_WORDS.has(word)) { wordCounts[word] = (wordCounts[word] || 0) + 1; } }); }}));
+        const commonTopics = Object.entries(wordCounts).sort(([,a],[,b]) => b - a).slice(0, 7).map(([name, count]) => ({ name, count }));
+
+        const visitorStats: {[key: string]: { visits: number, conversations: number}} = {};
+        filteredConversations.forEach(c => {
+            if(!visitorStats[c.visitorId]) visitorStats[c.visitorId] = { visits: c.visitCount, conversations: 0 };
+            visitorStats[c.visitorId].conversations++;
+        });
+        const visitorBreakdown = Object.entries(visitorStats).map(([id, stats]) => ({ id, ...stats })).sort((a,b) => b.conversations - a.conversations);
+
+        return { total, resolved, resolutionRate, avgInteractions, avgResponseTime, conversationsByDay, commonTopics, visitorBreakdown };
+    }, [filteredConversations, dateFilter, dateLocale]);
+
+    const handleMarkAsResolved = (id: string) => onUpdateConversations(conversations.map(c => (c.id === id ? { ...c, status: 'resolved' } : c)));
+
+    const handleSaveResponse = (newText: string) => {
+        if(!editingMessage) return;
+        const { convId, msg } = editingMessage;
+        const updatedConversations = conversations.map(c => {
+            if (c.id === convId) {
+                return { ...c, interactions: c.interactions.map(i => i.id === msg.id ? { ...i, text: newText } : i) };
+            }
+            return c;
+        });
+        onUpdateConversations(updatedConversations);
+        setEditingMessage(null);
+        alert(t('analytics.interactions.update_success'));
+    };
+    
+    return (
+        <div className="p-6 space-y-6">
+            {editingMessage && <EditResponseModal message={editingMessage.msg} onClose={() => setEditingMessage(null)} onSave={handleSaveResponse} />}
+            <h2 className="text-2xl font-bold text-gray-900 dark:text-white">{t('analytics.interactions.title')}</h2>
+            
+            <div className="bg-white dark:bg-gray-800 p-4 rounded-lg flex flex-wrap items-center gap-4">
+                <div className="relative flex-1 min-w-[200px]">
+                     <input type="text" placeholder={t('analytics.interactions.search_placeholder')} value={searchTerm} onChange={e => setSearchTerm(e.target.value)} className="w-full bg-gray-100 dark:bg-gray-700 p-2 pl-10 rounded-md border border-gray-300 dark:border-gray-600 focus:ring-brand-primary" />
+                     <Icon className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400 dark:text-gray-400"><path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></Icon>
+                </div>
+                <div className="flex items-center gap-2">
+                    <label className="text-sm text-gray-500 dark:text-gray-400">{t('analytics.interactions.date_label')}</label>
+                    <select value={dateFilter} onChange={e => setDateFilter(e.target.value)} className="bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600 focus:ring-brand-primary">
+                        <option value="7">{t('analytics.interactions.last_7_days')}</option>
+                        <option value="28">{t('analytics.interactions.last_28_days')}</option>
+                    </select>
+                </div>
+                <div className="flex items-center gap-1 bg-gray-200 dark:bg-gray-700 p-1 rounded-lg">
+                    {(['all', 'open', 'resolved'] as const).map(status => (
+                        <button key={status} onClick={() => setStatusFilter(status)} className={`px-3 py-1 text-sm rounded-md capitalize ${statusFilter === status ? 'bg-brand-primary text-white' : 'hover:bg-gray-300 dark:hover:bg-gray-600'}`}>
+                           {t(status)}
+                        </button>
+                    ))}
+                </div>
+            </div>
+
+            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg"><h3 className="text-gray-500 dark:text-gray-400 text-sm font-medium">{t('analytics.interactions.total_conversations')}</h3><p className="text-3xl font-bold text-gray-900 dark:text-white mt-1">{analyticsSummary.total}</p></div>
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg"><h3 className="text-gray-500 dark:text-gray-400 text-sm font-medium">{t('analytics.interactions.resolution_rate')}</h3><p className="text-3xl font-bold text-gray-900 dark:text-white mt-1">{analyticsSummary.resolutionRate}</p></div>
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg"><h3 className="text-gray-500 dark:text-gray-400 text-sm font-medium">{t('analytics.interactions.avg_interactions')}</h3><p className="text-3xl font-bold text-gray-900 dark:text-white mt-1">{analyticsSummary.avgInteractions}</p></div>
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg"><h3 className="text-gray-500 dark:text-gray-400 text-sm font-medium">{t('analytics.interactions.avg_first_response')}</h3><p className="text-3xl font-bold text-gray-900 dark:text-white mt-1">{analyticsSummary.avgResponseTime}</p></div>
+            </div>
+
+            <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg">
+                    <h3 className="font-semibold text-gray-900 dark:text-white mb-4">{t('analytics.interactions.visitor_breakdown')}</h3>
+                    <div className="h-[300px] overflow-y-auto">
+                        <table className="min-w-full">
+                            <thead className="sticky top-0 bg-white dark:bg-gray-800"><tr><th className="text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase py-2">{t('analytics.interactions.visitor')}</th><th className="text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase py-2">{t('analytics.interactions.conversations')}</th></tr></thead>
+                            <tbody>{analyticsSummary.visitorBreakdown.map(v => <tr key={v.id} className="border-t border-gray-200 dark:border-gray-700"><td className="py-2 font-semibold text-sm text-brand-primary">{v.id}</td><td className="py-2 text-sm">{v.conversations}</td></tr>)}</tbody>
+                        </table>
+                    </div>
+                </div>
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg">
+                    <h3 className="font-semibold text-gray-900 dark:text-white mb-4">{t('analytics.interactions.common_topics')}</h3>
+                    <ResponsiveContainer width="100%" height={300}>
+                        <BarChart data={analyticsSummary.commonTopics} layout="vertical" margin={{ top: 5, right: 20, left: 30, bottom: 5 }}>
+                            <CartesianGrid strokeDasharray="3 3" stroke={chartColors.grid} horizontal={false} />
+                            <XAxis type="number" stroke={chartColors.text} fontSize={12} />
+                            <YAxis type="category" dataKey="name" stroke={chartColors.text} fontSize={12} width={80} />
+                            <Tooltip contentStyle={{ backgroundColor: chartColors.tooltipBg, border: `1px solid ${chartColors.tooltipBorder}` }} cursor={{fill: isDarkMode ? '#374151' : '#f3f4f6'}}/>
+                            <Bar dataKey="count" fill={chartColors.bar2} />
+                        </BarChart>
+                    </ResponsiveContainer>
+                </div>
+            </div>
+
+            <div className="bg-white dark:bg-gray-800 rounded-lg">
+                <div className="p-4 space-y-4">
+                    {filteredConversations.length > 0 ? filteredConversations.map(conv => (
+                        <div key={conv.id} className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg">
+                            <div className="flex flex-wrap justify-between items-center mb-3 pb-3 border-b border-gray-200 dark:border-gray-700 gap-2">
+                                <p className="text-sm font-semibold text-gray-900 dark:text-white">{t('analytics.interactions.visitor')} <span className="text-brand-primary">{conv.visitorId}</span></p>
+                                <div className="flex items-center gap-4 text-xs text-gray-500 dark:text-gray-400">
+                                    <span>{new Date(conv.timestamp).toLocaleString(language)}</span>
+                                    <span>{conv.visitCount} visits</span>
+                                    <span className={`px-2 py-1 rounded-full text-xs font-semibold ${conv.status === 'resolved' ? 'bg-green-500/20 text-green-400' : 'bg-yellow-500/20 text-yellow-400'}`}>{conv.status}</span>
+                                    {conv.status === 'open' && ( <button onClick={() => handleMarkAsResolved(conv.id)} className="px-2 py-1 rounded bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-800 dark:text-white text-xs">{t('analytics.interactions.mark_resolved')}</button> )}
+                                </div>
+                            </div>
+                            <div className="space-y-3 max-h-48 overflow-y-auto pr-2">
+                                {conv.interactions.map(msg => (
+                                    <div key={msg.id} className={`group flex items-start gap-3 text-sm ${msg.sender === 'user' ? 'justify-end' : 'justify-start'}`}>
+                                        {msg.sender === 'ai' && <div className="w-6 h-6 rounded-full bg-brand-primary flex items-center justify-center flex-shrink-0 text-xs">🤖</div>}
+                                        <div className={`max-w-xl p-2 rounded-lg ${msg.sender === 'user' ? 'bg-blue-600 text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200'}`}>{msg.text}</div>
+                                        {msg.sender === 'ai' && <button onClick={() => setEditingMessage({convId: conv.id, msg: msg})} className="opacity-0 group-hover:opacity-100 transition-opacity text-gray-400 hover:text-gray-700 dark:hover:text-white"><Icon className="h-4 w-4"><path d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.536L16.732 3.732z" /></Icon></button>}
+                                        {msg.sender === 'user' && <div className="w-6 h-6 rounded-full bg-gray-400 dark:bg-gray-600 flex items-center justify-center flex-shrink-0 text-xs">👤</div>}
+                                    </div>
+                                ))}
+                            </div>
+                        </div>
+                    )) : (
+                        <div className="text-center py-12 text-gray-400 dark:text-gray-500">
+                            <p>{t('analytics.interactions.no_conversations')}</p>
+                        </div>
+                    )}
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default InteractionAnalytics;

+ 1028 - 0
components/LinkEditor.tsx

@@ -0,0 +1,1028 @@
+import * as React from 'react';
+// FIX: Import FormBlock and related types to handle form creation and editing.
+import { Block, BlockType, LinkBlock, HeaderBlock, SocialBlock, VideoBlock, ImageBlock, TextBlock, MapBlock, PdfBlock, SocialLink, MediaSource, EmailBlock, PhoneBlock, AIGCVideo, AIGCArticle, NewsBlock, NewsItemFromUrl, ProductBlock, ProductItem, SocialPlatform, ChatBlock, EnterpriseInfoBlock, EnterpriseInfoItem, EnterpriseInfoIcon, FormBlock, FormField, FormFieldId, FormPurposeOption, AwardBlock, AwardItem, FooterBlock, FooterLink } from '../types';
+import { Icon } from './ui/Icon';
+import { parseProductUrl, mockProductDatabase } from '../services/geminiService';
+
+const blockTypeMetadata: Record<BlockType, { icon: JSX.Element; name: string }> = {
+    link: { icon: <path d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />, name: 'Link' },
+    header: { icon: <path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m-4-16v16m8-16v16M4 4h16M4 20h16" />, name: 'Header' },
+    text: { icon: <path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h7" />, name: 'Text' },
+    social: { icon: <path d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207" />, name: 'Social Icons' },
+    chat: { icon: <path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />, name: 'Chat Button' },
+    enterprise_info: { icon: <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 21h16.5M4.5 3h15M5.25 3v18m13.5-18v18M9 6.75h6.375a.375.375 0 01.375.375v1.5a.375.375 0 01-.375.375H9a.375.375 0 01-.375-.375v-1.5A.375.375 0 019 6.75zM9 12.75h6.375a.375.375 0 01.375.375v1.5a.375.375 0 01-.375.375H9a.375.375 0 01-.375-.375v-1.5A.375.375 0 019 12.75z" />, name: 'Enterprise Info'},
+    video: { icon: <path d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />, name: 'Video' },
+    image: { icon: <path d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />, name: 'Image' },
+    map: { icon: <path d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 9m-6 3l6-3" />, name: 'Map' },
+    pdf: { icon: <path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />, name: 'PDF' },
+    email: { icon: <path strokeLinecap="round" strokeLinejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />, name: 'Email' },
+    phone: { icon: <path strokeLinecap="round" strokeLinejoin="round" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />, name: 'Phone' },
+    news: { icon: <path strokeLinecap="round" strokeLinejoin="round" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3h3m-3 4h3m-3 4h3" />, name: 'News' },
+    product: { icon: <path strokeLinecap="round" strokeLinejoin="round" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />, name: 'Product' },
+    award: { icon: <path strokeLinecap="round" strokeLinejoin="round" d="M9 21h6m-3-4v4m-3-11h6m-3 0V4M3 11h18M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />, name: 'Awards' },
+    form: { icon: <path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />, name: 'Form' },
+    footer: { icon: <path strokeLinecap="round" strokeLinejoin="round" d="M5 21V3m0 18h14V3m-7 18v-8" />, name: 'Footer' },
+};
+
+const AddBlockModal: React.FC<{ onSelect: (type: BlockType) => void; onClose: () => void; hasChatBlock: boolean; hasFooterBlock: boolean; }> = ({ onSelect, onClose, hasChatBlock, hasFooterBlock }) => {
+    return (
+        <div className="fixed inset-0 bg-black/70 z-50 flex items-center justify-center backdrop-blur-sm" onClick={onClose}>
+            <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl w-full max-w-2xl p-8 border border-gray-200 dark:border-gray-700" onClick={e => e.stopPropagation()}>
+                <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">Add Block</h2>
+                <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
+                    {(Object.keys(blockTypeMetadata) as BlockType[]).map(type => {
+                        const isDisabled = (type === 'chat' && hasChatBlock) || (type === 'footer' && hasFooterBlock);
+                        return (
+                        <button key={type} onMouseDown={(e) => e.preventDefault()} onClick={() => !isDisabled && onSelect(type)} className={`flex flex-col items-center justify-center p-4 bg-gray-100 dark:bg-gray-800 rounded-lg transition-colors text-gray-700 dark:text-gray-300 aspect-square ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-brand-primary'}`}>
+                            <Icon className="h-8 w-8 mb-2">{blockTypeMetadata[type].icon}</Icon>
+                            <span className="text-sm font-semibold">{blockTypeMetadata[type].name}</span>
+                        </button>
+                        )
+                    })}
+                </div>
+            </div>
+        </div>
+    );
+};
+
+
+const LayoutToggle: React.FC<{ label: string; options: {label: string, value: string}[]; value: string; onLayoutChange: (value: any) => void; }> = ({ label, options, value, onLayoutChange }) => (
+    <div>
+        <label className="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2 block">{label}</label>
+        <div className="flex items-center gap-1 bg-gray-200 dark:bg-gray-700 p-1 rounded-lg">
+            {options.map(opt => (
+                <button key={opt.value} onMouseDown={(e) => e.preventDefault()} onClick={() => onLayoutChange(opt.value)} className={`flex-1 px-3 py-1 text-sm rounded-md capitalize transition-colors ${value === opt.value ? 'bg-brand-primary text-white shadow' : 'hover:bg-gray-300 dark:hover:bg-gray-600'}`}>{opt.label}</button>
+            ))}
+        </div>
+    </div>
+);
+
+const SourceTypeToggle: React.FC<{ isVideo: boolean, type: 'url' | 'file' | 'aigc'; onTypeChange: (type: any) => void; onAIGCOpen: () => void; }> = ({ isVideo, type, onTypeChange, onAIGCOpen }) => (
+    <div className="flex items-center gap-2">
+        <label className="text-sm font-semibold text-gray-600 dark:text-gray-300">Source:</label>
+        <button onMouseDown={(e) => e.preventDefault()} onClick={() => onTypeChange('url')} className={`px-3 py-1 text-sm rounded-md ${type === 'url' ? 'bg-brand-primary text-white' : 'bg-gray-200 dark:bg-gray-600'}`}>URL</button>
+        <button onMouseDown={(e) => e.preventDefault()} onClick={() => onTypeChange('file')} className={`px-3 py-1 text-sm rounded-md ${type === 'file' ? 'bg-brand-primary text-white' : 'bg-gray-200 dark:bg-gray-600'}`}>Upload</button>
+        {isVideo && <button onMouseDown={(e) => e.preventDefault()} onClick={onAIGCOpen} className={`px-3 py-1 text-sm rounded-md ${type === 'aigc' ? 'bg-brand-primary text-white' : 'bg-gray-200 dark:bg-gray-600'}`}>AIGC Library</button>}
+    </div>
+);
+
+const AIGCNewsLibraryModal: React.FC<{
+    articles: AIGCArticle[];
+    selectedArticleIds: string[];
+    onClose: () => void;
+    onSave: (selectedIds: string[]) => void;
+}> = ({ articles, selectedArticleIds, onClose, onSave }) => {
+    const [localSelected, setLocalSelected] = React.useState(selectedArticleIds);
+
+    const toggleSelection = (id: string) => {
+        setLocalSelected(prev => prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]);
+    };
+
+    return (
+        <div className="fixed inset-0 bg-black/70 z-50 flex items-center justify-center backdrop-blur-sm" onClick={onClose}>
+            <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl w-full max-w-4xl h-[80vh] p-8 border border-gray-200 dark:border-gray-700 flex flex-col" onClick={e => e.stopPropagation()}>
+                <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-6 flex-shrink-0">Select News Articles</h2>
+                <div className="flex-1 overflow-y-auto pr-4 -mr-4">
+                    {articles.length === 0 ? (
+                        <p className="text-center text-gray-400 dark:text-gray-500 py-12">No articles in your library. Create some in the AIGC News Creator!</p>
+                    ) : (
+                        <div className="space-y-3">
+                            {articles.map(article => (
+                                <button key={article.id} onClick={() => toggleSelection(article.id)} className={`w-full text-left p-4 rounded-lg border transition-colors flex items-center gap-4 ${localSelected.includes(article.id) ? 'bg-brand-primary/10 border-brand-primary' : 'bg-gray-100 dark:bg-gray-900/50 border-gray-200 dark:border-gray-700 hover:border-gray-400'}`}>
+                                    <div className={`w-5 h-5 rounded-sm flex-shrink-0 flex items-center justify-center border-2 ${localSelected.includes(article.id) ? 'bg-brand-primary border-brand-primary' : 'bg-white dark:bg-gray-800 border-gray-300'}`}>
+                                        {localSelected.includes(article.id) && <Icon className="h-4 w-4 text-white"><path d="M5 13l4 4L19 7" /></Icon>}
+                                    </div>
+                                    <div className="flex-1 min-w-0">
+                                        <p className="font-semibold truncate">{article.title}</p>
+                                        <p className="text-sm text-gray-500 dark:text-gray-400 truncate">{article.summary}</p>
+                                    </div>
+                                </button>
+                            ))}
+                        </div>
+                    )}
+                </div>
+                <div className="flex justify-end gap-4 mt-6 flex-shrink-0">
+                    <button onClick={onClose} className="px-4 py-2 rounded-md font-semibold bg-gray-200 dark:bg-gray-700">Cancel</button>
+                    <button onClick={() => onSave(localSelected)} className="px-4 py-2 rounded-md font-semibold bg-brand-primary text-white">Save Selection</button>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+const SparkleIcon = () => (
+    <Icon className="h-4 w-4 text-brand-primary">
+        <path strokeLinecap="round" strokeLinejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM18 15.75l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 20l-1.035.259a3.375 3.375 0 00-2.456 2.456L18 23.75l-.259-1.035a3.375 3.375 0 00-2.456-2.456L14.25 20l1.035-.259a3.375 3.375 0 002.456-2.456L18 15.75z" />
+    </Icon>
+);
+
+
+const BlockItemEditor: React.FC<{ block: Block; onUpdate: (updatedBlock: Block) => void; onAIGCOpen: () => void, aigcVideos: AIGCVideo[]; aigcArticles: AIGCArticle[] }> = ({ block, onUpdate, onAIGCOpen, aigcVideos, aigcArticles }) => {
+    const inputClasses = "w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border-2 border-gray-300 dark:border-gray-600 focus:outline-none focus:border-brand-primary";
+    const labelClasses = "text-sm font-semibold text-gray-600 dark:text-gray-300";
+    
+    const handleUrlBlur = (e: React.FocusEvent<HTMLInputElement>) => {
+        const { value: url } = e.target;
+        const linkBlock = block as LinkBlock;
+        if (!url || !url.startsWith('http')) {
+            onUpdate({ ...linkBlock, iconUrl: undefined, url });
+            return;
+        }
+        try {
+            const domain = new URL(url).hostname;
+            const iconUrl = `https://www.google.com/s2/favicons?domain=${domain}&sz=64`;
+            onUpdate({ ...linkBlock, iconUrl, url });
+        } catch (error) {
+            console.error("Invalid URL for favicon:", error);
+            onUpdate({ ...linkBlock, iconUrl: undefined, url });
+        }
+    };
+
+    switch (block.type) {
+        case 'header': {
+            const headerBlock = block as HeaderBlock;
+            return <div className="space-y-4">
+                <div><label className={labelClasses}>Header Text</label><input type="text" value={headerBlock.text} onChange={e => onUpdate({ ...block, text: e.target.value })} className={`${inputClasses} mt-1`} /></div>
+                <div><LayoutToggle label="Alignment" options={[{label: 'Left', value: 'left'}, {label: 'Center', value: 'center'}]} value={headerBlock.titleAlignment || 'left'} onLayoutChange={align => onUpdate({ ...headerBlock, titleAlignment: align })} /></div>
+            </div>
+        }
+        case 'link': {
+            const linkBlock = block as LinkBlock;
+            return <div className="space-y-4">
+                <div><label className={labelClasses}>Title</label><input type="text" value={linkBlock.title} onChange={e => onUpdate({ ...block, title: e.target.value })} className={`${inputClasses} mt-1`} /></div>
+                <div><label className={labelClasses}>URL</label><input type="url" defaultValue={linkBlock.url} onBlur={handleUrlBlur} className={`${inputClasses} mt-1`} /></div>
+                <div><label className={labelClasses}>Thumbnail URL (Optional)</label><input type="url" placeholder="https://..." value={linkBlock.thumbnailUrl || ''} onChange={e => onUpdate({ ...block, thumbnailUrl: e.target.value })} className={`${inputClasses} mt-1`} /></div>
+            </div>
+        }
+        case 'chat': {
+            const chatBlock = block as ChatBlock;
+            return (
+                <div className="space-y-4">
+                    <LayoutToggle 
+                        label="Display Style" 
+                        options={[
+                            {label: 'Under Avatar', value: 'under_avatar'}, 
+                            {label: 'Block Button', value: 'button'},
+                            {label: 'Floating Widget', value: 'widget'}
+                        ]} 
+                        value={chatBlock.layout} 
+                        onLayoutChange={(layout: 'button' | 'under_avatar' | 'widget') => onUpdate({ ...chatBlock, layout })} 
+                    />
+                     <p className="text-xs text-gray-500 dark:text-gray-400 -mt-2">
+                        <strong>Under Avatar:</strong> Only visible in Personal mode.<br/>
+                        <strong>Floating Widget:</strong> Recommended for Enterprise mode.<br/>
+                        <strong>Block Button:</strong> A standard button for any mode.
+                    </p>
+                </div>
+            )
+        }
+        case 'enterprise_info': {
+            const infoBlock = block as EnterpriseInfoBlock;
+            const availableIcons: { value: EnterpriseInfoIcon; label: string }[] = [
+                { value: 'building', label: 'Building' },
+                { value: 'bank', label: 'Bank' },
+                { value: 'money', label: 'Money' },
+                { value: 'location', label: 'Location' },
+                { value: 'calendar', label: 'Calendar' },
+                { value: 'users', label: 'Users' },
+                { value: 'lightbulb', label: 'Lightbulb' },
+            ];
+            
+            const handleUpdateItem = (id: string, field: 'icon' | 'label' | 'value', value: string) => {
+                const updatedItems = infoBlock.items.map(item => item.id === id ? { ...item, [field]: value } : item);
+                onUpdate({ ...infoBlock, items: updatedItems });
+            };
+
+            const handleAddItem = () => {
+                const newItem: EnterpriseInfoItem = {
+                    id: `ei-${Date.now()}`,
+                    icon: 'lightbulb',
+                    label: 'New Item',
+                    value: 'Value'
+                };
+                onUpdate({ ...infoBlock, items: [...infoBlock.items, newItem] });
+            };
+
+            const handleRemoveItem = (id: string) => {
+                onUpdate({ ...infoBlock, items: infoBlock.items.filter(item => item.id !== id) });
+            };
+
+            return (
+                <div className="space-y-4">
+                     <LayoutToggle 
+                        label="Alignment" 
+                        options={[
+                            {label: 'Left', value: 'left'}, 
+                            {label: 'Center', value: 'center'},
+                        ]} 
+                        value={infoBlock.alignment || 'left'} 
+                        onLayoutChange={(align: 'left' | 'center') => onUpdate({ ...infoBlock, alignment: align })} 
+                    />
+                    {infoBlock.items.map(item => (
+                        <div key={item.id} className="p-3 bg-gray-100 dark:bg-gray-900 rounded-md space-y-2 relative">
+                            <button onClick={() => handleRemoveItem(item.id)} className="absolute top-2 right-2 text-gray-400 hover:text-red-500"><Icon className="h-4 w-4"><path d="M6 18L18 6M6 6l12 12" /></Icon></button>
+                            <div className="grid grid-cols-1 md:grid-cols-3 gap-2">
+                                <div>
+                                    <label className="text-xs font-semibold text-gray-500">Icon</label>
+                                    <select value={item.icon} onChange={e => handleUpdateItem(item.id, 'icon', e.target.value)} className="w-full capitalize bg-white dark:bg-gray-700 p-1 text-sm rounded border border-gray-300 dark:border-gray-600">
+                                        {availableIcons.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
+                                    </select>
+                                </div>
+                                <div className="md:col-span-2">
+                                    <label className="text-xs font-semibold text-gray-500">Label</label>
+                                    <input type="text" value={item.label} onChange={e => handleUpdateItem(item.id, 'label', e.target.value)} className="w-full bg-white dark:bg-gray-700 p-1 text-sm rounded border border-gray-300 dark:border-gray-600" />
+                                </div>
+                            </div>
+                            <div>
+                                <label className="text-xs font-semibold text-gray-500">Value</label>
+                                <input type="text" value={item.value} onChange={e => handleUpdateItem(item.id, 'value', e.target.value)} className="w-full bg-white dark:bg-gray-700 p-1 text-sm rounded border border-gray-300 dark:border-gray-600" />
+                            </div>
+                        </div>
+                    ))}
+                    <button onClick={handleAddItem} className="w-full justify-center p-2 bg-brand-primary/10 rounded-md text-sm text-brand-primary hover:underline font-semibold flex items-center gap-1">
+                        <Icon className="h-4 w-4"><path d="M12 4v16m8-8H4" /></Icon>Add Info Item
+                    </button>
+                </div>
+            )
+        }
+        case 'social': {
+            const socialBlock = block as SocialBlock;
+            const availablePlatforms: SocialPlatform[] = ['twitter', 'instagram', 'facebook', 'linkedin', 'youtube', 'tiktok', 'github'];
+
+            const handleAddLink = () => {
+                const usedPlatforms = new Set(socialBlock.links.map(l => l.platform));
+                const nextPlatform = availablePlatforms.find(p => !usedPlatforms.has(p)) || 'twitter';
+                const newLink: SocialLink = { id: `sl-${Date.now()}`, platform: nextPlatform, url: '' };
+                onUpdate({ ...socialBlock, links: [...socialBlock.links, newLink] });
+            };
+            const handleAddMockLinks = () => {
+                const mockLinks: SocialLink[] = [
+                    { id: 'sl-mock-twitter', platform: 'twitter', url: 'https://twitter.com/johndoe' },
+                    { id: 'sl-mock-github', platform: 'github', url: 'https://github.com/johndoe' },
+                    { id: 'sl-mock-linkedin', platform: 'linkedin', url: 'https://linkedin.com/in/johndoe' },
+                ];
+                const existingPlatforms = new Set(socialBlock.links.map(l => l.platform));
+                const newMockLinks = mockLinks.filter(ml => !existingPlatforms.has(ml.platform));
+                onUpdate({ ...socialBlock, links: [...socialBlock.links, ...newMockLinks] });
+            };
+            const handleRemoveLink = (linkId: string) => {
+                onUpdate({ ...socialBlock, links: socialBlock.links.filter(l => l.id !== linkId) });
+            };
+            const handleUpdateLink = (linkId: string, field: 'platform' | 'url', value: string) => {
+                onUpdate({ ...socialBlock, links: socialBlock.links.map(l => l.id === linkId ? { ...l, [field]: value } as SocialLink : l) });
+            };
+            
+            return <div className="space-y-3">
+                {socialBlock.links.map(link => 
+                    <div key={link.id} className="flex items-center gap-2">
+                        <select value={link.platform} onChange={e => handleUpdateLink(link.id, 'platform', e.target.value)} className="capitalize bg-gray-100 dark:bg-gray-700 p-2 rounded-md border-2 border-gray-300 dark:border-gray-600">
+                            {availablePlatforms.map(p => <option key={p} value={p}>{p}</option>)}
+                        </select>
+                        <input type="url" value={link.url} onChange={e => handleUpdateLink(link.id, 'url', e.target.value)} placeholder="https://..." className={`flex-1 ${inputClasses}`} />
+                        <button onClick={() => handleRemoveLink(link.id)} className="text-gray-400 hover:text-red-500"><Icon className="h-5 w-5"><path d="M6 18L18 6M6 6l12 12" /></Icon></button>
+                    </div>
+                )}
+                <div className="flex gap-4 pt-2">
+                    <button onClick={handleAddLink} className="text-sm text-brand-primary hover:underline font-semibold flex items-center gap-1">
+                        <Icon className="h-4 w-4"><path d="M12 4v16m8-8H4" /></Icon>Add Link
+                    </button>
+                    <button onClick={handleAddMockLinks} className="text-sm text-brand-primary hover:underline font-semibold flex items-center gap-1">
+                        <SparkleIcon />Add Mock Links
+                    </button>
+                </div>
+            </div>
+        }
+        case 'image':
+        case 'video': {
+            const mediaBlock = block as ImageBlock | VideoBlock;
+            const addMedia = () => {
+                const newSource: MediaSource = mediaBlock.type === 'image' ? { type: 'url', value: '' } : { type: 'url', value: '' };
+                onUpdate({ ...mediaBlock, sources: [...mediaBlock.sources, newSource] });
+            };
+            const updateMediaSource = (index: number, newSource: MediaSource) => onUpdate({ ...mediaBlock, sources: mediaBlock.sources.map((s, i) => i === index ? newSource : s) });
+            const removeMedia = (index: number) => onUpdate({ ...mediaBlock, sources: mediaBlock.sources.filter((_, i) => i !== index) });
+            const displayClasses = "w-full text-sm p-2 bg-gray-100 dark:bg-gray-700 rounded-md border-2 border-gray-300 dark:border-gray-600";
+            
+            const handleAddMockMedia = () => {
+                const newSources: MediaSource[] = Array.from({ length: 3 }).map((_, i) => {
+                    if (mediaBlock.type === 'image') {
+                        return { type: 'url', value: `https://picsum.photos/seed/${Date.now() + i}/400/300` };
+                    } else {
+                        const mockVideoUrls = [
+                            'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4',
+                            'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4',
+                            'https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'
+                        ];
+                        return { type: 'url', value: mockVideoUrls[i % mockVideoUrls.length] };
+                    }
+                });
+                onUpdate({ ...mediaBlock, sources: [...mediaBlock.sources, ...newSources] });
+            };
+
+            return <div className="space-y-6">
+                 <LayoutToggle label="Layout Mode" options={[{label: 'Single Column', value: 'single'}, {label: 'Grid', value: 'grid'}]} value={mediaBlock.layout} onLayoutChange={layout => onUpdate({ ...mediaBlock, layout })} />
+                <div className="space-y-4">
+                    {mediaBlock.sources.map((source, index) => {
+                         const videoFromAIGC = source.type === 'aigc' ? aigcVideos.find(v => v.id === source.videoId) : null;
+                         return (
+                            <div key={index} className="bg-gray-100 dark:bg-gray-900 p-4 rounded-lg space-y-3 border border-gray-200 dark:border-gray-700">
+                                <div className="flex justify-between items-center">
+                                    <SourceTypeToggle isVideo={mediaBlock.type === 'video'} type={source.type} onTypeChange={type => {
+                                        const newSource: MediaSource = type === 'url' ? { type: 'url', value: '' } : { type: 'file', value: { name: 'example.file', size: 12345, previewUrl: `https://picsum.photos/seed/upload-${Date.now()}/200` } };
+                                        updateMediaSource(index, newSource);
+                                    }} onAIGCOpen={onAIGCOpen} />
+                                     <button onMouseDown={(e) => e.preventDefault()} onClick={() => removeMedia(index)} className="text-gray-400 hover:text-red-500 flex-shrink-0"><Icon className="h-5 w-5"><path d="M6 18L18 6M6 6l12 12" /></Icon></button>
+                                </div>
+                                
+                                {source.type === 'url' ?
+                                    <input type="url" value={source.value} onChange={e => updateMediaSource(index, { ...source, value: e.target.value })} className={inputClasses} placeholder="https://..." /> :
+                                 source.type === 'file' ?
+                                    <div className={displayClasses}>Simulated Upload: {source.value.name}</div> :
+                                 videoFromAIGC ?
+                                    <div className={`${displayClasses} flex items-center gap-2`}>
+                                        <img src={videoFromAIGC.thumbnailUrl} className="h-8 w-12 rounded-sm object-cover" />
+                                        <span>{videoFromAIGC.title}</span>
+                                    </div> :
+                                    <div className="text-sm p-2 bg-yellow-100 dark:bg-yellow-900/50 text-yellow-800 dark:text-yellow-200 rounded-md">AIGC video not found.</div>
+                                }
+                            </div>
+                        );
+                    })}
+                </div>
+                 <div className="flex gap-4">
+                    <button onClick={addMedia} className="text-sm text-brand-primary hover:underline font-semibold flex items-center gap-1">
+                        <Icon className="h-4 w-4"><path d="M12 4v16m8-8H4" /></Icon>Add {mediaBlock.type}
+                    </button>
+                    <button onClick={handleAddMockMedia} className="text-sm text-brand-primary hover:underline font-semibold flex items-center gap-1">
+                        <SparkleIcon />Add 3 Mock {mediaBlock.type === 'image' ? 'Images' : 'Videos'}
+                    </button>
+                </div>
+            </div>
+        }
+        case 'text': {
+            const textBlock = block as TextBlock;
+            return <div className="space-y-4">
+                <div className="flex items-center gap-2 p-2 bg-gray-100 dark:bg-gray-800 rounded-md">
+                    <button type="button" onMouseDown={(e) => e.preventDefault()} onClick={() => onUpdate({ ...textBlock, textAlign: 'left' })} className={`p-2 rounded ${textBlock.textAlign === 'left' ? 'bg-brand-primary text-white' : ''}`}><Icon className="h-4 w-4"><path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12" /></Icon></button>
+                    <button type="button" onMouseDown={(e) => e.preventDefault()} onClick={() => onUpdate({ ...textBlock, textAlign: 'center' })} className={`p-2 rounded ${textBlock.textAlign === 'center' ? 'bg-brand-primary text-white' : ''}`}><Icon className="h-4 w-4"><path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /></Icon></button>
+                    <button type="button" onMouseDown={(e) => e.preventDefault()} onClick={() => onUpdate({ ...textBlock, textAlign: 'right' })} className={`p-2 rounded ${textBlock.textAlign === 'right' ? 'bg-brand-primary text-white' : ''}`}><Icon className="h-4 w-4"><path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5M12 17.25h8.25" /></Icon></button>
+                    <div className="h-6 border-l border-gray-300 dark:border-gray-600 mx-2" />
+                    <button type="button" onMouseDown={(e) => e.preventDefault()} onClick={() => onUpdate({ ...textBlock, isBold: !textBlock.isBold })} className={`p-2 rounded ${textBlock.isBold ? 'bg-brand-primary text-white' : ''}`}><strong>B</strong></button>
+                    <button type="button" onMouseDown={(e) => e.preventDefault()} onClick={() => onUpdate({ ...textBlock, isItalic: !textBlock.isItalic })} className={`p-2 rounded ${textBlock.isItalic ? 'bg-brand-primary text-white' : ''}`}><em>I</em></button>
+                    <div className="h-6 border-l border-gray-300 dark:border-gray-600 mx-2" />
+                    <input type="text" value={textBlock.fontSize} onChange={e => onUpdate({ ...textBlock, fontSize: e.target.value })} className="w-16 p-1 text-sm bg-white dark:bg-gray-700 rounded" placeholder="16px" />
+                    <input type="color" value={textBlock.fontColor} onChange={e => onUpdate({ ...textBlock, fontColor: e.target.value })} className="h-8 w-8 rounded-md" />
+                </div>
+                <textarea value={textBlock.content} onChange={e => onUpdate({ ...textBlock, content: e.target.value })} rows={5} className={inputClasses} placeholder="Enter your text here..." />
+            </div>
+        }
+        case 'map': {
+             const mapBlock = block as MapBlock;
+             const displayStyle = mapBlock.displayStyle || 'interactiveMap';
+             const bgSource = mapBlock.backgroundImageSource;
+
+            return <div className="space-y-4">
+                 <div>
+                    <label className={labelClasses}>Address or Location</label>
+                    <input type="text" value={mapBlock.address} onChange={e => onUpdate({ ...block, address: e.target.value })} className={`${inputClasses} mt-1`} placeholder="e.g., Eiffel Tower, Paris" />
+                 </div>
+                 <LayoutToggle 
+                    label="Display Style" 
+                    options={[{label: 'Map Preview', value: 'interactiveMap'}, {label: 'Image Overlay', value: 'imageOverlay'}]} 
+                    value={displayStyle} 
+                    onLayoutChange={style => onUpdate({ ...mapBlock, displayStyle: style })} 
+                 />
+                 {displayStyle === 'imageOverlay' && (
+                    <div className="bg-gray-100 dark:bg-gray-900 p-4 rounded-lg space-y-3 border border-gray-200 dark:border-gray-700">
+                        <h4 className="text-sm font-semibold text-gray-600 dark:text-gray-300">Background Image</h4>
+                        <SourceTypeToggle 
+                            isVideo={false} 
+                            type={bgSource?.type || 'url'} 
+                            onTypeChange={type => {
+                                const newSource: MediaSource = type === 'url' ? { type: 'url', value: '' } : { type: 'file', value: { name: 'background.jpg', size: 12345, previewUrl: `https://picsum.photos/seed/mapbg-${Date.now()}/400` } };
+                                onUpdate({ ...mapBlock, backgroundImageSource: newSource });
+                            }} 
+                            onAIGCOpen={() => {}} 
+                        />
+                        {bgSource?.type === 'url' &&
+                            <input type="url" value={bgSource.value} onChange={e => onUpdate({ ...mapBlock, backgroundImageSource: { type: 'url', value: e.target.value } })} className={inputClasses} placeholder="https://..."/>
+                        }
+                        {bgSource?.type === 'file' &&
+                            <div className="w-full text-sm p-2 bg-gray-100 dark:bg-gray-700 rounded-md border-2 border-gray-300 dark:border-gray-600">Simulated Upload: {bgSource.value.name}</div>
+                        }
+                    </div>
+                 )}
+             </div>
+        }
+        case 'pdf': {
+            const pdfBlock = block as PdfBlock;
+            const source = pdfBlock.source;
+            const displayClasses = "w-full text-sm p-2 bg-gray-100 dark:bg-gray-700 rounded-md border-2 border-gray-300 dark:border-gray-600";
+
+            return <div className="bg-gray-100 dark:bg-gray-900 p-4 rounded-lg space-y-3 border border-gray-200 dark:border-gray-700">
+                <SourceTypeToggle isVideo={false} type={source.type} onTypeChange={type => {
+                    const newSource: MediaSource = type === 'url' ? { type: 'url', value: '' } : { type: 'file', value: { name: 'document.pdf', size: 123456, previewUrl: `https://picsum.photos/seed/pdf-${Date.now()}/200/280` } };
+                    onUpdate({ ...pdfBlock, source: newSource });
+                }} onAIGCOpen={() => {}} />
+                {source.type === 'url' &&
+                    <input type="url" value={source.value} onChange={e => onUpdate({ ...pdfBlock, source: { type: 'url', value: e.target.value } })} className={inputClasses} placeholder="https://example.com/document.pdf"/>
+                }
+                {source.type === 'file' &&
+                    <div className={displayClasses}>Simulated Upload: {source.value.name}</div>
+                }
+            </div>
+        }
+        case 'email': {
+            const emailBlock = block as EmailBlock;
+            return <div className="space-y-4">
+                <div><label className={labelClasses}>Button Label</label><input type="text" value={emailBlock.label} onChange={e => onUpdate({ ...emailBlock, label: e.target.value })} className={`${inputClasses} mt-1`} /></div>
+                <div><label className={labelClasses}>Email Address</label><input type="email" value={emailBlock.email} onChange={e => onUpdate({ ...emailBlock, email: e.target.value })} className={`${inputClasses} mt-1`} /></div>
+                <LayoutToggle label="Display Mode" options={[{label: 'Label Only', value: 'labelOnly'}, {label: 'Label + Email', value: 'labelAndValue'}]} value={emailBlock.displayMode} onLayoutChange={mode => onUpdate({ ...emailBlock, displayMode: mode })} />
+            </div>
+        }
+        case 'phone': {
+            const phoneBlock = block as PhoneBlock;
+            return <div className="space-y-4">
+                <div><label className={labelClasses}>Button Label</label><input type="text" value={phoneBlock.label} onChange={e => onUpdate({ ...phoneBlock, label: e.target.value })} className={`${inputClasses} mt-1`} /></div>
+                <div><label className={labelClasses}>Phone Number</label><input type="tel" value={phoneBlock.phone} onChange={e => onUpdate({ ...phoneBlock, phone: e.target.value })} className={`${inputClasses} mt-1`} /></div>
+                <LayoutToggle label="Display Mode" options={[{label: 'Label Only', value: 'labelOnly'}, {label: 'Label + Phone', value: 'labelAndValue'}]} value={phoneBlock.displayMode} onLayoutChange={mode => onUpdate({ ...phoneBlock, displayMode: mode })} />
+            </div>
+        }
+        case 'news': {
+            const newsBlock = block as NewsBlock;
+            const [isNewsSelectorOpen, setIsNewsSelectorOpen] = React.useState(false);
+            const selectedArticles = (newsBlock.source === 'aigc' && newsBlock.articleIds) ? aigcArticles.filter(a => newsBlock.articleIds.includes(a.id)) : [];
+
+            const handleSaveSelection = (selectedIds: string[]) => {
+                const { customItems, ...rest } = newsBlock;
+                onUpdate({ ...rest, source: 'aigc', articleIds: selectedIds });
+                setIsNewsSelectorOpen(false);
+            };
+        
+            const handleSourceChange = (newSource: 'aigc' | 'custom') => {
+                if (newSource === 'aigc') {
+                    const { customItems, ...rest } = newsBlock;
+                    onUpdate({ ...rest, source: 'aigc', articleIds: [] });
+                } else {
+                    const { articleIds, ...rest } = newsBlock;
+                    onUpdate({ ...rest, source: 'custom', customItems: [] });
+                }
+            };
+            
+            const handleUpdateCustomItem = (itemId: string, field: 'title' | 'summary' | 'url', value: string) => {
+                if(newsBlock.source !== 'custom') return;
+                const updatedItems = newsBlock.customItems.map(item => 
+                    item.id === itemId ? { ...item, [field]: value } : item
+                );
+                onUpdate({ ...newsBlock, customItems: updatedItems });
+            };
+        
+            const handleAddCustomItem = () => {
+                if(newsBlock.source !== 'custom') return;
+                const newItem: NewsItemFromUrl = { id: `custom-news-${Date.now()}`, title: 'New Article', summary: '', url: '' };
+                onUpdate({ ...newsBlock, customItems: [...(newsBlock.customItems || []), newItem] });
+            };
+            
+            const handleAddMockCustomItems = () => {
+                if (newsBlock.source !== 'custom') return;
+                const mockItems: NewsItemFromUrl[] = [
+                    { id: `custom-news-mock-${Date.now()}`, title: 'Mock Article: The Future of AI', summary: 'A fascinating look into what comes next.', url: '#' },
+                    { id: `custom-news-mock-${Date.now()+1}`, title: 'Mock Post: 10 Design Trends for 2025', summary: 'Stay ahead of the curve with these new styles.', url: '#' },
+                    { id: `custom-news-mock-${Date.now()+2}`, title: 'Mock Update: Company Hits New Milestone', summary: 'Celebrating a new achievement in our journey.', url: '#' },
+                ];
+                onUpdate({ ...newsBlock, customItems: [...(newsBlock.customItems || []), ...mockItems] });
+            };
+        
+            const handleRemoveCustomItem = (itemId: string) => {
+                if(newsBlock.source !== 'custom') return;
+                const updatedItems = newsBlock.customItems.filter(item => item.id !== itemId);
+                onUpdate({ ...newsBlock, customItems: updatedItems });
+            };
+
+            return (
+                <>
+                    {isNewsSelectorOpen && (
+                        <AIGCNewsLibraryModal 
+                            articles={aigcArticles}
+                            selectedArticleIds={newsBlock.source === 'aigc' ? newsBlock.articleIds : []}
+                            onClose={() => setIsNewsSelectorOpen(false)}
+                            onSave={handleSaveSelection}
+                        />
+                    )}
+                    <div className="space-y-4">
+                        <LayoutToggle label="Layout" options={[{label: 'List', value: 'list'}, {label: 'Grid', value: 'grid'}]} value={newsBlock.layout} onLayoutChange={layout => onUpdate({ ...newsBlock, layout })} />
+                        <LayoutToggle label="Content Source" options={[{label: 'AIGC Library', value: 'aigc'}, {label: 'Custom URLs', value: 'custom'}]} value={newsBlock.source || 'aigc'} onLayoutChange={handleSourceChange} />
+                        
+                        {newsBlock.source === 'aigc' ? (
+                            <div>
+                                <h4 className="text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2">Selected Articles ({selectedArticles.length})</h4>
+                                <div className="space-y-2 p-3 bg-gray-100 dark:bg-gray-900 rounded-md max-h-48 overflow-y-auto">
+                                    {selectedArticles.length > 0 ? selectedArticles.map(article => (
+                                        <p key={article.id} className="text-sm truncate p-2 bg-white dark:bg-gray-800 rounded">{article.title}</p>
+                                    )) : <p className="text-sm text-gray-400 text-center py-2">No articles selected.</p>}
+                                </div>
+                                <button onClick={() => setIsNewsSelectorOpen(true)} className="mt-2 w-full text-sm text-brand-primary hover:underline font-semibold flex items-center gap-1 justify-center p-2 bg-brand-primary/10 rounded-md">
+                                    <Icon className="h-4 w-4"><path d="M12 4v16m8-8H4" /></Icon>
+                                    Select from AIGC Library
+                                </button>
+                            </div>
+                        ) : (
+                            <div className="space-y-3">
+                                {(newsBlock.customItems || []).map(item => (
+                                    <div key={item.id} className="p-3 bg-gray-100 dark:bg-gray-900 rounded-md space-y-2 relative">
+                                        <button onClick={() => handleRemoveCustomItem(item.id)} className="absolute top-2 right-2 text-gray-400 hover:text-red-500"><Icon className="h-4 w-4"><path d="M6 18L18 6M6 6l12 12" /></Icon></button>
+                                        <div><label className="text-xs font-semibold text-gray-500">Title</label><input type="text" value={item.title} onChange={e => handleUpdateCustomItem(item.id, 'title', e.target.value)} className="w-full bg-white dark:bg-gray-700 p-1 text-sm rounded" /></div>
+                                        <div><label className="text-xs font-semibold text-gray-500">Summary</label><input type="text" value={item.summary} onChange={e => handleUpdateCustomItem(item.id, 'summary', e.target.value)} className="w-full bg-white dark:bg-gray-700 p-1 text-sm rounded" /></div>
+                                        <div><label className="text-xs font-semibold text-gray-500">URL</label><input type="url" value={item.url} onChange={e => handleUpdateCustomItem(item.id, 'url', e.target.value)} className="w-full bg-white dark:bg-gray-700 p-1 text-sm rounded" /></div>
+                                    </div>
+                                ))}
+                                <div className="flex gap-2">
+                                    <button onClick={handleAddCustomItem} className="flex-1 justify-center p-2 bg-brand-primary/10 rounded-md text-sm text-brand-primary hover:underline font-semibold flex items-center gap-1">
+                                        <Icon className="h-4 w-4"><path d="M12 4v16m8-8H4" /></Icon>Add URL
+                                    </button>
+                                    <button onClick={handleAddMockCustomItems} className="flex-1 justify-center p-2 bg-brand-primary/10 rounded-md text-sm text-brand-primary hover:underline font-semibold flex items-center gap-1">
+                                        <SparkleIcon />Add 3 Mock Items
+                                    </button>
+                                </div>
+                            </div>
+                        )}
+                    </div>
+                </>
+            );
+        }
+        case 'product': {
+            const productBlock = block as ProductBlock;
+            const [newUrls, setNewUrls] = React.useState('');
+            const [isFetching, setIsFetching] = React.useState(false);
+            const [isMocking, setIsMocking] = React.useState(false);
+
+            const handleAddProducts = async () => {
+                const urls = newUrls.trim().split('\n').filter(url => url.trim().startsWith('http'));
+                if (urls.length === 0) return;
+
+                setIsFetching(true);
+                try {
+                    const newItemsPromises = urls.map(async (url) => {
+                        const productData = await parseProductUrl(url);
+                        return {
+                            id: `prod-${Date.now()}-${Math.random()}`,
+                            url,
+                            ...productData,
+                        };
+                    });
+                    const newItems = await Promise.all(newItemsPromises);
+                    onUpdate({ ...productBlock, items: [...productBlock.items, ...newItems] });
+                    setNewUrls('');
+                } catch (error) {
+                    alert("Could not fetch product data from one or more URLs.");
+                    console.error(error);
+                } finally {
+                    setIsFetching(false);
+                }
+            };
+            
+            const handleAddMockProducts = () => {
+                setIsMocking(true);
+                setTimeout(() => {
+                    const newItems: ProductItem[] = [];
+                    for (let i = 0; i < 3; i++) {
+                        const randomProduct = mockProductDatabase[Math.floor(Math.random() * mockProductDatabase.length)];
+                        const randomId = `prod-mock-${Date.now()}-${Math.random()}`;
+                        newItems.push({
+                            id: randomId,
+                            url: '#',
+                            title: randomProduct.title,
+                            price: randomProduct.price,
+                            imageUrl: `https://picsum.photos/seed/${randomId}/400/400`,
+                        });
+                    }
+                    onUpdate({ ...productBlock, items: [...productBlock.items, ...newItems] });
+                    setIsMocking(false);
+                }, 500);
+            };
+
+            const handleRemoveProduct = (itemId: string) => {
+                onUpdate({ ...productBlock, items: productBlock.items.filter(item => item.id !== itemId) });
+            };
+
+            return (
+                <div className="space-y-4">
+                    <LayoutToggle label="Layout" options={[{label: 'Grid', value: 'grid'}, {label: 'List', value: 'list'}]} value={productBlock.layout} onLayoutChange={(layout: 'grid' | 'list') => onUpdate({ ...productBlock, layout })} />
+                    <div>
+                        <label className={labelClasses}>Add Products from URL</label>
+                        <div className="flex flex-col items-stretch gap-2 mt-1">
+                            <textarea
+                                rows={3}
+                                value={newUrls}
+                                onChange={e => setNewUrls(e.target.value)}
+                                placeholder="Paste one URL per line...&#10;https://amazon.com/product...&#10;https://shopee.com/item..."
+                                className={inputClasses}
+                                disabled={isFetching || isMocking}
+                            />
+                            <button onClick={handleAddProducts} disabled={isFetching || isMocking} className="w-full px-4 py-2 bg-gray-200 dark:bg-gray-600 text-sm font-semibold rounded-md hover:bg-gray-300 dark:hover:bg-gray-500 disabled:opacity-50 whitespace-nowrap">
+                                {isFetching ? 'Fetching...' : `Add from URLs`}
+                            </button>
+                        </div>
+                    </div>
+                     <div>
+                        <label className={labelClasses}>Or Add Sample Products</label>
+                         <button onClick={handleAddMockProducts} disabled={isFetching || isMocking} className="mt-1 w-full flex items-center justify-center gap-2 px-4 py-2 bg-brand-primary/10 text-brand-primary text-sm font-semibold rounded-md hover:bg-brand-primary/20 disabled:opacity-50 whitespace-nowrap">
+                             <SparkleIcon />
+                            {isMocking ? 'Adding...' : `Add 3 Mock Items`}
+                        </button>
+                    </div>
+                    <div className="space-y-3 max-h-64 overflow-y-auto pr-2 -mr-2">
+                        {productBlock.items.map(item => (
+                            <div key={item.id} className="flex items-center gap-3 p-2 bg-gray-100 dark:bg-gray-900 rounded-md">
+                                <img src={item.imageUrl} alt={item.title} className="w-12 h-12 object-cover rounded flex-shrink-0" />
+                                <div className="flex-1 min-w-0">
+                                    <p className="font-semibold truncate text-gray-900 dark:text-white">{item.title}</p>
+                                    <p className="text-sm text-gray-500 dark:text-gray-400">{item.price}</p>
+                                </div>
+                                <button onClick={() => handleRemoveProduct(item.id)} className="text-gray-400 hover:text-red-500"><Icon className="h-5 w-5"><path d="M6 18L18 6M6 6l12 12" /></Icon></button>
+                            </div>
+                        ))}
+                    </div>
+                </div>
+            );
+        }
+        case 'award': {
+            const awardBlock = block as AwardBlock;
+
+            const handleUpdateItem = (id: string, field: keyof Omit<AwardItem, 'id'>, value: any) => {
+                const updatedItems = awardBlock.items.map(item =>
+                    item.id === id ? { ...item, [field]: value } : item
+                );
+                onUpdate({ ...awardBlock, items: updatedItems });
+            };
+
+            const handleAddItem = () => {
+                const newItem: AwardItem = {
+                    id: `award-${Date.now()}`,
+                    title: 'New Award',
+                    subtitle: 'Awarding Body',
+                    year: new Date().getFullYear().toString(),
+                    imageSource: { type: 'url', value: `https://picsum.photos/seed/award-${Date.now()}/100` }
+                };
+                onUpdate({ ...awardBlock, items: [...awardBlock.items, newItem] });
+            };
+            const handleRemoveItem = (id: string) => {
+                onUpdate({ ...awardBlock, items: awardBlock.items.filter(item => item.id !== id) });
+            };
+
+            return (
+                <div className="space-y-4">
+                    <LayoutToggle
+                        label="Layout"
+                        options={[{ label: 'Grid', value: 'grid' }, { label: 'Single', value: 'single' }]}
+                        value={awardBlock.layout}
+                        onLayoutChange={(layout: 'grid' | 'single') => onUpdate({ ...awardBlock, layout })}
+                    />
+                    <div className="space-y-3">
+                        {awardBlock.items.map(item => (
+                            <div key={item.id} className="p-3 bg-gray-100 dark:bg-gray-900 rounded-md space-y-2 relative">
+                                <button onClick={() => handleRemoveItem(item.id)} className="absolute top-2 right-2 text-gray-400 hover:text-red-500"><Icon className="h-4 w-4"><path d="M6 18L18 6M6 6l12 12" /></Icon></button>
+                                <div><label className="text-xs font-semibold text-gray-500">Title</label><input type="text" value={item.title} onChange={e => handleUpdateItem(item.id, 'title', e.target.value)} className="w-full bg-white dark:bg-gray-700 p-1 text-sm rounded" /></div>
+                                <div><label className="text-xs font-semibold text-gray-500">Subtitle (e.g., Awarded by)</label><input type="text" value={item.subtitle || ''} onChange={e => handleUpdateItem(item.id, 'subtitle', e.target.value)} className="w-full bg-white dark:bg-gray-700 p-1 text-sm rounded" /></div>
+                                <div className="grid grid-cols-2 gap-2">
+                                    <div><label className="text-xs font-semibold text-gray-500">Year</label><input type="text" value={item.year || ''} onChange={e => handleUpdateItem(item.id, 'year', e.target.value)} className="w-full bg-white dark:bg-gray-700 p-1 text-sm rounded" /></div>
+                                    <div><label className="text-xs font-semibold text-gray-500">Image URL</label><input type="url" value={item.imageSource?.type === 'url' ? item.imageSource.value : ''} onChange={e => handleUpdateItem(item.id, 'imageSource', { type: 'url', value: e.target.value })} className="w-full bg-white dark:bg-gray-700 p-1 text-sm rounded" /></div>
+                                </div>
+                            </div>
+                        ))}
+                    </div>
+                    <button onClick={handleAddItem} className="w-full justify-center p-2 bg-brand-primary/10 rounded-md text-sm text-brand-primary hover:underline font-semibold flex items-center gap-1">
+                        <Icon className="h-4 w-4"><path d="M12 4v16m8-8H4" /></Icon>Add Award
+                    </button>
+                </div>
+            );
+        }
+        case 'form': {
+            const formBlock = block as FormBlock;
+
+            const handleFieldChange = (fieldId: FormFieldId, prop: keyof FormField, value: any) => {
+                const updatedFields = formBlock.fields.map(f => f.id === fieldId ? { ...f, [prop]: value } : f);
+                onUpdate({ ...formBlock, fields: updatedFields });
+            };
+
+            const handlePurposeChange = (optionId: string, newLabel: string) => {
+                const updatedOptions = formBlock.purposeOptions.map(o => o.id === optionId ? { ...o, label: newLabel } : o);
+                onUpdate({ ...formBlock, purposeOptions: updatedOptions });
+            };
+            const handleAddPurpose = () => {
+                const newOption: FormPurposeOption = { id: `po-${Date.now()}`, label: 'New Option' };
+                onUpdate({ ...formBlock, purposeOptions: [...formBlock.purposeOptions, newOption] });
+            };
+            const handleRemovePurpose = (optionId: string) => {
+                const updatedOptions = formBlock.purposeOptions.filter(o => o.id !== optionId);
+                onUpdate({ ...formBlock, purposeOptions: updatedOptions });
+            };
+
+            return (
+                <div className="space-y-4">
+                    <div><label className={labelClasses}>Title</label><input type="text" value={formBlock.title} onChange={e => onUpdate({ ...block, title: e.target.value })} className={`${inputClasses} mt-1`} /></div>
+                    <div><label className={labelClasses}>Description</label><textarea value={formBlock.description} onChange={e => onUpdate({ ...block, description: e.target.value })} rows={3} className={`${inputClasses} mt-1`} /></div>
+                    
+                    <div>
+                        <label className={labelClasses}>Form Fields</label>
+                        <div className="space-y-2 mt-2 p-3 bg-gray-100 dark:bg-gray-900 rounded-md">
+                            {formBlock.fields.map(field => (
+                                <div key={field.id} className="flex items-center justify-between p-2 rounded-md hover:bg-gray-200 dark:hover:bg-gray-800/50">
+                                    <label htmlFor={`field-enabled-${field.id}`} className="flex items-center gap-3 cursor-pointer text-sm font-medium">
+                                        <input type="checkbox" id={`field-enabled-${field.id}`} checked={field.enabled} onChange={e => handleFieldChange(field.id, 'enabled', e.target.checked)} className="h-4 w-4 rounded bg-gray-200 dark:bg-gray-900 border-gray-300 dark:border-gray-600 text-brand-primary focus:ring-brand-secondary" />
+                                        <span className="capitalize text-gray-800 dark:text-gray-200">{field.label}</span>
+                                    </label>
+                                    {field.enabled && (
+                                        <label htmlFor={`field-required-${field.id}`} className="flex items-center gap-1.5 cursor-pointer text-xs text-gray-500 dark:text-gray-400">
+                                            <input type="checkbox" id={`field-required-${field.id}`} checked={field.required} onChange={e => handleFieldChange(field.id, 'required', e.target.checked)} className="h-3 w-3 rounded-sm bg-gray-200 dark:bg-gray-900 border-gray-300 dark:border-gray-600 text-brand-primary focus:ring-brand-secondary" />
+                                            <span>Required</span>
+                                        </label>
+                                    )}
+                                </div>
+                            ))}
+                        </div>
+                    </div>
+        
+                    <div>
+                        <label className={labelClasses}>Purpose Options</label>
+                        <div className="space-y-2 mt-2">
+                            {formBlock.purposeOptions.map(option => (
+                                <div key={option.id} className="flex items-center gap-2">
+                                    <input type="text" value={option.label} onChange={e => handlePurposeChange(option.id, e.target.value)} className={`${inputClasses} flex-1`} />
+                                    <button onClick={() => handleRemovePurpose(option.id)} className="text-gray-400 hover:text-red-500"><Icon className="h-5 w-5"><path d="M6 18L18 6M6 6l12 12" /></Icon></button>
+                                </div>
+                            ))}
+                            <button onClick={handleAddPurpose} className="text-sm text-brand-primary hover:underline font-semibold flex items-center gap-1">
+                               <Icon className="h-4 w-4"><path d="M12 4v16m8-8H4" /></Icon> Add Option
+                            </button>
+                        </div>
+                    </div>
+                    
+                    <div><label className={labelClasses}>Submit Button Text</label><input type="text" value={formBlock.submitButtonText} onChange={e => onUpdate({ ...block, submitButtonText: e.target.value })} className={`${inputClasses} mt-1`} /></div>
+                </div>
+            );
+        }
+        case 'footer': {
+            const footerBlock = block as FooterBlock;
+
+            const handleUpdateLink = (list: 'navLinks' | 'otherLinks', id: string, field: 'title' | 'url', value: string) => {
+                const updatedLinks = footerBlock[list].map(link => link.id === id ? { ...link, [field]: value } : link);
+                onUpdate({ ...footerBlock, [list]: updatedLinks });
+            };
+        
+            const handleAddLink = (list: 'navLinks' | 'otherLinks') => {
+                const newLink: FooterLink = { id: `fl-${Date.now()}`, title: 'New Link', url: '#' };
+                onUpdate({ ...footerBlock, [list]: [...footerBlock[list], newLink] });
+            };
+        
+            const handleRemoveLink = (list: 'navLinks' | 'otherLinks', id: string) => {
+                onUpdate({ ...footerBlock, [list]: footerBlock[list].filter(link => link.id !== id) });
+            };
+
+            const LinkEditorList: React.FC<{ list: FooterLink[], listKey: 'navLinks' | 'otherLinks', title: string }> = ({ list, listKey, title }) => (
+                <div>
+                    <h4 className="text-base font-semibold text-gray-700 dark:text-gray-200 mb-2">{title}</h4>
+                    <div className="space-y-2 p-3 bg-gray-100 dark:bg-gray-900 rounded-md">
+                        {list.map(link => (
+                            <div key={link.id} className="flex items-center gap-2 p-2 rounded-md bg-white dark:bg-gray-800/50">
+                                <input type="text" placeholder="Title" value={link.title} onChange={e => handleUpdateLink(listKey, link.id, 'title', e.target.value)} className={`${inputClasses} text-sm`} />
+                                <input type="url" placeholder="URL" value={link.url} onChange={e => handleUpdateLink(listKey, link.id, 'url', e.target.value)} className={`${inputClasses} text-sm`} />
+                                <button onClick={() => handleRemoveLink(listKey, link.id)} className="text-gray-400 hover:text-red-500"><Icon className="h-4 w-4"><path d="M6 18L18 6M6 6l12 12" /></Icon></button>
+                            </div>
+                        ))}
+                        <button onClick={() => handleAddLink(listKey)} className="w-full text-sm text-brand-primary hover:underline font-semibold flex items-center gap-1 justify-center p-2 bg-brand-primary/10 rounded-md">
+                            <Icon className="h-4 w-4"><path d="M12 4v16m8-8H4" /></Icon> Add Link
+                        </button>
+                    </div>
+                </div>
+            );
+
+            return (
+                <div className="space-y-4">
+                    <LayoutToggle label="Layout" options={[{label: 'Standard', value: 'standard'}, {label: 'Centered', value: 'centered'}]} value={footerBlock.layout} onLayoutChange={(layout: 'standard' | 'centered') => onUpdate({ ...footerBlock, layout })} />
+                    <div><label className={labelClasses}>Copyright Text</label><input type="text" value={footerBlock.copyrightText} onChange={e => onUpdate({ ...block, copyrightText: e.target.value })} className={`${inputClasses} mt-1`} /></div>
+                    <div><label className={labelClasses}>Legal / Filing Number (Optional)</label><input type="text" value={footerBlock.legalText || ''} onChange={e => onUpdate({ ...block, legalText: e.target.value })} className={`${inputClasses} mt-1`} /></div>
+                    <div><label className={labelClasses}>Special Statement (Optional)</label><textarea value={footerBlock.statement || ''} onChange={e => onUpdate({ ...block, statement: e.target.value })} rows={4} className={`${inputClasses} mt-1`} /></div>
+                    <LinkEditorList list={footerBlock.navLinks} listKey="navLinks" title="Navigation Links" />
+                    <LinkEditorList list={footerBlock.otherLinks} listKey="otherLinks" title="Other Hyperlinks" />
+                </div>
+            );
+        }
+        default: return null
+    }
+};
+
+const AIGCLibraryModal: React.FC<{
+    videos: AIGCVideo[];
+    onClose: () => void;
+    onSelect: (videoId: string) => void;
+}> = ({ videos, onClose, onSelect }) => {
+    return (
+        <div className="fixed inset-0 bg-black/70 z-50 flex items-center justify-center backdrop-blur-sm" onClick={onClose}>
+            <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl w-full max-w-4xl h-[80vh] p-8 border border-gray-200 dark:border-gray-700 flex flex-col" onClick={e => e.stopPropagation()}>
+                <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-6 flex-shrink-0">Select a Video from AIGC Library</h2>
+                <div className="flex-1 overflow-y-auto pr-4 -mr-4">
+                    <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
+                        {videos.map(video => (
+                            <button key={video.id} onMouseDown={(e) => e.preventDefault()} onClick={() => onSelect(video.id)} className="group text-left p-2 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-brand-primary transition-colors">
+                                <img src={video.thumbnailUrl} alt={video.title} className="w-full h-24 object-cover rounded-md mb-2" />
+                                <p className="text-sm font-semibold truncate group-hover:text-brand-primary">{video.title}</p>
+                            </button>
+                        ))}
+                    </div>
+                     {videos.length === 0 && <p className="text-center text-gray-400 dark:text-gray-500 py-12">No videos in your AIGC library. Create some in the AIGC Creator!</p>}
+                </div>
+            </div>
+        </div>
+    );
+};
+
+const LinkEditor: React.FC<{ blocks: Block[]; setBlocks: (newBlocks: Block[]) => void; aigcVideos: AIGCVideo[]; aigcArticles: AIGCArticle[] }> = ({ blocks, setBlocks, aigcVideos, aigcArticles }) => {
+    const [expandedBlockId, setExpandedBlockId] = React.useState<string | null>(null);
+    const [draggedItemId, setDraggedItemId] = React.useState<string | null>(null);
+    const [isAIGCModalOpen, setIsAIGCModalOpen] = React.useState(false);
+    const [activeBlockForAIGC, setActiveBlockForAIGC] = React.useState<VideoBlock | null>(null);
+    const [isAddBlockModalOpen, setIsAddBlockModalOpen] = React.useState(false);
+    
+    const hasChatBlock = React.useMemo(() => blocks.some(b => b.type === 'chat'), [blocks]);
+    const hasFooterBlock = React.useMemo(() => blocks.some(b => b.type === 'footer'), [blocks]);
+    
+    const addBlock = (type: BlockType) => {
+        const newBlock: Block = {
+            id: `${type}-${Date.now()}`, type, visible: true,
+            ...(type === 'link' && { title: 'New Link', url: '', iconUrl: '', thumbnailUrl: '' }),
+            ...(type === 'header' && { text: 'New Header', titleAlignment: 'left' }),
+            ...(type === 'social' && { links: [] }),
+            ...(type === 'chat' && { layout: 'under_avatar' }),
+            ...(type === 'enterprise_info' && { items: [
+                { id: `ei-${Date.now()}-1`, icon: 'building', label: 'Company Name', value: 'Innovatech Inc.' },
+                { id: `ei-${Date.now()}-2`, icon: 'location', label: 'Address', value: '123 Tech Avenue, Silicon Valley, CA' },
+                { id: `ei-${Date.now()}-3`, icon: 'calendar', label: 'Founded', value: '2021' },
+            ], alignment: 'left' }),
+            ...(type === 'video' && { sources: [], layout: 'single' }),
+            ...(type === 'image' && { sources: [], layout: 'grid' }),
+            ...(type === 'text' && { content: 'This is a new text block. You can edit this content.', textAlign: 'left', fontSize: '16px', fontColor: '#E5E7EB', isBold: false, isItalic: false }),
+            ...(type === 'map' && { address: 'New York, NY', displayStyle: 'interactiveMap', backgroundImageSource: { type: 'url', value: `https://picsum.photos/seed/new-map-${Date.now()}/600/400` } }),
+            ...(type === 'pdf' && { source: { type: 'url', value: '' } }),
+            ...(type === 'email' && { email: 'contact@example.com', label: 'Email Me', displayMode: 'labelAndValue' }),
+            ...(type === 'phone' && { phone: '+1234567890', label: 'Call Me', displayMode: 'labelAndValue' }),
+            ...(type === 'news' && { layout: 'list', source: 'aigc', articleIds: [] }),
+            ...(type === 'product' && { items: [], layout: 'grid' }),
+            ...(type === 'award' && {
+                items: [
+                    { id: `award-${Date.now()}-1`, title: 'Design of the Year', subtitle: 'Global Design Awards', year: '2023', imageSource: { type: 'url', value: 'https://picsum.photos/seed/award1/100' }},
+                    { id: `award-${Date.now()}-2`, title: 'Top Innovator', subtitle: 'Tech Weekly Magazine', year: '2022', imageSource: { type: 'url', value: 'https://picsum.photos/seed/award2/100' }}
+                ],
+                layout: 'grid'
+            }),
+            ...(type === 'form' && {
+                title: 'New Form',
+                description: 'Collect information from your visitors.',
+                submitButtonText: 'Submit',
+                fields: [
+                    { id: 'name', label: 'Name', enabled: true, required: true },
+                    { id: 'email', label: 'Email', enabled: true, required: true },
+                    { id: 'company', label: 'Company', enabled: false, required: false },
+                    { id: 'phone', label: 'Phone', enabled: false, required: false },
+                    { id: 'industry', label: 'Industry', enabled: false, required: false },
+                    { id: 'position', label: 'Position', enabled: false, required: false },
+                    { id: 'country', label: 'Country', enabled: false, required: false },
+                ],
+                purposeOptions: [
+                    { id: `po-new-${Date.now()}`, label: 'General Inquiry' },
+                ]
+            }),
+            ...(type === 'footer' && {
+                layout: 'standard',
+                copyrightText: `© ${new Date().getFullYear()} Your Company Name. All Rights Reserved.`,
+                legalText: 'Your Legal ID / Filing Number',
+                statement: 'This is a special statement or disclaimer area for your website footer.',
+                navLinks: [
+                    { id: 'fl-1', title: 'Home', url: '#' },
+                    { id: 'fl-2', title: 'About Us', url: '#' },
+                    { id: 'fl-3', title: 'Services', url: '#' },
+                    { id: 'fl-4', title: 'Contact', url: '#' },
+                ],
+                otherLinks: [
+                    { id: 'fl-5', title: 'Privacy Policy', url: '#' },
+                    { id: 'fl-6', title: 'Terms of Service', url: '#' },
+                ]
+            }),
+        } as Block;
+        setBlocks([...blocks, newBlock]);
+        setExpandedBlockId(newBlock.id);
+    };
+
+    const handleAddBlockFromModal = (type: BlockType) => {
+        addBlock(type);
+        setIsAddBlockModalOpen(false);
+    };
+
+    const updateBlock = (updatedBlock: Block) => setBlocks(blocks.map(b => b.id === updatedBlock.id ? updatedBlock : b));
+    const deleteBlock = (id: string) => setBlocks(blocks.filter(b => b.id !== id));
+    const toggleVisibility = (id: string) => setBlocks(blocks.map(b => b.id === id ? { ...b, visible: !b.visible } : b));
+
+    const handleDragStart = (e: React.DragEvent<HTMLElement>, id: string) => { setDraggedItemId(id); e.dataTransfer.effectAllowed = 'move'; };
+    const handleDragOver = (e: React.DragEvent<HTMLElement>) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; };
+    const handleDrop = (e: React.DragEvent<HTMLElement>, targetId: string) => {
+        e.preventDefault();
+        if (!draggedItemId || draggedItemId === targetId) return;
+        const draggedIndex = blocks.findIndex(b => b.id === draggedItemId);
+        const targetIndex = blocks.findIndex(b => b.id === targetId);
+        if(draggedIndex === -1 || targetIndex === -1) return;
+        const newBlocks = [...blocks];
+        const [draggedItem] = newBlocks.splice(draggedIndex, 1);
+        newBlocks.splice(targetIndex, 0, draggedItem);
+        setBlocks(newBlocks);
+        setDraggedItemId(null);
+    };
+
+    const handleOpenAIGCModal = (block: VideoBlock) => {
+        setActiveBlockForAIGC(block);
+        setIsAIGCModalOpen(true);
+    };
+
+    const handleSelectAIGCVideo = (videoId: string) => {
+        if (!activeBlockForAIGC) return;
+        const newSource: MediaSource = { type: 'aigc', videoId };
+        updateBlock({
+            ...activeBlockForAIGC,
+            sources: [...activeBlockForAIGC.sources, newSource],
+        });
+        setIsAIGCModalOpen(false);
+        setActiveBlockForAIGC(null);
+    };
+
+    const getBlockTitle = (block: Block) => {
+        switch (block.type) {
+            case 'header': return block.text;
+            case 'link': return block.title;
+            case 'image': return `Image Gallery (${block.sources.length})`;
+            case 'video': return `Video Gallery (${block.sources.length})`;
+            case 'text': return `${block.content.substring(0, 30)}...`;
+            case 'email': return block.label;
+            case 'phone': return block.label;
+            case 'chat': return 'Chat Button';
+            case 'enterprise_info': return 'Enterprise Information';
+            case 'social': return "Social Media Icons";
+            case 'map': return block.address || "Map Location";
+            case 'news': return `News Feed (${block.source === 'aigc' ? (block.articleIds || []).length : (block.customItems || []).length} items)`;
+            case 'product': return `Product Showcase (${block.items.length} items)`;
+            case 'form': return block.title;
+            case 'award': return `Awards (${block.items.length} items)`;
+            case 'footer': return 'Page Footer';
+            case 'pdf': {
+                const source = block.source;
+                if (source.type === 'file') {
+                    return source.value.name;
+                }
+                if (source.type === 'url' && source.value) {
+                    const urlParts = source.value.split('/');
+                    return urlParts[urlParts.length - 1] || "PDF Document";
+                }
+                return "PDF Document";
+            }
+            default:
+                const exhaustiveCheck = block as Block;
+                return blockTypeMetadata[exhaustiveCheck.type]?.name || 'Unknown Block';
+        }
+    };
+
+    return (
+        <div>
+            {isAIGCModalOpen && <AIGCLibraryModal videos={aigcVideos} onClose={() => setIsAIGCModalOpen(false)} onSelect={handleSelectAIGCVideo} />}
+            {isAddBlockModalOpen && <AddBlockModal hasChatBlock={hasChatBlock} hasFooterBlock={hasFooterBlock} onSelect={handleAddBlockFromModal} onClose={() => setIsAddBlockModalOpen(false)} />}
+            
+            <button
+                onClick={() => setIsAddBlockModalOpen(true)}
+                className="w-full flex items-center justify-center gap-2 p-3 mb-8 bg-brand-primary text-white font-semibold rounded-lg hover:bg-brand-secondary transition-colors shadow-lg"
+            >
+                <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" /></Icon>
+                Add Block
+            </button>
+            
+            <div>
+                {blocks.map(block => {
+                    const isHeader = block.type === 'header';
+                    const containerClass = `${isHeader ? 'mt-8 mb-2 border-b-2 border-gray-200 dark:border-gray-700' : 'bg-white dark:bg-gray-800 rounded-lg mt-3'} transition-shadow ${draggedItemId === block.id ? 'opacity-50 shadow-2xl' : ''}`;
+                    const titleClass = isHeader ? 'font-bold text-gray-400 dark:text-gray-500 uppercase tracking-wider' : 'font-semibold text-gray-900 dark:text-white';
+
+                     return <div key={block.id} draggable onDragStart={e => handleDragStart(e, block.id)} onDragOver={handleDragOver} onDrop={e => handleDrop(e, block.id)} className={containerClass}>
+                        <div className="p-3 flex items-center gap-3 cursor-pointer" onClick={() => setExpandedBlockId(expandedBlockId === block.id ? null : block.id)}>
+                            <div onClick={e => e.stopPropagation()} className="cursor-grab text-gray-400 dark:text-gray-500"><Icon className="h-5 w-5"><path d="M4 8h16M4 16h16" /></Icon></div>
+                            <div className="flex-1 truncate"><p className={`${titleClass} truncate`}>{getBlockTitle(block)}</p></div>
+                            <button onMouseDown={(e) => e.preventDefault()} onClick={e => { e.stopPropagation(); toggleVisibility(block.id); }} className="text-gray-400 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"><Icon className="h-5 w-5">{block.visible ? <><path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></> : <path d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />}</Icon></button>
+                            <button onMouseDown={(e) => e.preventDefault()} onClick={e => { e.stopPropagation(); deleteBlock(block.id); }} className="text-gray-400 dark:text-gray-500 hover:text-red-500"><Icon className="h-5 w-5"><path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></Icon></button>
+                            <div className="text-gray-400 dark:text-gray-400">
+                                <Icon className={`h-5 w-5 transition-transform ${expandedBlockId === block.id ? 'rotate-180' : ''}`}><path d="M19 9l-7 7-7-7" /></Icon>
+                            </div>
+                        </div>
+                         {expandedBlockId === block.id && (<div className={`p-4 ${isHeader ? '' : 'border-t border-gray-200 dark:border-gray-700'}`}><BlockItemEditor block={block} onUpdate={updateBlock} onAIGCOpen={() => handleOpenAIGCModal(block as VideoBlock)} aigcVideos={aigcVideos} aigcArticles={aigcArticles} /></div>)}
+                    </div>
+                })}
+            </div>
+        </div>
+    );
+};
+
+export default LinkEditor;

+ 54 - 0
components/LinksDomains.tsx

@@ -0,0 +1,54 @@
+import * as React from 'react';
+
+interface LinksDomainsProps {
+    slug: string;
+    onUpdateSlug: (newSlug: string) => void;
+}
+
+const LinksDomains: React.FC<LinksDomainsProps> = ({ slug, onUpdateSlug }) => {
+    const [customDomain, setCustomDomain] = React.useState('');
+
+    return (
+        <div className="p-6 max-w-4xl mx-auto">
+            <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">Links & Custom Domains</h2>
+            <div className="space-y-8">
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg">
+                    <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">Your GreenPage URL</h3>
+                    <p className="text-gray-500 dark:text-gray-400 mb-2">This is your main link that you can share anywhere.</p>
+                    <div className="flex items-center bg-gray-100 dark:bg-gray-700 rounded-md">
+                        <span className="px-4 text-gray-400 dark:text-gray-400">greenpage.ai/</span>
+                        <input
+                            type="text"
+                            value={slug}
+                            onChange={(e) => onUpdateSlug(e.target.value)}
+                            className="flex-1 bg-transparent p-3 text-gray-900 dark:text-white focus:outline-none"
+                        />
+                        <button className="bg-brand-primary text-white font-semibold py-2 px-4 rounded-r-md hover:bg-brand-secondary transition-colors">
+                            Save
+                        </button>
+                    </div>
+                </div>
+
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg">
+                    <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">Connect a Custom Domain</h3>
+                    <p className="text-gray-500 dark:text-gray-400 mb-2">Use your own domain for a more professional look (e.g., links.yourcompany.com).</p>
+                    <div className="flex items-center bg-gray-100 dark:bg-gray-700 rounded-md">
+                        <input
+                            type="text"
+                            value={customDomain}
+                            onChange={(e) => setCustomDomain(e.target.value)}
+                            placeholder="your-domain.com"
+                            className="w-full bg-transparent p-3 text-gray-900 dark:text-white focus:outline-none rounded-md"
+                        />
+                    </div>
+                    <button className="mt-4 w-full md:w-auto bg-brand-primary text-white font-semibold py-2 px-5 rounded-md hover:bg-brand-secondary transition-colors">
+                        Connect Domain
+                    </button>
+                    <p className="text-xs text-gray-400 dark:text-gray-500 mt-4">You'll need to update your DNS records after connecting. We'll provide the instructions.</p>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default LinksDomains;

+ 332 - 0
components/NewsCreator.tsx

@@ -0,0 +1,332 @@
+import * as React from 'react';
+import { AIGCSettings, AIGCArticle } from '../types';
+import { Icon } from './ui/Icon';
+import { generateArticle } from '../services/geminiService';
+import { format, parseISO } from 'date-fns';
+import { useTranslation } from '../hooks/useI18n';
+
+// --- Helper Components ---
+
+const ArticlePreview: React.FC<{ article: AIGCArticle; onEdit: () => void }> = ({ article, onEdit }) => {
+    const { t } = useTranslation();
+    return (
+        <div className="h-full flex flex-col bg-white dark:bg-gray-800">
+            <header className="p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0 flex justify-between items-center">
+                <div className="min-w-0">
+                    <h3 className="text-xl font-semibold text-gray-900 dark:text-white truncate">{article.title}</h3>
+                </div>
+                <button onClick={onEdit} className="flex items-center gap-2 px-4 py-2 bg-gray-200 dark:bg-gray-700 text-sm font-semibold rounded-md hover:bg-gray-300 dark:hover:bg-gray-600">
+                    <Icon className="h-4 w-4"><path d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.536L16.732 3.732z" /></Icon>
+                    {t('edit')}
+                </button>
+            </header>
+            <div className="flex-1 overflow-y-auto">
+                <article className="prose prose-lg dark:prose-invert max-w-3xl mx-auto py-8 px-6">
+                    <h1>{article.title}</h1>
+                    <p className="lead">{article.summary}</p>
+                    <p className="text-sm text-gray-500 dark:text-gray-400">Published: {format(parseISO(article.publicationDate), 'MMMM d, yyyy')}</p>
+                    <hr className="dark:border-gray-600"/>
+                    <div dangerouslySetInnerHTML={{ __html: article.content }} />
+                </article>
+            </div>
+        </div>
+    );
+};
+
+const EditorToolbar: React.FC<{ onExecCommand: (command: string, value?: string) => void }> = ({ onExecCommand }) => {
+    const inputClasses = "bg-gray-200 dark:bg-gray-600 border-gray-300 dark:border-gray-500 rounded p-1 text-xs focus:outline-none focus:ring-1 focus:ring-brand-primary text-gray-900 dark:text-white";
+    const buttonClasses = "p-2 rounded hover:bg-gray-200 dark:hover:bg-gray-600";
+    
+    return (
+        <div className="flex flex-wrap items-center gap-2 p-2 bg-gray-100 dark:bg-gray-900/50 rounded-t-lg border-b border-gray-300 dark:border-gray-600 sticky top-0 z-10">
+            {/* Style */}
+            <button title="Bold" onMouseDown={e => e.preventDefault()} onClick={() => onExecCommand('bold')} className={buttonClasses}><strong>B</strong></button>
+            <button title="Italic" onMouseDown={e => e.preventDefault()} onClick={() => onExecCommand('italic')} className={buttonClasses}><em>I</em></button>
+            <div className="h-6 border-l border-gray-300 dark:border-gray-600 mx-1" />
+            {/* Align */}
+            <button title="Align Left" onMouseDown={e => e.preventDefault()} onClick={() => onExecCommand('justifyLeft')} className={buttonClasses}><Icon className="h-4 w-4"><path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12" /></Icon></button>
+            <button title="Align Center" onMouseDown={e => e.preventDefault()} onClick={() => onExecCommand('justifyCenter')} className={buttonClasses}><Icon className="h-4 w-4"><path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /></Icon></button>
+            <button title="Align Right" onMouseDown={e => e.preventDefault()} onClick={() => onExecCommand('justifyRight')} className={buttonClasses}><Icon className="h-4 w-4"><path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5M12 17.25h8.25" /></Icon></button>
+            <div className="h-6 border-l border-gray-300 dark:border-gray-600 mx-1" />
+            {/* Font */}
+            <select onChange={(e) => onExecCommand('fontSize', e.target.value)} defaultValue="3" className={inputClasses}>
+                <option value="1">Small</option>
+                <option value="3">Normal</option>
+                <option value="5">Large</option>
+                <option value="7">Huge</option>
+            </select>
+            <input type="color" onChange={(e) => onExecCommand('foreColor', e.target.value)} defaultValue="#E5E7EB" className={`${inputClasses} h-8 w-8 p-0 align-middle`} />
+            <div className="h-6 border-l border-gray-300 dark:border-gray-600 mx-1" />
+            {/* Insert */}
+            <button title="Insert Link" onMouseDown={e => e.preventDefault()} onClick={() => { const url = prompt('Enter URL:'); if (url) onExecCommand('createLink', url); }} className={buttonClasses}><Icon className="h-4 w-4"><path d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" /></Icon></button>
+            <button title="Insert Image" onMouseDown={e => e.preventDefault()} onClick={() => { const url = prompt('Enter Image URL:'); if (url) onExecCommand('insertImage', url); }} className={buttonClasses}><Icon className="h-4 w-4"><path d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" /></Icon></button>
+        </div>
+    );
+};
+
+
+const ArticleEditor: React.FC<{
+    initialData: Partial<AIGCArticle>;
+    onSave: (data: Omit<AIGCArticle, 'id' | 'publicationDate'>) => void;
+    onCancel: () => void;
+    isNew: boolean;
+}> = ({ initialData, onSave, onCancel, isNew }) => {
+    const { t } = useTranslation();
+    const [data, setData] = React.useState(initialData);
+    const contentEditableRef = React.useRef<HTMLDivElement>(null);
+    const inputClasses = "w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-1 focus:ring-brand-primary";
+    const labelClasses = "block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1";
+
+    const [generationMode, setGenerationMode] = React.useState<'ai' | 'url'>('ai');
+    const [generationInput, setGenerationInput] = React.useState('');
+    const [isGenerating, setIsGenerating] = React.useState(false);
+    const [showGenerator, setShowGenerator] = React.useState(false);
+    
+    const handleSave = () => {
+        if (!data.title?.trim()) {
+            alert(t('aigc.news.title_req'));
+            return;
+        }
+        onSave({
+            title: data.title,
+            summary: data.summary || '',
+            content: data.content || '',
+            sourceType: data.sourceType || 'text',
+            sourceUrl: data.sourceUrl
+        });
+    };
+
+    const handleExecCommand = (command: string, value?: string) => {
+        document.execCommand(command, false, value);
+        contentEditableRef.current?.focus();
+    };
+    
+    const handleContentChange = (e: React.FormEvent<HTMLDivElement>) => {
+        setData(d => ({ ...d, content: e.currentTarget.innerHTML }));
+    };
+
+     const handleGenerate = async () => {
+        setIsGenerating(true);
+        try {
+            const prompt = generationMode === 'url' ? `Parse and summarize this article: ${generationInput}` : generationInput;
+            if (!generationInput.trim()) {
+                alert(`Please enter a ${generationMode === 'url' ? 'URL' : 'topic'}.`);
+                return;
+            }
+            const result = await generateArticle(prompt);
+            setData(d => ({
+                ...d,
+                ...result,
+                sourceType: generationMode === 'url' ? 'url' : 'generated',
+                sourceUrl: generationMode === 'url' ? generationInput : undefined,
+            }));
+            // Keep the generator open for potential re-generation
+        } catch (error) {
+            console.error("Failed to generate article:", error);
+            alert("Sorry, we couldn't generate the article at this time.");
+        } finally {
+            setIsGenerating(false);
+        }
+    };
+    
+    return (
+         <div className="h-full flex flex-col">
+            <header className="p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0 flex justify-between items-center">
+                <h3 className="text-xl font-semibold text-gray-900 dark:text-white">{isNew ? t('aigc.news.create') : t('aigc.news.edit')}</h3>
+                <div className="flex items-center gap-4">
+                    <button onClick={onCancel} className="text-sm font-semibold text-gray-600 dark:text-gray-300 hover:underline">{t('cancel')}</button>
+                    <button onClick={handleSave} className="px-5 py-2 bg-brand-primary text-white font-bold rounded-md hover:bg-brand-secondary">
+                        {isNew ? t('aigc.news.save_lib') : t('aigc.news.save_changes')}
+                    </button>
+                </div>
+            </header>
+            <div className="flex-1 overflow-y-auto p-6 space-y-4">
+                <div className="mb-4">
+                    <button
+                        onClick={() => setShowGenerator(prev => !prev)}
+                        className="w-full flex justify-between items-center p-3 bg-gray-100 dark:bg-gray-900/50 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
+                    >
+                        <div className="flex items-center gap-2">
+                            <Icon className="h-5 w-5 text-brand-primary">
+                                <path strokeLinecap="round" strokeLinejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c.251.023.501.05.75.082a9.75 9.75 0 016 6.062c.313.958.5 1.965.5 3.004v.75a2.25 2.25 0 01-2.25 2.25H3.75a2.25 2.25 0 01-2.25-2.25v-.75c0-1.04.187-2.046.5-3.004a9.75 9.75 0 016-6.062 12.312 12.312 0 01.75-.082zM9.75 18.75c-2.482 0-4.72-1.22-6.16-3.223" />
+                            </Icon>
+                            <span className="font-semibold text-gray-800 dark:text-gray-200">{t('aigc.news.ai_tools')}</span>
+                        </div>
+                        <Icon className={`h-5 w-5 text-gray-500 dark:text-gray-400 transition-transform ${showGenerator ? 'rotate-180' : ''}`}>
+                            <path d="M19 9l-7 7-7-7" />
+                        </Icon>
+                    </button>
+
+                    {showGenerator && (
+                        <div className="p-4 mt-2 bg-gray-50 dark:bg-gray-900/50 rounded-lg border border-gray-200 dark:border-gray-700 space-y-3">
+                            <div className="flex items-center gap-2 bg-gray-200 dark:bg-gray-800 p-1 rounded-lg">
+                                {(['ai', 'url'] as const).map(m => (
+                                    <button key={m} onClick={() => setGenerationMode(m)} className={`flex-1 px-3 py-1.5 text-sm rounded-md capitalize transition-colors ${generationMode === m ? 'bg-brand-primary text-white shadow' : 'hover:bg-gray-300 dark:hover:bg-gray-700'}`}>
+                                        {m === 'ai' ? t('aigc.news.generate_ai') : t('aigc.news.import_url')}
+                                    </button>
+                                ))}
+                            </div>
+                            <div className="flex items-center gap-2">
+                                <input 
+                                    type="text"
+                                    value={generationInput}
+                                    onChange={e => setGenerationInput(e.target.value)}
+                                    onKeyPress={e => e.key === 'Enter' && handleGenerate()}
+                                    placeholder={generationMode === 'ai' ? t('aigc.news.enter_topic') : t('aigc.news.enter_url')}
+                                    className={inputClasses}
+                                />
+                                <button onClick={handleGenerate} disabled={isGenerating} className="px-4 py-2 bg-gray-200 dark:bg-gray-600 text-sm font-semibold rounded-md hover:bg-gray-300 dark:hover:bg-gray-500 disabled:opacity-50">
+                                    {isGenerating ? t('aigc.news.generating') : t('aigc.news.generate')}
+                                </button>
+                            </div>
+                        </div>
+                    )}
+                </div>
+                <div>
+                    <label htmlFor="title-write" className={labelClasses}>{t('link_editor.title')}</label>
+                    <input id="title-write" type="text" value={data.title || ''} 
+                        onChange={e => setData(d => ({...d, title: e.target.value}))} 
+                        className={inputClasses} />
+                </div>
+                <div>
+                    <label htmlFor="summary-write" className={labelClasses}>{t('aigc.news.summary')}</label>
+                    <textarea id="summary-write" rows={3} value={data.summary || ''} 
+                        onChange={e => setData(d => ({...d, summary: e.target.value}))} 
+                        className={inputClasses}></textarea>
+                </div>
+                <div>
+                    <label className={labelClasses}>{t('aigc.news.full_content')}</label>
+                    <div className="mt-1 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800">
+                        <EditorToolbar onExecCommand={handleExecCommand} />
+                        <div
+                            ref={contentEditableRef}
+                            contentEditable
+                            onInput={handleContentChange}
+                            dangerouslySetInnerHTML={{ __html: data.content || '' }}
+                            className="w-full min-h-[400px] bg-white dark:bg-gray-800 p-4 rounded-b-lg prose dark:prose-invert max-w-none focus:outline-none"
+                        />
+                    </div>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+// --- Main Component ---
+
+interface NewsCreatorProps {
+    aigcSettings: AIGCSettings;
+    onUpdateAIGCSettings: (newSettings: AIGCSettings) => void;
+}
+
+const NewsCreator: React.FC<NewsCreatorProps> = ({ aigcSettings, onUpdateAIGCSettings }) => {
+    const { t } = useTranslation();
+    const [selectedArticleId, setSelectedArticleId] = React.useState<string | null>(null);
+    const [isEditing, setIsEditing] = React.useState(false);
+    
+    const articles = aigcSettings.articles || [];
+    const selectedArticle = React.useMemo(() => articles.find(a => a.id === selectedArticleId), [articles, selectedArticleId]);
+
+    const handleSaveArticle = (data: Omit<AIGCArticle, 'id' | 'publicationDate'>) => {
+        let updatedArticles;
+        if (selectedArticleId && !selectedArticleId.startsWith('new-')) { // Editing existing article
+            updatedArticles = articles.map(a => a.id === selectedArticleId ? { ...a, ...data, publicationDate: new Date().toISOString() } : a);
+        } else { // Creating new article
+            const newArticle: AIGCArticle = { id: `article-${Date.now()}`, publicationDate: new Date().toISOString(), ...data };
+            if (selectedArticleId?.startsWith('new-')) {
+                updatedArticles = articles.map(a => a.id === selectedArticleId ? newArticle : a);
+            } else {
+                 updatedArticles = [newArticle, ...articles];
+            }
+            setSelectedArticleId(newArticle.id);
+        }
+        onUpdateAIGCSettings({ ...aigcSettings, articles: updatedArticles });
+        setIsEditing(false);
+    };
+    
+    const handleDeleteArticle = (id: string) => {
+        if (!window.confirm(t('aigc.news.delete_confirm'))) return;
+        const updatedArticles = articles.filter(a => a.id !== id);
+        onUpdateAIGCSettings({ ...aigcSettings, articles: updatedArticles });
+        if (selectedArticleId === id) {
+            setSelectedArticleId(null);
+            setIsEditing(false);
+        }
+    };
+    
+    const handleCreateNewArticle = () => {
+        if (selectedArticleId?.startsWith('new-')) return; // Don't create another if one is in progress
+        const tempId = `new-${Date.now()}`;
+        const tempArticle: AIGCArticle = {
+            id: tempId,
+            publicationDate: new Date().toISOString(),
+            title: '',
+            summary: '',
+            content: '',
+            sourceType: 'text',
+        };
+        onUpdateAIGCSettings({ ...aigcSettings, articles: [tempArticle, ...articles] });
+        setSelectedArticleId(tempId);
+        setIsEditing(true);
+    };
+    
+    const handleCancelEdit = () => {
+        if (selectedArticleId && selectedArticleId.startsWith('new-')) {
+            const updatedArticles = articles.filter(a => a.id !== selectedArticleId);
+            onUpdateAIGCSettings({ ...aigcSettings, articles: updatedArticles });
+            const firstRealArticle = updatedArticles.find(a => !a.id.startsWith('new-'));
+            setSelectedArticleId(firstRealArticle ? firstRealArticle.id : null);
+        }
+        setIsEditing(false);
+    };
+    
+    return (
+        <div className="flex h-full bg-white dark:bg-gray-800">
+            <aside className="w-1/3 max-w-sm flex flex-col border-r border-gray-200 dark:border-gray-700">
+                <header className="p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
+                    <h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">{t('aigc.news.title')}</h2>
+                    <button 
+                        onClick={handleCreateNewArticle}
+                        className="w-full flex items-center justify-center gap-2 px-4 py-3 bg-brand-primary text-white font-semibold rounded-lg hover:bg-brand-secondary transition-colors shadow-sm"
+                    >
+                        <Icon className="h-5 w-5"><path d="M12 4v16m8-8H4" /></Icon>
+                        {t('aigc.news.create')}
+                    </button>
+                </header>
+                <div className="flex-1 overflow-y-auto p-2 space-y-2">
+                    {articles.filter(a => !a.id.startsWith('new-')).sort((a,b) => parseISO(b.publicationDate).getTime() - parseISO(a.publicationDate).getTime()).map(article => (
+                        <button key={article.id} onClick={() => { setSelectedArticleId(article.id); setIsEditing(false); }} className={`w-full p-3 rounded-lg text-left group transition-colors ${selectedArticleId === article.id && !isEditing ? 'bg-brand-primary/10' : 'hover:bg-gray-100 dark:hover:bg-gray-800'}`}>
+                            <div className="flex justify-between items-start">
+                                <p className={`font-semibold text-gray-900 dark:text-white truncate ${selectedArticleId === article.id && !isEditing ? 'text-brand-primary' : ''}`}>{article.title}</p>
+                                <button onClick={(e) => { e.stopPropagation(); handleDeleteArticle(article.id); }} className="text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 flex-shrink-0 ml-2"><Icon className="h-4 w-4"><path d="M6 18L18 6M6 6l12 12" /></Icon></button>
+                            </div>
+                            <p className="text-xs text-gray-500 dark:text-gray-400 truncate mt-1">{article.summary}</p>
+                        </button>
+                    ))}
+                </div>
+            </aside>
+            <main className="flex-1">
+                {selectedArticle ? (
+                    isEditing ? (
+                        <ArticleEditor
+                            initialData={selectedArticle}
+                            onSave={handleSaveArticle}
+                            onCancel={handleCancelEdit}
+                            isNew={selectedArticle.id.startsWith('new-')}
+                        />
+                    ) : (
+                        <ArticlePreview article={selectedArticle} onEdit={() => setIsEditing(true)} />
+                    )
+                ) : (
+                    <div className="flex flex-col items-center justify-center h-full text-center text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-800/50">
+                        <Icon className="h-16 w-16 mb-4 text-gray-300 dark:text-gray-600"><path strokeLinecap="round" strokeLinejoin="round" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3h3m-3 4h3m-3 4h3" /></Icon>
+                        <h3 className="text-xl font-semibold">{t('aigc.news.empty_title')}</h3>
+                        <p>{t('aigc.news.empty_desc')}</p>
+                    </div>
+                )}
+            </main>
+        </div>
+    );
+};
+
+export default NewsCreator;

+ 239 - 0
components/PageAnalytics.tsx

@@ -0,0 +1,239 @@
+import * as React from 'react';
+import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, BarChart, Bar, PieChart, Pie, Cell, Sector } from 'recharts';
+import { subDays, format } from 'date-fns';
+import { AnalyticsData } from '../types';
+import { useTranslation } from '../hooks/useI18n';
+
+const generateAllTimeData = () => {
+    const data = [];
+    for (let i = 60; i >= 0; i--) {
+        const date = subDays(new Date(), i);
+        data.push({
+            date: format(date, 'MMM d'),
+            views: 1000 + Math.floor(Math.random() * 800) + (60 - i) * 15,
+            clicks: 600 + Math.floor(Math.random() * 500) + (60 - i) * 10,
+            uniqueVisitors: 700 + Math.floor(Math.random() * 400) + (60 - i) * 12,
+        });
+    }
+    return data;
+};
+
+const allTimeData = generateAllTimeData();
+
+const allTimeReferrers = [
+    { source: 'Instagram', visits: 12450 },
+    { source: 'Twitter / X', visits: 9870 },
+    { source: 'Direct', visits: 8320 },
+    { source: 'LinkedIn', visits: 6400 },
+    { source: 'Facebook', visits: 4100 },
+];
+
+const allTimeLocations = [
+    { country: 'USA', visits: 22500 },
+    { country: 'India', visits: 15300 },
+    { country: 'UK', visits: 11800 },
+    { country: 'Canada', visits: 8900 },
+    { country: 'Germany', visits: 7200 },
+];
+
+const allTimeDevices = [
+    { name: 'Mobile', value: 38400 },
+    { name: 'Desktop', value: 16600 },
+];
+
+const COLORS = ['#10b981', '#4b5563'];
+
+interface PageAnalyticsProps {
+    analyticsData: AnalyticsData;
+}
+
+const PageAnalytics: React.FC<PageAnalyticsProps> = ({ analyticsData }) => {
+    const { t } = useTranslation();
+    const [timeRange, setTimeRange] = React.useState<'7' | '28' | 'all'>('7');
+    const [isDarkMode, setIsDarkMode] = React.useState(document.documentElement.classList.contains('dark'));
+
+    React.useEffect(() => {
+        const observer = new MutationObserver(() => {
+            setIsDarkMode(document.documentElement.classList.contains('dark'));
+        });
+        observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
+        return () => observer.disconnect();
+    }, []);
+
+    const chartColors = {
+        grid: isDarkMode ? '#374151' : '#e5e7eb',
+        text: isDarkMode ? '#9CA3AF' : '#6b7280',
+        tooltipBg: isDarkMode ? '#1F2937' : '#FFFFFF',
+        tooltipBorder: isDarkMode ? '#4B5563' : '#d1d5db',
+    };
+
+    const processedAnalytics = React.useMemo(() => {
+        const range = timeRange === 'all' ? allTimeData.length : parseInt(timeRange);
+        const chartData = allTimeData.slice(-range);
+        
+        const summary = chartData.reduce((acc, day) => {
+            acc.views += day.views;
+            acc.clicks += day.clicks;
+            acc.uniqueVisitors += day.uniqueVisitors;
+            return acc;
+        }, { views: 0, clicks: 0, uniqueVisitors: 0 });
+
+        const ctr = summary.views > 0 ? `${((summary.clicks / summary.views) * 100).toFixed(1)}%` : '0%';
+        
+        const multiplier = range / allTimeData.length;
+        const topReferrers = allTimeReferrers.map(r => ({ ...r, visits: Math.round(r.visits * multiplier) })).sort((a,b) => b.visits - a.visits);
+        const topLocations = allTimeLocations.map(l => ({ ...l, visits: Math.round(l.visits * multiplier) })).sort((a,b) => b.visits - a.visits);
+        const deviceData = allTimeDevices.map(d => ({ ...d, value: Math.round(d.value * multiplier) }));
+
+        return { chartData, summary, ctr, topReferrers, topLocations, deviceData };
+
+    }, [timeRange]);
+
+    const renderActiveShape = (props: any) => {
+      const RADIAN = Math.PI / 180;
+      const { cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle, fill, payload, percent, value } = props;
+      const sin = Math.sin(-RADIAN * midAngle);
+      const cos = Math.cos(-RADIAN * midAngle);
+      const sx = cx + (outerRadius + 10) * cos;
+      const sy = cy + (outerRadius + 10) * sin;
+      const mx = cx + (outerRadius + 30) * cos;
+      const my = cy + (outerRadius + 30) * sin;
+      const ex = mx + (cos >= 0 ? 1 : -1) * 22;
+      const ey = my;
+      const textAnchor = cos >= 0 ? 'start' : 'end';
+
+      return (
+        <g>
+          <text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>{payload.name}</text>
+          <Sector
+            cx={cx}
+            cy={cy}
+            innerRadius={innerRadius}
+            outerRadius={outerRadius}
+            startAngle={startAngle}
+            endAngle={endAngle}
+            fill={fill}
+          />
+          <Sector
+            cx={cx}
+            cy={cy}
+            startAngle={startAngle}
+            endAngle={endAngle}
+            innerRadius={outerRadius + 6}
+            outerRadius={outerRadius + 10}
+            fill={fill}
+          />
+          <path d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`} stroke={fill} fill="none" />
+          <circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" />
+          <text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} textAnchor={textAnchor} fill={isDarkMode ? '#fff' : '#000'}>{`${value.toLocaleString()}`}</text>
+          <text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} dy={18} textAnchor={textAnchor} fill="#9ca3af">{`(Rate ${(percent * 100).toFixed(2)}%)`}</text>
+        </g>
+      );
+    };
+
+    const [activeIndex, setActiveIndex] = React.useState(0);
+    const onPieEnter = (_: any, index: number) => setActiveIndex(index);
+
+    return (
+        <div className="p-6">
+            <div className="flex justify-between items-center mb-6">
+                <h2 className="text-2xl font-bold text-gray-900 dark:text-white">{t('analytics.page.title')}</h2>
+                <div className="flex items-center gap-1 bg-gray-200 dark:bg-gray-800 p-1 rounded-lg">
+                    {(['7', '28', 'all'] as const).map(range => (
+                        <button key={range} onClick={() => setTimeRange(range)} className={`px-3 py-1 text-sm rounded-md ${timeRange === range ? 'bg-brand-primary text-white' : 'hover:bg-gray-300 dark:hover:bg-gray-700'}`}>
+                           {range === 'all' ? t('analytics.page.all_time') : t('analytics.page.last_days', { range })}
+                        </button>
+                    ))}
+                </div>
+            </div>
+            <div className="space-y-6">
+                 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
+                    <div className="bg-white dark:bg-gray-800 p-6 rounded-lg">
+                        <h3 className="text-gray-500 dark:text-gray-400 text-sm font-medium">{t('analytics.page.views')}</h3>
+                        <p className="text-3xl font-bold text-gray-900 dark:text-white mt-1">{processedAnalytics.summary.views.toLocaleString()}</p>
+                    </div>
+                    <div className="bg-white dark:bg-gray-800 p-6 rounded-lg">
+                        <h3 className="text-gray-500 dark:text-gray-400 text-sm font-medium">{t('analytics.page.clicks')}</h3>
+                        <p className="text-3xl font-bold text-gray-900 dark:text-white mt-1">{processedAnalytics.summary.clicks.toLocaleString()}</p>
+                    </div>
+                    <div className="bg-white dark:bg-gray-800 p-6 rounded-lg">
+                        <h3 className="text-gray-500 dark:text-gray-400 text-sm font-medium">{t('analytics.page.ctr')}</h3>
+                        <p className="text-3xl font-bold text-gray-900 dark:text-white mt-1">{processedAnalytics.ctr}</p>
+                    </div>
+                     <div className="bg-white dark:bg-gray-800 p-6 rounded-lg">
+                        <h3 className="text-gray-500 dark:text-gray-400 text-sm font-medium">{t('analytics.page.unique_visitors')}</h3>
+                        <p className="text-3xl font-bold text-gray-900 dark:text-white mt-1">{processedAnalytics.summary.uniqueVisitors.toLocaleString()}</p>
+                    </div>
+                </div>
+
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg">
+                    <h3 className="font-semibold text-gray-900 dark:text-white mb-4">{t('analytics.page.performance')}</h3>
+                    <ResponsiveContainer width="100%" height={350}>
+                        <LineChart data={processedAnalytics.chartData}>
+                            <CartesianGrid strokeDasharray="3 3" stroke={chartColors.grid} />
+                            <XAxis dataKey="date" stroke={chartColors.text} fontSize={12} />
+                            <YAxis stroke={chartColors.text} fontSize={12} />
+                            <Tooltip contentStyle={{ backgroundColor: chartColors.tooltipBg, border: `1px solid ${chartColors.tooltipBorder}` }} />
+                            <Legend wrapperStyle={{ color: chartColors.text } as any} />
+                            <Line type="monotone" dataKey="views" name={t('analytics.page.views')} stroke="#10b981" strokeWidth={2} activeDot={{ r: 8 }} dot={false} />
+                            <Line type="monotone" dataKey="clicks" name={t('analytics.page.clicks')} stroke="#3b82f6" strokeWidth={2} activeDot={{ r: 8 }} dot={false} />
+                        </LineChart>
+                    </ResponsiveContainer>
+                </div>
+
+                <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
+                    <div className="bg-white dark:bg-gray-800 p-6 rounded-lg lg:col-span-1">
+                        <h3 className="font-semibold text-gray-900 dark:text-white mb-4">{t('analytics.page.device_breakdown')}</h3>
+                         <ResponsiveContainer width="100%" height={300}>
+                            <PieChart>
+                                <Pie
+                                  // @ts-ignore The recharts Pie component's type definition is missing the 'activeIndex' prop.
+                                  activeIndex={activeIndex}
+                                  activeShape={renderActiveShape}
+                                  data={processedAnalytics.deviceData}
+                                  cx="50%"
+                                  cy="50%"
+                                  innerRadius={60}
+                                  outerRadius={80}
+                                  fill="#8884d8"
+                                  dataKey="value"
+                                  onMouseEnter={onPieEnter}
+                                >
+                                {processedAnalytics.deviceData.map((entry, index) => (
+                                    <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
+                                ))}
+                                </Pie>
+                            </PieChart>
+                        </ResponsiveContainer>
+                    </div>
+                    <div className="bg-white dark:bg-gray-800 p-6 rounded-lg lg:col-span-2">
+                        <h3 className="font-semibold text-gray-900 dark:text-white mb-4">{t('analytics.page.top_locations')}</h3>
+                         <ResponsiveContainer width="100%" height={300}>
+                            <BarChart data={processedAnalytics.topLocations} layout="vertical" margin={{ top: 5, right: 20, left: 20, bottom: 5 }}>
+                                <CartesianGrid strokeDasharray="3 3" stroke={chartColors.grid} horizontal={false} />
+                                <XAxis type="number" stroke={chartColors.text} fontSize={12} />
+                                <YAxis type="category" dataKey="country" stroke={chartColors.text} fontSize={12} width={80} />
+                                <Tooltip contentStyle={{ backgroundColor: chartColors.tooltipBg, border: `1px solid ${chartColors.tooltipBorder}` }} cursor={{fill: isDarkMode ? '#374151' : '#f3f4f6'}}/>
+                                <Bar dataKey="visits" fill="#10b981" />
+                            </BarChart>
+                         </ResponsiveContainer>
+                    </div>
+                </div>
+                 <div className="bg-white dark:bg-gray-800 p-6 rounded-lg">
+                    <h3 className="font-semibold text-gray-900 dark:text-white mb-4">{t('analytics.page.top_referrers')}</h3>
+                     <ResponsiveContainer width="100%" height={300}>
+                        <BarChart data={processedAnalytics.topReferrers} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
+                            <CartesianGrid strokeDasharray="3 3" stroke={chartColors.grid} vertical={false} />
+                            <XAxis dataKey="source" stroke={chartColors.text} fontSize={12}/>
+                            <YAxis stroke={chartColors.text} fontSize={12}/>
+                            <Tooltip contentStyle={{ backgroundColor: chartColors.tooltipBg, border: `1px solid ${chartColors.tooltipBorder}` }} cursor={{fill: isDarkMode ? '#374151' : '#f3f4f6'}}/>
+                            <Bar dataKey="visits" fill="#10b981" />
+                        </BarChart>
+                     </ResponsiveContainer>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default PageAnalytics;

+ 125 - 0
components/PageBuilder.tsx

@@ -0,0 +1,125 @@
+
+
+import * as React from 'react';
+import { PageSettings, AIGCVideo, AIGCArticle, FormSubmission } from '../types';
+import LinkEditor from './LinkEditor';
+import DesignEditor from './DesignEditor';
+import { Preview } from './Preview';
+import { Icon } from './ui/Icon';
+import { useTranslation } from '../hooks/useI18n';
+
+interface PageBuilderProps {
+  initialTab: 'link' | 'media' | 'design';
+  pageSettings: PageSettings;
+  onUpdate: (newSettings: PageSettings) => void;
+  aigcVideos: AIGCVideo[];
+  aigcArticles: AIGCArticle[];
+  onOpenShareModal: () => void;
+  onFormSubmit: (submission: FormSubmission) => void;
+}
+
+export const PageBuilder: React.FC<PageBuilderProps> = ({ initialTab, pageSettings, onUpdate, aigcVideos, aigcArticles, onOpenShareModal, onFormSubmit }) => {
+    const { t } = useTranslation();
+    const [previewMode, setPreviewMode] = React.useState<'personal' | 'enterprise'>('personal');
+    const [deviceView, setDeviceView] = React.useState<'pc' | 'mobile'>('pc');
+    const [isPreviewFullScreen, setIsPreviewFullScreen] = React.useState(false);
+
+    const handleBlocksUpdate = (newBlocks: any) => {
+        onUpdate({ ...pageSettings, blocks: newBlocks });
+    };
+
+    const handleDesignUpdate = (newDesign: any) => {
+        onUpdate({ ...pageSettings, design: newDesign });
+    };
+
+    const renderEditor = () => {
+        switch (initialTab) {
+            case 'link':
+                return <LinkEditor blocks={pageSettings.blocks} setBlocks={handleBlocksUpdate} aigcVideos={aigcVideos} aigcArticles={aigcArticles} />
+            case 'media':
+                return <div className="p-8 text-gray-500 dark:text-gray-400">Media content editor will be here.</div>
+            case 'design':
+                return <DesignEditor design={pageSettings.design} setDesign={handleDesignUpdate} />
+            default:
+                return null
+        }
+    };
+    
+    if (isPreviewFullScreen) {
+        return (
+            <div className="fixed inset-0 bg-gray-100 dark:bg-gray-900 z-50 flex flex-col">
+                <div className="flex-shrink-0 bg-white dark:bg-gray-800 p-2 border-b border-gray-200 dark:border-gray-700 flex justify-end items-center">
+                    <button onClick={() => setIsPreviewFullScreen(false)} className="flex items-center gap-2 bg-brand-primary text-white font-semibold py-2 px-4 rounded-lg hover:bg-brand-secondary transition-colors">
+                        <Icon className='h-5 w-5'><path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /></Icon>
+                        {t('page_builder.exit_fullscreen')}
+                    </button>
+                </div>
+                <div className="flex-1 overflow-hidden relative">
+                    {/* FIX: Pass onFormSubmit prop to Preview component to handle form submissions. */}
+                    <Preview
+                        pageSettings={pageSettings}
+                        aigcVideos={aigcVideos}
+                        aigcArticles={aigcArticles}
+                        previewMode={previewMode}
+                        deviceView={deviceView}
+                        onFormSubmit={onFormSubmit}
+                    />
+                </div>
+            </div>
+        );
+    }
+
+    return (
+        <div className="flex h-full">
+            <div className="w-1/2 flex flex-col">
+                <header className="p-6 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
+                    <div>
+                        <h2 className="text-2xl font-bold text-gray-900 dark:text-white">{t('page_builder.title')}</h2>
+                        <p className="text-gray-500 dark:text-gray-400 mt-1">{t('page_builder.url_prompt')} <a href="#" className="text-brand-primary hover:underline">greenpage.ai/johndoe</a></p>
+                    </div>
+                    <button onClick={onOpenShareModal} className="flex items-center gap-2 bg-brand-primary text-white font-semibold py-2 px-4 rounded-lg hover:bg-brand-secondary transition-colors">
+                        <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M8.684 13.342C8.886 12.938 9 12.482 9 12s-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 100-6 3 3 0 000 6z" /></Icon>
+                        {t('page_builder.share')}
+                    </button>
+                </header>
+                <div className="flex-1 overflow-y-auto p-6">
+                    {renderEditor()}
+                </div>
+            </div>
+            <div className="w-1/2 border-l border-gray-200 dark:border-gray-700 flex flex-col">
+                <div className="flex-shrink-0 bg-white dark:bg-gray-800 p-2 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
+                    <div className="flex items-center gap-1 bg-gray-200 dark:bg-gray-700 p-1 rounded-lg">
+                        <button onClick={() => setPreviewMode('personal')} className={`px-3 py-1 text-sm rounded-md ${previewMode === 'personal' ? 'bg-brand-primary text-white' : 'hover:bg-gray-300 dark:hover:bg-gray-600'}`}>{t('page_builder.personal_mode')}</button>
+                        <button onClick={() => setPreviewMode('enterprise')} className={`px-3 py-1 text-sm rounded-md flex items-center gap-2 ${previewMode === 'enterprise' ? 'bg-brand-primary text-white' : 'hover:bg-gray-300 dark:hover:bg-gray-600'}`}>
+                            {t('page_builder.enterprise_mode')}
+                            <Icon className='h-4 w-4 text-yellow-400'><path fillRule="evenodd" d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" clipRule="evenodd" /></Icon>
+                        </button>
+                    </div>
+                    <div className="flex items-center gap-1 bg-gray-200 dark:bg-gray-700 p-1 rounded-lg">
+                        <button onClick={() => setDeviceView('pc')} className={`p-2 rounded-md ${deviceView === 'pc' ? 'bg-brand-primary text-white' : ''}`}> 
+                            <Icon className='h-5 w-5'><path strokeLinecap="round" strokeLinejoin="round" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></Icon>
+                        </button>
+                        <button onClick={() => setDeviceView('mobile')} className={`p-2 rounded-md ${deviceView === 'mobile' ? 'bg-brand-primary text-white' : ''}`}> 
+                            <Icon className='h-5 w-5'><path strokeLinecap="round" strokeLinejoin="round" d="M12 18h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" /></Icon>
+                        </button>
+                         <button onClick={() => setIsPreviewFullScreen(true)} className="p-2 rounded-md">
+                            <Icon className='h-5 w-5'><path strokeLinecap="round" strokeLinejoin="round" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5v-4m0 4h-4m4 0l-5-5" /></Icon>
+                        </button>
+                    </div>
+                </div>
+                <div className="flex-1 overflow-hidden relative">
+                    {/* FIX: Pass onFormSubmit prop to Preview component to handle form submissions. */}
+                    <Preview
+                        key={`${previewMode}-${deviceView}`}
+                        pageSettings={pageSettings}
+                        aigcVideos={aigcVideos}
+                        aigcArticles={aigcArticles}
+                        previewMode={previewMode}
+                        deviceView={deviceView}
+                        onFormSubmit={onFormSubmit}
+                    />
+                </div>
+            </div>
+        </div>
+    );
+};

+ 134 - 0
components/PageManagementModal.tsx

@@ -0,0 +1,134 @@
+import * as React from 'react';
+import { GreenPage } from '../types';
+import { Icon } from './ui/Icon';
+import { useTranslation } from '../hooks/useI18n';
+
+interface PageManagementModalProps {
+    pages: GreenPage[];
+    activePageId: string;
+    onSelectPage: (id: string) => void;
+    onCreatePage: (name: string) => void;
+    onUpdatePage: (id: string, newName: string) => void;
+    onClose: () => void;
+}
+
+const PageManagementModal: React.FC<PageManagementModalProps> = ({ pages, activePageId, onSelectPage, onCreatePage, onUpdatePage, onClose }) => {
+    const { t } = useTranslation();
+    const [isCreating, setIsCreating] = React.useState(false);
+    const [newPageName, setNewPageName] = React.useState('');
+    const [editingPageId, setEditingPageId] = React.useState<string | null>(null);
+    const [editingPageName, setEditingPageName] = React.useState('');
+
+    const handleCreate = () => {
+        if (newPageName.trim()) {
+            onCreatePage(newPageName.trim());
+            setNewPageName('');
+            setIsCreating(false);
+        }
+    };
+    
+    const handleEdit = (page: GreenPage) => {
+        setEditingPageId(page.id);
+        setEditingPageName(page.name);
+    };
+
+    const handleCancelEdit = () => {
+        setEditingPageId(null);
+        setEditingPageName('');
+    };
+
+    const handleSaveEdit = () => {
+        if (editingPageId && editingPageName.trim()) {
+            onUpdatePage(editingPageId, editingPageName.trim());
+            handleCancelEdit();
+        }
+    };
+
+
+    return (
+        <div className="fixed inset-0 bg-black/70 z-50 flex items-center justify-center backdrop-blur-sm" onClick={onClose}>
+            <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl w-full max-w-4xl p-8 border border-gray-200 dark:border-gray-700" onClick={e => e.stopPropagation()}>
+                <div className="flex justify-between items-center mb-6">
+                    <h2 className="text-3xl font-bold text-gray-900 dark:text-white">{t('modal.pages.title')}</h2>
+                    <button onClick={onClose} className="text-gray-400 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">
+                        <Icon className="h-8 w-8">
+                           <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
+                        </Icon>
+                    </button>
+                </div>
+                <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+                    {pages.map(page => {
+                        if (editingPageId === page.id) {
+                            return (
+                                <div key={page.id} className="p-4 rounded-xl bg-gray-100 dark:bg-gray-700 flex flex-col justify-center gap-3">
+                                    <input
+                                        type="text"
+                                        autoFocus
+                                        value={editingPageName}
+                                        onChange={e => setEditingPageName(e.target.value)}
+                                        onKeyPress={e => e.key === 'Enter' && handleSaveEdit()}
+                                        className="w-full bg-white dark:bg-gray-800 p-2 rounded-md border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white"
+                                    />
+                                    <div className="flex gap-2">
+                                        <button onClick={handleCancelEdit} className="flex-1 bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-500 text-gray-800 dark:text-white font-semibold py-2 rounded-md text-sm">{t('cancel')}</button>
+                                        <button onClick={handleSaveEdit} className="flex-1 bg-brand-primary hover:bg-brand-secondary text-white font-semibold py-2 rounded-md text-sm">{t('save')}</button>
+                                    </div>
+                                </div>
+                            );
+                        }
+
+                        return (
+                            <div key={page.id} className={`relative group p-6 rounded-xl text-left bg-gradient-to-br ${page.themeColor} transition-all duration-300 transform hover:scale-105`}>
+                                <button
+                                    onClick={() => onSelectPage(page.id)}
+                                    className={`w-full h-full text-left focus:outline-none focus:ring-4 focus:ring-white/50 rounded-xl ${page.id === activePageId ? 'ring-4 ring-white' : 'ring-2 ring-transparent'}`}
+                                >
+                                    <p className="text-2xl font-bold text-white break-words">{page.name}</p>
+                                    <p className="text-white/70 text-sm mt-1">{`greenpage.ai/${page.slug}`}</p>
+                                </button>
+                                {page.id === activePageId && (
+                                    <div className="absolute top-4 right-4 bg-white/20 rounded-full h-6 w-6 flex items-center justify-center">
+                                        <Icon className="h-4 w-4 text-white">
+                                            <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
+                                        </Icon>
+                                    </div>
+                                )}
+                                 <button onClick={() => handleEdit(page)} title={t('edit')} className="absolute bottom-3 right-3 bg-black/20 text-white/70 hover:bg-black/40 hover:text-white p-1 rounded-full opacity-0 group-hover:opacity-100 transition-opacity">
+                                    <Icon className="h-4 w-4"><path d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.536L16.732 3.732z" /></Icon>
+                                </button>
+                            </div>
+                        )
+                    })}
+
+                    {!isCreating ? (
+                        <button
+                            onClick={() => setIsCreating(true)}
+                            className="p-6 rounded-xl flex flex-col items-center justify-center border-2 border-dashed border-gray-300 dark:border-gray-600 hover:border-brand-primary hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"
+                        >
+                            <Icon className="h-12 w-12 text-gray-400 dark:text-gray-500 mb-2"><path d="M12 4v16m8-8H4" /></Icon>
+                            <span className="font-semibold text-gray-500 dark:text-gray-300">{t('modal.pages.create_new')}</span>
+                        </button>
+                    ) : (
+                        <div className="p-6 rounded-xl bg-gray-100 dark:bg-gray-700 flex flex-col justify-center gap-4">
+                            <input
+                                type="text"
+                                autoFocus
+                                value={newPageName}
+                                onChange={e => setNewPageName(e.target.value)}
+                                onKeyPress={e => e.key === 'Enter' && handleCreate()}
+                                placeholder={`${t('name')}...`}
+                                className="w-full bg-white dark:bg-gray-800 p-3 rounded-md border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white"
+                            />
+                            <div className="flex gap-2">
+                                <button onClick={() => setIsCreating(false)} className="flex-1 bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-500 text-gray-800 dark:text-white font-semibold py-2 rounded-md">{t('cancel')}</button>
+                                <button onClick={handleCreate} className="flex-1 bg-brand-primary hover:bg-brand-secondary text-white font-semibold py-2 rounded-md">{t('add')}</button>
+                            </div>
+                        </div>
+                    )}
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default PageManagementModal;

+ 50 - 0
components/PageSwitcher.tsx

@@ -0,0 +1,50 @@
+import * as React from 'react';
+import { GreenPage } from '../types';
+import { Icon } from './ui/Icon';
+
+interface PageSwitcherProps {
+    pages: GreenPage[];
+    activePageId: string;
+    onSelectPage: (id: string) => void;
+    onCreatePage: () => void;
+}
+
+const PageSwitcher: React.FC<PageSwitcherProps> = ({ pages, activePageId, onSelectPage, onCreatePage }) => {
+    const activePage = pages.find(p => p.id === activePageId);
+
+    return (
+        <div>
+            <div className="flex items-center space-x-3 mb-4">
+                <div className="bg-gradient-to-r from-green-400 to-blue-500 p-2 rounded-lg">
+                    <Icon className="h-8 w-8 text-white">
+                        <path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
+                    </Icon>
+                </div>
+                <h1 className="text-xl font-bold text-white">GreenPage AI</h1>
+            </div>
+            <div className="relative">
+                <select
+                    value={activePageId}
+                    onChange={(e) => onSelectPage(e.target.value)}
+                    className="w-full bg-gray-700 border border-gray-600 text-white text-sm font-semibold rounded-lg focus:ring-brand-primary focus:border-brand-primary block p-2.5 appearance-none"
+                >
+                    {pages.map(page => (
+                        <option key={page.id} value={page.id}>{page.name}</option>
+                    ))}
+                </select>
+                <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-400">
+                    <Icon className="w-4 h-4"><path d="M8 9l4-4 4 4m0 6l-4 4-4-4" /></Icon>
+                </div>
+            </div>
+            <button
+                onClick={onCreatePage}
+                className="mt-3 w-full flex items-center justify-center gap-2 text-sm text-gray-300 bg-gray-700/50 hover:bg-gray-700 py-2 rounded-lg transition-colors"
+            >
+                <Icon className="w-4 h-4"><path d="M12 4v16m8-8H4" /></Icon>
+                Create New Page
+            </button>
+        </div>
+    );
+};
+
+export default PageSwitcher;

+ 1135 - 0
components/Preview.tsx

@@ -0,0 +1,1135 @@
+import * as React from 'react';
+// FIX: Import FormBlock and FormSubmission to handle form rendering and data submission.
+import { PageSettings, ThemeName, Block, SocialPlatform, HeaderBlock, DesignSettings, ButtonShape, ButtonStyle, FontFamily, BackgroundType, LinkBlock, VideoBlock, ImageBlock, EmailBlock, PhoneBlock, BannerSettings, MediaSource, TextBlock, AIGCVideo, SocialBlock, ChatMessage, ChatWidgetSettings, PdfBlock, MapBlock, SideNavSettings, NewsBlock, AIGCArticle, ProductBlock, ChatBlock, EnterpriseInfoBlock, EnterpriseInfoIcon, FormBlock, FormSubmission, AwardBlock, FooterBlock } from '../types';
+import { Icon } from './ui/Icon';
+import ChatWidget from './ChatWidget';
+import { sendChatMessage } from '../services/geminiService';
+import EnterpriseNav from './EnterpriseNav';
+import { format, parseISO } from 'date-fns';
+
+
+// --- STYLING HELPERS ---
+
+const getImageUrl = (source?: MediaSource): string | undefined => {
+    if (!source) return undefined;
+    switch (source.type) {
+        case 'url': return source.value;
+        case 'file': return source.value.previewUrl;
+        default: return undefined;
+    }
+};
+
+var themePresets: Record<ThemeName, { bg: string, text: string, button: string, buttonText: string }> = {
+    light: { bg: '#F3F4F6', text: '#1F2937', button: '#FFFFFF', buttonText: '#1F2937' },
+    dark: { bg: '#1F2937', text: '#FFFFFF', button: '#374151', buttonText: '#FFFFFF' },
+    synthwave: { bg: '#2D3748', text: '#F0D43A', button: '#EC4899', buttonText: '#FFFFFF' },
+    retro: { bg: '#FEF3C7', text: '#854D0E', button: '#FBBF24', buttonText: '#000000' },
+    custom: { bg: '', text: '', button: '', buttonText: '' } // Handled separately
+};
+
+var socialIconPaths: Record<SocialPlatform, React.ReactElement> = {
+    twitter: React.createElement("path", { d: "M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.223.085a4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z" }),
+    instagram: React.createElement("path", { d: "M12 2.163c3.204 0 3.584.012 4.85.07 1.17.055 1.805.248 2.227.415.562.217.96.477 1.382.896.413.42.67.824.896 1.383.167.422.36 1.057.413 2.227.057 1.266.07 1.646.07 4.85s-.013 3.583-.07 4.85c-.054 1.17-.246 1.805-.413 2.227-.228.562-.483.96-.896 1.383-.42.413-.824-.67-1.382-.896-.422-.167-1.057.36-2.227.413-1.266.057-1.646.07-4.85.07s-3.585-.013-4.85-.07c-1.17-.054-1.805-.246-2.227-.413-.562-.228-.96-.483-1.382-.896-.413-.42-.67-.824-.896-1.383-.167-.422-.36-1.057-.413-2.227-.057-1.266-.07-1.646-.07-4.85s.013-3.585.07-4.85c.054-1.17.246 1.805.413-2.227.228.562.483.96.896-1.382.42-.413.824-.67 1.382-.896.422-.167 1.057.36 2.227-.413 1.265-.057 1.646-.07 4.85-.07M12 0C8.74 0 8.333.014 7.053.072 5.775.132 4.905.333 4.14.63c-.784.3-1.2.623-1.77.933-.448.174-.9.34-1.356.59-.446.243-.823.533-1.116.823-.293.29-.58.67-.823 1.116-.25.456-.417.908-.59 1.356-.31.77-.63 1.2-.933 1.77-.298.765-.498 1.635-.558 2.913-.058 1.28-.072 1.687-.072 4.947s.014 3.667.072 4.947c.06 1.278.26 2.148.558 2.913.3.77.623 1.2.933 1.77.174.448.34.9.59 1.356.243.446.533.823.823 1.116.29.293.67.58 1.116.823.456.25.908.417 1.356.59.77.31 1.2.63 1.77.933.765.298 1.635.498 2.913.558 1.28.058 1.687.072 4.947.072s3.667-.014 4.947-.072c1.278-.06 2.148-.26 2.913-.558.77-.3 1.2-.623 1.77-.933.448-.174.9-.34 1.356.59.446-.243.823-.533 1.116.823.293-.29.58-.67.823-1.116.25-.456-.417-.908-.59-1.356.31-.77.63-1.2.933-1.77.298-.765.498 1.635.558-2.913.058-1.28.072-1.687.072-4.947s-.014-3.667-.072-4.947c-.06-1.278-.26-2.148-.558-2.913-.3-.77-.623-1.2-.933-1.77-.174-.448-.34-.9-.59-1.356-.243-.446-.533-.823-.823-1.116-.29-.293-.67-.58-1.116-.823-.456-.25-.908-.417-1.356-.59-.77-.31-1.2-.63-1.77-.933C16.947.333 16.077.132 14.797.072 13.517.014 13.11 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.88 1.44 1.44 0 000-2.88z" }),
+    facebook: React.createElement("path", { d: "M22.675 0H1.325C.593 0 0 .593 0 1.325v21.351C0 23.407.593 24 1.325 24H12.82v-9.294H9.692v-3.622h3.128V8.413c0-3.1 1.893-4.788 4.659-4.788 1.325 0 2.463.099 2.795.143v3.24l-1.918.001c-1.504 0-1.795.715-1.795 1.763v2.313h3.587l-.467 3.622h-3.12V24h6.116c.732 0 1.325-.593 1.325-1.325V1.325C24 .593 23.407 0 22.675 0z" }),
+    linkedin: React.createElement("path", { d: "M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z" }),
+    youtube: React.createElement("path", { d: "M21.582 6.186A2.69 2.69 0 0019.54 4.54a49.123 49.123 0 00-7.54-.38A49.123 49.123 0 004.46 4.54a2.69 2.69 0 00-2.042 1.646A28.024 28.024 0 002.31 12a28.024 28.024 0 00.108 5.814A2.69 2.69 0 004.46 19.46a49.123 4.9123 0 007.54.38 49.123 49.123 0 007.54-.38a2.69 2.69 0 002.042-1.646A28.024 28.024 0 0021.69 12a28.024 28.024 0 00-.108-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" }),
+    tiktok: React.createElement("path", { d: "M12.525.02c1.31-.02 2.61-.01 3.91.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.65 4.31 1.72v3.28c-1.31.08-2.62.06-3.92-.01-1.25-.07-2.47-.62-3.38-1.5-1.2-1.15-1.85-2.76-1.9-4.48v5.72c0 2.55-2.09 4.63-4.66 4.63-2.58 0-4.66-2.09-4.66-4.66s2.08-4.66 4.66-4.66c.33 0 .66.04.98.11v3.24c-1.31-.08-2.62-.06-3.92.01-1.25.07-2.47.62-3.38 1.5-1.2 1.15-1.85 2.76-1.9 4.48v-2.32c0-2.55 2.09-4.63 4.66-4.63 2.58 0 4.66 2.09 4.66 4.66s-2.08 4.66-4.66 4.66c-.33 0-.66-.04-.98-.11C6.25 19.38 5.14 18.25 4.2 17.29v-5.72c0-2.55 2.09-4.63 4.66-4.63.1 0 .2.01.29.02z" }),
+    github: React.createElement("path", { d: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" })
+};
+
+const enterpriseInfoIcons: Record<EnterpriseInfoIcon, React.ReactElement> = {
+    building: <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 21h16.5M4.5 3h15M5.25 3v18m13.5-18v18M9 6.75h6.375a.375.375 0 01.375.375v1.5a.375.375 0 01-.375.375H9a.375.375 0 01-.375-.375v-1.5A.375.375 0 019 6.75zM9 12.75h6.375a.375.375 0 01.375.375v1.5a.375.375 0 01-.375.375H9a.375.375 0 01-.375-.375v-1.5A.375.375 0 019 12.75z" />,
+    bank: <path strokeLinecap="round" strokeLinejoin="round" d="M12 21v-8.25M15.75 21v-8.25M8.25 21v-8.25M3 9l9-6 9 6m-1.5 12V5.625A2.625 2.625 0 0017.25 3H6.75A2.625 2.625 0 004.5 5.625V21" />,
+    money: <path strokeLinecap="round" strokeLinejoin="round" d="M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 013 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-15c-.621 0-1.125-.504-1.125-1.125v-9.75c0-.621.504-1.125 1.125-1.125h.375m15-1.5v-1.5A.75.75 0 0020.25 4.5h-15a.75.75 0 00-.75.75v1.5m15 0a.75.75 0 01-.75.75H4.5a.75.75 0 01-.75-.75m15 0v.375c0 .621-.504 1.125-1.125 1.125h-15.75c-.621 0-1.125-.504-1.125-1.125v-.375m15.75 0v3.472a60.1 60.1 0 01-15.797-2.102c-.727-.198-1.453.342-1.453 1.096V4.5" />,
+    location: React.createElement(React.Fragment, {}, React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 10.5a3 3 0 11-6 0 3 3 0 616 0z" }), React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z" })),
+    calendar: <path strokeLinecap="round" strokeLinejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0h18M12 15h.008v.008H12V15zm0 2.25h.008v.008H12v-.008zm2.25-2.25h.008v.008H14.25v-.008zm0 2.25h.008v.008H14.25v-.008zm2.25-2.25h.008v.008H16.5v-.008zm0 2.25h.008v.008H16.5v-.008zm-4.5-2.25h.008v.008H9.75v-.008zm0 2.25h.008v.008H9.75v-.008z" />,
+    users: <path strokeLinecap="round" strokeLinejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.53-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-4.663M12 12a4.5 4.5 0 100-9 4.5 4.5 0 000 9z" />,
+    lightbulb: <path strokeLinecap="round" strokeLinejoin="round" d="M12 18v-5.25m0 0a3 3 0 00-3-3 3 3 0 00-3 3 3 3 0 003 3zm0 0c-1.125 0-2.25.5-2.25 1.5V18m0-5.25c1.125 0 2.25.5 2.25 1.5V18M12 3a9 9 0 100 18 9 9 0 000-18z" />,
+};
+
+var getFontClass = function(font: FontFamily) {
+    switch(font) {
+        case 'serif': return 'font-serif';
+        case 'mono': return 'font-mono';
+        case 'sans': default: return 'font-sans';
+    }
+};
+
+const getFontFamilyValue = (font: FontFamily) => {
+    switch (font) {
+        case 'serif': return 'serif';
+        case 'mono': return 'monospace';
+        case 'sans': default: return 'sans-serif';
+    }
+}
+
+var getButtonClasses = function(shape: ButtonShape, style: ButtonStyle) {
+    var classes = 'w-full p-3 font-semibold transition-transform transform hover:scale-105 shadow-md flex items-center justify-center gap-4 text-center ';
+    if(shape === 'pill') classes += 'rounded-full';
+    else if(shape === 'square') classes += 'rounded-none';
+    else classes += 'rounded-lg';
+    if(style === 'outline') classes += ' bg-transparent border-2';
+    return classes;
+};
+
+var getBackgroundStyle = function(type: BackgroundType, value: string) {
+    if (type === 'image') return { backgroundImage: "url(" + value + ")", backgroundSize: 'cover', backgroundPosition: 'center' };
+    if (type === 'color') return { backgroundColor: value };
+    return {}; // for gradient, we use a class
+};
+
+var getBackgroundClass = function(type: BackgroundType, value: string) {
+    return type === 'gradient' ? value : '';
+};
+
+var getBannerStyle = function(settings: BannerSettings): React.CSSProperties {
+    var style: React.CSSProperties = { height: settings.height + "px" };
+    const imageUrl = getImageUrl(settings.imageSource);
+    if (settings.type === 'image' && imageUrl) {
+        style.backgroundImage = "url(" + imageUrl + ")";
+        style.backgroundSize = 'cover';
+        style.backgroundPosition = 'center';
+    }
+    if (settings.type === 'color') {
+       style = Object.assign({}, style, { backgroundColor: settings.value });
+    }
+    return style;
+};
+
+var getBannerClass = function(settings: BannerSettings) {
+    var classes = '';
+    if (settings.type === 'gradient') {
+        classes += " " + settings.value;
+    }
+    return classes;
+};
+
+// --- Child Components ---
+
+var ActionModal: React.FC<{
+    data: { type: 'email' | 'phone'; value: string; label: string; } | null;
+    onClose: () => void;
+}> = function(props) {
+    var data = props.data, onClose = props.onClose;
+    if (!data) return null;
+    
+    var isEmail = data.type === 'email';
+    var actionText = isEmail ? 'Open Mail App' : 'Call';
+    var actionHref = isEmail ? "mailto:" + data.value : "tel:" + data.value;
+
+    var handleCopy = function() {
+        navigator.clipboard.writeText(data.value).then(function() {
+            alert((isEmail ? 'Email' : 'Number') + " copied to clipboard!");
+            onClose();
+        });
+    };
+
+    return (
+        React.createElement("div", { className: "absolute inset-0 bg-black/50 flex items-center justify-center z-[100]", onClick: () => onClose() } as any,
+            React.createElement("div", { className: "bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-xs p-6 text-center", onClick: function(e) { e.stopPropagation(); } } as any,
+                React.createElement("h3", { className: "font-bold text-lg mb-2 text-gray-900 dark:text-white" }, data.label),
+                React.createElement("p", { className: "text-gray-600 dark:text-gray-300 mb-6 break-words" }, data.value),
+                React.createElement("div", { className: "space-y-3" },
+                    React.createElement("button", { onClick: handleCopy, className: "w-full bg-brand-primary text-white font-semibold py-3 rounded-lg" }, "Copy " + (isEmail ? 'Email' : 'Number')),
+                    React.createElement("a", { href: actionHref, className: "block w-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-semibold py-3 rounded-lg" }, actionText),
+                    React.createElement("button", { onClick: () => onClose(), className: "mt-4 w-full text-sm text-gray-500 dark:text-gray-400" } as any, "Cancel")
+                )
+            )
+        )
+    );
+};
+
+
+var PersonalChatOverlay: React.FC<{ isOpen: boolean; onClose: () => void; settings: ChatWidgetSettings }> = function(props) {
+    var isOpen = props.isOpen, onClose = props.onClose, settings = props.settings;
+    var messagesState = React.useState<ChatMessage[]>([]);
+    var messages = messagesState[0];
+    var setMessages = messagesState[1];
+    var userInputState = React.useState('');
+    var userInput = userInputState[0];
+    var setUserInput = userInputState[1];
+    var isLoadingState = React.useState(false);
+    var isLoading = isLoadingState[0];
+    var setIsLoading = isLoadingState[1];
+    var isRecordingState = React.useState(false);
+    var isRecording = isRecordingState[0];
+    var setIsRecording = isRecordingState[1];
+
+    var messagesEndRef = React.useRef<HTMLDivElement>(null);
+    var recognitionRef = React.useRef<any>(null);
+
+    React.useEffect(function() {
+        // @ts-ignore
+        var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
+        if (SpeechRecognition) {
+            recognitionRef.current = new SpeechRecognition();
+            recognitionRef.current.continuous = false;
+            recognitionRef.current.lang = 'en-US';
+            recognitionRef.current.interimResults = false;
+            recognitionRef.current.maxAlternatives = 1;
+
+            recognitionRef.current.onresult = function(event: any) {
+                var transcript = event.results[0][0].transcript;
+                setUserInput(transcript);
+                setIsRecording(false);
+            };
+
+            recognitionRef.current.onerror = function(event: any) {
+                console.error('Speech recognition error:', event.error);
+                setIsRecording(false);
+            };
+            
+            recognitionRef.current.onend = function() {
+                setIsRecording(false);
+            };
+        }
+    }, []);
+
+    React.useEffect(function() {
+        if (isOpen && messages.length === 0) {
+            var initialMessage: ChatMessage = { id: "msg-ai-" + Date.now(), sender: 'ai', text: 'Hello! How can I help you today?' };
+            setMessages([initialMessage]);
+        }
+    }, [isOpen, messages.length]);
+
+    React.useEffect(function() {
+        if (messagesEndRef.current) {
+            messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
+        }
+    }, [messages, isLoading]);
+
+    var handleSendMessage = React.useCallback(function() {
+        if (!userInput.trim()) return;
+        var userMessage: ChatMessage = { id: "msg-user-" + Date.now(), sender: 'user', text: userInput };
+        setMessages(function(prev) { return prev.concat([userMessage]); });
+        var messageToSend = userInput;
+        setUserInput('');
+        setIsLoading(true);
+
+        sendChatMessage(messageToSend).then(function(aiResponse) {
+            setMessages(function(prev) { return prev.concat([aiResponse]); });
+            setIsLoading(false);
+        });
+    }, [userInput]);
+    
+    var toggleRecording = function() {
+        if (!recognitionRef.current) {
+            alert("Speech recognition is not supported by your browser.");
+            return;
+        }
+        if (isRecording) {
+            recognitionRef.current.stop();
+        } else {
+            recognitionRef.current.start();
+        }
+        setIsRecording(!isRecording);
+    };
+
+    return React.createElement("div", {
+        className: "absolute inset-0 bg-black/30 z-50 transition-opacity duration-300 " + (isOpen ? "opacity-100" : "opacity-0 pointer-events-none"),
+        onClick: function() { return onClose(); }
+    } as any,
+        React.createElement("div", {
+            className: "absolute bottom-0 left-0 right-0 h-3/4 bg-white dark:bg-gray-800 rounded-t-2xl shadow-2xl flex flex-col transition-transform duration-300 " + (isOpen ? "translate-y-0" : "translate-y-full"),
+            style: { backgroundColor: settings.panelBackgroundColor },
+            onClick: function(e) { e.stopPropagation(); }
+        } as any,
+            React.createElement("div", { className: "flex-shrink-0 p-4 flex justify-between items-center rounded-t-2xl", style: { backgroundColor: settings.headerBackgroundColor, color: settings.headerTextColor } } as any,
+                React.createElement("h3", { className: "font-bold text-lg" }, "AI Assistant"),
+                React.createElement("button", { onClick: () => onClose(), style: { color: settings.headerTextColor } } as any,
+                    React.createElement(Icon, { className: "h-6 w-6", children: React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })
+                )
+            ),
+            React.createElement("div", { className: "flex-1 p-4 overflow-y-auto space-y-4" } as any,
+                 messages.map(function(msg) {
+                    return React.createElement("div", { key: msg.id, className: "flex items-start gap-3 " + (msg.sender === 'user' ? 'justify-end' : '') },
+                        msg.sender === 'ai' && React.createElement("div", { className: "w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0", style: { backgroundColor: settings.aiMessageBackgroundColor, color: settings.aiMessageTextColor } }, "🤖"),
+                        React.createElement("div", { className: "max-w-xs p-3 rounded-lg text-sm", style: { backgroundColor: msg.sender === 'user' ? settings.userMessageBackgroundColor : settings.aiMessageBackgroundColor, color: msg.sender === 'user' ? settings.userMessageTextColor : settings.aiMessageTextColor, borderRadius: msg.sender === 'user' ? '1rem 1rem 0.25rem 1rem' : '1rem 1rem 1rem 0.25rem' } } as any,
+                            React.createElement("p", null, msg.text)
+                        )
+                    );
+                }),
+                 isLoading && React.createElement("div", { className: "flex items-start gap-3" },
+                     React.createElement("div", { className: "w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0", style: { backgroundColor: settings.aiMessageBackgroundColor } }, "🤖"),
+                     React.createElement("div", { className: "max-w-xs p-3 rounded-lg", style: { backgroundColor: settings.aiMessageBackgroundColor } } as any,
+                        React.createElement("div", { className: "flex items-center space-x-1" },
+                             React.createElement("span", { className: "w-2 h-2 rounded-full animate-pulse delay-0", style: { backgroundColor: settings.aiMessageTextColor } }),
+                             React.createElement("span", { className: "w-2 h-2 rounded-full animate-pulse delay-150", style: { backgroundColor: settings.aiMessageTextColor } }),
+                             React.createElement("span", { className: "w-2 h-2 rounded-full animate-pulse delay-300", style: { backgroundColor: settings.aiMessageTextColor } })
+                        )
+                    )
+                 ),
+                 React.createElement("div", { ref: messagesEndRef })
+            ),
+            React.createElement("div", { className: "flex-shrink-0 p-4 border-t border-gray-200 dark:border-gray-700 flex items-center gap-3" } as any,
+                React.createElement("button", { onClick: toggleRecording, className: "p-2 rounded-full " + (isRecording ? "bg-red-500 animate-pulse" : "bg-gray-200 dark:bg-gray-700") } as any,
+                    React.createElement(Icon, { className: "h-6 w-6 text-gray-800 dark:text-white", children: React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" }) })
+                ),
+                React.createElement("input", { type: "text", value: userInput, onChange: function(e) { return setUserInput(e.target.value); }, onKeyPress: function(e) { return e.key === 'Enter' && !isLoading && handleSendMessage(); }, placeholder: "Ask me anything...", className: "flex-1 bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white p-2 rounded-md border border-gray-300 dark:border-gray-600 focus:ring-2 focus:ring-brand-primary focus:outline-none", disabled: isLoading } as any),
+                React.createElement("button", { onClick: handleSendMessage, disabled: isLoading, className: "p-2 rounded-full text-white disabled:opacity-50", style: { backgroundColor: settings.headerBackgroundColor } } as any,
+                    React.createElement(Icon, { className: "h-6 w-6", children: React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 10l7-7m0 0l7 7m-7-7v18" }) })
+                )
+            )
+        )
+    );
+};
+
+
+// --- MAIN PREVIEW COMPONENT ---
+
+interface PreviewProps {
+    pageSettings: PageSettings;
+    aigcVideos: AIGCVideo[];
+    aigcArticles: AIGCArticle[];
+    previewMode: 'personal' | 'enterprise';
+    deviceView: 'pc' | 'mobile';
+    onFormSubmit: (submission: FormSubmission) => void;
+}
+
+const FormBlockComponent: React.FC<{
+    block: FormBlock;
+    theme: { text: string; button: string; buttonText: string };
+    onFormSubmit: (submission: FormSubmission) => void;
+}> = ({ block, theme, onFormSubmit }) => {
+    const [formData, setFormData] = React.useState<Record<string, string>>({});
+    const inputClasses = "w-full p-2 rounded-md bg-white/10 border border-white/20 focus:outline-none focus:ring-1 focus:ring-white";
+
+    const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
+        setFormData({
+            ...formData,
+            [e.target.name]: e.target.value,
+        });
+    };
+
+    const handleSubmit = (e: React.FormEvent) => {
+        e.preventDefault();
+        const submission: FormSubmission = {
+            id: `sub_${Date.now()}`,
+            formId: block.id,
+            visitorId: 'visitor_123', // mock visitor id
+            timestamp: new Date().toISOString(),
+            data: formData,
+        };
+        onFormSubmit(submission);
+        // Reset form
+        const emptyData: Record<string, string> = {};
+        block.fields.filter(f => f.enabled).forEach(f => emptyData[f.id] = '');
+        if (block.purposeOptions.length > 0) {
+            emptyData['purpose'] = block.purposeOptions[0].label;
+        }
+        setFormData(emptyData);
+    };
+
+    React.useEffect(() => {
+        const initialData: Record<string, string> = {};
+        if (block.purposeOptions.length > 0) {
+            initialData['purpose'] = block.purposeOptions[0].label;
+        }
+        setFormData(initialData);
+    }, [block.purposeOptions]);
+
+    return (
+        <div style={{ backgroundColor: 'rgba(128,128,128,0.1)', color: theme.text }} className="p-6 rounded-lg">
+            <h3 className="text-xl font-bold mb-2 text-center" style={{ color: theme.text }}>{block.title}</h3>
+            <p className="text-sm opacity-80 mb-6 text-center" style={{ color: theme.text }}>{block.description}</p>
+            <form onSubmit={handleSubmit} className="space-y-4">
+                {block.fields.filter(f => f.enabled).map(field => (
+                    <div key={field.id} className="flex items-center gap-4">
+                        <label htmlFor={field.id} className="w-1/3 text-sm font-medium" style={{ color: theme.text }}>
+                            {field.label} {field.required && <span className="text-red-500">*</span>}
+                        </label>
+                        <input
+                            type={field.id === 'email' ? 'email' : field.id === 'phone' ? 'tel' : 'text'}
+                            id={field.id}
+                            name={field.id}
+                            required={field.required}
+                            onChange={handleInputChange}
+                            value={formData[field.id] || ''}
+                            className={`${inputClasses} flex-1`}
+                            style={{color: theme.text, backgroundColor: 'rgba(255,255,255,0.1)'}}
+                        />
+                    </div>
+                ))}
+                {block.purposeOptions.length > 0 && (
+                    <div className="flex items-center gap-4">
+                        <label htmlFor="purpose" className="w-1/3 text-sm font-medium" style={{ color: theme.text }}>
+                            Purpose
+                        </label>
+                        <select
+                            id="purpose"
+                            name="purpose"
+                            onChange={handleInputChange}
+                            value={formData['purpose'] || block.purposeOptions[0].label}
+                            className={`${inputClasses} flex-1`}
+                            style={{color: theme.text, backgroundColor: 'rgba(255,255,255,0.1)'}}
+                        >
+                            {block.purposeOptions.map(opt => <option key={opt.id} value={opt.label} style={{backgroundColor: '#374151'}}>{opt.label}</option>)}
+                        </select>
+                    </div>
+                )}
+                <button type="submit" style={{ backgroundColor: theme.button, color: theme.buttonText }} className="w-full p-3 font-semibold rounded-lg">
+                    {block.submitButtonText}
+                </button>
+            </form>
+        </div>
+    );
+};
+
+
+export var Preview: React.FC<PreviewProps> = function(props) {
+    var { pageSettings, aigcVideos, aigcArticles, previewMode, deviceView, onFormSubmit } = props;
+    var design = pageSettings.design;
+    var blocks = pageSettings.blocks;
+    var theme = design.theme === 'custom' 
+        ? { 
+            bg: design.customThemeColors.background, 
+            text: design.customThemeColors.text, 
+            button: design.customThemeColors.button, 
+            buttonText: design.customThemeColors.buttonText 
+          } 
+        : themePresets[design.theme];
+    
+    if (design.fontColor) {
+        theme.text = design.fontColor;
+    }
+
+    var actionModalDataState = React.useState<{ type: 'email' | 'phone'; value: string; label: string; } | null>(null);
+    var actionModalData = actionModalDataState[0];
+    var setActionModalData = actionModalDataState[1];
+    
+    var isChatOpenState = React.useState(false);
+    var isChatOpen = isChatOpenState[0];
+    var setIsChatOpen = isChatOpenState[1];
+    
+    var scrollContainerRef = React.useRef<HTMLDivElement>(null);
+    const chatBlock = blocks.find(b => b.type === 'chat' && b.visible) as ChatBlock | undefined;
+
+    const handleViewGeneratedArticle = (article: AIGCArticle) => {
+        const isDark = design.theme === 'dark' || design.theme === 'synthwave';
+        const styles = `
+            body { 
+                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; 
+                line-height: 1.6; 
+                color: ${isDark ? '#e5e7eb' : '#1f2937'}; 
+                background-color: ${isDark ? '#111827' : '#ffffff'};
+                max-width: 800px; 
+                margin: 40px auto; 
+                padding: 20px; 
+            }
+            .prose { max-width: 65ch; margin: 0 auto; }
+            .prose h1, .prose h2, .prose h3 { color: inherit; }
+            .prose a { color: #10b981; }
+            .prose img { max-width: 100%; height: auto; border-radius: 8px; }
+            .prose .lead { font-size: 1.25em; color: ${isDark ? '#9ca3af' : '#6b7280'}; }
+        `;
+    
+        const articleHtml = `
+          <!DOCTYPE html>
+          <html lang="en">
+          <head>
+            <meta charset="UTF-8">
+            <meta name="viewport" content="width=device-width, initial-scale=1.0">
+            <title>${article.title}</title>
+            <style>${styles}</style>
+          </head>
+          <body>
+            <article class="prose">
+              <h1>${article.title}</h1>
+              <p class="lead">${article.summary}</p>
+              <hr>
+              <div>${article.content}</div>
+            </article>
+          </body>
+          </html>
+        `;
+        const newWindow = window.open();
+        if (newWindow) {
+            newWindow.document.write(articleHtml);
+            newWindow.document.close();
+        }
+    };
+
+    React.useEffect(function() {
+        if (scrollContainerRef.current) {
+            scrollContainerRef.current.scrollTop = 0;
+        }
+    }, [previewMode, deviceView]);
+
+    React.useEffect(function() {
+        if (isChatOpen) {
+            document.body.style.overflow = 'hidden';
+        } else {
+            document.body.style.overflow = 'unset';
+        }
+        return function() {
+            document.body.style.overflow = 'unset';
+        };
+    }, [isChatOpen]);
+
+    var visibleBlocks = blocks.filter(function(b) { return b.visible; });
+    
+    var renderBlock = function(block: Block) {
+        var isMobile = deviceView === 'mobile';
+        const lineClampStyle = (lines: number): React.CSSProperties => ({
+            display: '-webkit-box',
+            WebkitLineClamp: lines,
+            WebkitBoxOrient: 'vertical',
+            overflow: 'hidden',
+            textOverflow: 'ellipsis',
+        });
+
+        switch (block.type) {
+            case 'header':
+                var headerBlock = block as HeaderBlock;
+                return React.createElement("h2", { className: "text-2xl font-bold pt-4", style: { color: theme.text, textAlign: headerBlock.titleAlignment } }, headerBlock.text);
+            
+            case 'link':
+                var linkBlock = block as LinkBlock;
+                return (
+                    React.createElement("a", { href: linkBlock.url, target: "_blank", rel: "noopener noreferrer", className: getButtonClasses(design.buttonShape, design.buttonStyle), style: { backgroundColor: design.buttonStyle === 'filled' ? theme.button : 'transparent', color: theme.buttonText, borderColor: theme.buttonText } },
+                        linkBlock.iconUrl && React.createElement("img", { src: linkBlock.iconUrl, alt: "", className: "w-6 h-6 rounded-md" }),
+                        React.createElement("span", { className: "flex-1" }, linkBlock.title)
+                    )
+                );
+            case 'chat': {
+                const currentChatBlock = block as ChatBlock;
+                if (previewMode === 'personal' && currentChatBlock.layout !== 'button') return null;
+                if (currentChatBlock.layout === 'widget') return null;
+    
+                const chatButtonStyle: React.CSSProperties = {};
+                if (design.buttonStyle === 'filled') {
+                    chatButtonStyle.backgroundColor = '#10b981';
+                    chatButtonStyle.color = '#ffffff';
+                } else { 
+                    chatButtonStyle.backgroundColor = 'transparent';
+                    chatButtonStyle.color = '#10b981';
+                    chatButtonStyle.borderColor = '#10b981';
+                }
+
+                return React.createElement("button", {
+                    onClick: () => setIsChatOpen(true),
+                    className: getButtonClasses(design.buttonShape, design.buttonStyle) + ' h-16',
+                    style: chatButtonStyle
+                } as any, "Chat with me");
+            }
+
+            case 'social':
+                 var socialBlock = block as SocialBlock;
+                 return (
+                     React.createElement("div", { className: "flex justify-center gap-4" },
+                         socialBlock.links.map(function(link) {
+                             var icon = socialIconPaths[link.platform];
+                             return React.createElement("a", { key: link.id, href: link.url, target: "_blank", rel: "noopener noreferrer", style: { color: theme.text }, className: "hover:opacity-75" },
+                                 React.createElement("svg", {
+                                    className: "h-8 w-8",
+                                    fill: "currentColor",
+                                    viewBox: "0 0 24 24"
+                                 }, icon)
+                             );
+                         })
+                     )
+                 );
+
+            case 'video':
+                var videoBlock = block as VideoBlock;
+                var videoLayoutClass = videoBlock.layout === 'grid' ? 'grid grid-cols-2 gap-2' : 'space-y-2';
+                return (
+                    React.createElement("div", { className: videoLayoutClass },
+                        videoBlock.sources.map(function(source, index) {
+                            var video = source.type === 'aigc' ? aigcVideos.filter(function(v) { return v.id === source.videoId; })[0] : null;
+                            var url = source.type === 'url' ? source.value : (video && video.videoUrl);
+                            if (!url) return null;
+                            return React.createElement("video", { key: index, src: url, controls: true, className: "w-full rounded-lg aspect-video" });
+                        })
+                    )
+                );
+
+            case 'image':
+                var imageBlock = block as ImageBlock;
+                var imageLayoutClass = imageBlock.layout === 'grid' ? 'grid grid-cols-2 gap-2' : 'space-y-2';
+                return (
+                     React.createElement("div", { className: imageLayoutClass },
+                        imageBlock.sources.map(function(source, index) {
+                            var url = source.type === 'url' ? source.value : (source.type === 'file' ? source.value.previewUrl : '');
+                            return React.createElement("img", { key: index, src: url, alt: "", className: "w-full rounded-lg object-cover aspect-video" });
+                        })
+                    )
+                );
+
+            case 'text':
+                var textBlock = block as TextBlock;
+                return (
+                    React.createElement("p", { style: {
+                        color: textBlock.fontColor || theme.text,
+                        fontSize: textBlock.fontSize,
+                        textAlign: textBlock.textAlign,
+                        fontWeight: textBlock.isBold ? 'bold' : 'normal',
+                        fontStyle: textBlock.isItalic ? 'italic' : 'normal'
+                    } },
+                        textBlock.content
+                    )
+                );
+            case 'enterprise_info': {
+                const infoBlock = block as EnterpriseInfoBlock;
+                if (!infoBlock.items || infoBlock.items.length === 0) return null;
+
+                const isCentered = infoBlock.alignment === 'center';
+                const containerClasses = isCentered ? 'flex flex-col items-center text-center gap-6' : 'space-y-4';
+                const itemClasses = isCentered ? 'flex flex-col items-center gap-2' : 'flex items-start gap-4';
+
+                return (
+                    React.createElement("div", { className: `p-4 rounded-lg`, style: { backgroundColor: 'rgba(128,128,128,0.1)' } },
+                        React.createElement("div", { className: containerClasses },
+                            infoBlock.items.map(item => (
+                                React.createElement("div", { key: item.id, className: itemClasses },
+                                    React.createElement("div", { className: `flex-shrink-0 w-10 h-10 flex items-center justify-center rounded-lg`, style: { backgroundColor: 'rgba(128,128,128,0.1)', color: theme.text } },
+                                        React.createElement(Icon, { className: "h-6 w-6", children: enterpriseInfoIcons[item.icon] })
+                                    ),
+                                    React.createElement("div", { className: "min-w-0" },
+                                        React.createElement("p", { className: "text-sm font-semibold uppercase tracking-wider", style: { color: theme.text, opacity: 0.7 } }, item.label),
+                                        React.createElement("p", { className: "font-semibold text-base break-words", style: { color: theme.text } }, item.value)
+                                    )
+                                )
+                            ))
+                        )
+                    )
+                );
+            }
+            case 'news': {
+                const newsBlock = block as NewsBlock;
+                const isGrid = newsBlock.layout === 'grid';
+                const containerClass = isGrid ? 'grid grid-cols-1 md:grid-cols-2 gap-4' : 'space-y-4';
+                
+                const extractFirstImageUrl = (htmlContent: string): string | null => {
+                    if (typeof DOMParser === 'undefined') return null;
+                    try {
+                        const doc = new DOMParser().parseFromString(htmlContent, 'text/html');
+                        const img = doc.querySelector('img');
+                        return img ? img.src : null;
+                    } catch (e) {
+                        console.error("Error parsing HTML for image extraction", e);
+                        return null;
+                    }
+                };
+            
+                if (newsBlock.source === 'custom') {
+                    const customItems = newsBlock.customItems || [];
+                    if (customItems.length === 0) return null;
+            
+                    return React.createElement("div", { className: containerClass },
+                        customItems.map(item => (
+                            React.createElement("a", {
+                                key: item.id,
+                                href: item.url,
+                                target: "_blank",
+                                rel: "noopener noreferrer",
+                                className: `block w-full text-left p-4 rounded-lg transition-shadow hover:shadow-lg bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700`
+                            },
+                                React.createElement("h3", { className: "font-bold text-lg", style: { color: theme.text } }, item.title),
+                                React.createElement("p", { className: "text-sm text-brand-primary truncate mt-1" }, item.url),
+                                React.createElement("p", { className: "text-sm mt-2", style: { color: theme.text, opacity: 0.9 } }, item.summary)
+                            )
+                        ))
+                    );
+                }
+                
+                const articlesToShow = (newsBlock.articleIds || [])
+                    .map(id => aigcArticles.find(a => a.id === id))
+                    .filter((a): a is AIGCArticle => !!a);
+            
+                if (articlesToShow.length === 0) return null;
+            
+                return React.createElement("div", { className: containerClass },
+                    articlesToShow.map(article => {
+                        const imageUrl = extractFirstImageUrl(article.content);
+                        const hasUrl = article.sourceType === 'url' && article.sourceUrl;
+                        
+                        const linkProps: any = {
+                            key: article.id,
+                            className: `block w-full text-left rounded-lg transition-shadow hover:shadow-lg bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 overflow-hidden`,
+                        };
+
+                        if (hasUrl) {
+                            linkProps.href = article.sourceUrl;
+                            linkProps.target = "_blank";
+                            linkProps.rel = "noopener noreferrer";
+                        } else {
+                            linkProps.href = "#";
+                            linkProps.onClick = (e: React.MouseEvent) => {
+                                e.preventDefault();
+                                handleViewGeneratedArticle(article);
+                            };
+                        }
+                        
+                        const articleContent = isGrid ? (
+                            React.createElement(React.Fragment, null,
+                                imageUrl && React.createElement("img", { src: imageUrl, alt: article.title, className: "w-full h-32 object-cover" }),
+                                React.createElement("div", { className: "p-4" },
+                                    React.createElement("h3", { className: "font-bold text-lg", style: { color: theme.text } }, article.title),
+                                    React.createElement("p", { className: "text-sm text-gray-600 dark:text-gray-400 mt-1" }, format(parseISO(article.publicationDate), 'MMM d, yyyy')),
+                                    React.createElement("p", { className: "text-sm mt-2", style: { color: theme.text, opacity: 0.9 } }, article.summary)
+                                )
+                            )
+                        ) : ( // List View
+                            React.createElement("div", { className: "flex items-start gap-4 p-4" },
+                                imageUrl && React.createElement("img", { src: imageUrl, alt: article.title, className: "w-40 h-24 object-cover rounded-md flex-shrink-0" }),
+                                React.createElement("div", { className: "flex-1 min-w-0" },
+                                    React.createElement("h3", { className: "font-bold text-lg", style: { ...{ color: theme.text }, ...lineClampStyle(2) } }, article.title),
+                                    React.createElement("p", { className: "text-sm text-gray-600 dark:text-gray-400 mt-1" }, format(parseISO(article.publicationDate), 'MMM d, yyyy')),
+                                    React.createElement("p", { className: "text-sm mt-2", style: { ...{ color: theme.text, opacity: 0.9 }, ...lineClampStyle(2) } }, article.summary)
+                                )
+                            )
+                        );
+
+                        return React.createElement("a", linkProps, articleContent);
+                    })
+                );
+            }
+
+            case 'email':
+            case 'phone':
+                var actionBlock = block as EmailBlock | PhoneBlock;
+
+                var iconPath = actionBlock.type === 'email' ?
+                    React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) :
+                    React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" });
+
+                var buttonContent = React.createElement("div", {
+                    className: "flex flex-col items-center justify-center text-center"
+                },
+                    React.createElement("div", { className: "flex items-center gap-2" },
+                        React.createElement(Icon, { className: "h-5 w-5 flex-shrink-0", children: iconPath }),
+                        React.createElement("span", null, actionBlock.label)
+                    ),
+                    actionBlock.displayMode === 'labelAndValue' && React.createElement("span", {
+                        className: "text-xs opacity-80 mt-1 font-normal"
+                    }, actionBlock.type === 'email' ? actionBlock.email : actionBlock.phone)
+                );
+
+                 return React.createElement("button", {
+                    onClick: function() { return setActionModalData({ type: actionBlock.type, value: actionBlock.type === 'email' ? actionBlock.email : actionBlock.phone, label: actionBlock.label }); },
+                    className: getButtonClasses(design.buttonShape, design.buttonStyle),
+                    style: {
+                        backgroundColor: design.buttonStyle === 'filled' ? theme.button : 'transparent',
+                        color: theme.buttonText,
+                        borderColor: theme.buttonText
+                    }
+                } as any, buttonContent);
+            
+            case 'map':
+                var mapBlock = block as MapBlock;
+                var encodedAddress = encodeURIComponent(mapBlock.address);
+                var displayStyle = mapBlock.displayStyle || 'interactiveMap';
+                
+                if (displayStyle === 'imageOverlay') {
+                    var imageUrl = getImageUrl(mapBlock.backgroundImageSource);
+                    return React.createElement("a", {
+                        href: "https://www.google.com/maps/search/?api=1&query=" + encodedAddress,
+                        target: "_blank",
+                        rel: "noopener noreferrer",
+                        className: "block rounded-lg overflow-hidden relative h-48 group"
+                    },
+                        React.createElement("div", {
+                            className: "absolute inset-0 bg-cover bg-center transition-transform duration-300 group-hover:scale-105",
+                            style: { backgroundImage: "url(" + (imageUrl || ("https://picsum.photos/seed/mapbg-" + mapBlock.id + "/600/400")) + ")" }
+                        }),
+                        React.createElement("div", {
+                            className: "absolute inset-0 bg-black/60 flex items-end p-4"
+                        },
+                            React.createElement("p", { className: "text-white font-semibold" }, mapBlock.address)
+                        )
+                    );
+                }
+                
+                return React.createElement("a", {
+                    href: "https://www.google.com/maps/search/?api=1&query=" + encodedAddress,
+                    target: "_blank",
+                    rel: "noopener noreferrer",
+                    className: "block rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 hover:border-brand-primary transition-colors"
+                },
+                    React.createElement("div", {
+                      className: "relative h-48 bg-cover bg-center",
+                      style: { backgroundImage: "url(https://picsum.photos/seed/map" + mapBlock.id + "/600/400)" }
+                    },
+                      React.createElement("div", { className: "absolute inset-0 bg-black/50 flex items-end p-4" },
+                        React.createElement("p", { className: "text-white font-semibold" }, mapBlock.address)
+                      )
+                    )
+                );
+                
+            case 'pdf':
+                var pdfBlock = block as PdfBlock;
+                var source = pdfBlock.source;
+                var url: string | undefined;
+                var name: string | undefined;
+                var previewUrl: string | undefined;
+
+                if (source.type === 'file') {
+                    url = '#';
+                    name = source.value.name;
+                    previewUrl = source.value.previewUrl;
+                } else if (source.type === 'url') {
+                    url = source.value;
+                    try {
+                       var urlParts = new URL(url).pathname.split('/');
+                       name = urlParts[urlParts.length - 1] || "Document.pdf";
+                    } catch (e) {
+                       name = "Document.pdf";
+                    }
+                    previewUrl = 'https://api.iconify.design/mdi:file-pdf-box.svg?color=%23' + (theme.text.replace('#',''));
+                } else {
+                    return null; 
+                }
+                
+                var isFile = source.type === 'file';
+
+                var content = React.createElement("div", { className: "flex items-center gap-4" },
+                    React.createElement("img", { src: previewUrl, className: "w-16 h-20 object-contain rounded-md flex-shrink-0 bg-gray-200 dark:bg-gray-600 p-2" }),
+                    React.createElement("div", { className: "flex-1 min-w-0" },
+                        React.createElement("p", { className: "font-semibold truncate", style: { color: theme.text } }, name),
+                        !isFile && url && React.createElement("p", { className: "text-sm text-brand-primary" }, "View PDF")
+                    )
+                );
+
+                if (!isFile && url) {
+                    return React.createElement("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "block bg-gray-100 dark:bg-gray-700/50 p-4 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-brand-primary" }, content);
+                } else {
+                    return React.createElement("div", { className: "bg-gray-100 dark:bg-gray-700/50 p-4 rounded-lg border border-gray-200 dark:border-gray-700" }, content);
+                }
+            
+            case 'product': {
+                const productBlock = block as ProductBlock;
+                if (productBlock.items.length === 0) return null;
+            
+                const isGrid = productBlock.layout === 'grid';
+                const containerClass = isGrid ? 'grid grid-cols-2 gap-4' : 'space-y-4';
+            
+                const cardClass = `block w-full text-left rounded-lg transition-shadow hover:shadow-lg overflow-hidden border`;
+                const cardBgStyle = { 
+                    backgroundColor: theme.button,
+                    borderColor: design.buttonStyle === 'outline' ? theme.buttonText : 'transparent',
+                };
+            
+                return (
+                    React.createElement("div", { className: containerClass },
+                        productBlock.items.map(item => (
+                            React.createElement("a", {
+                                key: item.id,
+                                href: item.url,
+                                target: "_blank",
+                                rel: "noopener noreferrer",
+                                className: cardClass,
+                                style: cardBgStyle
+                            },
+                                React.createElement("img", { src: item.imageUrl, alt: item.title, className: `w-full object-cover ${isGrid ? 'aspect-square' : 'aspect-video'}` }),
+                                React.createElement("div", { className: "p-4" },
+                                    React.createElement("h3", { className: "font-bold", style: { color: theme.buttonText } }, item.title),
+                                    React.createElement("p", { className: "font-semibold mt-1", style: { color: theme.buttonText, opacity: 0.9 } }, item.price)
+                                )
+                            )
+                        ))
+                    )
+                );
+            }
+            case 'award': {
+                const awardBlock = block as AwardBlock;
+                if (!awardBlock.items || awardBlock.items.length === 0) return null;
+
+                const isGrid = awardBlock.layout === 'grid';
+                const containerClass = isGrid ? `grid ${isMobile ? 'grid-cols-1' : 'grid-cols-2'} gap-4` : 'space-y-4';
+
+                return (
+                    React.createElement("div", { className: containerClass },
+                        awardBlock.items.map(item => {
+                            const imageUrl = getImageUrl(item.imageSource);
+
+                            if (isGrid) {
+                                return (
+                                    React.createElement("div", { key: item.id, className: "flex flex-col items-center text-center gap-3 p-6 rounded-lg shadow-sm", style: { backgroundColor: 'rgba(128,128,128,0.1)' } },
+                                        imageUrl && (
+                                            React.createElement("div", { className: "p-1 rounded-full bg-gradient-to-br from-yellow-300 via-amber-400 to-orange-500 flex-shrink-0" },
+                                                React.createElement("img", { src: imageUrl, alt: item.title, className: "w-24 h-24 rounded-full object-cover border-4 border-transparent", style: { borderColor: theme.bg } })
+                                            )
+                                        ),
+                                        React.createElement("div", { className: "flex-1 min-w-0" },
+                                            item.year && (
+                                                React.createElement("span", { className: "text-sm font-semibold opacity-70", style: { color: theme.text } },
+                                                    item.year
+                                                )
+                                            ),
+                                            React.createElement("h4", { className: "font-bold text-xl truncate w-full", style: { color: theme.text }, title: item.title }, item.title),
+                                            item.subtitle && (
+                                                React.createElement("p", { className: "text-base opacity-80 break-words", style: { color: theme.text } }, item.subtitle)
+                                            )
+                                        )
+                                    )
+                                );
+                            } else {
+                                return (
+                                    React.createElement("div", { key: item.id, className: "flex items-center gap-4 p-4 rounded-lg shadow-sm", style: { backgroundColor: 'rgba(128,128,128,0.1)' } },
+                                        imageUrl && (
+                                            React.createElement("div", { className: "p-0.5 rounded-full bg-gradient-to-br from-yellow-300 via-amber-400 to-orange-500 flex-shrink-0" },
+                                                React.createElement("img", { src: imageUrl, alt: item.title, className: "w-16 h-16 rounded-full object-cover border-2 border-transparent", style: { borderColor: theme.bg } })
+                                            )
+                                        ),
+                                        React.createElement("div", { className: "flex-1 min-w-0" },
+                                            React.createElement("div", { className: "flex justify-between items-start" },
+                                                React.createElement("h4", { className: "font-bold text-lg", style: { color: theme.text } }, item.title),
+                                                item.year && (
+                                                    React.createElement("span", { className: "text-xs font-semibold px-2 py-0.5 rounded-full flex-shrink-0 ml-2", style: { backgroundColor: 'rgba(128,128,128,0.1)', color: theme.text } },
+                                                        item.year
+                                                    )
+                                                )
+                                            ),
+                                            item.subtitle && (
+                                                React.createElement("p", { className: "text-sm opacity-80", style: { color: theme.text } }, item.subtitle)
+                                            )
+                                        )
+                                    )
+                                );
+                            }
+                        })
+                    )
+                );
+            }
+            case 'form': {
+                const formBlock = block as FormBlock;
+                return <FormBlockComponent block={formBlock} theme={theme} onFormSubmit={onFormSubmit} />;
+            }
+            case 'footer': {
+                const footerBlock = block as FooterBlock;
+                const footerTextColor = isMobile ? theme.text : (design.sideNavSettings.textColor || theme.text);
+                
+                if (footerBlock.layout === 'centered') {
+                    return React.createElement("footer", { className: "mt-8 py-8 px-4 text-center space-y-4", style: { backgroundColor: isMobile ? 'rgba(128,128,128,0.1)' : design.sideNavSettings.backgroundColor, color: footerTextColor } },
+                        (footerBlock.navLinks.length > 0 || footerBlock.otherLinks.length > 0) && React.createElement("div", { className: "flex justify-center flex-wrap gap-x-6 gap-y-2" },
+                            [...footerBlock.navLinks, ...footerBlock.otherLinks].map(link => 
+                                React.createElement("a", { key: link.id, href: link.url, className: "text-sm hover:underline", style: { color: footerTextColor } }, link.title)
+                            )
+                        ),
+                        footerBlock.statement && React.createElement("p", { className: "text-xs max-w-2xl mx-auto", style: { opacity: 0.8 } }, footerBlock.statement),
+                        React.createElement("div", { className: "text-xs", style: { opacity: 0.7 } },
+                            React.createElement("p", null, footerBlock.copyrightText),
+                            footerBlock.legalText && React.createElement("p", null, footerBlock.legalText)
+                        )
+                    );
+                }
+
+                // Standard Layout
+                return React.createElement("footer", { className: "mt-8 py-10 px-6", style: { backgroundColor: isMobile ? 'rgba(128,128,128,0.1)' : design.sideNavSettings.backgroundColor, color: footerTextColor } },
+                    React.createElement("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-8" },
+                        React.createElement("div", { className: "md:col-span-2" },
+                            footerBlock.statement && React.createElement("p", { className: "text-sm", style: { opacity: 0.8 } }, footerBlock.statement)
+                        ),
+                        React.createElement("div", null,
+                            React.createElement("h4", { className: "font-semibold mb-3", style: { color: footerTextColor } }, "Navigation"),
+                            React.createElement("ul", { className: "space-y-2" },
+                                footerBlock.navLinks.map(link => React.createElement("li", { key: link.id }, React.createElement("a", { href: link.url, className: "text-sm hover:underline", style: { color: footerTextColor, opacity: 0.9 } }, link.title)))
+                            )
+                        ),
+                        React.createElement("div", null,
+                            React.createElement("h4", { className: "font-semibold mb-3", style: { color: footerTextColor } }, "Links"),
+                             React.createElement("ul", { className: "space-y-2" },
+                                footerBlock.otherLinks.map(link => React.createElement("li", { key: link.id }, React.createElement("a", { href: link.url, className: "text-sm hover:underline", style: { color: footerTextColor, opacity: 0.9 } }, link.title)))
+                            )
+                        )
+                    ),
+                    React.createElement("div", { className: "mt-8 pt-6 border-t", style: { borderColor: isMobile ? 'rgba(128,128,128,0.2)' : design.sideNavSettings.hoverBackgroundColor, color: footerTextColor, opacity: 0.7 } },
+                        React.createElement("div", { className: "flex flex-col md:flex-row justify-between text-xs" },
+                            React.createElement("p", null, footerBlock.copyrightText),
+                            footerBlock.legalText && React.createElement("p", null, footerBlock.legalText)
+                        )
+                    )
+                );
+            }
+
+            default:
+                return null;
+        }
+    };
+    
+    if (previewMode === 'personal') {
+        const avatarUrl = getImageUrl(design.avatarSource);
+        
+        if (deviceView === 'mobile') {
+            const chatButtonStyle: React.CSSProperties = {
+                animationDuration: '3s',
+            };
+            if (design.buttonStyle === 'filled') {
+                chatButtonStyle.backgroundColor = '#10b981';
+                chatButtonStyle.color = '#ffffff';
+            } else { 
+                chatButtonStyle.backgroundColor = 'transparent';
+                chatButtonStyle.color = '#10b981';
+                chatButtonStyle.borderColor = '#10b981';
+            }
+
+            return React.createElement("div", { className: "absolute inset-0 flex items-center justify-center p-4 bg-gray-200 dark:bg-gray-700" },
+                React.createElement("div", {
+                    key: "personal-preview-screen",
+                    ref: scrollContainerRef,
+                    className: "relative w-[375px] h-[812px] max-h-full max-w-full overflow-y-auto bg-white dark:bg-gray-900 rounded-2xl shadow-xl " + getFontClass(design.fontFamily) + " " + design.fontSize + " " + getBackgroundClass(design.backgroundType, design.backgroundValue),
+                    style: getBackgroundStyle(design.backgroundType, design.backgroundValue)
+                },
+                React.createElement("div", { className: "w-full p-4 text-center flex flex-col min-h-full" },
+                    React.createElement("div", { className: "flex-grow" },
+                        avatarUrl && React.createElement("img", { src: avatarUrl, alt: "Avatar", className: "w-32 h-32 rounded-full mx-auto mt-8 mb-6 border-4 border-white dark:border-gray-700 shadow-lg" }),
+                         chatBlock && chatBlock.layout === 'under_avatar' && React.createElement("button", {
+                            onClick: () => setIsChatOpen(true),
+                            className: getButtonClasses(design.buttonShape, design.buttonStyle).replace('w-full p-3', 'w-auto p-3 px-6') + " mb-8 animate-breathing mx-auto",
+                            style: chatButtonStyle
+                        } as any, "Chat with me"),
+                        React.createElement("div", { className: "space-y-4" },
+                            visibleBlocks.filter(b => b.type !== 'footer').map(function(block) {
+                                return React.createElement("div", { key: block.id }, renderBlock(block));
+                            })
+                        )
+                    ),
+                    React.createElement("div", { className: "flex-shrink-0 -mx-4 -mb-4" },
+                        visibleBlocks.filter(b => b.type === 'footer').map(block => React.createElement("div", { key: block.id }, renderBlock(block)))
+                    )
+                ),
+                chatBlock && chatBlock.layout === 'widget' && React.createElement(ChatWidget, { settings: design.chatWidgetSettings }),
+                chatBlock && chatBlock.layout !== 'widget' && React.createElement(PersonalChatOverlay, { isOpen: isChatOpen, onClose: function() { return setIsChatOpen(false); }, settings: design.chatWidgetSettings }),
+                React.createElement(ActionModal, { data: actionModalData, onClose: function() { return setActionModalData(null); } })
+                )
+            );
+        }
+
+        // PC View for Personal
+        const chatButtonStyle: React.CSSProperties = {
+            animationDuration: '3s',
+        };
+        if (design.buttonStyle === 'filled') {
+            chatButtonStyle.backgroundColor = '#10b981';
+            chatButtonStyle.color = '#ffffff';
+        } else {
+            chatButtonStyle.backgroundColor = 'transparent';
+            chatButtonStyle.color = '#10b981';
+            chatButtonStyle.borderColor = '#10b981';
+        }
+        return React.createElement("div", {
+            key: "personal-preview-pc",
+            ref: scrollContainerRef,
+            className: "absolute inset-0 overflow-y-auto " + getFontClass(design.fontFamily) + " " + design.fontSize + " " + getBackgroundClass(design.backgroundType, design.backgroundValue),
+            style: Object.assign({}, getBackgroundStyle(design.backgroundType, design.backgroundValue))
+        },
+            React.createElement("div", { className: "w-full max-w-md mx-auto py-8 px-4" },
+                React.createElement("div", null,
+                    avatarUrl && React.createElement("img", { src: avatarUrl, alt: "Avatar", className: "w-32 h-32 rounded-full mx-auto mb-6 border-4 border-white dark:border-gray-700 shadow-lg" })
+                ),
+                 chatBlock && chatBlock.layout === 'under_avatar' && React.createElement("button", {
+                    onClick: () => setIsChatOpen(true),
+                    className: getButtonClasses(design.buttonShape, design.buttonStyle).replace('w-full p-3', 'w-auto p-3 px-6') + " mb-8 animate-breathing mx-auto",
+                    style: chatButtonStyle
+                } as any, "Chat with me"),
+                React.createElement("div", { className: "space-y-4" },
+                    visibleBlocks.filter(b => b.type !== 'footer').map(function(block) {
+                        return React.createElement("div", { key: block.id }, renderBlock(block));
+                    })
+                )
+            ),
+             visibleBlocks.filter(b => b.type === 'footer').map(block => React.createElement("div", { key: block.id }, renderBlock(block))),
+            chatBlock && chatBlock.layout === 'widget' && React.createElement(ChatWidget, { settings: design.chatWidgetSettings }),
+            chatBlock && chatBlock.layout !== 'widget' && React.createElement(PersonalChatOverlay, { isOpen: isChatOpen, onClose: function() { return setIsChatOpen(false); }, settings: design.chatWidgetSettings }),
+            React.createElement(ActionModal, { data: actionModalData, onClose: function() { return setActionModalData(null); } })
+        );
+    }
+    
+    // Enterprise Mode
+    if (deviceView === 'mobile') {
+        return React.createElement("div", { className: "absolute inset-0 flex items-center justify-center p-4 bg-gray-200 dark:bg-gray-700" },
+            React.createElement("div", {
+                key: "enterprise-preview-screen",
+                className: "relative w-[375px] h-[812px] max-h-full max-w-full bg-white dark:bg-gray-900 rounded-2xl shadow-xl overflow-hidden " + getFontClass(design.fontFamily) + " " + design.fontSize + " " + getBackgroundClass(design.backgroundType, design.backgroundValue),
+                style: Object.assign({}, getBackgroundStyle(design.backgroundType, design.backgroundValue))
+            },
+            React.createElement(EnterpriseNav, { blocks: visibleBlocks, deviceView: deviceView, scrollContainerRef: scrollContainerRef, settings: design.sideNavSettings }),
+            React.createElement("div", {
+                ref: scrollContainerRef,
+                className: "h-full w-full overflow-y-auto scroll-smooth"
+            },
+            React.createElement("div", { className: "flex flex-col min-h-full" },
+                React.createElement("div", { className: "p-4 flex-grow" },
+                    design.bannerSettings.type !== 'none' && (
+                        React.createElement("div", { className: "mb-8 rounded-lg overflow-hidden " + getBannerClass(design.bannerSettings), style: getBannerStyle(design.bannerSettings) })
+                    ),
+                    React.createElement("div", { className: "space-y-4" },
+                        visibleBlocks.filter(b => b.type !== 'footer').map(function(block) {
+                            return React.createElement("div", { key: block.id, id: 'block-' + block.id }, renderBlock(block));
+                        })
+                    )
+                ),
+                React.createElement("div", { className: "flex-shrink-0" },
+                    visibleBlocks.filter(b => b.type === 'footer').map(block => React.createElement("div", { key: block.id, id: 'block-' + block.id }, renderBlock(block)))
+                )
+            )
+            ),
+            chatBlock && chatBlock.layout === 'widget' && React.createElement(ChatWidget, { settings: design.chatWidgetSettings }),
+            React.createElement(ActionModal, { data: actionModalData, onClose: function() { return setActionModalData(null); } })
+            )
+        );
+    }
+
+    // PC View for Enterprise
+    const navFloatStyle = design.sideNavSettings.navFloatStyle || 'normal';
+    const navBackgroundStyle = design.sideNavSettings.navBackgroundStyle || 'compact';
+    const navContent = React.createElement(EnterpriseNav, { blocks: visibleBlocks, deviceView: 'pc', scrollContainerRef: scrollContainerRef, settings: design.sideNavSettings });
+    let navColumn;
+
+    if (navBackgroundStyle === 'full') {
+        const columnClasses = "w-64 flex-shrink-0";
+        const columnStyle = { backgroundColor: design.sideNavSettings.backgroundColor };
+        let contentWrapperClasses = 'w-full';
+        if (navFloatStyle === 'normal') contentWrapperClasses += ' pt-8';
+        else if (navFloatStyle === 'top') contentWrapperClasses += ' sticky top-8';
+        else if (navFloatStyle === 'center') contentWrapperClasses += ' sticky top-0 h-screen flex flex-col justify-center';
+        navColumn = React.createElement("div", { className: columnClasses, style: columnStyle }, React.createElement("div", { className: contentWrapperClasses }, navContent));
+    } else { 
+        let columnClasses = 'w-64 flex-shrink-0';
+        if (navFloatStyle === 'normal') columnClasses += ' pt-8';
+        else if (navFloatStyle === 'top') columnClasses += ' sticky top-8 self-start';
+        else if (navFloatStyle === 'center') columnClasses += ' sticky top-0 h-screen flex flex-col justify-center';
+        navColumn = React.createElement("div", { className: columnClasses }, navContent);
+    }
+
+
+    return React.createElement("div", { 
+        key: "enterprise-preview",
+        className: "h-full relative " + getFontClass(design.fontFamily) + " " + design.fontSize + " " + getBackgroundClass(design.backgroundType, design.backgroundValue),
+        style: Object.assign({}, getBackgroundStyle(design.backgroundType, design.backgroundValue))
+    },
+        React.createElement("div", {
+            ref: scrollContainerRef,
+            className: "h-full w-full overflow-y-auto scroll-smooth"
+        },
+            React.createElement("div", {
+                className: "flex flex-col min-h-full"
+            },
+                React.createElement("div", {
+                    className: "mx-auto flex items-stretch gap-8 px-8 flex-grow",
+                    style: { maxWidth: '1344px' } 
+                },
+                    navColumn,
+                    React.createElement("main", {
+                        className: "flex-1 min-w-0 py-8"
+                    },
+                        design.bannerSettings.type !== 'none' && (
+                            React.createElement("div", { className: "mb-8 rounded-lg overflow-hidden " + (design.bannerSettings.width === 'contained' ? '' : '-mx-8') + " " + getBannerClass(design.bannerSettings), style: getBannerStyle(design.bannerSettings) })
+                        ),
+                        React.createElement("div", { className: "space-y-4" },
+                            visibleBlocks.filter(b => b.type !== 'footer').map(function(block) {
+                                return React.createElement("div", { key: block.id, id: 'block-' + block.id }, renderBlock(block));
+                            })
+                        )
+                    )
+                ),
+                React.createElement("div", { className: "flex-shrink-0" },
+                    visibleBlocks.filter(b => b.type === 'footer').map(block => React.createElement("div", { key: block.id, id: 'block-' + block.id }, renderBlock(block)))
+                )
+            )
+        ),
+        chatBlock && chatBlock.layout === 'widget' && React.createElement(ChatWidget, { settings: design.chatWidgetSettings }),
+        React.createElement(ActionModal, { data: actionModalData, onClose: function() { return setActionModalData(null); } })
+    );
+};

+ 61 - 0
components/SEOServices.tsx

@@ -0,0 +1,61 @@
+import * as React from 'react';
+import { Icon } from './ui/Icon';
+import { useTranslation } from '../hooks/useI18n';
+
+const SEOServices: React.FC = () => {
+    const { t } = useTranslation();
+    return (
+        <div className="p-8 max-w-4xl mx-auto">
+            <header className="mb-8">
+                <h2 className="text-3xl font-bold text-gray-900 dark:text-white">{t('seo.services.title')}</h2>
+                <p className="text-gray-500 dark:text-gray-400 mt-1">{t('seo.services.subtitle')}</p>
+            </header>
+            <div className="space-y-8">
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm">
+                    <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
+                        {t('seo.services.keyword_analysis')}
+                        <span className="text-xs bg-yellow-400 text-yellow-900 px-2 py-0.5 rounded-full font-bold">{t('pro')}</span>
+                    </h3>
+                    <p className="text-gray-500 dark:text-gray-400 mb-4">{t('seo.services.keyword_analysis_desc')}</p>
+                    <div className="flex items-center gap-2">
+                        <input
+                            type="text"
+                            placeholder="e.g., 'digital marketing for startups'"
+                            className="w-full bg-gray-100 dark:bg-gray-700 p-3 rounded-md border border-gray-300 dark:border-gray-600"
+                        />
+                        <button className="bg-brand-primary text-white font-semibold py-3 px-5 rounded-md hover:bg-brand-secondary">
+                            {t('seo.services.analyze')}
+                        </button>
+                    </div>
+                     <div className="mt-6 p-4 bg-gray-50 dark:bg-gray-900/50 rounded-lg text-center text-gray-400">
+                        <p>{t('seo.services.keyword_results_placeholder')}</p>
+                    </div>
+                </div>
+
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm">
+                     <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
+                        {t('seo.services.ai_content')}
+                        <span className="text-xs bg-yellow-400 text-yellow-900 px-2 py-0.5 rounded-full font-bold">{t('pro')}</span>
+                    </h3>
+                    <p className="text-gray-500 dark:text-gray-400 mb-4">
+                        {t('seo.services.ai_content_desc')}
+                    </p>
+                    <div className="p-4 bg-gray-100 dark:bg-gray-700 rounded-lg">
+                        <p className="font-semibold text-gray-800 dark:text-gray-200">{t('seo.services.how_it_works')}</p>
+                        <ol className="list-decimal list-inside mt-2 text-sm text-gray-600 dark:text-gray-300 space-y-1">
+                            <li>{t('seo.services.step1')}</li>
+                            <li>{t('seo.services.step2')}</li>
+                            <li>{t('seo.services.step3')}</li>
+                            <li>{t('seo.services.step4')}</li>
+                        </ol>
+                    </div>
+                    <button className="mt-4 bg-brand-primary/10 text-brand-primary font-semibold py-2 px-5 rounded-md hover:bg-brand-primary/20 transition-colors">
+                        {t('seo.services.go_to_news_creator')}
+                    </button>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default SEOServices;

+ 72 - 0
components/ShareModal.tsx

@@ -0,0 +1,72 @@
+import * as React from 'react';
+import { Icon } from './ui/Icon';
+import { useTranslation } from '../hooks/useI18n';
+
+interface ShareModalProps {
+    pageSlug: string;
+    onClose: () => void;
+}
+
+const QRCode: React.FC<{ url: string }> = ({ url }) => {
+    const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(url)}`;
+    return <img src={qrUrl} alt="QR Code" className="w-32 h-32 rounded-lg" />
+};
+
+const ShareModal: React.FC<ShareModalProps> = ({ pageSlug, onClose }) => {
+    const { t } = useTranslation();
+    const [copiedLink, setCopiedLink] = React.useState<string | null>(null);
+
+    const baseUrl = `${window.location.origin}${window.location.pathname.replace(/\/$/, '')}`;
+    const pcUrl = `${baseUrl}?page=${pageSlug}&view=pc`;
+    const mobileUrl = `${baseUrl}?page=${pageSlug}&view=mobile`;
+
+    const handleCopy = (url: string, type: string) => {
+        navigator.clipboard.writeText(url).then(() => {
+            setCopiedLink(type);
+            setTimeout(() => setCopiedLink(null), 2000);
+        });
+    };
+
+    return (
+        <div className="fixed inset-0 bg-black/70 z-50 flex items-center justify-center backdrop-blur-sm" onClick={onClose}>
+            <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl w-full max-w-3xl p-8 border border-gray-200 dark:border-gray-700" onClick={e => e.stopPropagation()}>
+                <div className="flex justify-between items-center mb-6">
+                    <h2 className="text-3xl font-bold text-gray-900 dark:text-white">{t('modal.share.title')}</h2>
+                    <button onClick={onClose} className="text-gray-400 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">
+                        <Icon className="h-8 w-8"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /></Icon>
+                    </button>
+                </div>
+                <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
+                    {/* PC Version */}
+                    <div className="text-center">
+                        <h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">{t('modal.share.pc_version')}</h3>
+                        <div className="flex justify-center mb-4">
+                            <QRCode url={pcUrl} />
+                        </div>
+                        <div className="flex items-center bg-gray-100 dark:bg-gray-700 rounded-md">
+                            <input type="text" readOnly value={pcUrl} className="flex-1 bg-transparent p-2 text-sm text-gray-600 dark:text-gray-300 focus:outline-none truncate" />
+                            <button onClick={() => handleCopy(pcUrl, 'pc')} className="bg-brand-primary text-white font-semibold py-2 px-3 rounded-r-md hover:bg-brand-secondary text-sm">
+                                {copiedLink === 'pc' ? t('modal.share.copied') : t('modal.share.copy')}
+                            </button>
+                        </div>
+                    </div>
+                    {/* Mobile Version */}
+                    <div className="text-center">
+                        <h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">{t('modal.share.mobile_version')}</h3>
+                         <div className="flex justify-center mb-4">
+                            <QRCode url={mobileUrl} />
+                        </div>
+                        <div className="flex items-center bg-gray-100 dark:bg-gray-700 rounded-md">
+                            <input type="text" readOnly value={mobileUrl} className="flex-1 bg-transparent p-2 text-sm text-gray-600 dark:text-gray-300 focus:outline-none truncate" />
+                            <button onClick={() => handleCopy(mobileUrl, 'mobile')} className="bg-brand-primary text-white font-semibold py-2 px-3 rounded-r-md hover:bg-brand-secondary text-sm">
+                                {copiedLink === 'mobile' ? t('modal.share.copied') : t('modal.share.copy')}
+                            </button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default ShareModal;

+ 39 - 0
components/ShortLinks.tsx

@@ -0,0 +1,39 @@
+import * as React from 'react';
+import { useTranslation } from '../hooks/useI18n';
+
+interface ShortLinksProps {
+    slug: string;
+    onUpdateSlug: (newSlug: string) => void;
+}
+
+const ShortLinks: React.FC<ShortLinksProps> = ({ slug, onUpdateSlug }) => {
+    const { t } = useTranslation();
+    return (
+        <div className="p-8 max-w-4xl mx-auto">
+            <header className="mb-8">
+                <h2 className="text-3xl font-bold text-gray-900 dark:text-white">{t('seo.short_links.title')}</h2>
+                <p className="text-gray-500 dark:text-gray-400 mt-1">{t('seo.short_links.subtitle')}</p>
+            </header>
+            <div className="space-y-8">
+                <div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm">
+                    <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">{t('seo.short_links.your_url')}</h3>
+                    <p className="text-gray-500 dark:text-gray-400 mb-2">{t('seo.short_links.your_url_desc')}</p>
+                    <div className="flex items-center bg-gray-100 dark:bg-gray-700 rounded-md">
+                        <span className="px-4 text-gray-400 dark:text-gray-400">greenpage.ai/</span>
+                        <input
+                            type="text"
+                            value={slug}
+                            onChange={(e) => onUpdateSlug(e.target.value)}
+                            className="flex-1 bg-transparent p-3 text-gray-900 dark:text-white focus:outline-none"
+                        />
+                        <button className="bg-brand-primary text-white font-semibold py-2 px-4 rounded-r-md hover:bg-brand-secondary transition-colors">
+                            {t('save')}
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default ShortLinks;

+ 93 - 0
components/Showcase.tsx

@@ -0,0 +1,93 @@
+import * as React from 'react';
+import { GreenPage } from '../types';
+import { Preview } from './Preview';
+import { Icon } from './ui/Icon';
+// FIX: Added 'generateEnterpriseShowcases' to the import as it is now correctly exported.
+import { generatePersonalShowcases, generateEnterpriseShowcases } from '../services/showcaseDataService';
+
+interface ShowcaseProps {
+  initialTab: 'personal' | 'enterprise';
+  onImportPage: (page: GreenPage) => void;
+}
+
+const Showcase: React.FC<ShowcaseProps> = ({ initialTab, onImportPage }) => {
+    const [activeTab, setActiveTab] = React.useState(initialTab);
+    const [showcasePages, setShowcasePages] = React.useState<GreenPage[]>([]);
+    const [selectedPage, setSelectedPage] = React.useState<GreenPage | null>(null);
+
+    const [previewMode, setPreviewMode] = React.useState<'personal' | 'enterprise'>(initialTab);
+    const [deviceView, setDeviceView] = React.useState<'pc' | 'mobile'>('pc');
+
+    React.useEffect(() => {
+        const pages = activeTab === 'personal' ? generatePersonalShowcases() : generateEnterpriseShowcases();
+        setShowcasePages(pages);
+        setSelectedPage(pages[0] || null);
+        setPreviewMode(activeTab);
+    }, [activeTab]);
+
+    return (
+        <div className="flex h-full">
+            <div className="w-1/3 max-w-sm flex flex-col border-r border-gray-200 dark:border-gray-700">
+                <header className="p-6 border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
+                    <h2 className="text-2xl font-bold text-gray-900 dark:text-white">Showcase</h2>
+                    <p className="text-gray-500 dark:text-gray-400 mt-1">Explore what you can build.</p>
+                     <div className="mt-4 flex items-center gap-1 bg-gray-200 dark:bg-gray-800 p-1 rounded-lg">
+                        <button onClick={() => setActiveTab('personal')} className={`flex-1 px-3 py-1 text-sm rounded-md capitalize transition-colors ${activeTab === 'personal' ? 'bg-brand-primary text-white shadow' : 'hover:bg-gray-300 dark:hover:bg-gray-600'}`}>Personal</button>
+                        <button onClick={() => setActiveTab('enterprise')} className={`flex-1 px-3 py-1 text-sm rounded-md capitalize transition-colors ${activeTab === 'enterprise' ? 'bg-brand-primary text-white shadow' : 'hover:bg-gray-300 dark:hover:bg-gray-600'}`}>Enterprise</button>
+                    </div>
+                </header>
+                <div className="flex-1 overflow-y-auto p-4 space-y-3">
+                    {showcasePages.map(page => (
+                         <button
+                            key={page.id}
+                            onClick={() => setSelectedPage(page)}
+                            className={`w-full p-4 rounded-lg text-left transition-all duration-200 ${selectedPage?.id === page.id ? 'bg-brand-primary/10 ring-2 ring-brand-primary' : 'bg-white dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700/50'}`}
+                        >
+                            <p className="font-semibold text-gray-900 dark:text-white">{page.name}</p>
+                            <p className="text-xs text-gray-500 dark:text-gray-400 truncate">{page.slug}</p>
+                        </button>
+                    ))}
+                </div>
+            </div>
+            <div className="flex-1 border-l border-gray-200 dark:border-gray-700 flex flex-col bg-gray-200 dark:bg-gray-900/50">
+                <div className="flex-shrink-0 bg-white dark:bg-gray-800 p-2 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
+                    {selectedPage ? (
+                         <button
+                            onClick={() => onImportPage(selectedPage)}
+                            className="flex items-center gap-2 bg-brand-primary text-white font-semibold py-2 px-4 rounded-lg hover:bg-brand-secondary transition-colors"
+                        >
+                            <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></Icon>
+                            Import Page
+                        </button>
+                    ) : <div />}
+                    <div className="flex items-center gap-1 bg-gray-200 dark:bg-gray-700 p-1 rounded-lg">
+                        <button onClick={() => setDeviceView('pc')} className={`p-2 rounded-md ${deviceView === 'pc' ? 'bg-brand-primary text-white' : ''}`}> 
+                            <Icon className='h-5 w-5'><path strokeLinecap="round" strokeLinejoin="round" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></Icon>
+                        </button>
+                        <button onClick={() => setDeviceView('mobile')} className={`p-2 rounded-md ${deviceView === 'mobile' ? 'bg-brand-primary text-white' : ''}`}> 
+                            <Icon className='h-5 w-5'><path strokeLinecap="round" strokeLinejoin="round" d="M12 18h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" /></Icon>
+                        </button>
+                    </div>
+                </div>
+                 <div className="flex-1 overflow-hidden relative">
+                    {selectedPage ? (
+                         <Preview
+                            key={`${selectedPage.id}-${previewMode}-${deviceView}`}
+                            pageSettings={selectedPage.pageSettings}
+                            aigcVideos={selectedPage.aigcSettings.videos}
+                            aigcArticles={selectedPage.aigcSettings.articles}
+                            previewMode={previewMode}
+                            deviceView={deviceView}
+                            // FIX: Added the required 'onFormSubmit' prop with a dummy function as it's not needed in the showcase.
+                            onFormSubmit={() => {}}
+                        />
+                    ) : (
+                        <div className="flex items-center justify-center h-full text-gray-500 dark:text-gray-400">Select a showcase to preview</div>
+                    )}
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default Showcase;

+ 162 - 0
components/Sidebar.tsx

@@ -0,0 +1,162 @@
+import * as React from 'react';
+import { NavItem, NavItemKey } from '../types';
+import { Icon } from './ui/Icon';
+import { useTranslation, Language } from '../hooks/useI18n';
+
+interface SidebarProps {
+    activePageName: string;
+    activePageThemeColor: string;
+    onOpenPageManager: () => void;
+    activeNavKey: NavItemKey;
+    setActiveNavKey: (page: NavItemKey) => void;
+    theme: 'light' | 'dark';
+    setTheme: (theme: 'light' | 'dark') => void;
+    currentUser: string;
+    onLogout: () => void;
+}
+
+const NavMenu: React.FC<{
+    activeKey: NavItemKey;
+    onSelect: (key: NavItemKey) => void;
+}> = ({ activeKey, onSelect }) => {
+    const { t } = useTranslation();
+
+    const navItems: NavItem[] = [
+        {
+            name: t('nav.page'),
+            key: 'Page',
+            icon: <path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />,
+            children: [
+                { name: t('nav.page.link'), key: 'Page.Link' },
+                { name: t('nav.page.media'), key: 'Page.Media' },
+                { name: t('nav.page.design'), key: 'Page.Design' },
+            ],
+        },
+        {
+            name: t('nav.ai_assistant'),
+            key: 'AI Assistant',
+            icon: <path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />,
+            children: [
+                { name: t('nav.ai_assistant.persona'), key: 'AI Assistant.Persona' },
+                { name: t('nav.ai_assistant.knowledge'), key: 'AI Assistant.Knowledge' },
+                { name: t('nav.ai_assistant.sensitivity'), key: 'AI Assistant.Sensitivity' },
+            ],
+        },
+        {
+            name: t('nav.analytics'),
+            key: 'Analytics',
+            icon: <path strokeLinecap="round" strokeLinejoin="round" d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4m-4-11v11" />,
+            children: [
+                { name: t('nav.analytics.page'), key: 'Analytics.Page' },
+                { name: t('nav.analytics.interactions'), key: 'Analytics.Interactions' },
+                { name: t('nav.analytics.crm'), key: 'Analytics.CRM' },
+            ],
+        },
+        {
+            name: t('nav.seo'),
+            key: 'SEO',
+            icon: <path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />,
+            children: [
+                { name: t('nav.seo.short_links'), key: 'SEO.ShortLinks' },
+                { name: t('nav.seo.hosting'), key: 'SEO.Hosting' },
+                { name: t('nav.seo.services'), key: 'SEO.Services' },
+            ],
+        },
+        {
+            name: t('nav.aigc'),
+            key: 'AIGC',
+            icon: <path strokeLinecap="round" strokeLinejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c.251.023.501.05.75.082a9.75 9.75 0 016 6.062c.313.958.5 1.965.5 3.004v.75a2.25 2.25 0 01-2.25 2.25H3.75a2.25 2.25 0 01-2.25-2.25v-.75c0-1.04.187-2.046.5-3.004a9.75 9.75 0 016-6.062 12.312 12.312 0 01.75-.082zM9.75 18.75c-2.482 0-4.72-1.22-6.16-3.223" />,
+            children: [
+                { name: t('nav.aigc.creator'), key: 'AIGC.Creator' },
+                { name: t('nav.aigc.news'), key: 'AIGC.News' },
+                { name: t('nav.aigc.scheduler'), key: 'AIGC.Scheduler' },
+            ]
+        },
+        {
+            name: t('nav.showcase'),
+            key: 'Showcase',
+            icon: <path strokeLinecap="round" strokeLinejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />,
+            children: [
+                { name: t('nav.showcase.personal'), key: 'Showcase.Personal' },
+                { name: t('nav.showcase.enterprise'), key: 'Showcase.Enterprise' },
+            ],
+        },
+    ];
+
+    return (
+        <nav>
+            {navItems.map(item => {
+                const isActive = activeKey.startsWith(item.key);
+                return (
+                    <div key={item.key} className="mb-2">
+                        <button
+                            onClick={() => onSelect(item.children ? item.children[0].key : item.key)}
+                            className={`w-full flex items-center gap-3 p-3 rounded-lg text-left transition-colors ${isActive ? 'bg-brand-primary/10 text-brand-primary' : 'hover:bg-gray-200 dark:hover:bg-gray-700'}`}
+                        >
+                            <Icon className="h-6 w-6">{item.icon}</Icon>
+                            <span className="font-semibold">{item.name}</span>
+                        </button>
+                        {item.children && isActive && (
+                            <div className="mt-2 pl-9 space-y-1">
+                                {item.children.map(child => (
+                                    <button
+                                        key={child.key}
+                                        onClick={() => onSelect(child.key)}
+                                        className={`block w-full text-left text-sm p-2 rounded-md transition-colors ${activeKey === child.key ? 'text-brand-primary font-semibold' : 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'}`}
+                                    >
+                                        {child.name}
+                                    </button>
+                                ))}
+                            </div>
+                        )}
+                    </div>
+                );
+            })}
+        </nav>
+    );
+};
+
+export const Sidebar: React.FC<SidebarProps> = ({ activePageName, activePageThemeColor, onOpenPageManager, activeNavKey, setActiveNavKey, theme, setTheme, currentUser, onLogout }) => {
+    const { t, language, setLanguage } = useTranslation();
+
+    return (
+        <aside className="w-64 bg-white dark:bg-gray-800 p-4 flex flex-col border-r border-gray-200 dark:border-gray-700">
+            <div className="flex-shrink-0 mb-6">
+                <button onClick={onOpenPageManager} className={`w-full bg-gradient-to-br ${activePageThemeColor} p-3 rounded-lg text-left shadow-lg`}>
+                    <div className="font-bold text-white truncate">{activePageName}</div>
+                    <div className="text-xs text-white/80">{t('sidebar.select_page')}</div>
+                </button>
+            </div>
+            <div className="flex-1 overflow-y-auto pr-2 -mr-4">
+                <NavMenu activeKey={activeNavKey} onSelect={setActiveNavKey} />
+            </div>
+            <div className="flex-shrink-0 space-y-4 pt-4">
+                 <div className="p-3 bg-gray-100 dark:bg-gray-900 rounded-lg flex items-center justify-between">
+                     <div className="min-w-0">
+                        <p className="text-sm font-semibold truncate text-gray-800 dark:text-gray-200" title={currentUser}>{currentUser}</p>
+                     </div>
+                     <button onClick={onLogout} title={t('sidebar.logout')} className="text-gray-400 dark:text-gray-500 hover:text-red-500">
+                        <Icon className="h-5 w-5"><path strokeLinecap="round" strokeLinejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /></Icon>
+                     </button>
+                </div>
+                <div className="flex items-center justify-center gap-2 p-2 bg-gray-100 dark:bg-gray-900 rounded-lg">
+                    <button onClick={() => setTheme('light')} className={`flex-1 p-2 text-sm rounded-md ${theme === 'light' ? 'bg-brand-primary text-white' : ''}`}>{t('sidebar.light_theme')}</button>
+                    <button onClick={() => setTheme('dark')} className={`flex-1 p-2 text-sm rounded-md ${theme === 'dark' ? 'bg-brand-primary text-white' : ''}`}>{t('sidebar.dark_theme')}</button>
+                </div>
+                <div>
+                    <select
+                        value={language}
+                        onChange={(e) => setLanguage(e.target.value as Language)}
+                        aria-label={t('sidebar.language')}
+                        className="w-full bg-gray-100 dark:bg-gray-900 p-2 rounded-lg text-sm text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700 focus:ring-brand-primary focus:border-brand-primary"
+                    >
+                        <option value="en">English</option>
+                        <option value="ja">日本語</option>
+                        <option value="zh">中文</option>
+                        <option value="ko">한국어</option>
+                    </select>
+                </div>
+            </div>
+        </aside>
+    );
+};

+ 636 - 0
components/VideoCreator.tsx

@@ -0,0 +1,636 @@
+import * as React from 'react';
+import { Avatar, Voice, VideoScene, VideoProject, AIGCVideo, AIGCSettings, MediaSource, ScriptContent, ScriptVersion } from '../types';
+import { Icon } from './ui/Icon';
+import { Tabs } from './ui/Tabs';
+import { polishScript } from '../services/geminiService';
+
+// --- MOCK DATA ---
+const mockAvatars: Avatar[] = [
+    { id: 'av1', name: 'Robotix', imageUrl: 'https://api.dicebear.com/8.x/bottts-neutral/svg?seed=robotix' },
+    { id: 'av2', name: 'Cyber', imageUrl: 'https://api.dicebear.com/8.x/bottts-neutral/svg?seed=cyber' },
+    { id: 'av3', name: 'Gizmo', imageUrl: 'https://api.dicebear.com/8.x/bottts-neutral/svg?seed=gizmo' },
+];
+
+const mockVoices: Voice[] = [ { id: 'vo1', name: 'Alloy', accent: 'Neutral' }, { id: 'vo2', name: 'Echo', accent: 'Warm' }, { id: 'vo3', name: 'Fable', accent: 'Professional' }];
+const mockBackgrounds = { 
+    colors: ['#111827', '#1f2937', '#374151', '#4b5563', '#6b7280', '#9ca3af', '#d1d5db', '#f3f4f6'], 
+    images: [
+        { type: 'url', value: 'https://picsum.photos/seed/bg1/800/450' }, 
+        { type: 'url', value: 'https://picsum.photos/seed/bg2/800/450' }, 
+        { type: 'url', value: 'https://picsum.photos/seed/bg3/800/450' }, 
+        { type: 'url', value: 'https://picsum.photos/seed/bg4/800/450' }
+    ] as MediaSource[]
+};
+const initialScriptVersionId = `v-init-${Date.now()}`;
+const initialProject: VideoProject = { 
+    id: `proj-${Date.now()}`, 
+    name: 'My First Video', 
+    aspectRatio: '16:9',
+    scenes: [{ 
+        id: `scene-${Date.now()}`, 
+        script: {
+            versions: [{ id: initialScriptVersionId, text: 'Welcome to our presentation!' }],
+            selectedVersionId: initialScriptVersionId
+        }, 
+        avatarId: 'av1', 
+        voiceId: 'vo1', 
+        background: { type: 'color', value: '#1f2937' }, 
+        avatarPosition: { x: 50, y: 10 },
+        avatarScale: 1,
+    }]
+};
+
+// --- Child Components ---
+const GenerationProgress: React.FC<{ progress: number; totalVideos: number; currentVideo: number }> = ({ progress, totalVideos, currentVideo }) => (
+    <div className="fixed inset-0 bg-black/80 flex flex-col items-center justify-center z-50">
+        <div className="bg-gray-800 p-8 rounded-lg text-center w-full max-w-md">
+            <h3 className="text-xl font-bold text-white mb-4">Generating Videos...</h3>
+            <p className="text-gray-400 mb-4">{`Processing video ${currentVideo} of ${totalVideos}. This may take a moment.`}</p>
+            <div className="w-full bg-gray-700 rounded-full h-4">
+                <div className="bg-brand-primary h-4 rounded-full transition-all duration-500" style={{ width: `${progress}%` }} />
+            </div>
+        </div>
+    </div>
+);
+
+const VideoCanvas: React.FC<{
+    scene: VideoScene;
+    aspectRatio: '16:9' | '9:16';
+    onAvatarMove: (pos: { x: number, y: number }) => void;
+    onAvatarScale: (newScale: number) => void;
+}> = ({ scene, aspectRatio, onAvatarMove, onAvatarScale }) => {
+    const canvasRef = React.useRef<HTMLDivElement>(null);
+    const avatarRef = React.useRef<HTMLImageElement>(null);
+
+    // Interaction state
+    const [isSelected, setIsSelected] = React.useState(false);
+    const [interactionState, setInteractionState] = React.useState<'idle' | 'dragging' | 'resizing'>('idle');
+
+    // Refs to store start positions for calculations
+    const interactionStartRef = React.useRef({
+        dragOffsetX: 0,
+        dragOffsetY: 0,
+        resizeStartX: 0,
+        resizeStartWidth: 0,
+        resizeStartScale: 1,
+    });
+
+    const activeAvatar = mockAvatars.find(a => a.id === scene.avatarId);
+
+    // Mouse Down on Avatar -> start dragging
+    const handleAvatarMouseDown = (e: React.MouseEvent<HTMLImageElement>) => {
+        e.preventDefault();
+        e.stopPropagation();
+        
+        if (!avatarRef.current || !canvasRef.current) return;
+        
+        setIsSelected(true);
+        setInteractionState('dragging');
+
+        const avatarRect = avatarRef.current.getBoundingClientRect();
+        interactionStartRef.current = {
+            ...interactionStartRef.current,
+            dragOffsetX: e.clientX - avatarRect.left,
+            dragOffsetY: e.clientY - avatarRect.top,
+        };
+    };
+
+    // Mouse Down on Resize Handle -> start resizing
+    const handleResizeMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
+        e.preventDefault();
+        e.stopPropagation();
+
+        if (!avatarRef.current) return;
+        
+        setInteractionState('resizing');
+        interactionStartRef.current = {
+            ...interactionStartRef.current,
+            resizeStartX: e.clientX,
+            resizeStartWidth: avatarRef.current.clientWidth,
+            resizeStartScale: scene.avatarScale,
+        };
+    };
+
+    // Effect for global mouse move and mouse up events
+    React.useEffect(() => {
+        const handleMouseMove = (e: MouseEvent) => {
+            if (interactionState === 'idle' || !canvasRef.current) return;
+            
+            e.preventDefault();
+
+            if (interactionState === 'dragging') {
+                if (!avatarRef.current) return;
+                const canvasRect = canvasRef.current.getBoundingClientRect();
+                
+                const newAvatarLeft = e.clientX - canvasRect.left - interactionStartRef.current.dragOffsetX;
+                const newAvatarTop = e.clientY - canvasRect.top - interactionStartRef.current.dragOffsetY;
+                
+                const avatarWidth = avatarRef.current.offsetWidth;
+                const avatarHeight = avatarRef.current.offsetHeight;
+
+                const newXPercent = ((newAvatarLeft + avatarWidth / 2) / canvasRect.width) * 100;
+                const newYFromBottom = canvasRect.height - newAvatarTop - avatarHeight;
+                const newYPercent = (newYFromBottom / canvasRect.height) * 100;
+                
+                const clampedX = Math.max((avatarWidth / 2 / canvasRect.width) * 100, Math.min(100 - (avatarWidth / 2 / canvasRect.width) * 100, newXPercent));
+                const clampedY = Math.max(0, Math.min(100 - (avatarHeight / canvasRect.height) * 100, newYPercent));
+
+                onAvatarMove({ x: clampedX, y: clampedY });
+            }
+
+            if (interactionState === 'resizing') {
+                 const deltaX = e.clientX - interactionStartRef.current.resizeStartX;
+                 const newWidth = interactionStartRef.current.resizeStartWidth + deltaX;
+                 const baseWidth = interactionStartRef.current.resizeStartWidth / interactionStartRef.current.resizeStartScale;
+                 if (baseWidth === 0) return;
+                 const newScale = newWidth / baseWidth;
+                 onAvatarScale(Math.max(0.2, Math.min(3.0, newScale)));
+            }
+        };
+
+        const handleMouseUp = () => {
+            setInteractionState('idle');
+        };
+
+        window.addEventListener('mousemove', handleMouseMove);
+        window.addEventListener('mouseup', handleMouseUp);
+        
+        return () => {
+            window.removeEventListener('mousemove', handleMouseMove);
+            window.removeEventListener('mouseup', handleMouseUp);
+        };
+    }, [interactionState, onAvatarMove, onAvatarScale]);
+    
+    // Deselect avatar when clicking on canvas background
+    const handleCanvasMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
+        if (e.target === e.currentTarget) {
+            setIsSelected(false);
+        }
+    };
+
+    const isVideoBg = scene.background.type === 'video';
+    const backgroundStyle = scene.background.type !== 'video' ? {
+        background: scene.background.type === 'color' ? scene.background.value : `url(${scene.background.value}) center/cover`
+    } : {};
+    
+    return (
+        <div ref={canvasRef} onMouseDown={handleCanvasMouseDown}
+            className={`bg-black rounded-lg relative overflow-hidden shadow-2xl transition-all duration-300 max-w-full max-h-full ${aspectRatio === '16:9' ? 'aspect-video w-full' : 'aspect-[9/16] h-full'}`}
+            style={backgroundStyle}>
+            {isVideoBg && (
+                <video key={scene.background.value} src={scene.background.value} autoPlay loop muted className="absolute top-0 left-0 w-full h-full object-cover" />
+            )}
+            {activeAvatar && 
+                <div
+                    className="absolute"
+                    style={{
+                        left: `${scene.avatarPosition.x}%`, 
+                        bottom: `${scene.avatarPosition.y}%`, 
+                        height: '80%',
+                        transform: `translateX(-50%)`,
+                        cursor: interactionState === 'dragging' ? 'grabbing' : 'grab',
+                    }}
+                >
+                    <div className="relative w-auto h-full" style={{ transform: `scale(${scene.avatarScale})`, transformOrigin: 'bottom center' }}>
+                        <img 
+                            ref={avatarRef} 
+                            src={activeAvatar.imageUrl} 
+                            alt={activeAvatar.name} 
+                            onMouseDown={handleAvatarMouseDown}
+                            className="h-full object-contain select-none pointer-events-auto"
+                        />
+                        {isSelected && (
+                            <>
+                                <div className="absolute inset-0 border-2 border-dashed border-brand-primary pointer-events-none" />
+                                <div 
+                                    onMouseDown={handleResizeMouseDown}
+                                    className="absolute -bottom-1 -right-1 w-4 h-4 bg-brand-primary border-2 border-white dark:border-gray-800 rounded-full cursor-se-resize pointer-events-auto"
+                                />
+                            </>
+                        )}
+                    </div>
+                </div>
+            }
+        </div>
+    );
+};
+
+
+// --- Main Component ---
+interface VideoCreatorProps {
+    aigcSettings: AIGCSettings;
+    onUpdateAIGCSettings: (newSettings: AIGCSettings) => void;
+    onVideosGenerated: (videos: AIGCVideo[]) => void;
+}
+const VideoCreator: React.FC<VideoCreatorProps> = ({ aigcSettings, onUpdateAIGCSettings, onVideosGenerated }) => {
+    const [projects, setProjects] = React.useState<VideoProject[]>([initialProject]);
+    const [activeProjectId, setActiveProjectId] = React.useState<string>(projects[0].id);
+    const [activeSceneId, setActiveSceneId] = React.useState<string | null>(projects[0].scenes[0]?.id || null);
+    const [selectedProjects, setSelectedProjects] = React.useState<string[]>([]);
+    const [isGenerating, setIsGenerating] = React.useState(false);
+    const [generationState, setGenerationState] = React.useState({ progress: 0, current: 0, total: 0 });
+    const [editorTab, setEditorTab] = React.useState<'avatar' | 'voice' | 'script' | 'background'>('script');
+    const [backgroundTab, setBackgroundTab] = React.useState<'color' | 'media'>('color');
+    const [isPolishing, setIsPolishing] = React.useState(false);
+    const [editingProjectId, setEditingProjectId] = React.useState<string | null>(null);
+    const [editingProjectName, setEditingProjectName] = React.useState<string>('');
+
+    const activeProject = React.useMemo(() => projects.find(p => p.id === activeProjectId), [projects, activeProjectId]);
+    const activeScene = React.useMemo(() => activeProject?.scenes.find(s => s.id === activeSceneId), [activeProject, activeSceneId]);
+
+    React.useEffect(() => {
+        if (activeProject && (!activeSceneId || !activeProject.scenes.some(s => s.id === activeSceneId))) {
+            setActiveSceneId(activeProject.scenes[0]?.id || null);
+        }
+    }, [activeProjectId, activeProject, activeSceneId]);
+
+    const updateProject = (updatedProject: VideoProject) => {
+        setProjects(prev => prev.map(p => p.id === updatedProject.id ? updatedProject : p));
+    };
+
+    const handleStartEditingProjectName = (project: VideoProject) => {
+        setEditingProjectId(project.id);
+        setEditingProjectName(project.name);
+    };
+
+    const handleCancelEditingProjectName = () => {
+        setEditingProjectId(null);
+        setEditingProjectName('');
+    };
+
+    const handleSaveProjectName = () => {
+        if (!editingProjectId || !editingProjectName.trim()) {
+            handleCancelEditingProjectName();
+            return;
+        }
+        setProjects(prev => prev.map(p =>
+            p.id === editingProjectId ? { ...p, name: editingProjectName.trim() } : p
+        ));
+        handleCancelEditingProjectName();
+    };
+    
+    const addProject = () => {
+        const newSceneId = `scene-${Date.now()}`;
+        const newScriptVersionId = `v-new-${Date.now()}`;
+        const newProject: VideoProject = { 
+            id: `proj-${Date.now()}`, 
+            name: `Untitled Video ${projects.length + 1}`, 
+            aspectRatio: '16:9',
+            scenes: [{ 
+                id: newSceneId, 
+                script: {
+                    versions: [{ id: newScriptVersionId, text: 'New video script.' }],
+                    selectedVersionId: newScriptVersionId,
+                },
+                avatarId: 'av1', 
+                voiceId: 'vo1', 
+                background: { type: 'image', value: (mockBackgrounds.images[0] as { type: 'url', value: string }).value }, 
+                avatarPosition: { x: 50, y: 10 },
+                avatarScale: 1
+            }]
+        };
+        setProjects(prev => [...prev, newProject]);
+        setActiveProjectId(newProject.id);
+    };
+    
+    const deleteProject = (id: string) => {
+        const newProjects = projects.filter(p => p.id !== id);
+        if (newProjects.length === 0) return;
+        setProjects(newProjects);
+        if (activeProjectId === id) setActiveProjectId(newProjects[0].id);
+    };
+
+    const updateScene = (updatedScene: VideoScene) => {
+        if (!activeProject) return;
+        const updatedScenes = activeProject.scenes.map(s => s.id === updatedScene.id ? updatedScene : s);
+        updateProject({ ...activeProject, scenes: updatedScenes });
+    };
+
+    const handleAddScene = () => {
+        if (!activeProject) return;
+        const newScriptVersionId = `v-new-${Date.now()}`;
+        const newScene: VideoScene = { 
+            id: `scene-${Date.now()}`, 
+            script: {
+                versions: [{ id: newScriptVersionId, text: 'New scene script.' }],
+                selectedVersionId: newScriptVersionId,
+            },
+            avatarId: 'av2', 
+            voiceId: 'vo2', 
+            background: {type: 'color', value: '#111827'}, 
+            avatarPosition: { x: 50, y: 10 },
+            avatarScale: 1
+        };
+        const updatedScenes = [...activeProject.scenes, newScene];
+        updateProject({ ...activeProject, scenes: updatedScenes });
+        setActiveSceneId(newScene.id);
+    };
+
+    const handleDeleteScene = (sceneId: string) => {
+        if (!activeProject || activeProject.scenes.length <= 1) {
+            alert("A project must have at least one scene.");
+            return;
+        }
+        const updatedScenes = activeProject.scenes.filter(s => s.id !== sceneId);
+        updateProject({ ...activeProject, scenes: updatedScenes });
+        if (activeSceneId === sceneId) {
+            setActiveSceneId(updatedScenes[0]?.id || null);
+        }
+    };
+
+    const handleGenerate = () => {
+        const projectsToGenerate = projects.filter(p => selectedProjects.includes(p.id));
+        if (projectsToGenerate.length === 0) return;
+        setIsGenerating(true);
+        setGenerationState({ progress: 0, current: 1, total: projectsToGenerate.length });
+    };
+
+    React.useEffect(() => {
+        if (isGenerating) {
+            const interval = setInterval(() => {
+                setGenerationState(prev => {
+                    const newProgress = prev.progress + 2;
+                    if (newProgress >= 100) {
+                        if (prev.current >= prev.total) {
+                            clearInterval(interval);
+                            const projectsToGenerate = projects.filter(p => selectedProjects.includes(p.id));
+                            const newVideos: AIGCVideo[] = projectsToGenerate.map(p => ({ id: `vid-gen-${p.id}`, title: p.name, thumbnailUrl: `https://picsum.photos/seed/gen${p.id}/200/120`, videoUrl: 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4' }));
+                            onVideosGenerated(newVideos);
+                            setIsGenerating(false);
+                            setSelectedProjects([]);
+                            return prev;
+                        }
+                        return { ...prev, progress: 0, current: prev.current + 1 };
+                    }
+                    return { ...prev, progress: newProgress };
+                });
+            }, 50);
+            return () => clearInterval(interval);
+        }
+    }, [isGenerating, projects, selectedProjects, onVideosGenerated]);
+
+    const handleUploadMedia = (type: 'image' | 'video') => {
+        const isVideo = type === 'video';
+        const newMedia: MediaSource = {
+            type: 'file', // Simulating a file upload
+            value: {
+                name: isVideo ? `user_video_${Date.now()}.mp4` : `user_image_${Date.now()}.jpg`,
+                size: 1234567,
+                previewUrl: isVideo 
+                    ? 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4'
+                    : `https://picsum.photos/seed/user-img-${Date.now()}/800/450`
+            }
+        };
+        onUpdateAIGCSettings({
+            ...aigcSettings,
+            userMedia: [...(aigcSettings.userMedia || []), newMedia]
+        });
+    };
+
+    const handleSelectMedia = (source: MediaSource) => {
+        if (!activeScene) return;
+        // FIX: Add explicit checks to ensure type-safe access to properties of the MediaSource union type.
+        let url: string | undefined;
+        let isVideo = false;
+
+        if (source.type === 'file') {
+            url = source.value.previewUrl;
+            isVideo = ['.mp4', '.webm', '.ogg'].some(ext => source.value.name.endsWith(ext));
+        } else if (source.type === 'url') {
+            url = source.value;
+            isVideo = ['.mp4', '.webm', '.ogg'].some(ext => url.endsWith(ext));
+        }
+
+        if (url) {
+            updateScene({ ...activeScene, background: { type: isVideo ? 'video' : 'image', value: url } });
+        }
+    };
+    
+    if (!activeProject || !activeScene) return <div className="p-8 text-center text-gray-500 dark:text-gray-400">Loading editor...</div>
+
+    const mediaGallery = [...mockBackgrounds.images, ...(aigcSettings.userMedia || [])];
+
+    const renderEditorContent = () => {
+        switch (editorTab) {
+            case 'avatar': return <div><h4 className="font-semibold text-gray-900 dark:text-white mb-3">Avatar</h4><div className="grid grid-cols-3 gap-2">{mockAvatars.map(avatar => <div key={avatar.id} onClick={() => updateScene({ ...activeScene, avatarId: avatar.id })} className={`rounded-lg overflow-hidden cursor-pointer border-2 ${activeScene.avatarId === avatar.id ? 'border-brand-primary ring-2 ring-brand-primary/50' : 'border-gray-200 dark:border-gray-700'}`}><img src={avatar.imageUrl} alt={avatar.name} className="w-full h-auto bg-gray-100 dark:bg-gray-700" /><p className="text-xs text-center bg-gray-200/50 dark:bg-gray-900/50 py-1 text-gray-900 dark:text-white">{avatar.name}</p></div>)}</div></div>
+            case 'voice': return <div> <h4 className="font-semibold text-gray-900 dark:text-white mb-3">Voice</h4> <select value={activeScene.voiceId} onChange={e => updateScene({ ...activeScene, voiceId: e.target.value })} className="w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600"> {mockVoices.map(voice => <option key={voice.id} value={voice.id}>{`${voice.name} (${voice.accent})`}</option>)}</select></div>
+            case 'script': {
+                const scriptContent = activeScene.script;
+                
+                const handleScriptTextChange = (versionId: string, newText: string) => {
+                    const newVersions = scriptContent.versions.map(v => 
+                        v.id === versionId ? { ...v, text: newText } : v
+                    );
+                    updateScene({
+                        ...activeScene,
+                        script: { ...scriptContent, versions: newVersions },
+                    });
+                };
+
+                const handleSelectVersion = (versionId: string) => {
+                    updateScene({
+                        ...activeScene,
+                        script: { ...scriptContent, selectedVersionId: versionId },
+                    });
+                };
+                
+                const handleAIPolish = async () => {
+                    if (!activeScene || isPolishing) return;
+                    const currentVersion = activeScene.script.versions.find(v => v.id === activeScene.script.selectedVersionId);
+                    if (!currentVersion || !currentVersion.text.trim()) {
+                        alert("Please write a script first.");
+                        return;
+                    }
+                    
+                    setIsPolishing(true);
+                    try {
+                        const polishedVariations = await polishScript(currentVersion.text);
+
+                        const newVersions = polishedVariations.map((text, i) => ({
+                            id: `v-polished-${Date.now()}-${i}`,
+                            text,
+                        }));
+                        
+                        updateScene({
+                            ...activeScene,
+                            script: {
+                                ...activeScene.script,
+                                versions: [...activeScene.script.versions, ...newVersions],
+                            },
+                        });
+                    } catch (error) {
+                        console.error("Failed to polish script:", error);
+                        alert("Sorry, we couldn't polish the script at this time.");
+                    } finally {
+                        setIsPolishing(false);
+                    }
+                };
+
+                return (
+                    <div>
+                        <div className="flex justify-between items-center mb-3">
+                            <h4 className="font-semibold text-gray-900 dark:text-white">Script</h4>
+                            <button 
+                                onClick={handleAIPolish}
+                                disabled={isPolishing}
+                                className="flex items-center gap-2 text-sm font-semibold bg-gray-200 dark:bg-gray-700 px-3 py-1.5 rounded-md hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors disabled:opacity-50"
+                            >
+                                <Icon className="h-4 w-4 text-brand-primary"><path strokeLinecap="round" strokeLinejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c.251.023.501.05.75.082a9.75 9.75 0 016 6.062c.313.958.5 1.965.5 3.004v.75a2.25 2.25 0 01-2.25 2.25H3.75a2.25 2.25 0 01-2.25-2.25v-.75c0-1.04.187-2.046.5-3.004a9.75 9.75 0 016-6.062 12.312 12.312 0 01.75-.082zM9.75 18.75c-2.482 0-4.72-1.22-6.16-3.223" /></Icon>
+                                {isPolishing ? 'Polishing...' : 'AI Polish'}
+                            </button>
+                        </div>
+                        <div className="space-y-4 max-h-[60vh] overflow-y-auto pr-2 -mr-4">
+                            {scriptContent.versions.map((version, index) => (
+                                <div key={version.id} className="flex items-start gap-3">
+                                    <input
+                                        type="radio"
+                                        name={`script-version-${activeScene.id}`}
+                                        id={`version-${version.id}`}
+                                        checked={scriptContent.selectedVersionId === version.id}
+                                        onChange={() => handleSelectVersion(version.id)}
+                                        className="mt-3 h-4 w-4 bg-gray-100 dark:bg-gray-900 border-gray-300 dark:border-gray-600 text-brand-primary focus:ring-brand-secondary"
+                                    />
+                                    <div className="flex-1">
+                                        <label htmlFor={`version-text-${version.id}`} className="sr-only">{`Script Version ${index + 1}`}</label>
+                                        <textarea
+                                            id={`version-text-${version.id}`}
+                                            rows={4}
+                                            value={version.text}
+                                            onChange={e => handleScriptTextChange(version.id, e.target.value)}
+                                            className="w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600 focus:ring-1 focus:ring-brand-primary focus:border-brand-primary"
+                                        />
+                                    </div>
+                                </div>
+                            ))}
+                        </div>
+                    </div>
+                );
+            }
+            case 'background': return (
+                <div className="flex flex-col h-full">
+                    <div className="flex-shrink-0">
+                        <h4 className="font-semibold text-gray-900 dark:text-white mb-3">Background</h4>
+                        <div className="flex gap-1 mb-3 p-1 bg-gray-200 dark:bg-gray-900 rounded-lg">
+                            <button onClick={() => { setBackgroundTab('color'); updateScene({ ...activeScene, background: { type: 'color', value: mockBackgrounds.colors[0] } })}} className={`flex-1 py-1 text-xs rounded-md ${backgroundTab === 'color' ? 'bg-brand-primary text-white shadow' : 'hover:bg-gray-300 dark:hover:bg-gray-700'}`}>Color</button>
+                            <button onClick={() => { setBackgroundTab('media'); if(mediaGallery.length > 0) handleSelectMedia(mediaGallery[0]); }} className={`flex-1 py-1 text-xs rounded-md ${backgroundTab === 'media' ? 'bg-brand-primary text-white shadow' : 'hover:bg-gray-300 dark:hover:bg-gray-700'}`}>Media</button>
+                        </div>
+                    </div>
+
+                    {backgroundTab === 'color' && (
+                        <div className="grid grid-cols-4 gap-2 flex-shrink-0">
+                            {mockBackgrounds.colors.map(color => (
+                                <button key={color} onClick={() => updateScene({ ...activeScene, background: { type: 'color', value: color } })} className={`h-12 rounded-md ${activeScene.background.value === color && activeScene.background.type === 'color' ? 'ring-2 ring-brand-primary ring-offset-2 ring-offset-gray-800' : ''}`} style={{ backgroundColor: color }} />
+                            ))}
+                        </div>
+                    )}
+
+                    {backgroundTab === 'media' && (
+                        <div className="flex flex-col flex-1 min-h-0">
+                             <div className="grid grid-cols-2 gap-2 mb-3 flex-shrink-0">
+                                <button onClick={() => handleUploadMedia('image')} className="w-full text-xs p-2 bg-gray-200 dark:bg-gray-700 rounded-md hover:bg-gray-300 dark:hover:bg-gray-600">Upload Image</button>
+                                <button onClick={() => handleUploadMedia('video')} className="w-full text-xs p-2 bg-gray-200 dark:bg-gray-700 rounded-md hover:bg-gray-300 dark:hover:bg-gray-600">Upload Video</button>
+                            </div>
+                            <div className="flex-1 overflow-y-auto pr-2 -mr-4">
+                                <div className="grid grid-cols-2 gap-3">
+                                    {mediaGallery.map((media, index) => {
+                                        let url: string | undefined;
+                                        let isVideo = false;
+                                        // FIX: Use explicit checks to ensure type-safe access to properties of the MediaSource union type.
+                                        if (media.type === 'file') {
+                                            url = media.value.previewUrl;
+                                            isVideo = ['.mp4', '.webm', '.ogg'].some(ext => media.value.name.endsWith(ext));
+                                        } else if (media.type === 'url') {
+                                            url = media.value;
+                                            isVideo = ['.mp4', '.webm', '.ogg'].some(ext => url.endsWith(ext));
+                                        }
+
+                                        if (!url) return null;
+
+                                        const thumbnailUrl = isVideo ? `https://picsum.photos/seed/vid-thumb-${index}/200` : url;
+
+                                        return (
+                                            <button key={`${url}-${index}`} onClick={() => handleSelectMedia(media)} className={`relative aspect-[16/9] rounded-lg bg-cover bg-center group ${activeScene.background.value === url ? 'ring-2 ring-brand-primary ring-offset-2 ring-offset-gray-800' : ''}`}>
+                                                <img src={thumbnailUrl} className="w-full h-full object-cover rounded-md" alt="Media thumbnail"/>
+                                                {isVideo && <div className="absolute inset-0 bg-black/50 flex items-center justify-center"><Icon className="h-6 w-6 text-white"><path strokeLinecap="round" strokeLinejoin="round" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /><path strokeLinecap="round" strokeLinejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></Icon></div>}
+                                            </button>
+                                        )
+                                    })}
+                                </div>
+                            </div>
+                        </div>
+                    )}
+                </div>
+            );
+        }
+    }
+
+    return (
+        <>
+            {isGenerating && <GenerationProgress progress={generationState.progress} totalVideos={generationState.total} currentVideo={generationState.current} />}
+            <div className="flex flex-col h-full bg-gray-100 dark:bg-gray-900">
+                 <div className="flex items-center gap-2 p-2 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 overflow-x-auto flex-shrink-0">
+                    {projects.map(p => (
+                        <div key={p.id} className={`flex items-center gap-2 p-2 rounded-md text-sm cursor-pointer border ${activeProjectId === p.id ? 'bg-brand-primary/10 border-brand-primary text-brand-primary' : 'border-transparent hover:bg-gray-200/50 dark:hover:bg-gray-700'}`}>
+                            <input type="checkbox" checked={selectedProjects.includes(p.id)} onChange={() => {setSelectedProjects(prev => prev.includes(p.id) ? prev.filter(id => id !== p.id) : [...prev, p.id]);}} className="h-4 w-4 rounded bg-gray-100 dark:bg-gray-900 border-gray-300 dark:border-gray-600 text-brand-primary focus:ring-brand-secondary"/>
+                            {editingProjectId === p.id ? (
+                                <input
+                                    type="text"
+                                    value={editingProjectName}
+                                    onChange={e => setEditingProjectName(e.target.value)}
+                                    onBlur={handleSaveProjectName}
+                                    onKeyDown={e => {
+                                        if (e.key === 'Enter') handleSaveProjectName();
+                                        if (e.key === 'Escape') handleCancelEditingProjectName();
+                                    }}
+                                    autoFocus
+                                    className="bg-white dark:bg-gray-700 text-sm p-1 rounded-sm border border-brand-primary focus:outline-none"
+                                />
+                            ) : (
+                                <span onDoubleClick={() => handleStartEditingProjectName(p)} onClick={() => setActiveProjectId(p.id)} title="Double-click to rename">
+                                    {p.name}
+                                </span>
+                            )}
+                            <button onClick={() => deleteProject(p.id)} className="text-gray-400 dark:text-gray-500 hover:text-red-500">×</button>
+                        </div>
+                    ))}
+                    <button onClick={addProject} className="p-2 rounded-full h-8 w-8 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-500 dark:text-gray-400 text-lg flex-shrink-0">+</button>
+                </div>
+
+                <div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-3 flex items-center gap-3 overflow-x-auto flex-shrink-0">
+                    {activeProject.scenes.map((scene, index) => (
+                        <div key={scene.id} onClick={() => setActiveSceneId(scene.id)} className={`group relative p-2 rounded-lg cursor-pointer border-2 flex flex-col gap-2 items-center justify-center h-24 aspect-video flex-shrink-0 ${activeSceneId === scene.id ? 'border-brand-primary bg-gray-100 dark:bg-gray-700/50' : 'border-transparent bg-gray-50 dark:bg-gray-900/50 hover:bg-gray-100 dark:hover:bg-gray-700'}`}>
+                             <div className="w-full h-full bg-cover bg-center rounded" style={{background: scene.background.type === 'color' ? scene.background.value : `url(${scene.background.value}) center/cover`}}/>
+                             <p className="text-xs font-semibold text-gray-700 dark:text-gray-300">{`Scene ${index + 1}`}</p>
+                             <button onClick={e => { e.stopPropagation(); handleDeleteScene(scene.id);}} className="absolute -top-1 -right-1 h-5 w-5 bg-red-500 text-white rounded-full flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-opacity">×</button>
+                         </div>
+                     ))}
+                     <button onClick={handleAddScene} className="h-24 aspect-video flex-shrink-0 flex flex-col items-center justify-center bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg text-gray-500 dark:text-gray-400">
+                        <Icon className="h-8 w-8"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" /></Icon>
+                        <span className="text-xs mt-1">Add Scene</span>
+                    </button>
+                </div>
+                
+                <div className="flex flex-1 overflow-hidden">
+                    <aside className="w-96 bg-white dark:bg-gray-800 p-4 flex flex-col border-r border-gray-200 dark:border-gray-700">
+                        <Tabs tabs={['avatar', 'voice', 'script', 'background']} activeTab={editorTab} onTabClick={(t) => setEditorTab(t as any)} />
+                        <div className="flex-1 overflow-y-auto pt-6 pr-2 -mr-4">{renderEditorContent()}</div>
+                         <div className="mt-4 flex-shrink-0"><button onClick={handleGenerate} disabled={selectedProjects.length === 0} className="w-full bg-brand-primary text-white font-bold py-3 px-4 rounded-md hover:bg-brand-secondary transition-colors disabled:bg-gray-500 disabled:cursor-not-allowed">{`Generate ${selectedProjects.length} Video${selectedProjects.length !== 1 ? 's' : ''}`}</button></div>
+                    </aside>
+                    
+                    <main className="flex-1 flex flex-col items-center justify-center p-8 bg-gray-100 dark:bg-gray-900 overflow-hidden space-y-4">
+                         <div className="flex items-center gap-1 bg-gray-200 dark:bg-gray-800 p-1 rounded-lg">
+                            <button onClick={() => updateProject({...activeProject, aspectRatio: '16:9'})} className={`px-3 py-1 text-sm rounded-md flex items-center gap-2 ${activeProject.aspectRatio === '16:9' ? 'bg-brand-primary text-white' : 'hover:bg-gray-300 dark:hover:bg-gray-700'}`}>16:9</button>
+                            <button onClick={() => updateProject({...activeProject, aspectRatio: '9:16'})} className={`px-3 py-1 text-sm rounded-md flex items-center gap-2 ${activeProject.aspectRatio === '9:16' ? 'bg-brand-primary text-white' : 'hover:bg-gray-300 dark:hover:bg-gray-700'}`}>9:16</button>
+                        </div>
+                        <div className="w-full flex-1 flex items-center justify-center overflow-hidden">
+                            <VideoCanvas scene={activeScene} aspectRatio={activeProject.aspectRatio} onAvatarMove={pos => updateScene({ ...activeScene, avatarPosition: pos })} onAvatarScale={scale => updateScene({ ...activeScene, avatarScale: scale })}/>
+                        </div>
+                    </main>
+                </div>
+
+            </div>
+        </>
+    );
+};
+
+export default VideoCreator;

+ 24 - 0
components/ui/Icon.tsx

@@ -0,0 +1,24 @@
+import * as React from 'react';
+
+interface IconProps {
+    children: React.ReactNode;
+    className?: string;
+    // FIX: Add style prop to allow inline styling of the SVG icon.
+    style?: React.CSSProperties;
+}
+
+export const Icon: React.FC<IconProps> = ({ children, className, style }) => {
+    return (
+        <svg
+            xmlns="http://www.w3.org/2000/svg"
+            className={className || 'h-6 w-6'}
+            fill="none"
+            viewBox="0 0 24 24"
+            stroke="currentColor"
+            strokeWidth={2}
+            style={style}
+        >
+            {children}
+        </svg>
+    );
+};

+ 31 - 0
components/ui/Tabs.tsx

@@ -0,0 +1,31 @@
+import * as React from 'react';
+
+interface TabsProps<T extends string> {
+    tabs: T[] | readonly T[];
+    activeTab: T;
+    onTabClick: (tab: T) => void;
+    labels?: Record<T, string>;
+}
+
+export const Tabs = <T extends string>({ tabs, activeTab, onTabClick, labels }: TabsProps<T>) => {
+    return (
+        <div className="border-b border-gray-700">
+            <nav className="-mb-px flex space-x-8" aria-label="Tabs">
+                {tabs.map(tab => (
+                    <button
+                        key={tab}
+                        onClick={() => onTabClick(tab)}
+                        className={
+                            `${(activeTab === tab
+                                ? 'border-brand-primary text-brand-primary'
+                                : 'border-transparent text-gray-400 hover:text-gray-300 hover:border-gray-500')}
+                            whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm capitalize transition-colors`
+                        }
+                    >
+                        {labels ? labels[tab] : tab}
+                    </button>
+                ))}
+            </nav>
+        </div>
+    );
+};

+ 693 - 0
hooks/useI18n.tsx

@@ -0,0 +1,693 @@
+import * as React from 'react';
+import { enUS, ja, zhCN, ko } from 'date-fns/locale';
+// FIX: Import the 'Locale' type from 'date-fns' to resolve the 'Cannot find name' error.
+import type { Locale } from 'date-fns';
+
+export type Language = 'en' | 'ja' | 'zh' | 'ko';
+
+export const resources = {
+  en: {
+    translation: {
+      // General
+      'add': 'Add',
+      'cancel': 'Cancel',
+      'save': 'Save',
+      'delete': 'Delete',
+      'update': 'Update',
+      'edit': 'Edit',
+      'name': 'Name',
+      'pro': 'PRO',
+      'all': 'All',
+      'for': 'For',
+      'no_items_found': 'No items found.',
+      'scheduled': 'Scheduled',
+      'unscheduled': 'Unscheduled',
+      'open': 'Open',
+      'resolved': 'Resolved',
+      
+      // Sidebar
+      'nav.page': 'Page',
+      'nav.page.link': 'Link',
+      'nav.page.media': 'Media',
+      'nav.page.design': 'Design',
+      'nav.ai_assistant': 'AI Assistant',
+      'nav.ai_assistant.persona': 'Persona',
+      'nav.ai_assistant.knowledge': 'Knowledge',
+      'nav.ai_assistant.sensitivity': 'Sensitivity',
+      'nav.analytics': 'Analytics',
+      'nav.analytics.page': 'Page',
+      'nav.analytics.interactions': 'Interactions',
+      'nav.analytics.crm': 'CRM',
+      'nav.seo': 'SEO',
+      'nav.seo.short_links': 'Short Links',
+      'nav.seo.hosting': 'Enterprise Hosting',
+      'nav.seo.services': 'SEO Services',
+      'nav.aigc': 'AIGC',
+      'nav.aigc.creator': 'Creator',
+      'nav.aigc.news': 'News',
+      'nav.aigc.scheduler': 'Scheduler',
+      'nav.showcase': 'Showcase',
+      'nav.showcase.personal': 'Personal',
+      'nav.showcase.enterprise': 'Enterprise',
+      'sidebar.select_page': 'Select Page',
+      'sidebar.logout': 'Logout',
+      'sidebar.light_theme': 'Light',
+      'sidebar.dark_theme': 'Dark',
+      'sidebar.language': 'Language',
+
+      // App
+      'app.welcome': 'Welcome!',
+      'app.no_pages_prompt': "You don't have any pages yet. Let's create your first one.",
+      'app.create_first_page': 'Create Your First Page',
+      'app.import_success': "'{{name}}' has been added to your pages! You can now edit it.",
+      'app.videos_generated_success': "{{count}} video(s) generated successfully! You can find them in the Content Scheduler's video library.",
+      'app.form_submission_success': 'New form submission received! You can view it in the CRM.',
+      'app.no_page_selected': 'No Page Selected',
+      
+      // Page Builder
+      'page_builder.title': 'My Page',
+      'page_builder.url_prompt': 'Your public URL:',
+      'page_builder.share': 'Share',
+      'page_builder.personal_mode': 'Personal',
+      'page_builder.enterprise_mode': 'Enterprise',
+      'page_builder.exit_fullscreen': 'Exit Fullscreen',
+      
+      // Link Editor
+      'link_editor.add_block': 'Add Block',
+      'link_editor.header_text': 'Header Text',
+      'link_editor.alignment': 'Alignment',
+      'link_editor.title': 'Title',
+      'link_editor.url': 'URL',
+      'link_editor.thumbnail_url': 'Thumbnail URL (Optional)',
+      'link_editor.display_style': 'Display Style',
+      'link_editor.under_avatar': 'Under Avatar',
+      'link_editor.block_button': 'Block Button',
+      'link_editor.floating_widget': 'Floating Widget',
+      'link_editor.icon': 'Icon',
+      'link_editor.label': 'Label',
+      'link_editor.value': 'Value',
+      'link_editor.add_info_item': 'Add Info Item',
+      'link_editor.add_link': 'Add Link',
+      'link_editor.add_mock_links': 'Add Mock Links',
+      'link_editor.layout_mode': 'Layout Mode',
+      'link_editor.single_column': 'Single Column',
+      'link_editor.grid': 'Grid',
+      'link_editor.source': 'Source',
+      'link_editor.upload': 'Upload',
+      'link_editor.aigc_library': 'AIGC Library',
+      'link_editor.add_media': 'Add {{type}}',
+      'link_editor.add_mock_media': 'Add 3 Mock {{type}}s',
+      'link_editor.address': 'Address or Location',
+      'link_editor.map_preview': 'Map Preview',
+      'link_editor.image_overlay': 'Image Overlay',
+      'link_editor.background_image': 'Background Image',
+      'link_editor.button_label': 'Button Label',
+      'link_editor.email_address': 'Email Address',
+      'link_editor.phone_number': 'Phone Number',
+      'link_editor.display_mode': 'Display Mode',
+      'link_editor.label_only': 'Label Only',
+      'link_editor.label_and_value': 'Label + Value',
+      'link_editor.layout': 'Layout',
+      'link_editor.list': 'List',
+      'link_editor.content_source': 'Content Source',
+      'link_editor.aigc_lib': 'AIGC Library',
+      'link_editor.custom_urls': 'Custom URLs',
+      'link_editor.select_articles': 'Select Articles',
+      'link_editor.no_articles_selected': 'No articles selected.',
+      'link_editor.select_from_aigc': 'Select from AIGC Library',
+      'link_editor.add_url': 'Add URL',
+      'link_editor.add_mock_items': 'Add 3 Mock Items',
+      'link_editor.add_products_from_url': 'Add Products from URL',
+      'link_editor.add_from_urls': 'Add from URLs',
+      'link_editor.fetching': 'Fetching...',
+      'link_editor.add_sample_products': 'Or Add Sample Products',
+      'link_editor.add_award': 'Add Award',
+      'link_editor.subtitle': 'Subtitle (e.g., Awarded by)',
+      'link_editor.year': 'Year',
+      'link_editor.image_url': 'Image URL',
+      'link_editor.description': 'Description',
+      'link_editor.form_fields': 'Form Fields',
+      'link_editor.purpose_options': 'Purpose Options',
+      'link_editor.submit_button_text': 'Submit Button Text',
+      'link_editor.required': 'Required',
+      'link_editor.add_option': 'Add Option',
+      'link_editor.aigc_select_video': 'Select a Video from AIGC Library',
+      
+      // Design Editor
+      'design_editor.templates': 'Templates',
+      'design_editor.profile': 'Profile',
+      'design_editor.appearance': 'Appearance',
+      'design_editor.typography': 'Typography',
+      'design_editor.buttons': 'Buttons',
+      'design_editor.background': 'Background',
+      'design_editor.enterprise_layout': 'Enterprise Layout',
+      'design_editor.more_themes': 'More Themes...',
+      'design_editor.avatar': 'Avatar',
+      'design_editor.source': 'Source',
+      'design_editor.custom': 'Custom',
+      'design_editor.background_color': 'Background',
+      'design_editor.text_color': 'Text Color',
+      'design_editor.button_color': 'Button Color',
+      'design_editor.button_text': 'Button Text',
+      'design_editor.font_color': 'Font Color',
+      'design_editor.font_size': 'Font Size',
+      'design_editor.font_family': 'Font Family',
+      'design_editor.button_style': 'Button Style',
+      'design_editor.button_shape': 'Button Shape',
+      'design_editor.upload_image': 'Upload Image',
+      'design_editor.banner': 'Banner',
+      'design_editor.banner_type': 'Banner Type',
+      'design_editor.banner_width': 'Banner Width',
+      'design_editor.contained': 'Contained',
+      'design_editor.full_width': 'Full Width',
+      'design_editor.banner_height': 'Banner Height (px)',
+      'design_editor.banner_color': 'Banner Color',
+      'design_editor.side_nav': 'Side Navigation',
+      'design_editor.position': 'Position',
+      'design_editor.normal': 'Normal',
+      'design_editor.float_top': 'Float Top',
+      'design_editor.float_center': 'Float Center',
+      'design_editor.background_style': 'Background Style',
+      'design_editor.compact': 'Compact',
+      'design_editor.full_height': 'Full Height',
+      'design_editor.active_link': 'Active Link',
+      'design_editor.hover_bg': 'Hover BG',
+      'design_editor.hover_text': 'Hover Text',
+      'design_editor.chat_widget': 'Chat Widget',
+      'design_editor.icon_color': 'Icon Color',
+      'design_editor.header_bg': 'Header BG',
+      'design_editor.header_text': 'Header Text',
+      'design_editor.panel_bg': 'Panel BG',
+      'design_editor.user_message': 'User Message',
+      'design_editor.user_text': 'User Text',
+      'design_editor.ai_message': 'AI Message',
+      'design_editor.ai_text': 'AI Text',
+      
+      // AI Assistant
+      'ai_assistant.title': 'AI Assistant',
+      'ai_assistant.subtitle': "Configure your AI's behavior and knowledge.",
+      'ai_assistant.test_title': 'Test your Assistant',
+      'ai_assistant.save_changes': 'Save Changes',
+      'ai_assistant.save_success': 'AI Assistant settings saved successfully!',
+      'ai_assistant.system_instruction': 'System Instruction',
+      'ai_assistant.system_instruction_desc': "Define the AI's core identity, personality, and instructions.",
+      'ai_assistant.voice': 'Voice',
+      'ai_assistant.voice_desc': 'Select a voice for audio responses.',
+      'ai_assistant.clone': 'Clone',
+      'ai_assistant.clone_desc': 'Create a clone of your own voice.',
+      'ai_assistant.clone_voice': 'Clone Voice',
+      'ai_assistant.language': 'Language',
+      'ai_assistant.language_desc': 'The primary language for the AI.',
+      'ai_assistant.conversation_style': 'Conversation Style',
+      'ai_assistant.conversation_style_desc': 'The overall tone of the AI.',
+      'ai_assistant.add_knowledge': 'Add Knowledge Source',
+      'ai_assistant.no_knowledge': 'No knowledge sources',
+      'ai_assistant.no_knowledge_desc': 'Add files or web pages for your AI to learn from.',
+      'ai_assistant.forbidden_user': 'Forbidden User Topics',
+      'ai_assistant.forbidden_user_desc': 'The AI will refuse to answer questions containing these keywords.',
+      'ai_assistant.forbidden_ai': 'Forbidden AI Responses',
+      'ai_assistant.forbidden_ai_desc': 'The AI will avoid using these keywords in its responses.',
+
+      // Analytics
+      'analytics.page.title': 'Page Analytics',
+      'analytics.page.last_days': 'Last {{range}} Days',
+      'analytics.page.all_time': 'All Time',
+      'analytics.page.views': 'Views',
+      'analytics.page.clicks': 'Clicks',
+      'analytics.page.ctr': 'Click-Through Rate',
+      'analytics.page.unique_visitors': 'Unique Visitors',
+      'analytics.page.performance': 'Performance',
+      'analytics.page.device_breakdown': 'Device Breakdown',
+      'analytics.page.top_locations': 'Top Locations',
+      'analytics.page.top_referrers': 'Top Referrers',
+      'analytics.interactions.title': 'Customer Interaction Analytics',
+      'analytics.interactions.search_placeholder': 'Search conversations...',
+      'analytics.interactions.date_label': 'Date:',
+      'analytics.interactions.last_7_days': 'Last 7 days',
+      'analytics.interactions.last_28_days': 'Last 28 days',
+      'analytics.interactions.total_conversations': 'Total Conversations',
+      'analytics.interactions.resolution_rate': 'Resolution Rate',
+      'analytics.interactions.avg_interactions': 'Avg. Interactions',
+      'analytics.interactions.avg_first_response': 'Avg. First Response',
+      'analytics.interactions.visitor_breakdown': 'Visitor Breakdown',
+      'analytics.interactions.common_topics': 'Common Topics',
+      'analytics.interactions.visitor': 'Visitor',
+      'analytics.interactions.conversations': 'Conversations',
+      'analytics.interactions.mark_resolved': 'Mark as Resolved',
+      'analytics.interactions.no_conversations': 'No conversations found for the selected filters.',
+      'analytics.interactions.edit_response_title': 'Edit AI Response',
+      'analytics.interactions.save_and_update': 'Save & Update Model',
+      'analytics.interactions.update_success': 'Response updated successfully. The AI will learn from this correction.',
+      'analytics.crm.title': 'Visitor CRM',
+      'analytics.crm.visitor_id': 'Visitor ID',
+      'analytics.crm.last_seen': 'Last Seen',
+      'analytics.crm.visits': 'Visits',
+      
+      // AIGC
+      'aigc.creator.generate_videos_progress': 'Generating Videos...',
+      'aigc.creator.processing_video': 'Processing video {{current}} of {{total}}. This may take a moment.',
+      'aigc.creator.my_first_video': 'My First Video',
+      'aigc.creator.untitled_video': 'Untitled Video {{count}}',
+      'aigc.creator.at_least_one_scene': 'A project must have at least one scene.',
+      'aigc.creator.script_first': 'Please write a script first.',
+      'aigc.creator.polish_error': "Sorry, we couldn't polish the script at this time.",
+      'aigc.creator.generate_videos': 'Generate {{count}} Video',
+      'aigc.creator.generate_videos_plural': 'Generate {{count}} Videos',
+      'aigc.creator.add_scene': 'Add Scene',
+      'aigc.creator.script': 'Script',
+      'aigc.creator.ai_polish': 'AI Polish',
+      'aigc.creator.polishing': 'Polishing...',
+      'aigc.creator.background': 'Background',
+      'aigc.creator.media': 'Media',
+      'aigc.creator.upload_image': 'Upload Image',
+      'aigc.creator.upload_video': 'Upload Video',
+      'aigc.news.title': 'News Library',
+      'aigc.news.create': 'Create New Article',
+      'aigc.news.edit': 'Edit Article',
+      'aigc.news.save_lib': 'Save to Library',
+      'aigc.news.save_changes': 'Save Changes',
+      'aigc.news.ai_tools': 'AI & Import Tools',
+      'aigc.news.generate_ai': 'Generate with AI',
+      'aigc.news.import_url': 'Import from URL',
+      'aigc.news.enter_topic': 'Enter a topic...',
+      'aigc.news.enter_url': 'https://example.com/article',
+      'aigc.news.generate': 'Generate',
+      'aigc.news.generating': '...',
+      'aigc.news.title_req': 'Title is required.',
+      'aigc.news.summary': 'Summary',
+      'aigc.news.full_content': 'Full Content',
+      'aigc.news.delete_confirm': 'Are you sure you want to delete this article?',
+      'aigc.news.empty_title': 'Select an article to view',
+      'aigc.news.empty_desc': 'Or create a new one to get started.',
+      'aigc.scheduler.update_post': 'Update Post',
+      'aigc.scheduler.schedule_post': 'Schedule Post',
+      'aigc.scheduler.time': 'Time',
+      'aigc.scheduler.caption': 'Caption',
+      'aigc.scheduler.post_to': 'Post to',
+      'aigc.scheduler.schedule': 'Schedule',
+      'aigc.scheduler.video_library': 'Video Library',
+      'aigc.scheduler.no_videos_filter': 'No videos match the current filter.',
+      
+      // Auth
+      'auth.welcome_back': 'Welcome Back',
+      'auth.create_account': 'Create an Account',
+      'auth.signin_prompt': 'Sign in to continue to your pages.',
+      'auth.signup_prompt': 'Start building your presence in seconds.',
+      'auth.email': 'Email Address',
+      'auth.password': 'Password',
+      'auth.processing': 'Processing...',
+      'auth.signin': 'Sign In',
+      'auth.signup': 'Sign Up',
+      'auth.no_account': "Don't have an account?",
+      'auth.has_account': 'Already have an account?',
+      
+      // Modals
+      'modal.share.title': 'Share Your Page',
+      'modal.share.pc_version': 'PC Version',
+      'modal.share.mobile_version': 'Mobile Version',
+      'modal.share.copy': 'Copy',
+      'modal.share.copied': 'Copied!',
+      'modal.pages.title': 'Select a Page',
+      'modal.pages.create_new': 'Create New Page',
+
+      // SEO & Hosting
+      'seo.short_links.title': 'Short Link',
+      'seo.short_links.subtitle': 'Customize your main page URL.',
+      'seo.short_links.your_url': 'Your GreenPage URL',
+      'seo.short_links.your_url_desc': 'This is your main link that you can share anywhere. Keep it short and memorable.',
+      'seo.hosting.title': 'Enterprise Hosting',
+      'seo.hosting.subtitle': 'Manage your domain, hosting, and subscription plan.',
+      'seo.hosting.custom_domain': 'Connect a Custom Domain',
+      'seo.hosting.custom_domain_desc': 'Use your own domain for a more professional look (e.g., links.yourcompany.com).',
+      'seo.hosting.connect_domain': 'Connect Domain',
+      'seo.hosting.dns_note': "You'll need to update your DNS records after connecting. We'll provide the instructions.",
+      'seo.hosting.subscription': 'Managed Hosting & Subscription',
+      'seo.hosting.subscription_desc': 'Choose a plan that fits your needs. All enterprise plans include hosting.',
+      'seo.hosting.current_plan': 'Current Plan',
+      'seo.hosting.select_plan': 'Select Plan',
+      'seo.services.title': 'SEO Services',
+      'seo.services.subtitle': "Boost your page's visibility and attract more organic traffic.",
+      'seo.services.keyword_analysis': 'Keyword Analysis & Optimization',
+      'seo.services.keyword_analysis_desc': 'Discover high-impact keywords for your content strategy. Enter a topic to get started.',
+      'seo.services.analyze': 'Analyze',
+      'seo.services.keyword_results_placeholder': 'Keyword analysis results will appear here.',
+      'seo.services.ai_content': 'AI Content with SEO',
+      'seo.services.ai_content_desc': 'Supercharge your content creation. When you generate news articles with AI, our SEO service will automatically analyze your topic and assemble relevant long-tail keywords to seamlessly integrate into the content. This helps your articles rank better on search engines.',
+      'seo.services.how_it_works': 'How it works:',
+      'seo.services.step1': 'You provide a topic in the AIGC News Creator.',
+      'seo.services.step2': 'Our AI analyzes the topic for primary and related keywords.',
+      'seo.services.step3': 'It generates a high-quality article, naturally weaving in these keywords.',
+      'seo.services.step4': 'Your content is published, optimized for search engines from the start.',
+      'seo.services.go_to_news_creator': 'Go to News Creator',
+    },
+  },
+  ja: {
+    translation: {
+      'add': '追加',
+      'cancel': 'キャンセル',
+      'save': '保存',
+      'delete': '削除',
+      'update': '更新',
+      'edit': '編集',
+      'name': '名前',
+      'pro': 'プロ',
+      'all': 'すべて',
+      'for': '対象',
+      'no_items_found': 'アイテムが見つかりません。',
+      'scheduled': '予約済み',
+      'unscheduled': '未予約',
+      'open': '未解決',
+      'resolved': '解決済み',
+      'nav.page': 'ページ',
+      'nav.page.link': 'リンク',
+      'nav.page.media': 'メディア',
+      'nav.page.design': 'デザイン',
+      'nav.ai_assistant': 'AIアシスタント',
+      'nav.ai_assistant.persona': 'ペルソナ',
+      'nav.ai_assistant.knowledge': '知識',
+      'nav.ai_assistant.sensitivity': '感度',
+      'nav.analytics': 'アナリティクス',
+      'nav.analytics.page': 'ページ',
+      'nav.analytics.interactions': 'インタラクション',
+      'nav.analytics.crm': 'CRM',
+      'nav.seo': 'SEO',
+      'nav.seo.short_links': '短縮リンク',
+      'nav.seo.hosting': '法人ホスティング',
+      'nav.seo.services': 'SEOサービス',
+      'nav.aigc': 'AIGC',
+      'nav.aigc.creator': 'クリエーター',
+      'nav.aigc.news': 'ニュース',
+      'nav.aigc.scheduler': 'スケジューラ',
+      'nav.showcase': 'ショーケース',
+      'nav.showcase.personal': '個人',
+      'nav.showcase.enterprise': '法人',
+      'sidebar.select_page': 'ページを選択',
+      'sidebar.logout': 'ログアウト',
+      'sidebar.light_theme': 'ライト',
+      'sidebar.dark_theme': 'ダーク',
+      'sidebar.language': '言語',
+      'app.welcome': 'ようこそ!',
+      'app.no_pages_prompt': 'まだページがありません。最初のページを作成しましょう。',
+      'app.create_first_page': '最初のページを作成',
+      'app.import_success': '「{{name}}」があなたのページに追加されました!編集可能です。',
+      'app.videos_generated_success': '{{count}}本の動画が正常に生成されました!コンテンツスケジューラの動画ライブラリにあります。',
+      'app.form_submission_success': '新しいフォーム送信を受け取りました!CRMで表示できます。',
+      'app.no_page_selected': 'ページが選択されていません',
+      'page_builder.title': 'マイページ',
+      'page_builder.url_prompt': 'あなたの公開URL:',
+      'page_builder.share': '共有',
+      'page_builder.personal_mode': '個人',
+      'page_builder.enterprise_mode': '法人',
+      'page_builder.exit_fullscreen': '全画面表示を終了',
+      'link_editor.add_block': 'ブロックを追加',
+      'design_editor.templates': 'テンプレート',
+      'design_editor.profile': 'プロフィール',
+      'design_editor.appearance': '外観',
+      'design_editor.typography': 'タイポグラフィ',
+      'design_editor.buttons': 'ボタン',
+      'design_editor.background': '背景',
+      'design_editor.enterprise_layout': '法人レイアウト',
+      'design_editor.more_themes': '他のテーマ...',
+      'ai_assistant.title': 'AIアシスタント',
+      'ai_assistant.subtitle': 'AIの動作と知識を設定します。',
+      'ai_assistant.test_title': 'アシスタントをテスト',
+      'ai_assistant.save_changes': '変更を保存',
+      'ai_assistant.save_success': 'AIアシスタントの設定が正常に保存されました!',
+      'analytics.page.title': 'ページ分析',
+      'analytics.interactions.title': '顧客インタラクション分析',
+      'analytics.crm.title': '訪問者CRM',
+      'aigc.creator.title': 'ビデオクリエーター',
+      'aigc.news.title': 'ニュースクリエーター',
+      'aigc.scheduler.title': 'コンテンツスケジューラ',
+      'auth.welcome_back': 'おかえりなさい',
+      'auth.create_account': 'アカウントを作成',
+      'auth.signin_prompt': 'あなたのページに進むためにサインインしてください。',
+      'auth.signup_prompt': '数秒であなたのプレゼンスを構築し始めましょう。',
+      'auth.email': 'メールアドレス',
+      'auth.password': 'パスワード',
+      'auth.processing': '処理中...',
+      'auth.signin': 'サインイン',
+      'auth.signup': 'サインアップ',
+      'auth.no_account': 'アカウントをお持ちでないですか?',
+      'auth.has_account': 'すでにアカウントをお持ちですか?',
+      'modal.share.title': 'ページを共有',
+      'modal.share.pc_version': 'PC版',
+      'modal.share.mobile_version': 'モバイル版',
+      'modal.share.copy': 'コピー',
+      'modal.share.copied': 'コピーしました!',
+      'modal.pages.title': 'ページを選択',
+      'modal.pages.create_new': '新しいページを作成',
+    }
+  },
+  zh: {
+    translation: {
+      'add': '添加',
+      'cancel': '取消',
+      'save': '保存',
+      'delete': '删除',
+      'update': '更新',
+      'edit': '编辑',
+      'name': '名称',
+      'pro': '专业版',
+      'all': '全部',
+      'for': '为',
+      'no_items_found': '未找到任何项目。',
+      'scheduled': '已安排',
+      'unscheduled': '未安排',
+      'open': '待处理',
+      'resolved': '已解决',
+      'nav.page': '页面',
+      'nav.page.link': '链接',
+      'nav.page.media': '媒体',
+      'nav.page.design': '设计',
+      'nav.ai_assistant': 'AI 助手',
+      'nav.ai_assistant.persona': '角色',
+      'nav.ai_assistant.knowledge': '知识库',
+      'nav.ai_assistant.sensitivity': '敏感度',
+      'nav.analytics': '分析',
+      'nav.analytics.page': '页面',
+      'nav.analytics.interactions': '互动',
+      'nav.analytics.crm': '客户关系管理',
+      'nav.seo': 'SEO',
+      'nav.seo.short_links': '短链接',
+      'nav.seo.hosting': '企业托管',
+      'nav.seo.services': 'SEO 服务',
+      'nav.aigc': 'AIGC',
+      'nav.aigc.creator': '创作器',
+      'nav.aigc.news': '新闻',
+      'nav.aigc.scheduler': '调度器',
+      'nav.showcase': '展示',
+      'nav.showcase.personal': '个人',
+      'nav.showcase.enterprise': '企业',
+      'sidebar.select_page': '选择页面',
+      'sidebar.logout': '登出',
+      'sidebar.light_theme': '浅色',
+      'sidebar.dark_theme': '深色',
+      'sidebar.language': '语言',
+      'app.welcome': '欢迎!',
+      'app.no_pages_prompt': '您还没有任何页面。让我们来创建您的第一个页面吧。',
+      'app.create_first_page': '创建您的第一个页面',
+      'app.import_success': '“{{name}}” 已添加到您的页面中!您现在可以对其进行编辑。',
+      'app.videos_generated_success': '{{count}} 个视频生成成功!您可以在内容调度器的视频库中找到它们。',
+      'app.form_submission_success': '收到新的表单提交!您可以在 CRM 中查看。',
+      'app.no_page_selected': '未选择页面',
+      'page_builder.title': '我的页面',
+      'page_builder.url_prompt': '您的公开 URL:',
+      'page_builder.share': '分享',
+      'page_builder.personal_mode': '个人',
+      'page_builder.enterprise_mode': '企业',
+      'page_builder.exit_fullscreen': '退出全屏',
+      'link_editor.add_block': '添加区块',
+      'design_editor.templates': '模板',
+      'design_editor.profile': '个人资料',
+      'design_editor.appearance': '外观',
+      'design_editor.typography': '字体',
+      'design_editor.buttons': '按钮',
+      'design_editor.background': '背景',
+      'design_editor.enterprise_layout': '企业布局',
+      'design_editor.more_themes': '更多主题...',
+      'ai_assistant.title': 'AI 助手',
+      'ai_assistant.subtitle': '配置您的 AI 的行为和知识。',
+      'ai_assistant.test_title': '测试您的助手',
+      'ai_assistant.save_changes': '保存更改',
+      'ai_assistant.save_success': 'AI 助手设置已成功保存!',
+      'analytics.page.title': '页面分析',
+      'analytics.interactions.title': '客户互动分析',
+      'analytics.crm.title': '访客 CRM',
+      'aigc.creator.title': '视频创作器',
+      'aigc.news.title': '新闻创作器',
+      'aigc.scheduler.title': '内容调度器',
+      'auth.welcome_back': '欢迎回来',
+      'auth.create_account': '创建账户',
+      'auth.signin_prompt': '登录以继续访问您的页面。',
+      'auth.signup_prompt': '在几秒钟内开始建立您的在线形象。',
+      'auth.email': '电子邮箱地址',
+      'auth.password': '密码',
+      'auth.processing': '处理中...',
+      'auth.signin': '登录',
+      'auth.signup': '注册',
+      'auth.no_account': '还没有账户?',
+      'auth.has_account': '已经有账户了?',
+      'modal.share.title': '分享您的页面',
+      'modal.share.pc_version': '桌面版',
+      'modal.share.mobile_version': '移动版',
+      'modal.share.copy': '复制',
+      'modal.share.copied': '已复制!',
+      'modal.pages.title': '选择一个页面',
+      'modal.pages.create_new': '创建新页面',
+    }
+  },
+  ko: {
+    translation: {
+      'add': '추가',
+      'cancel': '취소',
+      'save': '저장',
+      'delete': '삭제',
+      'update': '업데이트',
+      'edit': '편집',
+      'name': '이름',
+      'pro': '프로',
+      'all': '전체',
+      'for': '대상',
+      'no_items_found': '항목을 찾을 수 없습니다.',
+      'scheduled': '예약됨',
+      'unscheduled': '예약 안됨',
+      'open': '진행 중',
+      'resolved': '해결됨',
+      'nav.page': '페이지',
+      'nav.page.link': '링크',
+      'nav.page.media': '미디어',
+      'nav.page.design': '디자인',
+      'nav.ai_assistant': 'AI 어시스턴트',
+      'nav.ai_assistant.persona': '페르소나',
+      'nav.ai_assistant.knowledge': '지식',
+      'nav.ai_assistant.sensitivity': '민감도',
+      'nav.analytics': '분석',
+      'nav.analytics.page': '페이지',
+      'nav.analytics.interactions': '상호작용',
+      'nav.analytics.crm': 'CRM',
+      'nav.seo': 'SEO',
+      'nav.seo.short_links': '단축 링크',
+      'nav.seo.hosting': '기업 호스팅',
+      'nav.seo.services': 'SEO 서비스',
+      'nav.aigc': 'AIGC',
+      'nav.aigc.creator': '크리에이터',
+      'nav.aigc.news': '뉴스',
+      'nav.aigc.scheduler': '스케줄러',
+      'nav.showcase': '쇼케이스',
+      'nav.showcase.personal': '개인',
+      'nav.showcase.enterprise': '기업',
+      'sidebar.select_page': '페이지 선택',
+      'sidebar.logout': '로그아웃',
+      'sidebar.light_theme': '라이트',
+      'sidebar.dark_theme': '다크',
+      'sidebar.language': '언어',
+      'app.welcome': '환영합니다!',
+      'app.no_pages_prompt': '아직 페이지가 없습니다. 첫 페이지를 만들어 보세요.',
+      'app.create_first_page': '첫 페이지 만들기',
+      'app.import_success': "'{{name}}'이(가) 페이지에 추가되었습니다! 이제 편집할 수 있습니다.",
+      'app.videos_generated_success': '{{count}}개의 동영상이 성공적으로 생성되었습니다! 콘텐츠 스케줄러의 비디오 라이브러리에서 찾을 수 있습니다.',
+      'app.form_submission_success': '새로운 양식 제출을 받았습니다! CRM에서 볼 수 있습니다.',
+      'app.no_page_selected': '선택된 페이지 없음',
+      'page_builder.title': '내 페이지',
+      'page_builder.url_prompt': '공개 URL:',
+      'page_builder.share': '공유',
+      'page_builder.personal_mode': '개인',
+      'page_builder.enterprise_mode': '기업',
+      'page_builder.exit_fullscreen': '전체 화면 종료',
+      'link_editor.add_block': '블록 추가',
+      'design_editor.templates': '템플릿',
+      'design_editor.profile': '프로필',
+      'design_editor.appearance': '모양',
+      'design_editor.typography': '타이포그래피',
+      'design_editor.buttons': '버튼',
+      'design_editor.background': '배경',
+      'design_editor.enterprise_layout': '기업 레이아웃',
+      'design_editor.more_themes': '더 많은 테마...',
+      'ai_assistant.title': 'AI 어시스턴트',
+      'ai_assistant.subtitle': 'AI의 행동과 지식을 구성하세요.',
+      'ai_assistant.test_title': '어시스턴트 테스트',
+      'ai_assistant.save_changes': '변경 사항 저장',
+      'ai_assistant.save_success': 'AI 어시스턴트 설정이 성공적으로 저장되었습니다!',
+      'analytics.page.title': '페이지 분석',
+      'analytics.interactions.title': '고객 상호작용 분석',
+      'analytics.crm.title': '방문자 CRM',
+      'aigc.creator.title': '비디오 크리에이터',
+      'aigc.news.title': '뉴스 크리에이터',
+      'aigc.scheduler.title': '콘텐츠 스케줄러',
+      'auth.welcome_back': '다시 오신 것을 환영합니다',
+      'auth.create_account': '계정 만들기',
+      'auth.signin_prompt': '페이지를 계속 보려면 로그인하세요.',
+      'auth.signup_prompt': '몇 초 만에 당신의 존재감을 구축하세요.',
+      'auth.email': '이메일 주소',
+      'auth.password': '비밀번호',
+      'auth.processing': '처리 중...',
+      'auth.signin': '로그인',
+      'auth.signup': '가입하기',
+      'auth.no_account': '계정이 없으신가요?',
+      'auth.has_account': '이미 계정이 있으신가요?',
+      'modal.share.title': '페이지 공유',
+      'modal.share.pc_version': 'PC 버전',
+      'modal.share.mobile_version': '모바일 버전',
+      'modal.share.copy': '복사',
+      'modal.share.copied': '복사됨!',
+      'modal.pages.title': '페이지 선택',
+      'modal.pages.create_new': '새 페이지 만들기',
+    }
+  },
+};
+
+export const dateLocales: Record<Language, Locale> = {
+  en: enUS,
+  ja: ja,
+  zh: zhCN,
+  ko: ko,
+};
+
+type TranslationContextType = {
+  language: Language;
+  setLanguage: (language: Language) => void;
+  t: (key: string, options?: { [key: string]: string | number }) => string;
+  dateLocale: Locale;
+};
+
+const LanguageContext = React.createContext<TranslationContextType | undefined>(undefined);
+
+export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+  const [language, setLanguageState] = React.useState<Language>(() => {
+    const savedLang = localStorage.getItem('greenpage_lang');
+    return (savedLang && Object.keys(resources).includes(savedLang)) ? savedLang as Language : 'en';
+  });
+
+  const setLanguage = (lang: Language) => {
+    localStorage.setItem('greenpage_lang', lang);
+    setLanguageState(lang);
+  };
+  
+  const t = (key: string, options?: { [key: string]: string | number }) => {
+    const langResources = resources[language] || resources.en;
+    let text = (langResources.translation as any)[key] || key;
+    if (options) {
+      Object.keys(options).forEach(k => {
+        text = text.replace(new RegExp(`\\{\\{${k}\\}\\}`, 'g'), String(options[k]));
+      });
+    }
+    return text;
+  };
+
+  const dateLocale = dateLocales[language] || enUS;
+
+  return (
+    <LanguageContext.Provider value={{ language, setLanguage, t, dateLocale }}>
+      {children}
+    </LanguageContext.Provider>
+  );
+};
+
+export const useTranslation = () => {
+  const context = React.useContext(LanguageContext);
+  if (context === undefined) {
+    throw new Error('useTranslation must be used within a LanguageProvider');
+  }
+  return context;
+};

+ 74 - 0
index.html

@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>GreenPage AI Suite</title>
+    <script src="https://cdn.tailwindcss.com"></script>
+    <script>
+      tailwind.config = {
+        darkMode: 'class', // Enable class-based dark mode
+        theme: {
+          extend: {
+            colors: {
+              'brand-primary': '#10b981',
+              'brand-secondary': '#059669',
+              'gray-900': '#111827',
+              'gray-800': '#1f2937',
+              'gray-700': '#374151',
+              'gray-600': '#4b5563',
+              'gray-500': '#6b7280',
+              'gray-400': '#9ca3af',
+              'gray-300': '#d1d5db',
+              'gray-200': '#e5e7eb',
+              'gray-100': '#f3f4f6',
+              'white': '#ffffff',
+            },
+            keyframes: {
+                breathing: {
+                    '0%, 100%': { transform: 'scale(1)', boxShadow: '0 0 0 0 rgba(16, 185, 129, 0.7)' },
+                    '50%': { transform: 'scale(1.1)', boxShadow: '0 0 10px 10px rgba(16, 185, 129, 0)' },
+                }
+            },
+            animation: {
+                breathing: 'breathing 2.5s ease-in-out infinite',
+            }
+          }
+        }
+      }
+    </script>
+<style>
+  select {
+    -webkit-appearance: none;
+    -moz-appearance: none;
+    appearance: none;
+    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
+    background-position: right 0.5rem center;
+    background-repeat: no-repeat;
+    background-size: 1.5em 1.5em;
+    padding-right: 2.5rem;
+  }
+  html.dark select {
+     background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%239ca3af' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
+  }
+</style>
+<script type="importmap">
+{
+  "imports": {
+    "react": "https://aistudiocdn.com/react@^19.1.1",
+    "react-dom/": "https://aistudiocdn.com/react-dom@^19.1.1/",
+    "react/": "https://aistudiocdn.com/react@^19.1.1/",
+    "recharts": "https://aistudiocdn.com/recharts@^3.1.2",
+    "date-fns": "https://aistudiocdn.com/date-fns@^4.1.0",
+    "date-fns/": "https://aistudiocdn.com/date-fns@^4.1.0/"
+  }
+}
+</script>
+<link rel="stylesheet" href="/index.css">
+</head>
+<body class="bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
+<div id="root"></div>
+<script type="module" src="/index.tsx"></script>
+</body>
+</html>

+ 15 - 0
index.tsx

@@ -0,0 +1,15 @@
+import * as React from 'react';
+import { createRoot } from 'react-dom/client';
+import App from './App';
+
+const rootElement = document.getElementById('root');
+if (!rootElement) {
+  throw new Error("Could not find root element to mount to");
+}
+
+const root = createRoot(rootElement);
+root.render(
+  <React.StrictMode>
+    <App />
+  </React.StrictMode>
+);

+ 7 - 0
metadata.json

@@ -0,0 +1,7 @@
+{
+  "name": "GreenPage AI Suite",
+  "description": "An all-in-one platform to build your personal page, create an AI assistant, analyze traffic, manage links, and schedule AI-generated content for your social media.",
+  "requestFramePermissions": [
+    "microphone"
+  ]
+}

+ 23 - 0
package.json

@@ -0,0 +1,23 @@
+{
+  "name": "greenpage-ai-suite",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "react": "^19.1.1",
+    "react-dom": "^19.1.1",
+    "recharts": "^3.1.2",
+    "date-fns": "^4.1.0"
+  },
+  "devDependencies": {
+    "@types/node": "^22.14.0",
+    "@vitejs/plugin-react": "^5.0.0",
+    "typescript": "~5.8.2",
+    "vite": "^6.2.0"
+  }
+}

+ 1403 - 0
pnpm-lock.yaml

@@ -0,0 +1,1403 @@
+lockfileVersion: '9.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+importers:
+
+  .:
+    dependencies:
+      date-fns:
+        specifier: ^4.1.0
+        version: 4.1.0
+      react:
+        specifier: ^19.1.1
+        version: 19.1.1
+      react-dom:
+        specifier: ^19.1.1
+        version: 19.1.1(react@19.1.1)
+      recharts:
+        specifier: ^3.1.2
+        version: 3.2.1(react-dom@19.1.1(react@19.1.1))(react-is@19.1.1)(react@19.1.1)(redux@5.0.1)
+    devDependencies:
+      '@types/node':
+        specifier: ^22.14.0
+        version: 22.18.6
+      '@vitejs/plugin-react':
+        specifier: ^5.0.0
+        version: 5.0.3(vite@6.3.6(@types/node@22.18.6))
+      typescript:
+        specifier: ~5.8.2
+        version: 5.8.3
+      vite:
+        specifier: ^6.2.0
+        version: 6.3.6(@types/node@22.18.6)
+
+packages:
+
+  '@babel/code-frame@7.27.1':
+    resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/compat-data@7.28.4':
+    resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/core@7.28.4':
+    resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/generator@7.28.3':
+    resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-compilation-targets@7.27.2':
+    resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-globals@7.28.0':
+    resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-module-imports@7.27.1':
+    resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-module-transforms@7.28.3':
+    resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+
+  '@babel/helper-plugin-utils@7.27.1':
+    resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-string-parser@7.27.1':
+    resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-validator-identifier@7.27.1':
+    resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-validator-option@7.27.1':
+    resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helpers@7.28.4':
+    resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/parser@7.28.4':
+    resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+
+  '@babel/plugin-transform-react-jsx-self@7.27.1':
+    resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-transform-react-jsx-source@7.27.1':
+    resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/template@7.27.2':
+    resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/traverse@7.28.4':
+    resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/types@7.28.4':
+    resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
+    engines: {node: '>=6.9.0'}
+
+  '@esbuild/aix-ppc64@0.25.10':
+    resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [aix]
+
+  '@esbuild/android-arm64@0.25.10':
+    resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [android]
+
+  '@esbuild/android-arm@0.25.10':
+    resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [android]
+
+  '@esbuild/android-x64@0.25.10':
+    resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [android]
+
+  '@esbuild/darwin-arm64@0.25.10':
+    resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@esbuild/darwin-x64@0.25.10':
+    resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [darwin]
+
+  '@esbuild/freebsd-arm64@0.25.10':
+    resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@esbuild/freebsd-x64@0.25.10':
+    resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@esbuild/linux-arm64@0.25.10':
+    resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [linux]
+
+  '@esbuild/linux-arm@0.25.10':
+    resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [linux]
+
+  '@esbuild/linux-ia32@0.25.10':
+    resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [linux]
+
+  '@esbuild/linux-loong64@0.25.10':
+    resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==}
+    engines: {node: '>=18'}
+    cpu: [loong64]
+    os: [linux]
+
+  '@esbuild/linux-mips64el@0.25.10':
+    resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==}
+    engines: {node: '>=18'}
+    cpu: [mips64el]
+    os: [linux]
+
+  '@esbuild/linux-ppc64@0.25.10':
+    resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [linux]
+
+  '@esbuild/linux-riscv64@0.25.10':
+    resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==}
+    engines: {node: '>=18'}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@esbuild/linux-s390x@0.25.10':
+    resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==}
+    engines: {node: '>=18'}
+    cpu: [s390x]
+    os: [linux]
+
+  '@esbuild/linux-x64@0.25.10':
+    resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [linux]
+
+  '@esbuild/netbsd-arm64@0.25.10':
+    resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [netbsd]
+
+  '@esbuild/netbsd-x64@0.25.10':
+    resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [netbsd]
+
+  '@esbuild/openbsd-arm64@0.25.10':
+    resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openbsd]
+
+  '@esbuild/openbsd-x64@0.25.10':
+    resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [openbsd]
+
+  '@esbuild/openharmony-arm64@0.25.10':
+    resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openharmony]
+
+  '@esbuild/sunos-x64@0.25.10':
+    resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [sunos]
+
+  '@esbuild/win32-arm64@0.25.10':
+    resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [win32]
+
+  '@esbuild/win32-ia32@0.25.10':
+    resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [win32]
+
+  '@esbuild/win32-x64@0.25.10':
+    resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [win32]
+
+  '@jridgewell/gen-mapping@0.3.13':
+    resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+  '@jridgewell/remapping@2.3.5':
+    resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+  '@jridgewell/resolve-uri@3.1.2':
+    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/sourcemap-codec@1.5.5':
+    resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+  '@jridgewell/trace-mapping@0.3.31':
+    resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+  '@reduxjs/toolkit@2.9.0':
+    resolution: {integrity: sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==}
+    peerDependencies:
+      react: ^16.9.0 || ^17.0.0 || ^18 || ^19
+      react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0
+    peerDependenciesMeta:
+      react:
+        optional: true
+      react-redux:
+        optional: true
+
+  '@rolldown/pluginutils@1.0.0-beta.35':
+    resolution: {integrity: sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==}
+
+  '@rollup/rollup-android-arm-eabi@4.50.2':
+    resolution: {integrity: sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==}
+    cpu: [arm]
+    os: [android]
+
+  '@rollup/rollup-android-arm64@4.50.2':
+    resolution: {integrity: sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==}
+    cpu: [arm64]
+    os: [android]
+
+  '@rollup/rollup-darwin-arm64@4.50.2':
+    resolution: {integrity: sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@rollup/rollup-darwin-x64@4.50.2':
+    resolution: {integrity: sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==}
+    cpu: [x64]
+    os: [darwin]
+
+  '@rollup/rollup-freebsd-arm64@4.50.2':
+    resolution: {integrity: sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@rollup/rollup-freebsd-x64@4.50.2':
+    resolution: {integrity: sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.50.2':
+    resolution: {integrity: sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==}
+    cpu: [arm]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-arm-musleabihf@4.50.2':
+    resolution: {integrity: sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==}
+    cpu: [arm]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-arm64-gnu@4.50.2':
+    resolution: {integrity: sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-arm64-musl@4.50.2':
+    resolution: {integrity: sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-loong64-gnu@4.50.2':
+    resolution: {integrity: sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==}
+    cpu: [loong64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-ppc64-gnu@4.50.2':
+    resolution: {integrity: sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==}
+    cpu: [ppc64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-riscv64-gnu@4.50.2':
+    resolution: {integrity: sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==}
+    cpu: [riscv64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-riscv64-musl@4.50.2':
+    resolution: {integrity: sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==}
+    cpu: [riscv64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-s390x-gnu@4.50.2':
+    resolution: {integrity: sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==}
+    cpu: [s390x]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-x64-gnu@4.50.2':
+    resolution: {integrity: sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-x64-musl@4.50.2':
+    resolution: {integrity: sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-openharmony-arm64@4.50.2':
+    resolution: {integrity: sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==}
+    cpu: [arm64]
+    os: [openharmony]
+
+  '@rollup/rollup-win32-arm64-msvc@4.50.2':
+    resolution: {integrity: sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==}
+    cpu: [arm64]
+    os: [win32]
+
+  '@rollup/rollup-win32-ia32-msvc@4.50.2':
+    resolution: {integrity: sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==}
+    cpu: [ia32]
+    os: [win32]
+
+  '@rollup/rollup-win32-x64-msvc@4.50.2':
+    resolution: {integrity: sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==}
+    cpu: [x64]
+    os: [win32]
+
+  '@standard-schema/spec@1.0.0':
+    resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
+
+  '@standard-schema/utils@0.3.0':
+    resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
+
+  '@types/babel__core@7.20.5':
+    resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+  '@types/babel__generator@7.27.0':
+    resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+  '@types/babel__template@7.4.4':
+    resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+  '@types/babel__traverse@7.28.0':
+    resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+  '@types/d3-array@3.2.2':
+    resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
+
+  '@types/d3-color@3.1.3':
+    resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+  '@types/d3-ease@3.0.2':
+    resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
+
+  '@types/d3-interpolate@3.0.4':
+    resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+  '@types/d3-path@3.1.1':
+    resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
+
+  '@types/d3-scale@4.0.9':
+    resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
+
+  '@types/d3-shape@3.1.7':
+    resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
+
+  '@types/d3-time@3.0.4':
+    resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
+
+  '@types/d3-timer@3.0.2':
+    resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+
+  '@types/estree@1.0.8':
+    resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+  '@types/node@22.18.6':
+    resolution: {integrity: sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==}
+
+  '@types/use-sync-external-store@0.0.6':
+    resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
+
+  '@vitejs/plugin-react@5.0.3':
+    resolution: {integrity: sha512-PFVHhosKkofGH0Yzrw1BipSedTH68BFF8ZWy1kfUpCtJcouXXY0+racG8sExw7hw0HoX36813ga5o3LTWZ4FUg==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    peerDependencies:
+      vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
+  baseline-browser-mapping@2.8.6:
+    resolution: {integrity: sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==}
+    hasBin: true
+
+  browserslist@4.26.2:
+    resolution: {integrity: sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==}
+    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+    hasBin: true
+
+  caniuse-lite@1.0.30001743:
+    resolution: {integrity: sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==}
+
+  clsx@2.1.1:
+    resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+    engines: {node: '>=6'}
+
+  convert-source-map@2.0.0:
+    resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+  d3-array@3.2.4:
+    resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+    engines: {node: '>=12'}
+
+  d3-color@3.1.0:
+    resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+    engines: {node: '>=12'}
+
+  d3-ease@3.0.1:
+    resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+    engines: {node: '>=12'}
+
+  d3-format@3.1.0:
+    resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
+    engines: {node: '>=12'}
+
+  d3-interpolate@3.0.1:
+    resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+    engines: {node: '>=12'}
+
+  d3-path@3.1.0:
+    resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+    engines: {node: '>=12'}
+
+  d3-scale@4.0.2:
+    resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+    engines: {node: '>=12'}
+
+  d3-shape@3.2.0:
+    resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+    engines: {node: '>=12'}
+
+  d3-time-format@4.1.0:
+    resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+    engines: {node: '>=12'}
+
+  d3-time@3.1.0:
+    resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+    engines: {node: '>=12'}
+
+  d3-timer@3.0.1:
+    resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+    engines: {node: '>=12'}
+
+  date-fns@4.1.0:
+    resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+
+  debug@4.4.3:
+    resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
+  decimal.js-light@2.5.1:
+    resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
+
+  electron-to-chromium@1.5.222:
+    resolution: {integrity: sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w==}
+
+  es-toolkit@1.39.10:
+    resolution: {integrity: sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==}
+
+  esbuild@0.25.10:
+    resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==}
+    engines: {node: '>=18'}
+    hasBin: true
+
+  escalade@3.2.0:
+    resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+    engines: {node: '>=6'}
+
+  eventemitter3@5.0.1:
+    resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
+
+  fdir@6.5.0:
+    resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+    engines: {node: '>=12.0.0'}
+    peerDependencies:
+      picomatch: ^3 || ^4
+    peerDependenciesMeta:
+      picomatch:
+        optional: true
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
+  gensync@1.0.0-beta.2:
+    resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+    engines: {node: '>=6.9.0'}
+
+  immer@10.1.3:
+    resolution: {integrity: sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==}
+
+  internmap@2.0.3:
+    resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+    engines: {node: '>=12'}
+
+  js-tokens@4.0.0:
+    resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+  jsesc@3.1.0:
+    resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+    engines: {node: '>=6'}
+    hasBin: true
+
+  json5@2.2.3:
+    resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+    engines: {node: '>=6'}
+    hasBin: true
+
+  lru-cache@5.1.1:
+    resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+  ms@2.1.3:
+    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+  nanoid@3.3.11:
+    resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+
+  node-releases@2.0.21:
+    resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==}
+
+  picocolors@1.1.1:
+    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+  picomatch@4.0.3:
+    resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+    engines: {node: '>=12'}
+
+  postcss@8.5.6:
+    resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+    engines: {node: ^10 || ^12 || >=14}
+
+  react-dom@19.1.1:
+    resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==}
+    peerDependencies:
+      react: ^19.1.1
+
+  react-is@19.1.1:
+    resolution: {integrity: sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==}
+
+  react-redux@9.2.0:
+    resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==}
+    peerDependencies:
+      '@types/react': ^18.2.25 || ^19
+      react: ^18.0 || ^19
+      redux: ^5.0.0
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      redux:
+        optional: true
+
+  react-refresh@0.17.0:
+    resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+    engines: {node: '>=0.10.0'}
+
+  react@19.1.1:
+    resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
+    engines: {node: '>=0.10.0'}
+
+  recharts@3.2.1:
+    resolution: {integrity: sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+      react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+      react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+  redux-thunk@3.1.0:
+    resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
+    peerDependencies:
+      redux: ^5.0.0
+
+  redux@5.0.1:
+    resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==}
+
+  reselect@5.1.1:
+    resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
+
+  rollup@4.50.2:
+    resolution: {integrity: sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==}
+    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+    hasBin: true
+
+  scheduler@0.26.0:
+    resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
+
+  semver@6.3.1:
+    resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+    hasBin: true
+
+  source-map-js@1.2.1:
+    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+    engines: {node: '>=0.10.0'}
+
+  tiny-invariant@1.3.3:
+    resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+
+  tinyglobby@0.2.15:
+    resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+    engines: {node: '>=12.0.0'}
+
+  typescript@5.8.3:
+    resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
+  undici-types@6.21.0:
+    resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+  update-browserslist-db@1.1.3:
+    resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
+    hasBin: true
+    peerDependencies:
+      browserslist: '>= 4.21.0'
+
+  use-sync-external-store@1.5.0:
+    resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+  victory-vendor@37.3.6:
+    resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==}
+
+  vite@6.3.6:
+    resolution: {integrity: sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==}
+    engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+    hasBin: true
+    peerDependencies:
+      '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+      jiti: '>=1.21.0'
+      less: '*'
+      lightningcss: ^1.21.0
+      sass: '*'
+      sass-embedded: '*'
+      stylus: '*'
+      sugarss: '*'
+      terser: ^5.16.0
+      tsx: ^4.8.1
+      yaml: ^2.4.2
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+      jiti:
+        optional: true
+      less:
+        optional: true
+      lightningcss:
+        optional: true
+      sass:
+        optional: true
+      sass-embedded:
+        optional: true
+      stylus:
+        optional: true
+      sugarss:
+        optional: true
+      terser:
+        optional: true
+      tsx:
+        optional: true
+      yaml:
+        optional: true
+
+  yallist@3.1.1:
+    resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+snapshots:
+
+  '@babel/code-frame@7.27.1':
+    dependencies:
+      '@babel/helper-validator-identifier': 7.27.1
+      js-tokens: 4.0.0
+      picocolors: 1.1.1
+
+  '@babel/compat-data@7.28.4': {}
+
+  '@babel/core@7.28.4':
+    dependencies:
+      '@babel/code-frame': 7.27.1
+      '@babel/generator': 7.28.3
+      '@babel/helper-compilation-targets': 7.27.2
+      '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4)
+      '@babel/helpers': 7.28.4
+      '@babel/parser': 7.28.4
+      '@babel/template': 7.27.2
+      '@babel/traverse': 7.28.4
+      '@babel/types': 7.28.4
+      '@jridgewell/remapping': 2.3.5
+      convert-source-map: 2.0.0
+      debug: 4.4.3
+      gensync: 1.0.0-beta.2
+      json5: 2.2.3
+      semver: 6.3.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/generator@7.28.3':
+    dependencies:
+      '@babel/parser': 7.28.4
+      '@babel/types': 7.28.4
+      '@jridgewell/gen-mapping': 0.3.13
+      '@jridgewell/trace-mapping': 0.3.31
+      jsesc: 3.1.0
+
+  '@babel/helper-compilation-targets@7.27.2':
+    dependencies:
+      '@babel/compat-data': 7.28.4
+      '@babel/helper-validator-option': 7.27.1
+      browserslist: 4.26.2
+      lru-cache: 5.1.1
+      semver: 6.3.1
+
+  '@babel/helper-globals@7.28.0': {}
+
+  '@babel/helper-module-imports@7.27.1':
+    dependencies:
+      '@babel/traverse': 7.28.4
+      '@babel/types': 7.28.4
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)':
+    dependencies:
+      '@babel/core': 7.28.4
+      '@babel/helper-module-imports': 7.27.1
+      '@babel/helper-validator-identifier': 7.27.1
+      '@babel/traverse': 7.28.4
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-plugin-utils@7.27.1': {}
+
+  '@babel/helper-string-parser@7.27.1': {}
+
+  '@babel/helper-validator-identifier@7.27.1': {}
+
+  '@babel/helper-validator-option@7.27.1': {}
+
+  '@babel/helpers@7.28.4':
+    dependencies:
+      '@babel/template': 7.27.2
+      '@babel/types': 7.28.4
+
+  '@babel/parser@7.28.4':
+    dependencies:
+      '@babel/types': 7.28.4
+
+  '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)':
+    dependencies:
+      '@babel/core': 7.28.4
+      '@babel/helper-plugin-utils': 7.27.1
+
+  '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)':
+    dependencies:
+      '@babel/core': 7.28.4
+      '@babel/helper-plugin-utils': 7.27.1
+
+  '@babel/template@7.27.2':
+    dependencies:
+      '@babel/code-frame': 7.27.1
+      '@babel/parser': 7.28.4
+      '@babel/types': 7.28.4
+
+  '@babel/traverse@7.28.4':
+    dependencies:
+      '@babel/code-frame': 7.27.1
+      '@babel/generator': 7.28.3
+      '@babel/helper-globals': 7.28.0
+      '@babel/parser': 7.28.4
+      '@babel/template': 7.27.2
+      '@babel/types': 7.28.4
+      debug: 4.4.3
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/types@7.28.4':
+    dependencies:
+      '@babel/helper-string-parser': 7.27.1
+      '@babel/helper-validator-identifier': 7.27.1
+
+  '@esbuild/aix-ppc64@0.25.10':
+    optional: true
+
+  '@esbuild/android-arm64@0.25.10':
+    optional: true
+
+  '@esbuild/android-arm@0.25.10':
+    optional: true
+
+  '@esbuild/android-x64@0.25.10':
+    optional: true
+
+  '@esbuild/darwin-arm64@0.25.10':
+    optional: true
+
+  '@esbuild/darwin-x64@0.25.10':
+    optional: true
+
+  '@esbuild/freebsd-arm64@0.25.10':
+    optional: true
+
+  '@esbuild/freebsd-x64@0.25.10':
+    optional: true
+
+  '@esbuild/linux-arm64@0.25.10':
+    optional: true
+
+  '@esbuild/linux-arm@0.25.10':
+    optional: true
+
+  '@esbuild/linux-ia32@0.25.10':
+    optional: true
+
+  '@esbuild/linux-loong64@0.25.10':
+    optional: true
+
+  '@esbuild/linux-mips64el@0.25.10':
+    optional: true
+
+  '@esbuild/linux-ppc64@0.25.10':
+    optional: true
+
+  '@esbuild/linux-riscv64@0.25.10':
+    optional: true
+
+  '@esbuild/linux-s390x@0.25.10':
+    optional: true
+
+  '@esbuild/linux-x64@0.25.10':
+    optional: true
+
+  '@esbuild/netbsd-arm64@0.25.10':
+    optional: true
+
+  '@esbuild/netbsd-x64@0.25.10':
+    optional: true
+
+  '@esbuild/openbsd-arm64@0.25.10':
+    optional: true
+
+  '@esbuild/openbsd-x64@0.25.10':
+    optional: true
+
+  '@esbuild/openharmony-arm64@0.25.10':
+    optional: true
+
+  '@esbuild/sunos-x64@0.25.10':
+    optional: true
+
+  '@esbuild/win32-arm64@0.25.10':
+    optional: true
+
+  '@esbuild/win32-ia32@0.25.10':
+    optional: true
+
+  '@esbuild/win32-x64@0.25.10':
+    optional: true
+
+  '@jridgewell/gen-mapping@0.3.13':
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.5.5
+      '@jridgewell/trace-mapping': 0.3.31
+
+  '@jridgewell/remapping@2.3.5':
+    dependencies:
+      '@jridgewell/gen-mapping': 0.3.13
+      '@jridgewell/trace-mapping': 0.3.31
+
+  '@jridgewell/resolve-uri@3.1.2': {}
+
+  '@jridgewell/sourcemap-codec@1.5.5': {}
+
+  '@jridgewell/trace-mapping@0.3.31':
+    dependencies:
+      '@jridgewell/resolve-uri': 3.1.2
+      '@jridgewell/sourcemap-codec': 1.5.5
+
+  '@reduxjs/toolkit@2.9.0(react-redux@9.2.0(react@19.1.1)(redux@5.0.1))(react@19.1.1)':
+    dependencies:
+      '@standard-schema/spec': 1.0.0
+      '@standard-schema/utils': 0.3.0
+      immer: 10.1.3
+      redux: 5.0.1
+      redux-thunk: 3.1.0(redux@5.0.1)
+      reselect: 5.1.1
+    optionalDependencies:
+      react: 19.1.1
+      react-redux: 9.2.0(react@19.1.1)(redux@5.0.1)
+
+  '@rolldown/pluginutils@1.0.0-beta.35': {}
+
+  '@rollup/rollup-android-arm-eabi@4.50.2':
+    optional: true
+
+  '@rollup/rollup-android-arm64@4.50.2':
+    optional: true
+
+  '@rollup/rollup-darwin-arm64@4.50.2':
+    optional: true
+
+  '@rollup/rollup-darwin-x64@4.50.2':
+    optional: true
+
+  '@rollup/rollup-freebsd-arm64@4.50.2':
+    optional: true
+
+  '@rollup/rollup-freebsd-x64@4.50.2':
+    optional: true
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.50.2':
+    optional: true
+
+  '@rollup/rollup-linux-arm-musleabihf@4.50.2':
+    optional: true
+
+  '@rollup/rollup-linux-arm64-gnu@4.50.2':
+    optional: true
+
+  '@rollup/rollup-linux-arm64-musl@4.50.2':
+    optional: true
+
+  '@rollup/rollup-linux-loong64-gnu@4.50.2':
+    optional: true
+
+  '@rollup/rollup-linux-ppc64-gnu@4.50.2':
+    optional: true
+
+  '@rollup/rollup-linux-riscv64-gnu@4.50.2':
+    optional: true
+
+  '@rollup/rollup-linux-riscv64-musl@4.50.2':
+    optional: true
+
+  '@rollup/rollup-linux-s390x-gnu@4.50.2':
+    optional: true
+
+  '@rollup/rollup-linux-x64-gnu@4.50.2':
+    optional: true
+
+  '@rollup/rollup-linux-x64-musl@4.50.2':
+    optional: true
+
+  '@rollup/rollup-openharmony-arm64@4.50.2':
+    optional: true
+
+  '@rollup/rollup-win32-arm64-msvc@4.50.2':
+    optional: true
+
+  '@rollup/rollup-win32-ia32-msvc@4.50.2':
+    optional: true
+
+  '@rollup/rollup-win32-x64-msvc@4.50.2':
+    optional: true
+
+  '@standard-schema/spec@1.0.0': {}
+
+  '@standard-schema/utils@0.3.0': {}
+
+  '@types/babel__core@7.20.5':
+    dependencies:
+      '@babel/parser': 7.28.4
+      '@babel/types': 7.28.4
+      '@types/babel__generator': 7.27.0
+      '@types/babel__template': 7.4.4
+      '@types/babel__traverse': 7.28.0
+
+  '@types/babel__generator@7.27.0':
+    dependencies:
+      '@babel/types': 7.28.4
+
+  '@types/babel__template@7.4.4':
+    dependencies:
+      '@babel/parser': 7.28.4
+      '@babel/types': 7.28.4
+
+  '@types/babel__traverse@7.28.0':
+    dependencies:
+      '@babel/types': 7.28.4
+
+  '@types/d3-array@3.2.2': {}
+
+  '@types/d3-color@3.1.3': {}
+
+  '@types/d3-ease@3.0.2': {}
+
+  '@types/d3-interpolate@3.0.4':
+    dependencies:
+      '@types/d3-color': 3.1.3
+
+  '@types/d3-path@3.1.1': {}
+
+  '@types/d3-scale@4.0.9':
+    dependencies:
+      '@types/d3-time': 3.0.4
+
+  '@types/d3-shape@3.1.7':
+    dependencies:
+      '@types/d3-path': 3.1.1
+
+  '@types/d3-time@3.0.4': {}
+
+  '@types/d3-timer@3.0.2': {}
+
+  '@types/estree@1.0.8': {}
+
+  '@types/node@22.18.6':
+    dependencies:
+      undici-types: 6.21.0
+
+  '@types/use-sync-external-store@0.0.6': {}
+
+  '@vitejs/plugin-react@5.0.3(vite@6.3.6(@types/node@22.18.6))':
+    dependencies:
+      '@babel/core': 7.28.4
+      '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4)
+      '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4)
+      '@rolldown/pluginutils': 1.0.0-beta.35
+      '@types/babel__core': 7.20.5
+      react-refresh: 0.17.0
+      vite: 6.3.6(@types/node@22.18.6)
+    transitivePeerDependencies:
+      - supports-color
+
+  baseline-browser-mapping@2.8.6: {}
+
+  browserslist@4.26.2:
+    dependencies:
+      baseline-browser-mapping: 2.8.6
+      caniuse-lite: 1.0.30001743
+      electron-to-chromium: 1.5.222
+      node-releases: 2.0.21
+      update-browserslist-db: 1.1.3(browserslist@4.26.2)
+
+  caniuse-lite@1.0.30001743: {}
+
+  clsx@2.1.1: {}
+
+  convert-source-map@2.0.0: {}
+
+  d3-array@3.2.4:
+    dependencies:
+      internmap: 2.0.3
+
+  d3-color@3.1.0: {}
+
+  d3-ease@3.0.1: {}
+
+  d3-format@3.1.0: {}
+
+  d3-interpolate@3.0.1:
+    dependencies:
+      d3-color: 3.1.0
+
+  d3-path@3.1.0: {}
+
+  d3-scale@4.0.2:
+    dependencies:
+      d3-array: 3.2.4
+      d3-format: 3.1.0
+      d3-interpolate: 3.0.1
+      d3-time: 3.1.0
+      d3-time-format: 4.1.0
+
+  d3-shape@3.2.0:
+    dependencies:
+      d3-path: 3.1.0
+
+  d3-time-format@4.1.0:
+    dependencies:
+      d3-time: 3.1.0
+
+  d3-time@3.1.0:
+    dependencies:
+      d3-array: 3.2.4
+
+  d3-timer@3.0.1: {}
+
+  date-fns@4.1.0: {}
+
+  debug@4.4.3:
+    dependencies:
+      ms: 2.1.3
+
+  decimal.js-light@2.5.1: {}
+
+  electron-to-chromium@1.5.222: {}
+
+  es-toolkit@1.39.10: {}
+
+  esbuild@0.25.10:
+    optionalDependencies:
+      '@esbuild/aix-ppc64': 0.25.10
+      '@esbuild/android-arm': 0.25.10
+      '@esbuild/android-arm64': 0.25.10
+      '@esbuild/android-x64': 0.25.10
+      '@esbuild/darwin-arm64': 0.25.10
+      '@esbuild/darwin-x64': 0.25.10
+      '@esbuild/freebsd-arm64': 0.25.10
+      '@esbuild/freebsd-x64': 0.25.10
+      '@esbuild/linux-arm': 0.25.10
+      '@esbuild/linux-arm64': 0.25.10
+      '@esbuild/linux-ia32': 0.25.10
+      '@esbuild/linux-loong64': 0.25.10
+      '@esbuild/linux-mips64el': 0.25.10
+      '@esbuild/linux-ppc64': 0.25.10
+      '@esbuild/linux-riscv64': 0.25.10
+      '@esbuild/linux-s390x': 0.25.10
+      '@esbuild/linux-x64': 0.25.10
+      '@esbuild/netbsd-arm64': 0.25.10
+      '@esbuild/netbsd-x64': 0.25.10
+      '@esbuild/openbsd-arm64': 0.25.10
+      '@esbuild/openbsd-x64': 0.25.10
+      '@esbuild/openharmony-arm64': 0.25.10
+      '@esbuild/sunos-x64': 0.25.10
+      '@esbuild/win32-arm64': 0.25.10
+      '@esbuild/win32-ia32': 0.25.10
+      '@esbuild/win32-x64': 0.25.10
+
+  escalade@3.2.0: {}
+
+  eventemitter3@5.0.1: {}
+
+  fdir@6.5.0(picomatch@4.0.3):
+    optionalDependencies:
+      picomatch: 4.0.3
+
+  fsevents@2.3.3:
+    optional: true
+
+  gensync@1.0.0-beta.2: {}
+
+  immer@10.1.3: {}
+
+  internmap@2.0.3: {}
+
+  js-tokens@4.0.0: {}
+
+  jsesc@3.1.0: {}
+
+  json5@2.2.3: {}
+
+  lru-cache@5.1.1:
+    dependencies:
+      yallist: 3.1.1
+
+  ms@2.1.3: {}
+
+  nanoid@3.3.11: {}
+
+  node-releases@2.0.21: {}
+
+  picocolors@1.1.1: {}
+
+  picomatch@4.0.3: {}
+
+  postcss@8.5.6:
+    dependencies:
+      nanoid: 3.3.11
+      picocolors: 1.1.1
+      source-map-js: 1.2.1
+
+  react-dom@19.1.1(react@19.1.1):
+    dependencies:
+      react: 19.1.1
+      scheduler: 0.26.0
+
+  react-is@19.1.1: {}
+
+  react-redux@9.2.0(react@19.1.1)(redux@5.0.1):
+    dependencies:
+      '@types/use-sync-external-store': 0.0.6
+      react: 19.1.1
+      use-sync-external-store: 1.5.0(react@19.1.1)
+    optionalDependencies:
+      redux: 5.0.1
+
+  react-refresh@0.17.0: {}
+
+  react@19.1.1: {}
+
+  recharts@3.2.1(react-dom@19.1.1(react@19.1.1))(react-is@19.1.1)(react@19.1.1)(redux@5.0.1):
+    dependencies:
+      '@reduxjs/toolkit': 2.9.0(react-redux@9.2.0(react@19.1.1)(redux@5.0.1))(react@19.1.1)
+      clsx: 2.1.1
+      decimal.js-light: 2.5.1
+      es-toolkit: 1.39.10
+      eventemitter3: 5.0.1
+      immer: 10.1.3
+      react: 19.1.1
+      react-dom: 19.1.1(react@19.1.1)
+      react-is: 19.1.1
+      react-redux: 9.2.0(react@19.1.1)(redux@5.0.1)
+      reselect: 5.1.1
+      tiny-invariant: 1.3.3
+      use-sync-external-store: 1.5.0(react@19.1.1)
+      victory-vendor: 37.3.6
+    transitivePeerDependencies:
+      - '@types/react'
+      - redux
+
+  redux-thunk@3.1.0(redux@5.0.1):
+    dependencies:
+      redux: 5.0.1
+
+  redux@5.0.1: {}
+
+  reselect@5.1.1: {}
+
+  rollup@4.50.2:
+    dependencies:
+      '@types/estree': 1.0.8
+    optionalDependencies:
+      '@rollup/rollup-android-arm-eabi': 4.50.2
+      '@rollup/rollup-android-arm64': 4.50.2
+      '@rollup/rollup-darwin-arm64': 4.50.2
+      '@rollup/rollup-darwin-x64': 4.50.2
+      '@rollup/rollup-freebsd-arm64': 4.50.2
+      '@rollup/rollup-freebsd-x64': 4.50.2
+      '@rollup/rollup-linux-arm-gnueabihf': 4.50.2
+      '@rollup/rollup-linux-arm-musleabihf': 4.50.2
+      '@rollup/rollup-linux-arm64-gnu': 4.50.2
+      '@rollup/rollup-linux-arm64-musl': 4.50.2
+      '@rollup/rollup-linux-loong64-gnu': 4.50.2
+      '@rollup/rollup-linux-ppc64-gnu': 4.50.2
+      '@rollup/rollup-linux-riscv64-gnu': 4.50.2
+      '@rollup/rollup-linux-riscv64-musl': 4.50.2
+      '@rollup/rollup-linux-s390x-gnu': 4.50.2
+      '@rollup/rollup-linux-x64-gnu': 4.50.2
+      '@rollup/rollup-linux-x64-musl': 4.50.2
+      '@rollup/rollup-openharmony-arm64': 4.50.2
+      '@rollup/rollup-win32-arm64-msvc': 4.50.2
+      '@rollup/rollup-win32-ia32-msvc': 4.50.2
+      '@rollup/rollup-win32-x64-msvc': 4.50.2
+      fsevents: 2.3.3
+
+  scheduler@0.26.0: {}
+
+  semver@6.3.1: {}
+
+  source-map-js@1.2.1: {}
+
+  tiny-invariant@1.3.3: {}
+
+  tinyglobby@0.2.15:
+    dependencies:
+      fdir: 6.5.0(picomatch@4.0.3)
+      picomatch: 4.0.3
+
+  typescript@5.8.3: {}
+
+  undici-types@6.21.0: {}
+
+  update-browserslist-db@1.1.3(browserslist@4.26.2):
+    dependencies:
+      browserslist: 4.26.2
+      escalade: 3.2.0
+      picocolors: 1.1.1
+
+  use-sync-external-store@1.5.0(react@19.1.1):
+    dependencies:
+      react: 19.1.1
+
+  victory-vendor@37.3.6:
+    dependencies:
+      '@types/d3-array': 3.2.2
+      '@types/d3-ease': 3.0.2
+      '@types/d3-interpolate': 3.0.4
+      '@types/d3-scale': 4.0.9
+      '@types/d3-shape': 3.1.7
+      '@types/d3-time': 3.0.4
+      '@types/d3-timer': 3.0.2
+      d3-array: 3.2.4
+      d3-ease: 3.0.1
+      d3-interpolate: 3.0.1
+      d3-scale: 4.0.2
+      d3-shape: 3.2.0
+      d3-time: 3.1.0
+      d3-timer: 3.0.1
+
+  vite@6.3.6(@types/node@22.18.6):
+    dependencies:
+      esbuild: 0.25.10
+      fdir: 6.5.0(picomatch@4.0.3)
+      picomatch: 4.0.3
+      postcss: 8.5.6
+      rollup: 4.50.2
+      tinyglobby: 0.2.15
+    optionalDependencies:
+      '@types/node': 22.18.6
+      fsevents: 2.3.3
+
+  yallist@3.1.1: {}

+ 83 - 0
services/authService.ts

@@ -0,0 +1,83 @@
+import { GreenPage } from '../types';
+import { createEmptyPage, createNewPage } from './mockDataService';
+
+const USERS_KEY = 'greenpage_users';
+const CURRENT_USER_KEY = 'greenpage_currentUser';
+
+// Simulate a database
+const getDb = () => {
+    try {
+        const db = localStorage.getItem(USERS_KEY);
+        return db ? JSON.parse(db) : {};
+    } catch (error) {
+        console.error("Error reading from localStorage", error);
+        return {};
+    }
+};
+
+const saveDb = (db: any) => {
+    try {
+        localStorage.setItem(USERS_KEY, JSON.stringify(db));
+    } catch (error) {
+        console.error("Error writing to localStorage", error);
+    }
+};
+
+export const authService = {
+    register: (email: string, password: string): { success: boolean; message: string } => {
+        const db = getDb();
+        if (db[email]) {
+            return { success: false, message: 'User with this email already exists.' };
+        }
+        const initialPage = createNewPage('My First Page');
+        db[email] = { password, pages: [initialPage] };
+        saveDb(db);
+        authService.login(email, password);
+        return { success: true, message: 'Registration successful.' };
+    },
+
+    login: (email: string, password: string): { success: boolean; message: string } => {
+        const db = getDb();
+        const user = db[email];
+        if (user && user.password === password) {
+            localStorage.setItem(CURRENT_USER_KEY, email);
+            return { success: true, message: 'Login successful.' };
+        }
+        return { success: false, message: 'Invalid email or password.' };
+    },
+
+    logout: (): void => {
+        localStorage.removeItem(CURRENT_USER_KEY);
+    },
+
+    getCurrentUser: (): string | null => {
+        return localStorage.getItem(CURRENT_USER_KEY);
+    },
+
+    getUserPages: (): GreenPage[] | null => {
+        const currentUser = authService.getCurrentUser();
+        if (!currentUser) return null;
+
+        const db = getDb();
+        const user = db[currentUser];
+        
+        if (user && (!user.pages || user.pages.length === 0)) {
+            const initialPage = createNewPage('My First Page');
+            user.pages = [initialPage];
+            saveDb(db);
+        }
+
+        return user ? user.pages : null;
+    },
+
+    saveUserPages: (pages: GreenPage[]): void => {
+        const currentUser = authService.getCurrentUser();
+        if (!currentUser) return;
+
+        const db = getDb();
+        if (db[currentUser]) {
+            db[currentUser].pages = pages;
+            saveDb(db);
+        }
+    },
+};

+ 137 - 0
services/geminiService.ts

@@ -0,0 +1,137 @@
+// This is a mock service. In a real application, you would import and use the @google/genai library.
+// import { GoogleGenAI, Chat } from "@google/genai";
+
+import { ChatMessage } from '../types';
+
+// Mock response structure to emulate the real API
+interface MockGenerateContentResponse {
+    text: string;
+}
+
+class MockChat {
+    private systemInstruction: string;
+
+    constructor(config?: { systemInstruction: string }) {
+        this.systemInstruction = config?.systemInstruction || "You are a helpful assistant.";
+        console.log("MockChat initialized with instruction:", this.systemInstruction);
+    }
+
+    sendMessage(message: { message: string }): Promise<MockGenerateContentResponse> {
+        console.log("MockChat received message:", message.message);
+        
+        return new Promise(resolve => {
+            setTimeout(() => {
+                const responses = [
+                    "That's a very interesting question! Let me think...",
+                    "Based on your persona settings, I would say the best approach is to be direct and informative.",
+                    "I'm sorry, I cannot answer that question based on my current knowledge base.",
+                    "Of course! I can help with that. What are the specific details you need?",
+                    "Analyzing your request... It seems you're asking about product features. Our product excels in A, B, and C."
+                ];
+                
+                const randomResponse = responses[Math.floor(Math.random() * responses.length)];
+                resolve({ text: randomResponse });
+            }, 800);
+        });
+    }
+}
+
+class MockGoogleGenAI {
+    public chats = {
+        create: (config?: { systemInstruction: string }) => {
+            return new MockChat(config);
+        }
+    };
+}
+
+const mockAi = new MockGoogleGenAI();
+let mockChat: MockChat | null = null;
+
+export const createAIChatSession = (persona: string) => {
+    mockChat = mockAi.chats.create({ systemInstruction: persona });
+};
+
+export const sendChatMessage = async (message: string): Promise<ChatMessage> => {
+    if (!mockChat) {
+       createAIChatSession("You are a helpful assistant."); // Default persona
+    }
+    
+    const response = await mockChat!.sendMessage({ message });
+    const aiResponse: ChatMessage = { id: `msg-ai-${Date.now()}`, sender: 'ai', text: response.text };
+    return aiResponse;
+};
+
+export const polishScript = async (originalScript: string): Promise<string[]> => {
+    console.log("Mock polishScript called with:", originalScript);
+    // In a real app, you would call Gemini here.
+    // const response = await ai.models.generateContent({ model: 'gemini-2.5-flash', contents: `Rewrite the following script in 3 different styles (professional, witty, and casual):\n\n"${originalScript}"` });
+    // For now, return mock data.
+    return new Promise(resolve => {
+        setTimeout(() => {
+            resolve([
+                `[Professional] We are pleased to welcome you to our presentation. We have some exciting updates to share.`,
+                `[Witty] Settle in, folks! You're about to witness a presentation of epic proportions.`,
+                `[Casual] Hey everyone, thanks for joining. Let's get started with our presentation.`
+            ]);
+        }, 1200);
+    });
+};
+
+export const generateArticle = async (topic: string): Promise<{ title: string; summary: string; content: string; }> => {
+    console.log("Mock generateArticle called with:", topic);
+    // In a real app, you would call Gemini here.
+    return new Promise(resolve => {
+        setTimeout(() => {
+            resolve({
+                title: `Exploring the Wonders of ${topic}`,
+                summary: `A brief overview of the key aspects and fascinating details surrounding ${topic}. Discover why it's a subject of great interest.`,
+                content: `The topic of ${topic} is a multifaceted and deeply engaging subject. Historically, its roots can be traced back to ancient civilizations where it played a pivotal role in culture and daily life. \n\nScientifically, ${topic} presents a complex set of principles that continue to be studied by experts worldwide. The implications of these studies are far-reaching, potentially impacting technology, medicine, and our understanding of the universe. \n\nFrom a cultural standpoint, ${topic} has inspired countless works of art, literature, and music, proving its enduring relevance in the human experience. As we look to the future, the evolution of ${topic} promises even more exciting developments.`
+            });
+        }, 1500);
+    });
+};
+
+export const mockProductDatabase = [
+    { title: "Smart Noise-Cancelling Headphones", price: "$149.99" },
+    { title: "Ergonomic Mechanical Keyboard", price: "$89.99" },
+    { title: "4K Ultra-HD Webcam", price: "$69.99" },
+    { title: "Organic French Press Coffee Beans", price: "$22.50" },
+    { title: "Minimalist Desk Lamp with Wireless Charging", price: "$45.00" },
+    { title: "Hand-Poured Soy Wax Candle", price: "$18.00" },
+    { title: "The Alchemist by Paulo Coelho", price: "$12.99" },
+    { title: "Atomic Habits by James Clear", price: "$15.99" },
+    { title: "Lightweight Waterproof Hiking Backpack", price: "$79.95" },
+    { title: "Insulated Stainless Steel Water Bottle", price: "$25.00" },
+    { title: "Professional Chef's Knife - 8 Inch", price: "$120.00" },
+    { title: "Non-Stick Ceramic Frying Pan Set", price: "$59.99" },
+    { title: "Portable Blender for Shakes and Smoothies", price: "$34.99" },
+    { title: "Yoga Mat with Carrying Strap", price: "$29.99" },
+    { title: "Adjustable Dumbbell Set", price: "$199.99" },
+];
+
+// Simple hash function to get a consistent index from a string
+const simpleHash = (str: string) => {
+    let hash = 0;
+    for (let i = 0; i < str.length; i++) {
+        const char = str.charCodeAt(i);
+        hash = (hash << 5) - hash + char;
+        hash |= 0; // Convert to 32bit integer
+    }
+    return Math.abs(hash);
+};
+
+
+export const parseProductUrl = async (url: string): Promise<{ title: string; imageUrl: string; price: string; }> => {
+    console.log("Mock parseProductUrl called with:", url);
+    // In a real app, this might be a backend call that scrapes the page or calls an API.
+    // Here, we simulate it by using the URL to predictably pick from a larger mock database.
+    return new Promise(resolve => {
+        setTimeout(() => {
+            const hash = simpleHash(url);
+            const product = mockProductDatabase[hash % mockProductDatabase.length];
+            const imageUrl = `https://picsum.photos/seed/${encodeURIComponent(url)}/400/400`;
+            
+            resolve({ ...product, imageUrl });
+        }, 800 + Math.random() * 500); // Simulate network delay
+    });
+};

+ 354 - 0
services/mockDataService.ts

@@ -0,0 +1,354 @@
+import { GreenPage, Block, Conversation, DesignSettings, MediaSource, AIGCArticle, FormBlock, AwardBlock, FooterBlock } from '../types';
+import { subDays } from 'date-fns';
+
+const themeColors = [
+    'from-green-400 to-blue-500',
+    'from-purple-500 to-pink-500',
+    'from-yellow-400 to-orange-600',
+    'from-teal-400 to-cyan-500',
+    'from-red-500 to-indigo-600',
+    'from-rose-400 to-red-500',
+];
+
+const createInitialAIGCArticles = (): AIGCArticle[] => [
+    {
+        id: 'aigc-article-1',
+        title: 'The Rise of AI in Modern Web Development',
+        summary: 'An in-depth look at how artificial intelligence is revolutionizing the way we build and interact with websites, from automated testing to intelligent UI generation.',
+        content: `
+<h2>The Dawn of a New Era</h2>
+<p>From automated testing to intelligent UI generation, AI is no longer a futuristic concept but a <strong>present-day tool for developers</strong>. The landscape of web development is shifting at an unprecedented pace, with machine learning models now capable of writing code, designing user interfaces, and even deploying applications.</p>
+<p>One of the most significant impacts is in the realm of UI/UX design. Tools powered by generative AI can create stunning, user-friendly mockups from simple text prompts. <em>This dramatically accelerates the prototyping phase</em>, allowing teams to iterate on ideas faster than ever before. We're seeing a future where the line between designer and developer blurs, thanks to these intelligent assistants.</p>
+<img src="https://picsum.photos/seed/ai-article-1/600/400" alt="AI and code" style="width: 100%; height: auto; border-radius: 8px; margin: 1em 0;" />
+<h2>Challenges and Opportunities</h2>
+<p>Of course, this revolution doesn't come without its challenges. Questions around code quality, security vulnerabilities in AI-generated scripts, and the ethical implications of job displacement are paramount. However, the opportunities are boundless. Developers can now focus on more complex architectural problems, leaving routine coding tasks to their AI partners. For more information, you can read this <a href="https://example.com" target="_blank" rel="noopener noreferrer">in-depth analysis</a>.</p>
+`,
+        publicationDate: subDays(new Date(), 5).toISOString(),
+        sourceType: 'generated',
+    },
+    {
+        id: 'aigc-article-2',
+        title: 'Sustainable Living: 10 Tips for an Eco-Friendly Home',
+        summary: 'Discover simple yet effective ways to reduce your carbon footprint and create a greener living space, from your kitchen to your garden.',
+        content: `
+<h2>It Starts at Home</h2>
+<p>Sustainability is more than just a buzzword; it's a lifestyle that begins right in our own homes. Creating an eco-friendly living space doesn't require a complete overhaul. Simple, consistent changes can lead to a significant positive impact on the environment and even your wallet.</p>
+<img src="https://picsum.photos/seed/eco-article-1/600/400" alt="A green home" style="width: 100%; height: auto; border-radius: 8px; margin: 1em 0;" />
+<h2>Practical Steps You Can Take Today</h2>
+<p>Here are a few tips to get started:</p>
+<ol>
+  <li><strong>Reduce, Reuse, Recycle:</strong> The classic three R's are more important than ever.</li>
+  <li><strong>Compost Food Scraps:</strong> Reduce landfill waste and create nutrient-rich soil for your garden.</li>
+  <li><strong>Switch to LED Lighting:</strong> They use significantly less energy and last much longer than traditional bulbs.</li>
+  <li><em>Fix Leaky Faucets:</em> A small drip can waste gallons of water every day.</li>
+</ol>
+<p>By adopting these habits, you contribute to a healthier planet for future generations. It's a journey of continuous learning and improvement. Check out <a href="https://example.com/eco" target="_blank" rel="noopener noreferrer">this resource</a> for more ideas.</p>
+`,
+        publicationDate: subDays(new Date(), 12).toISOString(),
+        sourceType: 'generated',
+    },
+    {
+        id: 'aigc-article-3',
+        title: 'A Guide to Mindful Productivity in a Distracted World',
+        summary: 'Learn how to achieve more by focusing on the present moment, reducing stress, and working smarter, not harder, even with constant digital interruptions.',
+        content: `
+<h2>The Myth of Multitasking</h2>
+<p>In our fast-paced, hyper-connected world, the ability to focus is a superpower. We're often led to believe that multitasking is the key to productivity, but research shows the opposite is true. Juggling multiple tasks at once can lead to increased stress, more errors, and diminished creativity. <strong>True productivity comes from single-tasking with deep focus.</strong></p>
+<h2>Techniques for a Focused Mind</h2>
+<p>Mindfulness is the practice of being present and fully engaged with whatever we're doing. Here’s how to apply it to your work:</p>
+<ul>
+  <li><strong>The Pomodoro Technique:</strong> Work in focused 25-minute intervals, followed by a short break. This helps maintain high energy levels throughout the day.</li>
+  <li><strong>Digital Detox:</strong> Schedule specific times to check email and social media, rather than letting them interrupt your workflow.</li>
+  <li><em>Mindful Breathing:</em> Before starting a big task, take a minute to focus on your breath. This simple act can calm your nervous system and improve concentration.</li>
+</ul>
+<img src="https://picsum.photos/seed/mindful-article-1/600/400" alt="A calm workspace" style="width: 100%; height: auto; border-radius: 8px; margin: 1em 0;" />
+<p>By integrating mindfulness into your daily routine, you can transform your relationship with work, reducing burnout and achieving a greater sense of accomplishment.</p>
+`,
+        publicationDate: subDays(new Date(), 20).toISOString(),
+        sourceType: 'generated',
+    },
+];
+
+
+const createInitialBlocks = (): Block[] => {
+    const defaultFormBlock: FormBlock = {
+        id: 'form-1',
+        type: 'form',
+        visible: true,
+        title: 'Contact Us',
+        description: 'Have a question or want to work together? Drop us a line!',
+        submitButtonText: 'Submit Inquiry',
+        fields: [
+            { id: 'name', label: 'Full Name', enabled: true, required: true },
+            { id: 'email', label: 'Email Address', enabled: true, required: true },
+            { id: 'company', label: 'Company / Organization', enabled: true, required: false },
+            { id: 'phone', label: 'Phone Number', enabled: true, required: false },
+            { id: 'industry', label: 'Industry', enabled: false, required: false },
+            { id: 'position', label: 'Position', enabled: false, required: false },
+            { id: 'country', label: 'Country', enabled: false, required: false },
+        ],
+        purposeOptions: [
+            { id: `po-d-1`, label: 'General Inquiry' },
+            { id: `po-d-2`, label: 'Partnership' },
+            { id: `po-d-3`, label: 'Support Request' },
+        ]
+    };
+
+    const defaultFooterBlock: FooterBlock = {
+        id: 'footer-1',
+        type: 'footer',
+        visible: true,
+        layout: 'standard',
+        copyrightText: `© ${new Date().getFullYear()} GreenPage AI. All Rights Reserved.`,
+        legalText: 'Made with ❤️ by the GreenPage Team',
+        statement: '',
+        navLinks: [
+            { id: 'fl-1', title: 'Home', url: '#' },
+            { id: 'fl-2', title: 'About', url: '#' },
+            { id: 'fl-3', title: 'Contact', url: '#' },
+        ],
+        otherLinks: [
+            { id: 'fl-4', title: 'Privacy Policy', url: '#' },
+        ],
+    };
+
+    return [
+        { id: 'chat-1', type: 'chat', layout: 'under_avatar', visible: true },
+        { id: 'header-1', type: 'header', text: 'Welcome to my page!', visible: true, titleAlignment: 'center' },
+        { id: 'link-1', type: 'link', title: 'My Personal Website', url: 'https://example.com', iconUrl: 'https://www.google.com/s2/favicons?domain=example.com&sz=64', thumbnailUrl: "https://picsum.photos/seed/link-thumb-1/100/100", visible: true },
+        { id: 'link-2', type: 'link', title: 'Follow me on Twitter', url: 'https://twitter.com', iconUrl: 'https://www.google.com/s2/favicons?domain=twitter.com&sz=64', visible: true },
+        { id: 'header-2', type: 'header', text: 'My Work', visible: true, titleAlignment: 'left' },
+        { id: 'image-1', type: 'image', sources: [
+            { type: 'url', value: "https://picsum.photos/seed/gallery-image-1/400/300"}, 
+            { type: 'url', value: "https://picsum.photos/seed/gallery-image-2/400/300" }, 
+            { type: 'url', value: "https://picsum.photos/seed/gallery-image-3/400/300"}
+        ], layout: 'grid', visible: true },
+        { id: 'text-1', type: 'text', content: 'This is a gallery of my recent projects.\nFeel free to browse through and let me know what you think!', visible: true, textAlign: 'left', fontSize: '16px', fontColor: '#E5E7EB', isBold: false, isItalic: true },
+        { id: 'header-product', type: 'header', text: 'My Favorite Products', visible: true, titleAlignment: 'left' },
+        {
+            id: 'product-1',
+            type: 'product',
+            layout: 'grid',
+            items: [
+                { id: 'prod-mock-1', url: 'https://amazon.com', title: 'Amazon Echo Dot (5th Gen)', imageUrl: 'https://picsum.photos/seed/amazon-product/400/400', price: '$49.99' },
+                { id: 'prod-mock-2', url: 'https://shopee.com', title: 'Wireless Bluetooth Earbuds', imageUrl: 'https://picsum.photos/seed/shopee-product/400/400', price: '₱899.00' }
+            ],
+            visible: true,
+        },
+        { id: 'header-awards', type: 'header', text: 'Awards & Recognition', visible: true, titleAlignment: 'left' },
+        {
+            id: 'award-1',
+            type: 'award',
+            visible: true,
+            layout: 'grid',
+            items: [
+                { id: 'award-item-1', title: 'Webby Award Winner', subtitle: 'Internet Excellence', year: '2023', imageSource: { type: 'url', value: 'https://picsum.photos/seed/webby-award/100' }},
+                { id: 'award-item-2', title: 'Innovator of the Year', subtitle: 'Tech Forward Magazine', year: '2022', imageSource: { type: 'url', value: 'https://picsum.photos/seed/innovator-award/100' }},
+            ]
+        } as AwardBlock,
+        { id: 'header-4', type: 'header', text: 'Featured Videos', visible: true, titleAlignment: 'left' },
+        { id: 'video-2', type: 'video', sources: [{ type: 'url', value: 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4' }, { type: 'url', value: 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4' }], layout: 'grid', visible: true },
+        { id: 'video-1', type: 'video', sources: [{ type: 'url', value: 'https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' }], layout: 'single', visible: true },
+        { id: 'header-news', type: 'header', text: 'Latest News', visible: true, titleAlignment: 'left' },
+        {
+            id: 'news-aigc-1',
+            type: 'news',
+            layout: 'list',
+            source: 'aigc',
+            articleIds: ['aigc-article-1', 'aigc-article-3'],
+            visible: true,
+        },
+        { id: 'header-5', type: 'header', text: 'From Around the Web', visible: true, titleAlignment: 'left' },
+        {
+            id: 'news-custom-1',
+            type: 'news',
+            layout: 'grid',
+            source: 'custom',
+            customItems: [
+                { id: 'custom-news-1', title: 'TechCrunch: The Latest in Tech', summary: 'Your source for breaking technology news, analysis, and opinions.', url: 'https://techcrunch.com' },
+                { id: 'custom-news-2', title: 'A List Apart: For People Who Make Websites', summary: 'Exploring the design, development, and meaning of web content.', url: 'https://alistapart.com' },
+            ],
+            visible: true,
+        },
+        { id: 'header-3', type: 'header', text: 'Get In Touch', visible: true, titleAlignment: 'center' },
+        defaultFormBlock,
+        { id: 'email-1', type: 'email', label: 'Send me an Email', email: 'hello@example.com', visible: true, displayMode: 'labelAndValue' },
+        { id: 'phone-1', type: 'phone', label: 'Give me a Call', phone: '+1-234-567-8900', visible: true, displayMode: 'labelOnly' },
+        { id: 'map-1', type: 'map', address: 'Eiffel Tower, Champ de Mars, 5 Av. Anatole France, 75007 Paris, France', visible: true, displayStyle: 'interactiveMap', backgroundImageSource: { type: 'url', value: 'https://picsum.photos/seed/eiffel/600/400' } },
+        { id: 'pdf-1', type: 'pdf', source: { type: 'url', value: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf' }, visible: true},
+        { id: 'social-1', type: 'social', links: [
+            { id: 'social-link-1', platform: 'twitter', url: 'https://twitter.com/johndoe'},
+            { id: 'social-link-2', platform: 'github', url: 'https://github.com/johndoe'},
+        ], visible: true },
+        {
+            id: 'enterprise-info-1',
+            type: 'enterprise_info',
+            visible: true,
+            alignment: 'left',
+            items: [
+                { id: 'ei-1', icon: 'building', label: 'Company Name', value: 'GreenPage AI Suite' },
+                { id: 'ei-2', icon: 'location', label: 'Headquarters', value: '123 Innovation Drive, Tech City' },
+                { id: 'ei-3', icon: 'calendar', label: 'Founded', value: '2023' },
+                { id: 'ei-4', icon: 'users', label: 'Company Size', value: '50-100 employees' },
+            ]
+        },
+        defaultFooterBlock,
+    ];
+};
+
+const createInitialDesignSettings = (): DesignSettings => {
+    return {
+        theme: 'dark',
+        customThemeColors: {
+            background: '#111827',
+            text: '#FFFFFF',
+            button: '#374151',
+            buttonText: '#FFFFFF',
+        },
+        buttonStyle: 'filled',
+        buttonShape: 'rounded',
+        fontFamily: 'sans',
+        fontColor: '#FFFFFF',
+        fontSize: 'text-base',
+        backgroundType: 'gradient',
+        backgroundValue: 'bg-gradient-to-br from-gray-800 to-gray-900',
+        userBackgroundImages: [],
+        avatarSource: {
+            type: 'url',
+            value: 'https://api.dicebear.com/8.x/adventurer/svg?seed=GreenPageUser'
+        },
+        bannerSettings: {
+            type: 'image',
+            value: '', // Not used for image type, kept for color/gradient
+            imageSource: {
+                type: 'url',
+                value: "https://picsum.photos/seed/default-banner/1200/300",
+            },
+            height: 300,
+            width: 'contained',
+        },
+        sideNavSettings: {
+            backgroundColor: 'transparent',
+            textColor: '#d1d5db',
+            activeLinkColor: '#10b981',
+            hoverBackgroundColor: '#374151',
+            hoverTextColor: '#ffffff',
+            fontFamily: 'sans',
+            fontSize: '14px',
+            navFloatStyle: 'normal',
+            navBackgroundStyle: 'compact',
+        },
+        chatWidgetSettings: {
+            iconColor: '#10b981',
+            headerBackgroundColor: '#10b981',
+            headerTextColor: '#FFFFFF',
+            panelBackgroundColor: '#1f2937',
+            userMessageBackgroundColor: '#3b82f6',
+            userMessageTextColor: '#FFFFFF',
+            aiMessageBackgroundColor: '#374151',
+            aiMessageTextColor: '#FFFFFF',
+        },
+    };
+};
+
+const createInitialConversations = (): Conversation[] => {
+    return [
+        { id: 'c1', visitorId: 'v1', timestamp: new Date().toISOString(), visitCount: 3, interactions: [{id: 'm1', sender: 'user', text: 'What are your shipping policies?'}, {id: 'm2', sender: 'ai', text: 'We ship worldwide within 5-7 business days.'}], status: 'resolved', firstResponseTime: 12 },
+        { id: 'c2', visitorId: 'v2', timestamp: subDays(new Date(), 1).toISOString(), visitCount: 1, interactions: [{id: 'm3', sender: 'user', text: 'Hello?'}, {id: 'm4', sender: 'ai', text: 'Hi! How can I help you today?'}], status: 'open', firstResponseTime: 5 },
+        { id: 'c3', visitorId: 'v3', timestamp: subDays(new Date(), 3).toISOString(), visitCount: 2, interactions: [{id: 'm5', sender: 'user', text: 'Is this item in stock?'}, {id: 'm6', sender: 'ai', text: 'Let me check that for you.'}], status: 'open', firstResponseTime: 8 },
+        { id: 'c4', visitorId: 'v1', timestamp: subDays(new Date(), 2).toISOString(), visitCount: 3, interactions: [{id: 'm7', sender: 'user', text: 'Can I return an item?'}, {id: 'm8', sender: 'ai', text: 'Yes, we have a 30-day return policy on all items.'}], status: 'resolved', firstResponseTime: 25 },
+    ];
+};
+
+const createInitialVideos = () => {
+    return [
+        { id: 'vid1', title: 'Product Demo', thumbnailUrl: "https://picsum.photos/seed/vid1/200/120", videoUrl: 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4' },
+        { id: 'vid2', title: 'Weekly Update', thumbnailUrl: "https://picsum.photos/seed/vid2/200/120", videoUrl: 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4' },
+        { id: 'vid3', title: 'Team Introduction', thumbnailUrl: "https://picsum.photos/seed/vid3/200/120", videoUrl: 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4' },
+        { id: 'vid4', title: 'Behind the Scenes', thumbnailUrl: "https://picsum.photos/seed/vid4/200/120", videoUrl: 'https://storage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4' },
+        { id: 'vid5', title: 'Costa Rica Adventures', thumbnailUrl: "https://picsum.photos/seed/costa-rica/200/120", videoUrl: 'https://storage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnAnAdventure.mp4' },
+        { id: 'vid6', title: 'The Beauty of Art', thumbnailUrl: "https://picsum.photos/seed/art-beauty/200/120", videoUrl: 'https://storage.googleapis.com/gtv-videos-bucket/sample/WhatCarCanYouGetForAGrand.mp4' },
+    ];
+};
+
+export const createEmptyPage = (name: string): GreenPage => {
+    const pageId = `page_${Date.now()}_${Math.random()}`;
+    const designSettings = createInitialDesignSettings();
+    designSettings.avatarSource = {
+        type: 'url',
+        value: `https://api.dicebear.com/8.x/initials/svg?seed=${encodeURIComponent(name)}`
+    };
+
+    return {
+        id: pageId,
+        name: name,
+        slug: name.toLowerCase().replace(/\s+/g, '-'),
+        themeColor: themeColors[Math.floor(Math.random() * themeColors.length)],
+        pageSettings: {
+            blocks: [
+                { id: 'chat-1', type: 'chat', layout: 'under_avatar', visible: true }
+            ], // Start with just a chat block
+            design: designSettings,
+        },
+        aiAssistantSettings: {
+            persona: `You are a helpful assistant for the page "${name}". Be friendly and professional.`,
+            voiceId: 'vo1',
+            language: 'English',
+            conversationStyle: 'professional',
+            knowledgeBaseFiles: [],
+            knowledgeBaseUrls: [],
+            forbiddenUserKeywords: [],
+            forbiddenAIKeywords: [],
+        },
+        analyticsData: {
+            conversations: [],
+            formSubmissions: [],
+        },
+        aigcSettings: {
+            videos: [],
+            articles: [],
+            schedule: [],
+            userMedia: [],
+        }
+    };
+};
+
+export const createNewPage = (name: string): GreenPage => {
+    const pageId = `page_${Date.now()}_${Math.random()}`;
+    const designSettings = createInitialDesignSettings();
+    designSettings.avatarSource = {
+        type: 'url',
+        value: `https://api.dicebear.com/8.x/initials/svg?seed=${encodeURIComponent(name)}`
+    };
+
+    return {
+        id: pageId,
+        name: name,
+        slug: name.toLowerCase().replace(/\s+/g, '-'),
+        themeColor: themeColors[Math.floor(Math.random() * themeColors.length)],
+        pageSettings: {
+            blocks: createInitialBlocks(),
+            design: designSettings,
+        },
+        aiAssistantSettings: {
+            persona: `You are a helpful assistant for the page "${name}". Be friendly and professional.`,
+            voiceId: 'vo1',
+            language: 'English',
+            conversationStyle: 'professional',
+            knowledgeBaseFiles: [],
+            knowledgeBaseUrls: [],
+            forbiddenUserKeywords: [],
+            forbiddenAIKeywords: [],
+        },
+        analyticsData: {
+            conversations: [], // Start with no conversation data
+            formSubmissions: [],
+        },
+        aigcSettings: {
+            videos: createInitialVideos(), // Provide library content for user to explore
+            articles: createInitialAIGCArticles(), // Provide library content for user to explore
+            schedule: [],
+            userMedia: [],
+        }
+    };
+};

+ 376 - 0
services/showcaseDataService.ts

@@ -0,0 +1,376 @@
+import { GreenPage, Block, DesignSettings, MediaSource, AIGCArticle, ThemeName, BackgroundType, FontFamily, ButtonShape, ButtonStyle, SideNavSettings, BannerSettings, HeaderBlock, TextBlock, ImageBlock, VideoBlock, LinkBlock, ProductBlock, NewsBlock, MapBlock, EmailBlock, PhoneBlock, PdfBlock, SocialBlock, NewsItemFromUrl, ChatBlock, EnterpriseInfoBlock, FormBlock, AwardBlock, FooterBlock, BannerType } from '../types';
+import { subDays } from 'date-fns';
+
+const themeColors = [
+    'from-green-400 to-blue-500',
+    'from-purple-500 to-pink-500',
+    'from-yellow-400 to-orange-600',
+    'from-teal-400 to-cyan-500',
+    'from-red-500 to-indigo-600',
+    'from-rose-400 to-red-500',
+];
+
+const createInitialAIGCArticles = (): AIGCArticle[] => [
+    {
+        id: 'aigc-article-1',
+        title: 'The Rise of AI in Modern Web Development',
+        summary: 'An in-depth look at how artificial intelligence is revolutionizing the way we build and interact with websites, from automated testing to intelligent UI generation.',
+        content: `
+<h2>The Dawn of a New Era</h2>
+<p>From automated testing to intelligent UI generation, AI is no longer a futuristic concept but a <strong>present-day tool for developers</strong>. The landscape of web development is shifting at an unprecedented pace, with machine learning models now capable of writing code, designing user interfaces, and even deploying applications.</p>
+<p>One of the most significant impacts is in the realm of UI/UX design. Tools powered by generative AI can create stunning, user-friendly mockups from simple text prompts. <em>This dramatically accelerates the prototyping phase</em>, allowing teams to iterate on ideas faster than ever before. We're seeing a future where the line between designer and developer blurs, thanks to these intelligent assistants.</p>
+<img src="https://picsum.photos/seed/ai-article-1/600/400" alt="AI and code" style="width: 100%; height: auto; border-radius: 8px; margin: 1em 0;" />
+<h2>Challenges and Opportunities</h2>
+<p>Of course, this revolution doesn't come without its challenges. Questions around code quality, security vulnerabilities in AI-generated scripts, and the ethical implications of job displacement are paramount. However, the opportunities are boundless. Developers can now focus on more complex architectural problems, leaving routine coding tasks to their AI partners. For more information, you can read this <a href="https://example.com" target="_blank" rel="noopener noreferrer">in-depth analysis</a>.</p>
+`,
+        publicationDate: subDays(new Date(), 5).toISOString(),
+        sourceType: 'generated',
+    },
+    {
+        id: 'aigc-article-2',
+        title: 'Sustainable Living: 10 Tips for an Eco-Friendly Home',
+        summary: 'Discover simple yet effective ways to reduce your carbon footprint and create a greener living space, from your kitchen to your garden.',
+        content: `
+<h2>It Starts at Home</h2>
+<p>Sustainability is more than just a buzzword; it's a lifestyle that begins right in our own homes. Creating an eco-friendly living space doesn't require a complete overhaul. Simple, consistent changes can lead to a significant positive impact on the environment and even your wallet.</p>
+<img src="https://picsum.photos/seed/eco-article-1/600/400" alt="A green home" style="width: 100%; height: auto; border-radius: 8px; margin: 1em 0;" />
+<h2>Practical Steps You Can Take Today</h2>
+<p>Here are a few tips to get started:</p>
+<ol>
+  <li><strong>Reduce, Reuse, Recycle:</strong> The classic three R's are more important than ever.</li>
+  <li><strong>Compost Food Scraps:</strong> Reduce landfill waste and create nutrient-rich soil for your garden.</li>
+  <li><strong>Switch to LED Lighting:</strong> They use significantly less energy and last much longer than traditional bulbs.</li>
+  <li><em>Fix Leaky Faucets:</em> A small drip can waste gallons of water every day.</li>
+</ol>
+<p>By adopting these habits, you contribute to a healthier planet for future generations. It's a journey of continuous learning and improvement. Check out <a href="https://example.com/eco" target="_blank" rel="noopener noreferrer">this resource</a> for more ideas.</p>
+`,
+        publicationDate: subDays(new Date(), 12).toISOString(),
+        sourceType: 'generated',
+    },
+];
+
+const createInitialVideos = () => {
+    return [
+        { id: 'vid1', title: 'Product Demo', thumbnailUrl: "https://picsum.photos/seed/vid1/200/120", videoUrl: 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4' },
+        { id: 'vid2', title: 'Weekly Update', thumbnailUrl: "https://picsum.photos/seed/vid2/200/120", videoUrl: 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4' },
+    ];
+};
+
+const merge = <T extends object>(base: T, updates: Partial<T>): T => {
+    const result = { ...base };
+    for (const key in updates) {
+        if (Object.prototype.hasOwnProperty.call(updates, key)) {
+            const value = updates[key as keyof T];
+            if (typeof value === 'object' && value !== null && !Array.isArray(value) && base[key as keyof T] && typeof base[key as keyof T] === 'object') {
+                result[key as keyof T] = merge(base[key as keyof T] as any, value as any);
+            } else {
+                result[key as keyof T] = value;
+            }
+        }
+    }
+    return result;
+};
+
+
+const customizePage = (
+    basePage: GreenPage,
+    customizations: {
+        name: string;
+        slug: string;
+        design?: Partial<DesignSettings>;
+        blocks?: Block[];
+    }
+): GreenPage => {
+    const newPage = JSON.parse(JSON.stringify(basePage));
+
+    newPage.name = customizations.name;
+    newPage.slug = customizations.slug;
+
+    if (customizations.design) {
+        // Deep merge design settings
+        newPage.pageSettings.design = merge(newPage.pageSettings.design, customizations.design);
+    }
+
+    if (customizations.blocks) {
+        newPage.pageSettings.blocks = customizations.blocks;
+    }
+
+    return newPage;
+};
+
+
+const personalArchetypes = [
+    {
+        name: "Alex Doe - Software Developer",
+        slug: "alex-doe-dev",
+        design: {
+            theme: 'dark' as ThemeName,
+            backgroundType: 'gradient' as BackgroundType,
+            backgroundValue: 'bg-gradient-to-br from-gray-900 via-blue-900/50 to-gray-900',
+            fontFamily: 'mono' as FontFamily,
+            buttonShape: 'square' as ButtonShape,
+            buttonStyle: 'outline' as ButtonStyle,
+            avatarSource: { type: 'url', value: 'https://api.dicebear.com/8.x/adventurer-neutral/svg?seed=Alex' } as MediaSource
+        },
+        blocks: [
+            { type: 'chat', layout: 'under_avatar', visible: true, id: 'c1' } as ChatBlock,
+            { type: 'header', text: 'Alex Doe', visible: true, id: 'h1', titleAlignment: 'center' } as HeaderBlock,
+            { type: 'text', content: 'Full-Stack Developer | Open-Source Contributor | Tech Enthusiast', visible: true, id: 't1', textAlign: 'center', fontSize: '16px', fontColor: '#d1d5db', isBold: false, isItalic: false } as TextBlock,
+            { type: 'social', links: [
+                { id: 's1', platform: 'github', url: 'https://github.com' },
+                { id: 's2', platform: 'linkedin', url: 'https://linkedin.com' },
+                { id: 's3', platform: 'twitter', url: 'https://twitter.com' },
+            ], visible: true, id: 'so1' } as SocialBlock,
+            { type: 'header', text: 'Projects', visible: true, id: 'h2', titleAlignment: 'left' } as HeaderBlock,
+            { type: 'link', title: 'Project Titan - AI-Powered Analytics', url: '#', visible: true, id: 'l1' } as LinkBlock,
+            { type: 'link', title: 'QuantumLeap - A new JS Framework', url: '#', visible: true, id: 'l2' } as LinkBlock,
+        ]
+    },
+    {
+        name: "Jamie Lee - Photographer",
+        slug: "jamie-lee-photo",
+        design: {
+            theme: 'light' as ThemeName,
+            backgroundType: 'color' as BackgroundType,
+            backgroundValue: '#ffffff',
+            fontFamily: 'serif' as FontFamily,
+            fontColor: '#111827',
+            buttonShape: 'pill' as ButtonShape,
+            buttonStyle: 'filled' as ButtonStyle,
+            avatarSource: { type: 'url', value: 'https://api.dicebear.com/8.x/adventurer/svg?seed=Jamie' } as MediaSource,
+            customThemeColors: { background: '#ffffff', text: '#111827', button: '#f3f4f6', buttonText: '#111827' }
+        },
+        blocks: [
+             { type: 'chat', layout: 'under_avatar', visible: true, id: 'c1' } as ChatBlock,
+            { type: 'header', text: 'Jamie Lee', visible: true, id: 'h1', titleAlignment: 'center' } as HeaderBlock,
+            { type: 'text', content: 'Capturing moments, telling stories.', visible: true, id: 't1', textAlign: 'center', fontSize: '16px', fontColor: '#4b5563', isBold: false, isItalic: true } as TextBlock,
+            { type: 'image', layout: 'grid', sources: [
+                { type: 'url', value: 'https://picsum.photos/seed/jamie1/400/300' },
+                { type: 'url', value: 'https://picsum.photos/seed/jamie2/400/300' },
+                { type: 'url', value: 'https://picsum.photos/seed/jamie3/400/300' },
+                { type: 'url', value: 'https://picsum.photos/seed/jamie4/400/300' },
+            ], visible: true, id: 'i1' } as ImageBlock,
+            { type: 'header', text: 'Contact & Bookings', visible: true, id: 'h2', titleAlignment: 'left' } as HeaderBlock,
+            { type: 'email', label: 'Email for Inquiries', email: '#', visible: true, id: 'e1', displayMode: 'labelOnly' } as EmailBlock,
+            { type: 'social', links: [{ id: 's1', platform: 'instagram', url: 'https://instagram.com' }], visible: true, id: 'so1' } as SocialBlock,
+        ]
+    },
+    {
+        name: "Casey Solaris - Musician",
+        slug: "casey-solaris-music",
+        design: {
+            theme: 'synthwave' as ThemeName,
+            backgroundType: 'image' as BackgroundType,
+            backgroundValue: 'https://picsum.photos/seed/synth-bg/1200/800',
+            fontFamily: 'sans' as FontFamily,
+            buttonShape: 'rounded' as ButtonShape,
+            buttonStyle: 'outline' as ButtonStyle,
+            avatarSource: { type: 'url', value: 'https://api.dicebear.com/8.x/lorelei/svg?seed=Casey' } as MediaSource,
+        },
+        blocks: [
+             { type: 'chat', layout: 'under_avatar', visible: true, id: 'c1' } as ChatBlock,
+            { type: 'header', text: 'CASEY SOLARIS', visible: true, id: 'h1', titleAlignment: 'center' } as HeaderBlock,
+            { type: 'video', layout: 'single', sources: [{type: 'url', value: 'https://storage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4'}], visible: true, id: 'v1' } as VideoBlock,
+            { type: 'header', text: 'Stream My Music', visible: true, id: 'h2', titleAlignment: 'left' } as HeaderBlock,
+            { type: 'link', title: 'Spotify', url: '#', visible: true, id: 'l1' } as LinkBlock,
+            { type: 'link', title: 'Apple Music', url: '#', visible: true, id: 'l2' } as LinkBlock,
+            { type: 'link', title: 'Bandcamp', url: '#', visible: true, id: 'l3' } as LinkBlock,
+        ]
+    },
+];
+
+const enterpriseArchetypes = [
+    {
+        name: "Innovatech AI",
+        slug: "innovatech-ai",
+        design: {
+            theme: 'dark' as ThemeName,
+            fontFamily: 'sans' as FontFamily,
+            fontColor: '#e5e7eb',
+            backgroundType: 'gradient' as BackgroundType,
+            backgroundValue: 'bg-gradient-to-br from-gray-900 to-slate-800',
+            bannerSettings: {
+                type: 'image' as BannerType,
+// FIX: Added missing 'value' property to conform to the BannerSettings type.
+                value: '',
+                imageSource: { type: 'url', value: 'https://picsum.photos/seed/innovatech-banner/1200/400' } as MediaSource,
+                height: 400,
+                width: 'full'
+            },
+            sideNavSettings: {
+                backgroundColor: 'transparent',
+                textColor: '#9ca3af',
+                activeLinkColor: '#10b981',
+                hoverBackgroundColor: '#374151',
+                hoverTextColor: '#ffffff',
+// FIX: Added missing 'fontFamily' and 'fontSize' properties to conform to the SideNavSettings type.
+                fontFamily: 'sans',
+                fontSize: '14px',
+            }
+        },
+        blocks: [
+            { id: 'chat-enterprise-1', type: 'chat', layout: 'widget', visible: true } as ChatBlock,
+            { id: 'ent-h1', type: 'header', text: 'About Innovatech', visible: true, titleAlignment: 'left' } as HeaderBlock,
+            { id: 'ent-t1', type: 'text', content: "Innovatech AI is a leading provider of cutting-edge artificial intelligence solutions. Our mission is to empower businesses with transformative technologies that drive growth, efficiency, and innovation. We specialize in machine learning, natural language processing, and computer vision to solve complex challenges across various industries.", visible: true, textAlign: 'left', fontSize: '16px', fontColor: '#d1d5db', isBold: false, isItalic: false } as TextBlock,
+            { id: 'ent-h2', type: 'header', text: 'Our Solutions', visible: true, titleAlignment: 'left' } as HeaderBlock,
+            { id: 'ent-i1', type: 'image', layout: 'grid', sources: [{ type: 'url', value: 'https://picsum.photos/seed/solution1/400/300' }, { type: 'url', value: 'https://picsum.photos/seed/solution2/400/300' }], visible: true } as ImageBlock,
+            { id: 'ent-h3', type: 'header', text: 'Company Information', visible: true, titleAlignment: 'left' } as HeaderBlock,
+            { id: 'ent-info1', type: 'enterprise_info', alignment: 'left', items: [
+                { id: 'ei-1', icon: 'building', label: 'Legal Name', value: 'Innovatech AI Solutions, Inc.' },
+                { id: 'ei-2', icon: 'calendar', label: 'Founded', value: '2021' },
+                { id: 'ei-3', icon: 'location', label: 'Headquarters', value: 'San Francisco, CA' },
+            ], visible: true } as EnterpriseInfoBlock,
+            { id: 'ent-h4', type: 'header', text: 'Contact Us', visible: true, titleAlignment: 'left' } as HeaderBlock,
+            { id: 'ent-form1', type: 'form', title: 'Get in Touch', description: 'Fill out the form below and our team will get back to you shortly.', submitButtonText: 'Submit Request', fields: [
+                { id: 'name', label: 'Full Name', enabled: true, required: true },
+                { id: 'email', label: 'Work Email', enabled: true, required: true },
+                { id: 'company', label: 'Company', enabled: true, required: true },
+                { id: 'phone', label: 'Phone', enabled: true, required: false },
+            ], purposeOptions: [{id: 'po1', label: 'Sales Inquiry'}, {id: 'po2', label: 'Partnership'}], visible: true } as FormBlock,
+            { id: 'footer-ent-1', type: 'footer', layout: 'standard', copyrightText: '© 2024 Innovatech AI Solutions, Inc. All Rights Reserved.', legalText: 'Filing No. 123-456-789', navLinks: [{id:'fn1', title: 'Home', url:'#'}, {id:'fn2', title:'Solutions', url:'#'}], otherLinks: [{id:'fo1', title:'Privacy Policy', url:'#'}], statement: '', visible: true } as FooterBlock,
+        ]
+    },
+    {
+        name: "Sterling & Partners Law",
+        slug: "sterling-partners-law",
+        design: {
+            theme: 'light' as ThemeName,
+            fontFamily: 'serif' as FontFamily,
+            fontColor: '#1f2937',
+            backgroundType: 'color' as BackgroundType,
+            backgroundValue: '#f9fafb',
+            bannerSettings: {
+                type: 'gradient' as BannerType,
+                value: 'bg-gradient-to-r from-gray-700 via-gray-900 to-black',
+// FIX: Added missing 'imageSource' property to conform to the BannerSettings type.
+                imageSource: { type: 'url', value: 'https://picsum.photos/seed/law-firm-banner/1200/250' } as MediaSource,
+                height: 250,
+                width: 'full'
+            },
+            sideNavSettings: {
+                backgroundColor: 'transparent',
+                textColor: '#4b5563',
+                activeLinkColor: '#ca8a04', // a gold color
+                hoverBackgroundColor: '#f3f4f6',
+                hoverTextColor: '#111827',
+                navFloatStyle: 'top',
+// FIX: Added missing 'fontFamily' and 'fontSize' properties to conform to the SideNavSettings type.
+                fontFamily: 'serif',
+                fontSize: '14px',
+            }
+        },
+        blocks: [
+            { id: 'chat-law-1', type: 'chat', layout: 'widget', visible: true } as ChatBlock,
+            { id: 'law-h1', type: 'header', text: 'About Our Firm', visible: true, titleAlignment: 'left' } as HeaderBlock,
+            { id: 'law-t1', type: 'text', content: "Sterling & Partners is a premier law firm dedicated to providing exceptional legal services. With decades of combined experience, our attorneys are committed to achieving the best possible outcomes for our clients through rigorous advocacy and strategic counsel.", visible: true, textAlign: 'left', fontSize: '16px', fontColor: '#374151', isBold: false, isItalic: false } as TextBlock,
+            { id: 'law-h2', type: 'header', text: 'Practice Areas', visible: true, titleAlignment: 'left' } as HeaderBlock,
+            { id: 'law-l1', type: 'link', title: 'Corporate Law', url: '#', visible: true } as LinkBlock,
+            { id: 'law-l2', type: 'link', title: 'Intellectual Property', url: '#', visible: true } as LinkBlock,
+            { id: 'law-l3', type: 'link', title: 'Litigation & Arbitration', url: '#', visible: true } as LinkBlock,
+            { id: 'law-h3', type: 'header', text: 'Accolades', visible: true, titleAlignment: 'left' } as HeaderBlock,
+            { id: 'law-award1', type: 'award', layout: 'single', items: [
+                {id: 'aw1', title: 'Law Firm of the Year', subtitle: 'National Law Review', year: '2023', imageSource: {type: 'url', value: 'https://picsum.photos/seed/law-award1/100'}},
+                {id: 'aw2', title: 'Top Corporate Counsel', subtitle: 'Attorneys Weekly', year: '2022', imageSource: {type: 'url', value: 'https://picsum.photos/seed/law-award2/100'}}
+            ], visible: true} as AwardBlock,
+            { id: 'law-h4', type: 'header', text: 'Our Location', visible: true, titleAlignment: 'left' } as HeaderBlock,
+            { id: 'law-map1', type: 'map', address: '1 Wall Street, New York, NY', visible: true, displayStyle: 'interactiveMap' } as MapBlock,
+            { id: 'footer-law-1', type: 'footer', layout: 'centered', copyrightText: '© 2024 Sterling & Partners LLP. Attorney Advertising.', navLinks: [{id:'fn1', title: 'Home', url:'#'}, {id:'fn2', title:'Contact', url:'#'}], otherLinks: [{id:'fo1', title:'Disclaimer', url:'#'}], statement: 'Prior results do not guarantee a similar outcome.', visible: true } as FooterBlock,
+        ]
+    }
+];
+
+
+const basePage: GreenPage = {
+    id: 'base_page',
+    name: 'Base Page',
+    slug: 'base-page',
+    themeColor: 'from-gray-500 to-gray-700',
+    pageSettings: {
+        blocks: [],
+        design: {
+            theme: 'dark' as ThemeName,
+            customThemeColors: { background: '#111827', text: '#FFFFFF', button: '#374151', buttonText: '#FFFFFF' },
+            buttonStyle: 'filled' as ButtonStyle,
+            buttonShape: 'rounded' as ButtonShape,
+            fontFamily: 'sans' as FontFamily,
+            fontColor: '#FFFFFF',
+            fontSize: 'text-base',
+            backgroundType: 'gradient' as BackgroundType,
+            backgroundValue: 'bg-gradient-to-br from-gray-800 to-gray-900',
+            avatarSource: { type: 'url', value: 'https://api.dicebear.com/8.x/adventurer/svg?seed=Base' },
+            bannerSettings: {
+                type: 'image' as BannerType,
+                value: '',
+                imageSource: { type: 'url', value: "https://picsum.photos/seed/default-banner/1200/300" } as MediaSource,
+                height: 300,
+                width: 'contained' as 'contained',
+            },
+            sideNavSettings: {
+                backgroundColor: 'transparent',
+                textColor: '#d1d5db',
+                activeLinkColor: '#10b981',
+                hoverBackgroundColor: '#374151',
+                hoverTextColor: '#ffffff',
+                fontFamily: 'sans' as FontFamily,
+                fontSize: '14px',
+                navFloatStyle: 'normal' as 'normal',
+                navBackgroundStyle: 'compact' as 'compact',
+            },
+            chatWidgetSettings: {
+                iconColor: '#10b981', headerBackgroundColor: '#10b981', headerTextColor: '#FFFFFF',
+                panelBackgroundColor: '#1f2937', userMessageBackgroundColor: '#3b82f6',
+                userMessageTextColor: '#FFFFFF', aiMessageBackgroundColor: '#374151', aiMessageTextColor: '#FFFFFF',
+            },
+        },
+    },
+    aiAssistantSettings: {
+        persona: 'You are a helpful assistant.',
+        voiceId: 'vo1',
+        language: 'English',
+        conversationStyle: 'professional',
+        knowledgeBaseFiles: [],
+        knowledgeBaseUrls: [],
+        forbiddenUserKeywords: [],
+        forbiddenAIKeywords: [],
+    },
+    analyticsData: {
+        conversations: [],
+        formSubmissions: [],
+    },
+    aigcSettings: {
+        videos: createInitialVideos(),
+        articles: createInitialAIGCArticles(),
+        schedule: [],
+    }
+};
+
+export const generatePersonalShowcases = (): GreenPage[] => {
+    // FIX: Completed the .map() function by returning the result of customizePage.
+    return personalArchetypes.map((archetype, index) => {
+        const page = customizePage(basePage, {
+            name: archetype.name,
+            slug: archetype.slug,
+            design: archetype.design,
+            blocks: archetype.blocks,
+        });
+        page.id = `showcase-personal-${index}`;
+        // FIX: Simplified theme color selection to prevent type errors.
+        page.themeColor = themeColors[index % themeColors.length];
+        return page;
+    });
+};
+
+// FIX: Added the missing generateEnterpriseShowcases function to create and export enterprise-themed showcase pages.
+export const generateEnterpriseShowcases = (): GreenPage[] => {
+    return enterpriseArchetypes.map((archetype, index) => {
+        const page = customizePage(basePage, {
+            name: archetype.name,
+            slug: archetype.slug,
+            design: archetype.design,
+            blocks: archetype.blocks,
+        });
+        page.id = `showcase-enterprise-${index}`;
+        page.themeColor = themeColors[index % themeColors.length];
+        return page;
+    });
+};

+ 29 - 0
tsconfig.json

@@ -0,0 +1,29 @@
+{
+  "compilerOptions": {
+    "target": "ES2022",
+    "experimentalDecorators": true,
+    "useDefineForClassFields": false,
+    "module": "ESNext",
+    "lib": [
+      "ES2022",
+      "DOM",
+      "DOM.Iterable"
+    ],
+    "skipLibCheck": true,
+    "types": [
+      "node"
+    ],
+    "moduleResolution": "bundler",
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "allowJs": true,
+    "jsx": "react-jsx",
+    "paths": {
+      "@/*": [
+        "./*"
+      ]
+    },
+    "allowImportingTsExtensions": true,
+    "noEmit": true
+  }
+}

+ 432 - 0
types.ts

@@ -0,0 +1,432 @@
+import * as React from 'react';
+// FIX: Removed self-import of 'Block' which caused a conflict with the local declaration.
+export type NavItemKey = string;
+
+export interface NavSubItem {
+  name: string;
+  key: NavItemKey;
+}
+
+export interface NavItem {
+  name:string;
+  key: NavItemKey;
+  icon: JSX.Element;
+  children?: NavSubItem[];
+}
+
+// --- Page Builder Types ---
+export type BlockType = 'header' | 'link' | 'social' | 'video' | 'image' | 'text' | 'map' | 'pdf' | 'email' | 'phone' | 'news' | 'product' | 'chat' | 'enterprise_info' | 'form' | 'award' | 'footer';
+
+export interface BaseBlock {
+  id: string;
+  type: BlockType;
+  visible: boolean;
+  titleAlignment?: 'left' | 'center';
+}
+
+export interface HeaderBlock extends BaseBlock {
+  type: 'header';
+  text: string;
+}
+
+export interface LinkBlock extends BaseBlock {
+  type: 'link';
+  title: string;
+  url: string;
+  thumbnailUrl?: string;
+  iconUrl?: string;
+}
+
+export type SocialPlatform = 'twitter' | 'instagram' | 'facebook' | 'linkedin' | 'youtube' | 'tiktok' | 'github';
+
+export interface SocialLink {
+  id: string;
+  platform: SocialPlatform;
+  url: string;
+}
+
+export interface SocialBlock extends BaseBlock {
+  type: 'social';
+  links: SocialLink[];
+}
+
+export type MediaSource = 
+  | { type: 'url', value: string } 
+  | { type: 'file', value: { name: string, size: number, previewUrl: string } }
+  | { type: 'aigc', videoId: string };
+
+export interface VideoBlock extends BaseBlock {
+    type: 'video';
+    sources: MediaSource[]; 
+    layout: 'single' | 'grid';
+}
+
+export interface ImageBlock extends BaseBlock {
+    type: 'image';
+    sources: MediaSource[];
+    layout: 'single' | 'grid';
+}
+
+export interface TextBlock extends BaseBlock {
+    type: 'text';
+    content: string;
+    textAlign: 'left' | 'center' | 'right';
+    fontSize: string; // e.g., '16px'
+    fontColor: string; // e.g., '#000000'
+    isBold: boolean;
+    isItalic: boolean;
+}
+
+export interface MapBlock extends BaseBlock {
+    type: 'map';
+    address: string;
+    displayStyle?: 'interactiveMap' | 'imageOverlay';
+    backgroundImageSource?: MediaSource;
+}
+
+export interface PdfBlock extends BaseBlock {
+    type: 'pdf';
+    source: MediaSource;
+}
+
+export interface EmailBlock extends BaseBlock {
+    type: 'email';
+    email: string;
+    label: string;
+    displayMode: 'labelOnly' | 'labelAndValue';
+}
+
+export interface PhoneBlock extends BaseBlock {
+    type: 'phone';
+    phone: string;
+    label: string;
+    displayMode: 'labelOnly' | 'labelAndValue';
+}
+
+export interface NewsItemFromUrl {
+    id: string;
+    title: string;
+    summary: string;
+    url: string;
+}
+
+export type NewsBlock = BaseBlock & {
+    type: 'news';
+    layout: 'list' | 'grid';
+} & (
+    | { source: 'aigc'; articleIds: string[]; customItems?: never }
+    | { source: 'custom'; customItems: NewsItemFromUrl[]; articleIds?: never }
+);
+
+export interface ProductItem {
+    id: string;
+    url: string;
+    title: string;
+    imageUrl: string;
+    price: string;
+}
+
+export interface ProductBlock extends BaseBlock {
+    type: 'product';
+    items: ProductItem[];
+    layout: 'grid' | 'list';
+}
+
+export interface ChatBlock extends BaseBlock {
+    type: 'chat';
+    layout: 'button' | 'under_avatar' | 'widget';
+}
+
+export type EnterpriseInfoIcon = 'building' | 'bank' | 'money' | 'location' | 'calendar' | 'users' | 'lightbulb';
+
+export interface EnterpriseInfoItem {
+  id: string;
+  icon: EnterpriseInfoIcon;
+  label: string;
+  value: string;
+}
+
+export interface EnterpriseInfoBlock extends BaseBlock {
+  type: 'enterprise_info';
+  items: EnterpriseInfoItem[];
+  alignment?: 'left' | 'center';
+}
+
+export type FormFieldId = 'name' | 'email' | 'company' | 'phone' | 'industry' | 'position' | 'country';
+
+export interface FormField {
+  id: FormFieldId;
+  label: string;
+  enabled: boolean;
+  required: boolean;
+}
+
+export interface FormPurposeOption {
+  id: string;
+  label: string;
+}
+
+export interface FormBlock extends BaseBlock {
+  type: 'form';
+  title: string;
+  description: string;
+  fields: FormField[];
+  purposeOptions: FormPurposeOption[];
+  submitButtonText: string;
+}
+
+export interface AwardItem {
+  id: string;
+  title: string;
+  subtitle?: string;
+  year?: string;
+  imageSource?: MediaSource;
+}
+
+export interface AwardBlock extends BaseBlock {
+  type: 'award';
+  items: AwardItem[];
+  layout: 'grid' | 'single';
+}
+
+export interface FooterLink {
+  id: string;
+  title: string;
+  url: string;
+}
+
+export interface FooterBlock extends BaseBlock {
+  type: 'footer';
+  layout: 'standard' | 'centered';
+  copyrightText: string;
+  legalText?: string;
+  statement?: string;
+  navLinks: FooterLink[];
+  otherLinks: FooterLink[];
+}
+
+export type Block = HeaderBlock | LinkBlock | SocialBlock | VideoBlock | ImageBlock | TextBlock | MapBlock | PdfBlock | EmailBlock | PhoneBlock | NewsBlock | ProductBlock | ChatBlock | EnterpriseInfoBlock | FormBlock | AwardBlock | FooterBlock;
+
+export type ThemeName = 'light' | 'dark' | 'synthwave' | 'retro' | 'custom';
+export type ButtonStyle = 'filled' | 'outline';
+export type ButtonShape = 'rounded' | 'pill' | 'square';
+export type FontFamily = 'sans' | 'serif' | 'mono';
+export type BackgroundType = 'color' | 'gradient' | 'image';
+export type BannerType = 'color' | 'gradient' | 'image' | 'none';
+
+
+export interface CustomThemeColors {
+    background: string;
+    text: string;
+    button: string;
+    buttonText: string;
+}
+
+export interface BannerSettings {
+    type: BannerType;
+    value: string; // For color and gradient
+    imageSource: MediaSource; // For image type
+    height: number; // in pixels
+    width: 'full' | 'contained';
+}
+
+export interface SideNavSettings {
+    backgroundColor: string;
+    textColor: string;
+    activeLinkColor: string;
+    hoverBackgroundColor: string;
+    hoverTextColor: string;
+    fontFamily: FontFamily;
+    fontSize: string; // e.g. '14px'
+    navFloatStyle?: 'normal' | 'top' | 'center';
+    navBackgroundStyle?: 'compact' | 'full';
+}
+
+export interface ChatWidgetSettings {
+    iconColor: string;
+    headerBackgroundColor: string;
+    headerTextColor: string;
+    panelBackgroundColor: string;
+    userMessageBackgroundColor: string;
+    userMessageTextColor: string;
+    aiMessageBackgroundColor: string;
+    aiMessageTextColor: string;
+}
+
+export interface DesignSettings {
+  theme: ThemeName;
+  customThemeColors: CustomThemeColors;
+  buttonStyle: ButtonStyle;
+  buttonShape: ButtonShape;
+  fontFamily: FontFamily;
+  fontColor: string;
+  fontSize: string; // Tailwind class like 'text-base'
+  backgroundType: BackgroundType;
+  backgroundValue: string; // hex code, gradient class, or image URL
+  userBackgroundImages?: MediaSource[];
+  avatarSource?: MediaSource;
+  bannerSettings: BannerSettings;
+  sideNavSettings: SideNavSettings;
+  chatWidgetSettings: ChatWidgetSettings;
+}
+
+export interface PageSettings {
+  blocks: Block[];
+  design: DesignSettings;
+}
+
+// --- AI Assistant Types ---
+export interface ChatMessage {
+    id: string;
+    sender: 'user' | 'ai';
+    text: string;
+}
+
+export interface AIAssistantSettings {
+    persona: string;
+    voiceId: string;
+    language: string;
+    conversationStyle: 'friendly' | 'professional' | 'witty';
+    knowledgeBaseFiles: { name: string; size: number; type: string }[];
+    knowledgeBaseUrls: string[];
+    forbiddenUserKeywords: string[];
+    forbiddenAIKeywords: string[];
+}
+
+
+// --- Analytics Types ---
+export interface Conversation {
+    id: string;
+    visitorId: string;
+    timestamp: string;
+    interactions: ChatMessage[];
+    visitCount: number;
+    status: 'open' | 'resolved';
+    firstResponseTime?: number; // in seconds
+}
+
+export interface FormSubmissionData {
+    [key: string]: string;
+}
+
+export interface FormSubmission {
+    id: string;
+    formId: string;
+    visitorId: string;
+    timestamp: string;
+    data: FormSubmissionData;
+}
+
+export interface AnalyticsData {
+    conversations: Conversation[];
+    formSubmissions?: FormSubmission[];
+}
+
+export interface Visitor {
+    id: string;
+    lastSeen: string;
+    visitCount: number;
+    conversationCount: number;
+    formSubmissionCount?: number;
+    name?: string;
+    email?: string;
+    company?: string;
+}
+
+
+// --- AIGC Types ---
+export interface AIGCVideo {
+    id: string;
+    title: string;
+    thumbnailUrl: string;
+    videoUrl: string;
+}
+
+export interface AIGCArticle {
+    id: string;
+    title: string;
+    summary: string;
+    content: string;
+    publicationDate: string; // ISO string
+    sourceType: 'generated' | 'url' | 'text';
+    sourceUrl?: string; // For 'url' type
+}
+
+export interface SocialAccount {
+    id: string;
+    platform: 'Twitter' | 'Facebook' | 'Instagram' | 'LinkedIn';
+    username: string;
+    icon: JSX.Element;
+}
+
+export interface ScheduledPost {
+    id: string;
+    videoId: string;
+    date: string; // YYYY-MM-DD
+    time: string; // HH:MM
+    caption: string;
+    socialAccountIds: string[];
+    status: 'scheduled' | 'distributed';
+}
+
+export interface AIGCSettings {
+    videos: AIGCVideo[];
+    articles: AIGCArticle[];
+    schedule: ScheduledPost[];
+    userMedia?: MediaSource[];
+}
+
+// --- Video Creator Types ---
+export interface Avatar {
+    id: string;
+    name: string;
+    imageUrl: string;
+}
+
+export interface Voice {
+    id: string;
+    name: string;
+    accent: string;
+}
+
+export interface ScriptVersion {
+    id: string;
+    text: string;
+}
+
+export interface ScriptContent {
+    versions: ScriptVersion[];
+    selectedVersionId: string;
+}
+
+export interface VideoScene {
+    id: string;
+    script: ScriptContent;
+    avatarId: string;
+    voiceId: string;
+    background: {
+        type: 'color' | 'image' | 'video';
+        value: string; // hex code or image URL
+    };
+    avatarPosition: { x: number; y: number }; // In percentages
+    avatarScale: number;
+}
+
+export interface VideoProject {
+    id:string;
+    name: string;
+    scenes: VideoScene[];
+    aspectRatio: '16:9' | '9:16';
+}
+
+// --- MASTER PAGE TYPE ---
+export interface GreenPage {
+    id: string;
+    name: string;
+    slug: string;
+    themeColor: string;
+    pageSettings: PageSettings;
+    aiAssistantSettings: AIAssistantSettings;
+    analyticsData: AnalyticsData;
+    aigcSettings: AIGCSettings;
+}

+ 19 - 0
vite.config.ts

@@ -0,0 +1,19 @@
+import path from 'path';
+import { defineConfig, loadEnv } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig(({ mode }) => {
+    const env = loadEnv(mode, '.', '');
+    return {
+      plugins: [react()],
+      define: {
+        'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
+        'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
+      },
+      resolve: {
+        alias: {
+          '@': path.resolve(__dirname, '.'),
+        }
+      }
+    };
+});