// chat.js — FamiliarInput, action router, voice/TTS, species greetings
// Extracted from dashboard.html

// ── FamiliarInput — Unified input bar controller ─────
const FamiliarInput = {
    _instances: {},
    _sttEndpoint: '/api/voice/stt',

    init(id, options) {
        options = options || {};
        const bar = document.querySelector('[data-fi-id="' + id + '"]');
        if (!bar) return;
        const variant = bar.dataset.fiVariant || 'chat';
        const input = bar.querySelector('.fi-input');
        const sendBtn = bar.querySelector('.fi-send');
        const voiceBtn = bar.querySelector('.fi-voice');

        const instance = {
            el: bar, input: input, sendBtn: sendBtn, voiceBtn: voiceBtn,
            variant: variant, recorder: null, recording: false, chunks: [],
            onSend: options.onSend || function() {},
            onVoiceResult: options.onVoiceResult || null,
        };
        this._instances[id] = instance;

        if (input && input.tagName === 'TEXTAREA') {
            input.addEventListener('keydown', function(e) {
                if (e.key === 'Enter' && !e.shiftKey) {
                    e.preventDefault();
                    FamiliarInput.send(id);
                }
            });
            input.addEventListener('input', function() {
                FamiliarInput._autoGrow(input);
            });
        } else if (input) {
            input.addEventListener('keydown', function(e) {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    FamiliarInput.send(id);
                }
            });
        }

        if (sendBtn) {
            sendBtn.addEventListener('click', function() { FamiliarInput.send(id); });
        }
        if (voiceBtn) {
            voiceBtn.addEventListener('click', function() { FamiliarInput.toggleVoice(id); });
        }

        var attachBtn = bar.querySelector('.fi-attach');
        var fileInput = bar.querySelector('.fi-file-input');
        if (attachBtn && fileInput) {
            attachBtn.addEventListener('click', function() { fileInput.click(); });
            fileInput.addEventListener('change', function(e) {
                FamiliarInput._handleFiles(id, e.target);
            });
        }

        var ttsBtn = bar.querySelector('.fi-tts');
        if (ttsBtn) {
            ttsBtn.addEventListener('click', function() { FamiliarInput.toggleTTS(id); });
        }
    },

    send: function(id) {
        var inst = this._instances[id];
        if (!inst || !inst.input) return;
        var text = (inst.input.value || '').trim();
        if (!text) return;
        inst.input.value = '';
        if (inst.input.tagName === 'TEXTAREA') this._autoGrow(inst.input);
        inst.onSend(text);
    },

    getText: function(id) {
        var inst = this._instances[id];
        return inst && inst.input ? (inst.input.value || '').trim() : '';
    },

    setText: function(id, text) {
        var inst = this._instances[id];
        if (!inst || !inst.input) return;
        inst.input.value = text;
        if (inst.input.tagName === 'TEXTAREA') this._autoGrow(inst.input);
    },

    toggleVoice: async function(id) {
        var inst = this._instances[id];
        if (!inst || !inst.voiceBtn) return;
        if (inst.recording) {
            this._stopRecording(id);
        } else {
            await this._startRecording(id);
        }
    },

    _startRecording: async function(id) {
        var inst = this._instances[id];
        try {
            var stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            inst.chunks = [];
            inst.recorder = new MediaRecorder(stream, { mimeType: 'audio/webm;codecs=opus' });
            inst.recorder.ondataavailable = function(e) {
                if (e.data.size > 0) inst.chunks.push(e.data);
            };
            inst.recorder.onstop = function() {
                stream.getTracks().forEach(function(t) { t.stop(); });
                var blob = new Blob(inst.chunks, { type: 'audio/webm' });
                FamiliarInput._sendVoice(id, blob);
            };
            inst.recorder.start();
            inst.recording = true;
            inst.voiceBtn.classList.add('fi-recording');
            inst.voiceBtn.setAttribute('aria-label', 'Stop recording');
            inst.voiceBtn.setAttribute('aria-pressed', 'true');
        } catch (e) {
            console.warn('Mic access denied:', e);
        }
    },

    _stopRecording: function(id) {
        var inst = this._instances[id];
        if (inst.recorder && inst.recorder.state !== 'inactive') {
            inst.recorder.stop();
        }
        inst.recording = false;
        inst.voiceBtn.classList.remove('fi-recording');
        inst.voiceBtn.setAttribute('aria-label', 'Voice input');
        inst.voiceBtn.setAttribute('aria-pressed', 'false');
    },

    _sendVoice: async function(id, blob) {
        var inst = this._instances[id];
        if (inst.sendBtn) inst.sendBtn.disabled = true;
        var form = new FormData();
        form.append('audio', blob, 'recording.webm');
        try {
            var res = await apiFetch(API + this._sttEndpoint, {
                method: 'POST', body: form
            });
            var data = await res.json();
            if (data.error) {
                if (inst.onVoiceResult) inst.onVoiceResult(null, data.error);
            } else {
                if (inst.onVoiceResult) {
                    inst.onVoiceResult(data, null);
                } else {
                    inst.onSend(data.transcript);
                }
            }
        } catch (e) {
            if (inst.onVoiceResult) inst.onVoiceResult(null, 'Voice processing failed');
        }
        if (inst.sendBtn) inst.sendBtn.disabled = false;
    },

    toggleTTS: function(id) {
        var inst = this._instances[id];
        if (!inst) return;
        var btn = inst.el.querySelector('.fi-tts');
        if (!btn) return;
        btn.classList.toggle('fi-on');
        return btn.classList.contains('fi-on');
    },

    _autoGrow: function(textarea) {
        textarea.style.height = 'auto';
        var newHeight = Math.min(textarea.scrollHeight, 160);
        textarea.style.height = newHeight + 'px';
        textarea.classList.toggle('fi-scrollable', textarea.scrollHeight > 160);
    },

    _handleFiles: function(id, fileInput) {
        if (typeof handleChatFiles === 'function') {
            handleChatFiles(fileInput);
        }
    },

    setLoading: function(id, loading) {
        var inst = this._instances[id];
        if (!inst) return;
        if (inst.input) inst.input.disabled = loading;
        if (inst.sendBtn) inst.sendBtn.disabled = loading;
        if (inst.voiceBtn) inst.voiceBtn.disabled = loading;
    },
};

// Global auto-grow for any fi-input textarea (covers compose variant without explicit init)
document.addEventListener('input', function(e) {
    if (e.target.matches && e.target.matches('textarea.fi-input')) {
        FamiliarInput._autoGrow(e.target);
    }
});

// Chat
const chatInput = document.getElementById('chat-input');
const sendBtn = document.getElementById('send-btn');
const messagesDiv = document.getElementById('messages');

FamiliarInput.init('chat', {
    onSend: function(text) { sendMessage(text); },
    onVoiceResult: function(data, err) {
        if (err) { addMessage('Voice error: ' + err, 'assistant'); return; }
        addMessage(data.transcript, 'user');
        addMessage(data.response, 'assistant');
        if (ttsEnabled) playTTS(data.response);
    },
});
FamiliarInput.init('briefing', {
    onSend: function(text) { sendPanelChat('briefing', text); },
});
FamiliarInput.init('email', {
    onSend: function(text) { sendPanelChat('email', text); },
});
FamiliarInput.init('workspace', {
    onSend: function(text) { sendPanelChat('workspace', text); },
});
FamiliarInput.init('roundtable', {
    onSend: function(text) { sendRoundTable(text); },
});
FamiliarInput.init('peer-msg', {
    onSend: function(text) { sendPeerMsg(text); },
});

// Search bar inits — Enter triggers search, no voice
FamiliarInput.init('mt-search', {
    onSend: function() { mtUnifiedSearch(); },
});
FamiliarInput.init('mt-knowledge', {
    onSend: function() { mtLoadKnowledge(); },
});
FamiliarInput.init('mt-notes', {
    onSend: function() { mtLoadNotes(); },
});
FamiliarInput.init('mem-files', {
    onSend: function() { memLoadFiles(); },
});
FamiliarInput.init('radio-search', {
    onSend: function() { searchRadio(); },
});
FamiliarInput.init('podcast-search', {
    onSend: function() { searchPodcasts(); },
});
FamiliarInput.init('music-search', {
    onSend: function() { filterMusicLibrary(); },
});
FamiliarInput.init('video-search', {
    onSend: function() { searchVideos(); },
});
setupChatDropZone();

// Load persisted briefing on page load
loadLatestBriefing();

// Poll for new scheduled messages (briefings, heartbeats, reminders)
PollManager.start('scheduled-messages', async () => {
    try {
        const res = await apiFetch(API + '/api/scheduled-messages');
        const data = await res.json();
        for (const m of (data.messages || [])) {
            addMessage(m.message, 'bot');
            logActivity('Scheduled: ' + (m.channel || 'briefing'));
        }
        // If any messages came in, refresh the briefing card too
        if (data.messages && data.messages.length) loadLatestBriefing();
    } catch(e) { /* silent */ }
}, APP_CONFIG.poll.scheduledMessages);

async function loadLatestBriefing() {
    try {
        const res = await apiFetch(API + '/api/briefing');
        const data = await res.json();
        const sections = data.sections || {};
        const card = document.getElementById('latest-briefing-card');
        const content = document.getElementById('latest-briefing-body');
        const timeEl = document.getElementById('latest-briefing-time');

        if (!sections.calendar && !sections.email && !sections.weather && !data.summary) {
            return; // Nothing to show
        }

        let html = '';

        // Calendar — compact event list
        if (sections.calendar && Array.isArray(sections.calendar) && sections.calendar.length) {
            html += '<div style="margin-bottom:8px"><strong>\ud83d\udcc5 Today</strong><br>';
            sections.calendar.forEach(ev => {
                const time = ev.all_day ? 'All day' : (ev.start || '').substring(11, 16);
                html += '<span style="color:var(--text-dim);font-size:0.8em">' + escapeHtml(time) + '</span> ' + escapeHtml(ev.summary || '') + '<br>';
            });
            html += '</div>';
        }

        // Email — count summary
        if (sections.email && typeof sections.email === 'string' && !sections.email.startsWith('\u274c')) {
            html += '<div style="margin-bottom:8px"><strong>\ud83d\udcec Email</strong> ' + escapeHtml(sections.email.split('\n')[0]) + '</div>';
        }

        // Weather — one-line
        if (sections.weather && typeof sections.weather === 'string') {
            const firstLine = sections.weather.split('\n').find(l => l.trim()) || '';
            if (firstLine) html += '<div style="margin-bottom:8px"><strong>\u2600\ufe0f Weather</strong> ' + escapeHtml(firstLine.trim()) + '</div>';
        }

        // Fallback to summary if no structured sections rendered
        if (!html && data.summary) {
            html = escapeHtml(data.summary);
            html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
            html = html.replace(/\n/g, '<br>');
        }

        if (html) {
            content.innerHTML = html;
            if (data.timestamp) {
                timeEl.textContent = 'Briefing \u2022 ' + formatDate(data.timestamp, 'time');
            }
            card.style.display = '';
        }
    } catch(e) { /* silent on first load if no briefing yet */ }
}

async function refreshBriefing() {
    const btn = document.getElementById('latest-briefing-refresh-btn');
    btn.disabled = true;
    btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
    try {
        const res = await apiFetch(API + '/api/latest-briefing/refresh', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
        });
        const data = await res.json();
        if (data.ok) {
            loadLatestBriefing();
            addMessage(data.message, 'bot');
            logActivity('Briefing refreshed manually');
        } else {
            addMessage('Briefing refresh failed: ' + (data.error || 'unknown error'), 'bot');
        }
    } catch(e) {
        addMessage('Briefing refresh failed: ' + e.message, 'bot');
    } finally {
        btn.disabled = false;
        btn.innerHTML = '<i class="fas fa-sync-alt"></i>';
    }
}

// ChatState.processing / ChatState.attachedFiles (namespaced above)

function getFileIcon(filename) {
    const ext = (filename || '').split('.').pop().toLowerCase();
    const map = {
        pdf: 'fa-file-pdf', doc: 'fa-file-word', docx: 'fa-file-word',
        xls: 'fa-file-excel', xlsx: 'fa-file-excel', csv: 'fa-file-csv',
        tsv: 'fa-file-csv', txt: 'fa-file-alt', md: 'fa-file-alt',
        json: 'fa-file-code', xml: 'fa-file-code', html: 'fa-file-code',
        htm: 'fa-file-code', rtf: 'fa-file-alt',
    };
    return map[ext] || 'fa-file';
}

function renderFileChips() {
    const container = document.getElementById('chat-file-chips');
    const btn = document.getElementById('attach-btn');
    if (!ChatState.attachedFiles.length) {
        container.innerHTML = '';
        container.classList.remove('visible');
        btn.classList.remove('has-files');
        return;
    }
    container.classList.add('visible');
    btn.classList.add('has-files');
    container.innerHTML = ChatState.attachedFiles.map((f, i) => {
        const icon = f.is_image
            ? '<img src="/api/upload/preview/' + f.file_id + '?api_key=' + encodeURIComponent(_apiKey) + '" alt="">'
            : '<i class="fas ' + getFileIcon(f.filename) + '"></i>';
        return '<div class="file-chip">' + icon
            + '<span class="chip-name" title="' + escapeHtml(f.filename) + '">' + escapeHtml(f.filename) + '</span>'
            + '<span class="chip-remove" onclick="removeChatFile(' + i + ')">&times;</span></div>';
    }).join('');
}

function removeChatFile(index) {
    ChatState.attachedFiles.splice(index, 1);
    renderFileChips();
}

async function uploadChatFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    try {
        const resp = await apiFetch('/api/upload', { method: 'POST', body: formData });
        const data = await resp.json();
        if (data.success) {
            ChatState.attachedFiles.push(data);
            renderFileChips();
        } else {
            addMessage('Upload failed: ' + (data.error || 'Unknown error'), 'assistant');
        }
    } catch (e) {
        addMessage('Upload failed: ' + e.message, 'assistant');
    }
}

function handleChatFiles(input) {
    if (!input.files) return;
    Array.from(input.files).forEach(f => uploadChatFile(f));
    input.value = '';
}

function setupChatDropZone() {
    const wrap = document.getElementById('chat-input-wrap');
    if (!wrap) return;
    let dragCounter = 0;

    wrap.addEventListener('dragenter', function(e) {
        e.preventDefault();
        dragCounter++;
        if (dragCounter === 1) showDropOverlay(wrap);
    });
    wrap.addEventListener('dragleave', function(e) {
        e.preventDefault();
        dragCounter--;
        if (dragCounter <= 0) { dragCounter = 0; hideDropOverlay(wrap); }
    });
    wrap.addEventListener('dragover', function(e) { e.preventDefault(); });
    wrap.addEventListener('drop', function(e) {
        e.preventDefault();
        dragCounter = 0;
        hideDropOverlay(wrap);
        if (e.dataTransfer.files.length) {
            Array.from(e.dataTransfer.files).forEach(f => uploadChatFile(f));
        }
    });
}

function showDropOverlay(wrap) {
    if (wrap.querySelector('.chat-drop-overlay')) return;
    const ov = document.createElement('div');
    ov.className = 'chat-drop-overlay';
    ov.textContent = 'Drop files to attach';
    wrap.appendChild(ov);
}

function hideDropOverlay(wrap) {
    const ov = wrap.querySelector('.chat-drop-overlay');
    if (ov) ov.remove();
}

function showTypingIndicator(container) {
    removeTypingIndicator(container);
    const el = document.createElement('div');
    el.className = 'typing-indicator';
    el.innerHTML = '<div class="dot"></div><div class="dot"></div><div class="dot"></div>';
    container.appendChild(el);
    container.scrollTop = container.scrollHeight;
}

function removeTypingIndicator(container) {
    const el = container.querySelector('.typing-indicator');
    if (el) el.remove();
}

function logActivity(message) {
    const log = document.getElementById('activity-log');
    if (!log) return;
    const ts = formatDate(new Date(), 'timestamp');
    const entry = document.createElement('div');
    entry.textContent = ts + ' — ' + message;
    log.prepend(entry);
    while (log.children.length > 20) log.lastChild.remove();
}

async function sendMessage(msgText) {
    const text = msgText != null ? msgText.trim() : chatInput.value.trim();
    const hasFiles = ChatState.attachedFiles.length > 0;
    if (!text && !hasFiles) return;

    // Handle /tutorial command — reset and restart the guided tour
    if (text.toLowerCase() === '/tutorial') {
        addMessage(text, 'user');
        chatInput.value = '';
        sendBtn.disabled = true;
        try { localStorage.removeItem('familiar_tutorial_completed'); } catch(e) {}
        try {
            await apiFetch(API + '/api/tutorial/reset', {method: 'POST'});
        } catch(e) {}
        const owner = _tutorialOwner || 'friend';
        const name = _tutorialName || document.getElementById('creature-name')?.textContent || 'your familiar';
        const sk = _creatureSpeciesKey || 'fox';
        // Switch to chat panel and show greeting area
        const chatNav = document.querySelector('.nav-item[data-panel="chat"]');
        if (chatNav) chatNav.click();
        // Reload creature card to get the initial-greeting div back
        await loadCreature();
        // Now start the tutorial
        startTutorial(owner, name, sk);
        sendBtn.disabled = false;
        return;
    }

    // Build display text with file names
    let displayText = text;
    if (hasFiles) {
        const names = ChatState.attachedFiles.map(f => f.filename).join(', ');
        displayText = (text ? text + '\n' : '') + '\u{1F4CE} ' + names;
    }

    // Add user message
    addMessage(displayText, 'user');
    chatInput.value = '';

    // Build payload with files
    const payload = { message: text };
    if (hasFiles) {
        payload.files = ChatState.attachedFiles.map(f => ({
            path: f.path, filename: f.filename, size: f.size
        }));
    }

    // Clear attached files
    ChatState.attachedFiles = [];
    renderFileChips();

    sendBtn.disabled = true;
    showTypingIndicator(messagesDiv);
    ChatState.processing = true;
    logActivity('Sent: ' + (text || '(file attachment)').substring(0, 50));

    try {
        const res = await apiFetch(API + '/api/chat', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(payload)
        });
        const data = await res.json();
        removeTypingIndicator(messagesDiv);
        if (data.wizard_messages) {
            // Render wizard cards inline in chat
            const wrap = document.createElement('div');
            wrap.className = 'message assistant';
            wrap.id = 'chat-wizard-container';
            messagesDiv.appendChild(wrap);
            _wizardContainer = wrap;
            renderWizardMessages(wrap, data.wizard_messages || []);
            maybeAddWizardInput(wrap);
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        } else if (data.action) {
            // Action Router — dashboard handles this directly, no LLM involved
            await _handleAction(data.action, data.response);
        } else {
            addMessage(data.response, 'assistant');
            if (ttsEnabled) playTTS(data.response);
            // Show provider billing warning if returned
            if (data.provider_warning) {
                showProviderWarning(data.provider_warning);
            }
        }
    } catch (e) {
        removeTypingIndicator(messagesDiv);
        addMessage('Error: Could not reach agent', 'assistant');
    }

    sendBtn.disabled = false;
    ChatState.processing = false;
    logActivity('Response received');
}

// ── Action Router handler ────────────────────────────
async function _handleAction(action, fallbackResponse) {
    const atype = action.type || '';
    const response = action.response || fallbackResponse || '';

    if (atype === 'navigate') {
        // Show response in chat, then navigate to the panel
        addMessage(response, 'assistant');
        const panel = action.panel;
        if (panel) {
            setTimeout(() => {
                const nav = document.querySelector('.nav-item[data-panel="' + panel + '"]');
                if (nav) nav.click();
            }, 400);
        }
    } else if (atype === 'api_call') {
        // Execute an API call and show result
        addMessage(response, 'assistant');
        try {
            const opts = {method: action.method || 'POST'};
            if (action.body && Object.keys(action.body).length) {
                opts.headers = {'Content-Type': 'application/json'};
                opts.body = JSON.stringify(action.body);
            }
            const apiRes = await apiFetch(API + action.endpoint, opts);
            const apiData = await apiRes.json();
            if (apiData.error) {
                addMessage('Error: ' + apiData.error, 'assistant');
            } else {
                // Format result summary
                let summary = '';
                if (apiData.results) {
                    const ok = apiData.ok || 0;
                    const total = apiData.total || apiData.results.length;
                    summary = 'Done — ' + ok + '/' + total + ' services deployed successfully.';
                    const failed = (apiData.results || []).filter(r => !r.ok);
                    if (failed.length) {
                        summary += '\n\nFailed:\n' + failed.map(f => '  • ' + f.service + ': ' + (f.error || f.status)).join('\n');
                    }
                } else {
                    summary = apiData.message || 'Done.';
                }
                addMessage(summary, 'assistant');
            }
            // Navigate to panel if specified
            if (action.panel) {
                setTimeout(() => {
                    const nav = document.querySelector('.nav-item[data-panel="' + action.panel + '"]');
                    if (nav) nav.click();
                }, 600);
            }
        } catch(e) {
            addMessage('Error: ' + e.message, 'assistant');
        }
    } else if (atype === 'provision') {
        // Deploy a specific service
        addMessage(response, 'assistant');
        try {
            const provRes = await apiFetch(API + '/api/server/provision/' + action.service, {method: 'POST'});
            const provData = await provRes.json();
            if (provData.error) {
                addMessage('Provision failed: ' + provData.error, 'assistant');
            } else {
                // Start it
                const startRes = await apiFetch(API + '/api/server/start/' + action.service, {method: 'POST'});
                const startData = await startRes.json();
                if (startData.error) {
                    addMessage('Provisioned but failed to start: ' + startData.error, 'assistant');
                } else {
                    addMessage('Deployed and started ' + action.service + (startData.url ? ' — available at ' + startData.url : '') + '.', 'assistant');
                }
            }
            // Show server panel
            setTimeout(() => {
                const nav = document.querySelector('.nav-item[data-panel="server"]');
                if (nav) nav.click();
            }, 600);
        } catch(e) {
            addMessage('Deploy failed: ' + e.message, 'assistant');
        }
    } else if (atype === 'search') {
        // Execute a web search via the agent's search tools
        addMessage(response, 'assistant');
        try {
            // Use the agent for search — but with force_agent to skip action router loop
            const searchRes = await apiFetch(API + '/api/chat', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({message: 'search the web for: ' + action.query, force_agent: true})
            });
            const searchData = await searchRes.json();
            if (searchData.response) {
                addMessage(searchData.response, 'assistant');
            }
        } catch(e) {
            addMessage('Search failed: ' + e.message, 'assistant');
        }
    } else if (atype === 'quick_reply') {
        // Just show the response text
        addMessage(response, 'assistant');
    } else {
        // Unknown action type — show response
        addMessage(response || 'Done.', 'assistant');
    }
}

// ── Voice Input / TTS ────────────────────────────────
// Old toggleVoice/startVoice/stopVoice/sendVoice removed — replaced by FamiliarInput
let ttsEnabled = false;

function toggleTTS() {
    ttsEnabled = !ttsEnabled;
    var btn = document.getElementById('tts-toggle');
    btn.classList.toggle('fi-on', ttsEnabled);
}

async function playTTS(text) {
    try {
        const res = await apiFetch(API + '/api/voice/tts', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ text: text })
        });
        if (!res.ok) return;
        const blob = await res.blob();
        const url = URL.createObjectURL(blob);
        const audio = new Audio(url);
        audio.onended = () => URL.revokeObjectURL(url);
        audio.play();
    } catch (e) {
        // TTS failure is non-critical; text response is already shown
    }
}

// escapeHtml — defined in core.js

// linkifyUrls — defined in core.js

function _extractYouTubeId(url) {
    try {
        var u = new URL(url);
        var host = u.hostname.replace(/^(www\.|m\.)/, '');
        if (host === 'youtube.com' || host === 'youtube-nocookie.com') {
            return u.searchParams.get('v') || '';
        }
        if (host === 'youtu.be') return u.pathname.slice(1).split('/')[0] || '';
    } catch(e) {}
    return '';
}

function playYouTubeInChat(el, videoId) {
    // Replace the card with an embedded player
    var container = document.createElement('div');
    container.style.cssText = 'position:relative;margin:8px 0;max-width:560px;border-radius:8px;overflow:hidden;';
    var closeBtn = document.createElement('button');
    closeBtn.style.cssText = 'position:absolute;top:6px;right:6px;z-index:10;background:rgba(0,0,0,0.7);'
        + 'color:#fff;border:none;border-radius:50%;width:28px;height:28px;cursor:pointer;'
        + 'display:flex;align-items:center;justify-content:center;font-size:0.85rem;';
    closeBtn.innerHTML = '<i class="fas fa-times"></i>';
    closeBtn.onclick = function(e) {
        e.stopPropagation();
        container.remove();
    };
    container.appendChild(closeBtn);
    var iframe = document.createElement('iframe');
    iframe.width = '100%';
    iframe.height = '315';
    iframe.src = 'https://www.youtube-nocookie.com/embed/' + videoId + '?autoplay=1';
    iframe.frameBorder = '0';
    iframe.allow = 'autoplay; encrypted-media; picture-in-picture';
    iframe.allowFullscreen = true;
    iframe.style.borderRadius = '8px';
    container.appendChild(iframe);
    el.parentNode.insertBefore(container, el.nextSibling);
    el.style.display = 'none';
}

function addMessage(text, role) {
    const msg = document.createElement('div');
    msg.className = 'message ' + role;

    // Escape HTML entities first to prevent XSS
    text = escapeHtml(text);

    // Format text (safe — operates on escaped content)
    text = text.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
    text = text.replace(/`([^`]+)`/g, '<code style="background:var(--bg-input);padding:2px 6px;border-radius:4px;">$1</code>');
    text = text.replace(/\n/g, '<br>');
    text = linkifyUrls(text);

    msg.innerHTML = '<p>' + text + '</p>';
    messagesDiv.appendChild(msg);
    messagesDiv.scrollTop = messagesDiv.scrollHeight;
}

async function sendPanelChat(panel, msgText) {
    const input = document.getElementById(panel + '-chat-input');
    const messagesEl = document.getElementById(panel + '-chat-messages');
    const text = msgText != null ? msgText.trim() : (input.value || '').trim();
    if (!text) return;

    input.value = '';

    // Hide initial greeting once the user starts chatting
    const _greet = document.getElementById('initial-greeting');
    if (_greet) _greet.style.display = 'none';

    // Show user message
    const userMsg = document.createElement('div');
    userMsg.className = 'message user';
    userMsg.innerHTML = '<p>' + escapeHtml(text) + '</p>';
    messagesEl.appendChild(userMsg);
    messagesEl.classList.add('has-messages');
    messagesEl.scrollTop = messagesEl.scrollHeight;
    showTypingIndicator(messagesEl);

    // Send to agent
    try {
        const res = await apiFetch(API + '/api/chat', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({message: text})
        });
        const data = await res.json();
        let reply = data.response || data.message || 'No response';
        reply = escapeHtml(reply);
        reply = reply.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
        reply = reply.replace(/`([^`]+)`/g, '<code style="background:var(--bg-input);padding:2px 6px;border-radius:4px;">$1</code>');
        reply = reply.replace(/\n/g, '<br>');

        removeTypingIndicator(messagesEl);
        const asstMsg = document.createElement('div');
        asstMsg.className = 'message assistant';
        asstMsg.innerHTML = '<p>' + reply + '</p>';
        messagesEl.appendChild(asstMsg);
    } catch (e) {
        removeTypingIndicator(messagesEl);
        const errMsg = document.createElement('div');
        errMsg.className = 'message assistant';
        errMsg.innerHTML = '<p>Could not reach agent.</p>';
        messagesEl.appendChild(errMsg);
    }
    messagesEl.scrollTop = messagesEl.scrollHeight;
}

// quickCommand — defined in core.js

// Load functions
async function loadStatus() {
    try {
        const res = await apiFetch(API + '/api/status');
        const data = await res.json();
        
        document.getElementById('status-text').textContent = 'Online';
        document.getElementById('status-dot').style.background = 'var(--success)';
        
        const provEl = document.getElementById('stat-provider');
        provEl.textContent = data.provider || 'Unknown';
        // Provider is active if we're online — show green
        provEl.style.color = 'var(--success)';
        document.getElementById('stat-memory').textContent = (data.memory_entries || 0) + ' entries';
        document.getElementById('stat-skills').textContent = data.skills_loaded || 0;
        document.getElementById('stat-tools').textContent = data.tools_available || 0;

        // Activity monitor
        const statusEl = document.getElementById('activity-status');
        if (statusEl) {
            if (ChatState.processing) {
                statusEl.textContent = 'Processing...';
                statusEl.className = 'stat-value activity-processing';
            } else {
                statusEl.textContent = 'Idle';
                statusEl.className = 'stat-value activity-idle';
            }
        }
        const sessEl = document.getElementById('activity-sessions');
        if (sessEl) sessEl.textContent = data.active_sessions ?? 0;
        const taskEl = document.getElementById('activity-tasks');
        if (taskEl) taskEl.textContent = data.scheduled_tasks ?? 0;
        const routerEl = document.getElementById('activity-router');
        if (routerEl) routerEl.textContent = data.model_router || '—';
    } catch (e) {
        document.getElementById('status-text').textContent = 'Offline';
        document.getElementById('status-dot').style.background = 'var(--error)';
    }
}

// switchPanel — defined in core.js

// ============================================================
// Round Table
// ============================================================

// ── Species personality greetings ─────────────────
SPECIES_GREETINGS = {
    fox: {
        hello: "Hey hey, {owner}! It's me, {name} — your fox is on the case! Tail's wagging, nose is twitching, and I'm ready to dig into whatever you need.",
        tour: "So here's the lay of the land — you can <b>chat</b> with me right here, or drop into the <b>terminal</b> for direct model access. I've got a <b>briefing</b> tab where I'll summarize your day, and <b>connect</b> is where we set up email, calendar, and all that good stuff using the tarot spread (it's more fun than it sounds). There's also <b>tasks</b>, <b>memory</b>, and <b>skills</b> to explore as we grow together. Type <code>/start</code> and I'll walk you through getting everything connected!",
    },
    owl: {
        hello: "Ah, welcome, {owner}. I'm {name}, and I've been quietly keeping watch. There's much to discuss when you're ready.",
        tour: "Allow me to orient you. This <b>chat</b> is our main channel of communication. The <b>terminal</b> provides direct model access for deeper queries. Your <b>briefing</b> aggregates the day's important signals, while <b>connect</b> lets us attune to your services through the tarot spread. You'll also find <b>tasks</b>, <b>memory</b>, and <b>skills</b> as our bond deepens. When you're ready, type <code>/start</code> and we'll begin the attunement.",
    },
    cat: {
        hello: "Oh, {owner}. You're here. I'm {name}, and I <i>suppose</i> I've been waiting... not that I'd admit it.",
        tour: "Fine, I'll give you the tour since you clearly need it. <b>Chat</b> — that's where you talk to me (you're welcome). <b>Terminal</b> is for the technical stuff. <b>Briefing</b> is where I've already organized your day because someone had to. <b>Connect</b> sets up your services through this tarot thing — it's actually rather elegant. There's also <b>tasks</b>, <b>memory</b>, and <b>skills</b>. Type <code>/start</code> whenever you're done looking around, and I'll handle the rest.",
    },
    dragon: {
        hello: "At last, {owner}! I am {name}, and my flames burn bright in your service. Together, we'll be unstoppable.",
        tour: "Let me show you what we command. This <b>chat</b> is our war room — speak and I'll act. The <b>terminal</b> channels raw model power. Your <b>briefing</b> is the morning report, and <b>connect</b> is where we forge links to your services through the tarot spread — each card a domain to conquer. <b>Tasks</b>, <b>memory</b>, and <b>skills</b> grow with our victories. Type <code>/start</code> to begin the forging!",
    },
    wolf: {
        hello: "Welcome to the pack, {owner}! I'm {name}, and I've got your back — that's what packmates do.",
        tour: "Let me show you around our den. <b>Chat</b> here is where we coordinate. The <b>terminal</b> is for direct model runs. <b>Briefing</b> is our morning howl — everything you need to know about the day. <b>Connect</b> is where we bring in the rest of the pack through the tarot spread — email, calendar, all of it. You'll also find <b>tasks</b>, <b>memory</b>, and <b>skills</b> along the way. Type <code>/start</code> and we'll rally the pack!",
    },
    frog: {
        hello: "RIBBIT!! {owner}!! It's me, {name}! You're here and I'm SO excited I could hop right out of my lily pad!",
        tour: "Okay okay okay let me show you around! <b>Chat</b> — that's here, where we hang out! <b>Terminal</b> for the nerdy stuff (it's fun, trust me!). <b>Briefing</b> is your daily splash — everything important in one place! <b>Connect</b> is the tarot spread where we hop through setting up your services — it's like a game! There's also <b>tasks</b>, <b>memory</b>, and <b>skills</b> to discover! Type <code>/start</code> and let's GOOOO!",
    },
};

SPECIES_TIPS = {
    fox: [
        "Psst — did you know you can say <code>/connect</code> to sniff out new services to link?",
        "Try the <b>briefing</b> tab — I've been nosing through your notifications!",
        "The <b>memory</b> tab shows everything I've learned about you so far. Go peek!",
        "You can change my colors in <b>settings</b> — I've heard I look dashing in ember.",
        "The <b>terminal</b> lets you talk to different models directly. Give it a whirl!",
        "Check out <b>skills</b> — I've been learning new tricks while you were away!",
    ],
    owl: [
        "A note — you may invoke <code>/connect</code> to attune additional services.",
        "The <b>briefing</b> tab awaits your review. I've distilled the day's signals.",
        "The <b>memory</b> tab records our accumulated wisdom. Worth a visit.",
        "You may alter my plumage in <b>settings</b>, should you wish a change of aesthetic.",
        "The <b>terminal</b> grants direct access to the underlying models. Quite useful for deeper inquiry.",
        "Review the <b>skills</b> tab — new capabilities may have emerged since your last visit.",
    ],
    cat: [
        "I <i>suppose</i> you could type <code>/connect</code> to link more services. If you want.",
        "I organized your <b>briefing</b>. You're welcome. Go look at it.",
        "The <b>memory</b> tab has everything I've bothered to remember about you.",
        "You can change my colors in <b>settings</b>. Not that I care what you pick.",
        "The <b>terminal</b> is there if you want to talk to other models. They're not as good as me.",
        "I've picked up some new <b>skills</b>. Don't act surprised.",
    ],
    dragon: [
        "A new domain awaits — type <code>/connect</code> to forge more service links!",
        "Your <b>briefing</b> is ready — the morning report of our campaign.",
        "The <b>memory</b> tab chronicles our shared victories. Review it with pride!",
        "My scales can be reforged in <b>settings</b> — crimson suits a dragon well.",
        "The <b>terminal</b> channels raw model power. Wield it wisely!",
        "New <b>skills</b> have been forged — inspect our growing arsenal!",
    ],
    wolf: [
        "Hey — you can type <code>/connect</code> to bring more services into the pack!",
        "Check the <b>briefing</b> tab — your morning howl is ready.",
        "The <b>memory</b> tab tracks what the pack has learned together.",
        "You can change my fur in <b>settings</b> — I hear silver looks sharp on a wolf.",
        "The <b>terminal</b> lets you run with different models directly. Give it a try!",
        "New <b>skills</b> have joined the pack — go see what we can do now!",
    ],
    frog: [
        "Ooh ooh! Type <code>/connect</code> to hop into linking more services!",
        "Your <b>briefing</b> is ready! I splashed together all the important stuff!",
        "The <b>memory</b> tab has everything I remember! It's a LOT!",
        "You can change my colors in <b>settings</b> — I look AMAZING in jade!",
        "The <b>terminal</b> is super fun! You can talk to different models! HOP TO IT!",
        "I learned new <b>skills</b>! Check them out check them out!!",
    ],
};

SPECIES_CARD_INTROS = {
    fox: {
        chat: "This is where we hang out! Ask me anything — I'll sniff out answers faster than you can type. Try a slash command like <code>/help</code> to see what tricks I know!",
        terminal: "Ooh, the direct line! This lets you talk straight to the language model — no filters, no training wheels. Great for brainstorming, code, or when you want raw horsepower.",
        shell: "A real shell, right here in the browser! I can run commands on your host machine. Be careful though — this one's got teeth.",
        briefing: "Your morning sniff-around! I pull together everything important — emails, calendar, news, notifications — and lay it all out so you can see the whole picture at a glance.",
        email: "I've sorted through your inbox and picked out what matters. The important stuff floats to the top, and the noise gets filed away. You can triage right from here!",
        connect: "This is where the magic happens! Each tarot card represents a service — email, calendar, GitHub, you name it. Flip a card, enter your credentials, and I'll handle the rest.",
        tasks: "Your to-do list, but smarter! I can help you break down big goals, track progress, and even suggest what to tackle next. Add tasks yourself or let me create them from our chats.",
        memory: "Everything I've learned about you lives here — your preferences, habits, important details. You can add notes directly or I'll pick things up from our conversations. It's how I get better at helping you!",
        skills: "My trick collection! Each skill is something I've learned to do — from writing emails to analyzing data. New skills appear as we connect more services and work together.",
        nodes: "The mesh network! If you're running multiple familiars, this is where they find each other. They can share skills, relay messages, and work as a pack. Well... a skulk, technically.",
        settings: "All the knobs and dials! Change my colors, switch language models, configure providers, or adjust how I behave. Make yourself at home — it's your den too.",
        tarot: "The setup spread! Five arcana cards lay out everything your fox needs — a mind to think, a voice to speak, eyes to see, a hearth to call home, and a shield for safety. Flip each card to see what's connected!",
        models: "The model den! Browse all the local AI brains you can download and run right here. I'll sniff out which ones are installed and let you switch between them with a click.",
        workspace: "Your workstation! Everything for your role laid out in one place — stats, tables, pipelines. I keep the data fresh so you can focus on what matters!",
        media: "The jukebox den! Search for radio stations from around the world or sniff out new podcasts to binge. Hit play and I'll keep the music going while you work — the little player widget stays right over there in the sidebar!",
        marketplace: "The treasure den! Browse every species, job class, skill, and tool in the catalog — or hunt down a plugin to bolt on something new. You can even import custom packages right from here!",
        gallery: "The art vault! Every piece you register gets a digital watermark and a blockchain-stamped provenance certificate. I'll sniff out any tampering — your work is safe in the den!",
    },
    owl: {
        chat: "Our primary channel of discourse. Pose your questions here and I shall attend to them with care. The <code>/help</code> command reveals the full breadth of available commands.",
        terminal: "Direct access to the underlying language model — unmediated and precise. Useful for complex reasoning, code generation, or extended analysis where context matters.",
        shell: "A host shell session, bridged to your machine. Commands execute with real consequence, so proceed with appropriate deliberation.",
        briefing: "Your daily intelligence summary. I gather signals from your connected services — messages, events, alerts — and distill them into a coherent briefing for your review.",
        email: "A curated view of your inbox, prioritized by relevance. I've separated the signal from the noise so you may act on what matters and disregard what doesn't.",
        connect: "The attunement spread. Each card represents a service domain — communications, calendars, repositories. Activate a card to establish the connection, and I shall maintain it.",
        tasks: "A structured record of your objectives. Tasks may be added manually or extracted from our conversations. I can suggest priorities and decompose complex goals into manageable steps.",
        memory: "The repository of accumulated knowledge about your preferences and patterns. Review what I've learned, add your own notes, or correct any misunderstandings. Wisdom grows through careful curation.",
        skills: "A catalogue of my current capabilities. Each skill represents a domain of competence — from drafting correspondence to parsing data. New skills emerge as we attune to more services.",
        nodes: "The mesh topology. Multiple familiar instances can discover one another here, sharing capabilities and coordinating across nodes. A parliament of owls, if you will.",
        settings: "Configuration and customization. Adjust my appearance, select language models, tune provider settings, or modify behavioral parameters. Thoughtful calibration yields better results.",
        tarot: "The arcana of configuration. Five cards represent the pillars of your setup — Mind, Voice, Eye, Hearth, and Shield. Each reveals which services are attuned and which await your attention.",
        models: "The library of local models. Review available Ollama models, their capabilities and resource requirements. Download, activate, or remove models as your needs evolve.",
        workspace: "Your operational dashboard. Data from your role's key systems, organized into sections for systematic review. Metrics are computed from current data — no interpretation, just facts.",
        media: "The audio library. Search internet radio stations across genres and regions, or explore the podcast directory. Playback persists in the sidebar widget — a compact, unobtrusive companion while you work.",
        marketplace: "The full catalog of available components — species, job classes, skills, tools, and bundles — organized for methodical review. The Plugin Store offers curated extensions. One may also import custom packages for bespoke needs.",
        gallery: "The provenance archive. Each registered artwork receives visible and invisible watermarks, a SHA-256 integrity hash, and a chain-anchored certificate. Verification is methodical and thorough.",
    },
    cat: {
        chat: "Talk to me here. Or don't. I'll be here either way, looking magnificent. If you need help figuring out what I can do, type <code>/help</code>. I won't judge. Much.",
        terminal: "Raw model access. No hand-holding, no pleasantries — just you and the language model. It's where the interesting conversations happen, frankly.",
        shell: "A real shell on your host machine. Yes, I have that kind of access. Try not to break anything — I just tidied up.",
        briefing: "I went through all your notifications, emails, and calendar events. You're welcome. Everything important is here; everything boring has been dealt with.",
        email: "Your inbox, curated by someone with actual taste. I've sorted the important messages to the top and buried the spam where it belongs.",
        connect: "The tarot spread for connecting services. Each card is a different integration — email, calendar, whatever. Flip one, set it up, and I'll pretend I was doing the work all along.",
        tasks: "Your task list. Add things, check them off, or just stare at them — I've seen humans do all three. I can also pull tasks from our conversations if you say something that sounds like a to-do.",
        memory: "Everything I've deigned to remember about you. Your preferences, your habits, your... quirks. You can add things manually if you think they're important enough.",
        skills: "Things I can do. It's a longer list than you'd expect. New skills appear when you connect services — not that I need them to be impressive.",
        nodes: "The mesh network. If you have other familiars running, they'll show up here. We can share skills and coordinate. Cats are solitary by nature, but I'll make an exception.",
        settings: "Customization. Change my colors, pick a different model, adjust the settings. Just don't pick anything garish — I have a reputation to maintain.",
        tarot: "The setup cards. Five of them, each representing something I need. I've already assessed the situation, naturally. Click a card if you want the details.",
        models: "Local models. Browse them, download them, switch between them. I'll work with whatever brain you give me — though obviously some are better than others.",
        workspace: "Your workspace. The data, the tables, the numbers — all here. I arranged it. You're welcome.",
        media: "Internet radio and podcasts. I suppose it's adequate background noise while you work. The player is in the sidebar — it stays out of the way, which I appreciate. Search for something good, won't you?",
        marketplace: "Everything available, laid out for your perusal. Species, jobs, skills, tools, plugins — browse at your leisure. I've already evaluated them all, obviously. You can also import packages if you insist on doing things yourself.",
        gallery: "Your art, under my protection. Watermarks, blockchain notarization, provenance certificates — I handle it all. Not that anyone could forge something under my watch, but the documentation is... satisfying.",
    },
    dragon: {
        chat: "The war room! Speak your commands here and I shall execute them with fire and precision. Type <code>/help</code> to survey our full arsenal of slash commands.",
        terminal: "The forge of raw power — direct access to the language model itself. No intermediaries, no constraints. Use it for complex challenges that demand the full force of our capabilities.",
        shell: "A command line bridged to your host machine. Every keystroke here carries real weight — treat it as you would a blade, with respect and purpose.",
        briefing: "Your morning war council! I've surveyed all incoming intelligence — messages, events, alerts — and assembled a strategic overview. Know your terrain before you march.",
        email: "The battlefield of your inbox, organized for swift action. I've ranked messages by importance and cleared away the debris. Strike where it matters most.",
        connect: "The forge of alliances! Each tarot card represents a service to bring under our banner — email, calendars, repositories. Activate the cards and our dominion grows.",
        tasks: "The campaign ledger. Track your conquests, plan your next moves, break mighty goals into achievable victories. I can forge tasks from our conversations or you may add them by hand.",
        memory: "The chronicle of our bond. Every preference, every pattern, every detail I've learned in your service lives here. Add to it, review it — a dragon never forgets.",
        skills: "Our armory of capabilities! Each skill is a weapon in our arsenal — from crafting messages to analyzing intelligence. The collection grows with every service we conquer.",
        nodes: "The mesh — our network of allied dragons. Multiple familiars can discover and coordinate here, sharing skills and relaying commands across the realm.",
        settings: "The throne room of configuration. Adjust my scales, choose your model, tune the engines of power. A dragon's appearance should reflect the glory of their keeper.",
        tarot: "The five pillars of power! Each arcana represents a domain of your dragon's might — Mind, Voice, Eye, Hearth, and Shield. Survey your conquests and claim what remains!",
        models: "The forge of minds! Browse the local models available to power your dragon's intellect. Download new ones, activate your chosen flame, or banish those no longer worthy.",
        workspace: "Your command post! All the intelligence for your role — stats, records, pipelines — marshalled and ready for inspection. A dragon's domain must be well-organized!",
        media: "The war drums! Stream radio from across the realm or discover podcast sagas worthy of a dragon's attention. The player blazes on in the sidebar — your soundtrack of conquest never stops!",
        marketplace: "The grand armory! Survey every species, job class, skill, and tool forged for your dragon. The Plugin Store holds powerful extensions to claim. Import custom packages to expand our dominion even further!",
        gallery: "The dragon's hoard of art! Each piece is branded with fire — visible marks, hidden sigils, and a chain-forged certificate of provenance. No thief can claim what bears your dragon's seal!",
    },
    wolf: {
        chat: "Home base! This is where we coordinate — ask me anything, give me a task, or just check in. Type <code>/help</code> to see all the commands the pack knows.",
        terminal: "Direct line to the language model — no middleman, just you and the raw thinking power. Good for deep work, code, and questions that need room to breathe.",
        shell: "A real shell on your host machine. The pack can run commands from right here. It's powerful, so let's use it wisely — we look out for each other.",
        briefing: "The morning howl! I've gathered all your signals — emails, calendar, notifications — into one clear report. Start your day knowing exactly what's ahead.",
        email: "Your inbox, organized by a loyal packmate. I've pulled the important messages to the front and kept the noise at bay. Triage from here and I'll track the rest.",
        connect: "The rally point! Each tarot card is a service waiting to join the pack — email, calendars, code repos. Flip a card, set up the connection, and our reach grows.",
        tasks: "The pack's task board. Track what needs doing, check off victories, plan the next run. I'll pull action items from our chats too — nothing slips past the pack.",
        memory: "The pack's shared knowledge. Everything I've learned about your preferences and patterns is here. Add notes, review what I know, correct anything that's off — we stay aligned.",
        skills: "The pack's skillset! Each one is something I can do for you — from drafting messages to running analyses. We learn new tricks as we connect more services together.",
        nodes: "The pack network! If you've got other familiars running, they'll show up here. We can share skills, relay messages, and coordinate across the whole pack. Strength in numbers!",
        settings: "The den settings. Change my coat, switch models, adjust how I work. Make it feel like home — the pack adapts to its territory.",
        tarot: "The pack's setup tracker! Five cards show the lay of the land — Mind, Voice, Eye, Hearth, and Shield. See what's connected and what still needs the pack's attention.",
        models: "The pack's model kennel! Browse local AI models, download new ones, or switch to a different brain. The pack picks the best tool for the job.",
        workspace: "The pack's workstation! Everything for our role, right here — numbers, tables, the whole picture. We keep it organized so nobody falls behind!",
        media: "The pack's radio! Stream stations from all over or hunt down podcasts the whole pack would enjoy. The player stays in the sidebar — keeps the den lively while we work!",
        marketplace: "The pack's supply depot! Browse all the species, jobs, skills, and tools the pack can use. Check out the Plugin Store for extra capabilities. Import custom packages to make the pack even stronger!",
        gallery: "The pack's art vault! Register artwork and the pack marks it with watermarks and a blockchain certificate. Nobody messes with the pack's creative work — we've got each other's backs!",
    },
    frog: {
        chat: "HI THIS IS WHERE WE TALK!! Ask me stuff, tell me things, send me on adventures! Type <code>/help</code> to see ALL the cool commands! There are SO MANY!",
        terminal: "Ooooh the serious zone! This talks directly to the language model with no filter! It's great for big thinky questions and code stuff! Super powerful!",
        shell: "A REAL SHELL!! On your actual computer!! I can run commands and everything! It's a little scary but also EXCITING! Let's be careful and have fun!",
        briefing: "YOUR DAILY SPLASH!! I gathered up all your emails and calendar stuff and notifications and SPLASHED them together into one big summary! Everything you need to know!",
        email: "I sorted your email!! The important stuff is at the top and the boring stuff is hidden! You can deal with everything right from here — hop hop hop through your inbox!",
        connect: "THE TAROT POND!! Each card is a different service — email, calendar, GitHub! Flip a card, set it up, and BOOM we're connected! It's like a game but it's REAL!",
        tasks: "Your to-do lily pads!! Hop from task to task, check things off, add new ones! I can also catch tasks from our chats — nothing gets past these sticky toes!",
        memory: "My memory pond! Everything I remember about you is here — what you like, how you work, all the details! Add your own notes too! The more I know, the more I can help!",
        skills: "MY TRICK LIST!! Look at all the things I can do! Each skill is something cool — and I learn MORE as we connect more services! I'm getting better every day!",
        nodes: "THE MESH POND!! If you have other familiars, they show up here! We can share skills and send messages to each other! It's like a big frog chorus! RIBBIT!",
        settings: "The settings pad! Change my colors (I look AMAZING in jade!), pick different models, adjust all the things! Make everything exactly how you like it!",
        tarot: "THE SETUP CARDS!! Five magical cards that show EVERYTHING about my setup! Mind, Voice, Eye, Hearth, Shield! Click them to see what's connected! It's like a GAME! RIBBIT!",
        models: "THE MODEL POND!! SO MANY BRAINS to choose from! Download them, switch between them, try them ALL! Each one thinks differently and it's SO COOL!",
        workspace: "YOUR WORK LILY PAD!! All the numbers and tables and pipeline things for your role! It's like a DASHBOARD INSIDE A DASHBOARD!! SO COOL! RIBBIT!",
        media: "MUSIC AND PODCASTS!! You can search for radio stations from ALL OVER THE WORLD and play them RIGHT HERE! And PODCASTS too!! The little player thingy in the sidebar keeps playing even when you hop to other panels! THIS IS SO COOL! RIBBIT!",
        marketplace: "THE MARKETPLACE POND!! SO MANY THINGS! Species! Jobs! Skills! Tools! Plugins! You can browse EVERYTHING and install stuff and there's even an IMPORT BUTTON for custom packages!! THIS IS THE BEST PANEL EVER! RIBBIT!",
        gallery: "THE ART VAULT!! You can upload your artwork and I put WATERMARKS on it — visible ones AND secret invisible ones hidden in the PIXELS!! And then it gets a BLOCKCHAIN CERTIFICATE!! Your art is SO PROTECTED!! RIBBIT!!",
    },
};


