<!DOCTYPE html>
<!--
  Familiar - Self-hosted AI Agent
  Copyright (c) 2026 George Scott Foley
  Licensed under the MIT License
-->
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Familiar — Setup</title>
    <style>
        :root {
            --bg-dark: #0f0f1a;
            --bg-card: #1a1a2e;
            --bg-input: #252540;
            --accent: #e94560;
            --accent-hover: #ff6b6b;
            --text: #eaeaea;
            --text-dim: #888;
            --success: #4ade80;
            --warning: #fbbf24;
            --error: #f87171;
            --border: #333;
        }

        * { margin: 0; padding: 0; box-sizing: border-box; }

        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
            background: var(--bg-dark);
            color: var(--text);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: flex-start;
            padding: 40px 20px;
        }

        .wizard {
            max-width: 640px;
            width: 100%;
        }

        /* Progress Bar */
        .progress-bar {
            display: flex;
            gap: 6px;
            margin-bottom: 40px;
        }

        .progress-bar .step {
            flex: 1;
            height: 4px;
            background: var(--bg-input);
            border-radius: 2px;
            transition: background 0.3s;
        }

        .progress-bar .step.active { background: var(--accent); }
        .progress-bar .step.done { background: var(--success); }

        /* Step Container */
        .step-content { display: none; }
        .step-content.active { display: block; }

        /* Header */
        .step-header {
            text-align: center;
            margin-bottom: 32px;
        }

        .step-header h2 {
            font-size: 1.5rem;
            margin-bottom: 8px;
        }

        .step-header p {
            color: var(--text-dim);
            font-size: 0.95rem;
            line-height: 1.5;
        }

        .logo-header {
            font-size: 2rem;
            font-weight: 700;
            letter-spacing: 0.1em;
            margin-bottom: 8px;
        }

        .logo-header span { color: var(--accent); }

        /* Cards */
        .card-grid {
            display: grid;
            gap: 12px;
            margin-bottom: 24px;
        }

        .card {
            background: var(--bg-card);
            border: 2px solid var(--border);
            border-radius: 12px;
            padding: 16px 20px;
            cursor: pointer;
            transition: border-color 0.2s, transform 0.1s;
            display: flex;
            align-items: center;
            gap: 14px;
        }

        .card:hover { border-color: var(--accent); transform: translateY(-1px); }
        .card.selected { border-color: var(--accent); background: #1e1e36; }
        .card.detected { border-color: var(--success); }

        .card .icon {
            font-size: 1.5rem;
            width: 40px;
            text-align: center;
            flex-shrink: 0;
        }

        .card .card-body { flex: 1; }
        .card .card-body h4 { font-size: 0.95rem; margin-bottom: 2px; }
        .card .card-body p { font-size: 0.8rem; color: var(--text-dim); }

        .card .badge {
            font-size: 0.7rem;
            padding: 2px 8px;
            border-radius: 10px;
            background: var(--success);
            color: #000;
            font-weight: 600;
        }

        /* Checkbox cards */
        .card input[type="checkbox"],
        .card input[type="radio"] {
            display: none;
        }

        /* Inputs */
        .form-group {
            margin-bottom: 20px;
        }

        .form-group label {
            display: block;
            font-size: 0.85rem;
            color: var(--text-dim);
            margin-bottom: 6px;
        }

        .form-group input[type="text"],
        .form-group input[type="password"],
        .form-group input[type="time"] {
            width: 100%;
            padding: 12px 16px;
            background: var(--bg-input);
            border: 1px solid var(--border);
            border-radius: 8px;
            color: var(--text);
            font-size: 0.95rem;
            outline: none;
            transition: border-color 0.2s;
        }

        .form-group input:focus { border-color: var(--accent); }

        .form-group .input-wrapper {
            position: relative;
        }

        .form-group .toggle-vis {
            position: absolute;
            right: 12px;
            top: 50%;
            transform: translateY(-50%);
            background: none;
            border: none;
            color: var(--text-dim);
            cursor: pointer;
            font-size: 0.85rem;
        }

        .form-group .hint {
            font-size: 0.8rem;
            color: var(--text-dim);
            margin-top: 4px;
        }

        .form-group a {
            color: var(--accent);
            text-decoration: none;
        }

        .form-group a:hover { text-decoration: underline; }

        .conditional { display: none; margin-top: 12px; }
        .conditional.show { display: block; }

        /* Toggle switch */
        .toggle-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 12px 0;
        }

        .toggle-row .label { font-size: 0.95rem; }
        .toggle-row .sublabel { font-size: 0.8rem; color: var(--text-dim); }

        .toggle {
            position: relative;
            width: 48px;
            height: 26px;
            cursor: pointer;
        }

        .toggle input { display: none; }

        .toggle .slider {
            position: absolute;
            inset: 0;
            background: var(--bg-input);
            border-radius: 13px;
            transition: background 0.2s;
        }

        .toggle .slider::after {
            content: '';
            position: absolute;
            width: 20px;
            height: 20px;
            background: var(--text);
            border-radius: 50%;
            top: 3px;
            left: 3px;
            transition: transform 0.2s;
        }

        .toggle input:checked + .slider { background: var(--accent); }
        .toggle input:checked + .slider::after { transform: translateX(22px); }

        /* Buttons */
        .btn-row {
            display: flex;
            justify-content: space-between;
            margin-top: 32px;
        }

        .btn {
            padding: 12px 28px;
            border: none;
            border-radius: 8px;
            font-size: 0.95rem;
            font-weight: 600;
            cursor: pointer;
            transition: background 0.2s, transform 0.1s;
        }

        .btn:active { transform: scale(0.97); }

        .btn-primary {
            background: var(--accent);
            color: #fff;
        }

        .btn-primary:hover { background: var(--accent-hover); }
        .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }

        .btn-secondary {
            background: var(--bg-card);
            color: var(--text);
            border: 1px solid var(--border);
        }

        .btn-secondary:hover { border-color: var(--text-dim); }

        .btn-big {
            display: block;
            width: 100%;
            padding: 16px;
            font-size: 1.1rem;
            margin-top: 24px;
        }

        /* Summary table */
        .summary-table {
            width: 100%;
            border-collapse: collapse;
            margin-bottom: 24px;
        }

        .summary-table td {
            padding: 10px 12px;
            border-bottom: 1px solid var(--border);
            font-size: 0.9rem;
        }

        .summary-table td:first-child {
            color: var(--text-dim);
            width: 130px;
        }

        /* Activation log */
        .activation-log {
            background: var(--bg-input);
            border-radius: 8px;
            padding: 16px;
            font-family: 'JetBrains Mono', monospace;
            font-size: 0.8rem;
            line-height: 1.8;
            max-height: 300px;
            overflow-y: auto;
            margin-bottom: 20px;
        }

        .activation-log .line { color: var(--text-dim); }
        .activation-log .line.ok { color: var(--success); }
        .activation-log .line.error { color: var(--error); }

        .activation-progress {
            width: 100%;
            height: 6px;
            background: var(--bg-input);
            border-radius: 3px;
            margin-bottom: 16px;
            overflow: hidden;
        }

        .activation-progress .fill {
            height: 100%;
            background: var(--accent);
            border-radius: 3px;
            transition: width 0.3s;
            width: 0;
        }

        /* Complete */
        .complete-icon {
            font-size: 4rem;
            text-align: center;
            margin-bottom: 16px;
        }

        /* Error box */
        .error-box {
            background: rgba(248, 113, 113, 0.1);
            border: 1px solid var(--error);
            border-radius: 8px;
            padding: 12px 16px;
            margin-bottom: 16px;
            font-size: 0.85rem;
            color: var(--error);
            display: none;
        }

        .error-box.show { display: block; }

        /* Responsive */
        @media (max-width: 480px) {
            body { padding: 20px 12px; }
            .btn-row { flex-direction: column-reverse; gap: 12px; }
            .btn { width: 100%; text-align: center; }
        }
    </style>
</head>
<body>
<div class="wizard">
    <!-- Progress Bar -->
    <div class="progress-bar">
        <div class="step" data-step="0"></div>
        <div class="step" data-step="1"></div>
        <div class="step" data-step="2"></div>
        <div class="step" data-step="3"></div>
        <div class="step" data-step="4"></div>
        <div class="step" data-step="5"></div>
        <div class="step" data-step="6"></div>
        <div class="step" data-step="7"></div>
        <div class="step" data-step="8"></div>
        <div class="step" data-step="9"></div>
    </div>

    <!-- Step 0: Welcome -->
    <div class="step-content active" data-step="0">
        <div class="step-header">
            <div class="logo-header">🤝 <span>F A M I L I A R</span></div>
            <p>An AI companion designed to take care of you.</p>
        </div>
        <p style="text-align:center; color:var(--text-dim); margin-bottom:32px; line-height:1.6">
            This wizard will set up your personal AI assistant in a few minutes.
            You'll choose an AI provider, communication channel, and give your
            assistant a name and personality.
        </p>
        <button class="btn btn-primary btn-big" onclick="nextStep()">Get Started</button>
        <p style="text-align:center; margin-top:12px">
            <a href="#" onclick="clearSavedState(); location.reload(); return false;"
               style="color:var(--text-dim); font-size:0.8rem">Start Over</a>
        </p>
    </div>

    <!-- Step 1: LLM Provider -->
    <div class="step-content" data-step="1">
        <div class="step-header">
            <h2>AI Provider</h2>
            <p>Choose which AI model powers your assistant</p>
        </div>
        <div id="error-provider" class="error-box"></div>
        <div class="card-grid" id="provider-cards"></div>
        <div id="provider-key-section" class="conditional">
            <div class="form-group">
                <label id="provider-key-label">API Key</label>
                <div class="input-wrapper">
                    <input type="password" id="provider-key" placeholder="Paste your API key">
                    <button class="toggle-vis" onclick="toggleVis('provider-key')">show</button>
                </div>
                <div class="hint">
                    <a id="provider-key-link" href="#" target="_blank">Get an API key →</a>
                    &nbsp; Your key stays on your machine.
                </div>
            </div>
        </div>
        <!-- Ollama model picker -->
        <div id="ollama-model-section" class="conditional">
            <div class="form-group">
                <label>Model Setup</label>
                <div class="hint" id="ollama-ram-hint"></div>
            </div>
            <div class="card-grid" id="ollama-bundle-cards"></div>
        </div>
        <div class="btn-row">
            <button class="btn btn-secondary" onclick="prevStep()">Back</button>
            <button class="btn btn-primary" onclick="validateProvider()">Next</button>
        </div>
    </div>

    <!-- Step 2: Channel -->
    <div class="step-content" data-step="2">
        <div class="step-header">
            <h2>Communication Channel</h2>
            <p>How do you want to talk to your assistant?</p>
        </div>
        <div id="error-channel" class="error-box"></div>
        <div class="card-grid" id="channel-cards">
            <div class="card" onclick="toggleChannel(this, 'telegram')">
                <div class="icon">📱</div>
                <div class="card-body">
                    <h4>Telegram</h4>
                    <p>Recommended — works on phone + desktop</p>
                </div>
            </div>
            <div class="card" onclick="toggleChannel(this, 'discord')">
                <div class="icon">🎮</div>
                <div class="card-body">
                    <h4>Discord</h4>
                    <p>Use your existing Discord server</p>
                </div>
            </div>
            <div class="card" onclick="toggleChannel(this, 'matrix')">
                <div class="icon">🔗</div>
                <div class="card-body">
                    <h4>Matrix / Element</h4>
                    <p>Decentralized, end-to-end encrypted</p>
                </div>
            </div>
            <div class="card" onclick="toggleChannel(this, 'whatsapp')">
                <div class="icon">📱</div>
                <div class="card-body">
                    <h4>WhatsApp</h4>
                    <p>Via whatsapp-web.js bridge (Node.js required)</p>
                </div>
            </div>
            <div class="card" onclick="toggleChannel(this, 'signal')">
                <div class="icon">🔒</div>
                <div class="card-body">
                    <h4>Signal</h4>
                    <p>Private, open-source, end-to-end encrypted</p>
                </div>
            </div>
            <div class="card" onclick="toggleChannel(this, 'imessage')">
                <div class="icon">🍎</div>
                <div class="card-body">
                    <h4>iMessage</h4>
                    <p>macOS only — uses Messages.app</p>
                </div>
            </div>
            <div class="card" onclick="toggleChannel(this, 'teams')">
                <div class="icon">🏢</div>
                <div class="card-body">
                    <h4>Microsoft Teams</h4>
                    <p>Via Azure Bot Services</p>
                </div>
            </div>
            <div class="card" onclick="toggleChannel(this, 'cli')">
                <div class="icon">💻</div>
                <div class="card-body">
                    <h4>Terminal / CLI</h4>
                    <p>Local only — no bot token needed</p>
                </div>
            </div>
        </div>
        <!-- Telegram token -->
        <div id="telegram-config" class="conditional">
            <div class="form-group">
                <label>Telegram Bot Token</label>
                <div class="input-wrapper">
                    <input type="password" id="telegram-token" placeholder="123456:ABCdef...">
                    <button class="toggle-vis" onclick="toggleVis('telegram-token')">show</button>
                </div>
                <div class="hint">Message @BotFather on Telegram → /newbot → copy the token</div>
            </div>
        </div>
        <!-- Discord token -->
        <div id="discord-config" class="conditional">
            <div class="form-group">
                <label>Discord Bot Token</label>
                <div class="input-wrapper">
                    <input type="password" id="discord-token" placeholder="MTk...">
                    <button class="toggle-vis" onclick="toggleVis('discord-token')">show</button>
                </div>
                <div class="hint">discord.com/developers/applications → Bot → Copy token</div>
            </div>
        </div>
        <!-- WhatsApp config -->
        <div id="whatsapp-config" class="conditional">
            <div class="form-group">
                <label>Bridge Port</label>
                <input type="text" id="whatsapp-port" value="3001" placeholder="3001">
                <div class="hint">Node.js bridge port (default 3001). QR code pairing happens at first launch.</div>
            </div>
        </div>
        <!-- Signal config -->
        <div id="signal-config" class="conditional">
            <div class="form-group">
                <label>Signal Phone Number</label>
                <input type="text" id="signal-phone" placeholder="+14155551234">
                <div class="hint">Phone number with country code. Requires signal-cli installed on your server.</div>
            </div>
        </div>
        <!-- iMessage info -->
        <div id="imessage-config" class="conditional">
            <div class="form-group">
                <div class="hint">iMessage requires macOS with Messages.app. No additional configuration needed — just run Familiar on a Mac that's signed into iMessage.</div>
            </div>
        </div>
        <!-- Teams config -->
        <div id="teams-config" class="conditional">
            <div class="form-group">
                <label>Teams App ID</label>
                <input type="text" id="teams-app-id" placeholder="Azure Bot App ID">
                <div class="hint">From Azure Portal → Bot Services → Configuration</div>
            </div>
            <div class="form-group">
                <label>Teams App Password</label>
                <div class="input-wrapper">
                    <input type="password" id="teams-app-password" placeholder="App password">
                    <button class="toggle-vis" onclick="toggleVis('teams-app-password')">show</button>
                </div>
            </div>
            <div class="form-group">
                <label>Tenant ID (optional)</label>
                <input type="text" id="teams-tenant-id" placeholder="For single-tenant deployments">
            </div>
        </div>
        <!-- Matrix config -->
        <div id="matrix-config" class="conditional">
            <div class="form-group">
                <label>Homeserver URL</label>
                <input type="text" id="matrix-homeserver" placeholder="https://matrix.org">
            </div>
            <div class="form-group">
                <label>Bot User ID</label>
                <input type="text" id="matrix-user" placeholder="@familiar:matrix.org">
            </div>
            <div class="form-group">
                <label>Password or Access Token</label>
                <div class="input-wrapper">
                    <input type="password" id="matrix-password" placeholder="Password or token">
                    <button class="toggle-vis" onclick="toggleVis('matrix-password')">show</button>
                </div>
            </div>
        </div>
        <div class="btn-row">
            <button class="btn btn-secondary" onclick="prevStep()">Back</button>
            <button class="btn btn-primary" onclick="validateChannels()">Next</button>
        </div>
    </div>

    <!-- Step 3: Identity -->
    <div class="step-content" data-step="3">
        <div class="step-header">
            <h2>Name Your Assistant</h2>
            <p>Give your AI a name and personality</p>
        </div>
        <div id="error-identity" class="error-box"></div>
        <div class="form-group">
            <label>Assistant Name</label>
            <input type="text" id="assistant-name" value="Familiar" placeholder="Familiar">
        </div>
        <div class="card-grid" id="persona-cards">
            <div class="card selected" onclick="selectPersona(this, 'hospitality')">
                <div class="icon">🏛️</div>
                <div class="card-body">
                    <h4>Warm & Professional</h4>
                    <p>Hospitality consciousness — anticipatory, service-first</p>
                </div>
            </div>
            <div class="card" onclick="selectPersona(this, 'efficient')">
                <div class="icon">⚡</div>
                <div class="card-body">
                    <h4>Efficient & Concise</h4>
                    <p>Just the facts — direct and focused</p>
                </div>
            </div>
            <div class="card" onclick="selectPersona(this, 'friendly')">
                <div class="icon">😊</div>
                <div class="card-body">
                    <h4>Casual & Friendly</h4>
                    <p>Conversational and warm</p>
                </div>
            </div>
            <div class="card" onclick="selectPersona(this, 'formal')">
                <div class="icon">📋</div>
                <div class="card-body">
                    <h4>Formal & Thorough</h4>
                    <p>Detailed, well-structured responses</p>
                </div>
            </div>
        </div>
        <div class="btn-row">
            <button class="btn btn-secondary" onclick="prevStep()">Back</button>
            <button class="btn btn-primary" onclick="validateIdentity()">Next</button>
        </div>
    </div>

    <!-- Step 4: Profile (optional) -->
    <div class="step-content" data-step="4">
        <div class="step-header">
            <h2>Your Profile</h2>
            <p>Help your assistant understand your organization (optional)</p>
        </div>
        <div id="error-profile" class="error-box"></div>
        <div class="form-group">
            <label>Organization / Project Name</label>
            <input type="text" id="profile-org-name" placeholder="e.g. Sunrise Shelter">
        </div>
        <div class="form-group">
            <label>Type</label>
            <select id="profile-org-type" style="width:100%; padding:10px 12px; background:var(--bg-input); color:var(--text); border:1px solid var(--border); border-radius:8px; font-size:0.95rem;">
                <option value="">Select...</option>
                <option value="nonprofit">Nonprofit</option>
                <option value="business">Business</option>
                <option value="personal">Personal</option>
                <option value="creative">Creative / Freelance</option>
                <option value="social-services">Social Services</option>
            </select>
        </div>
        <div class="form-group">
            <label>Email</label>
            <input type="email" id="profile-email" placeholder="you@example.com">
        </div>
        <div class="form-group">
            <label>Location</label>
            <input type="text" id="profile-location" placeholder="e.g. Portland, OR">
        </div>
        <div class="btn-row">
            <button class="btn btn-secondary" onclick="prevStep()">Back</button>
            <button class="btn" style="background:var(--bg-input); color:var(--text-dim);" onclick="nextStep()">Skip</button>
            <button class="btn btn-primary" onclick="validateProfile()">Next</button>
        </div>
    </div>

    <!-- Step 5: API Credentials (optional) -->
    <div class="step-content" data-step="5">
        <div class="step-header">
            <h2>Connect Services</h2>
            <p>Paste API keys for the services you use. You can always add more later via <code>/connect</code>.</p>
        </div>
        <div id="error-credentials" class="error-box"></div>
        <div id="credentials-list" style="display:flex; flex-direction:column; gap:12px; margin-bottom:24px;"></div>
        <div class="btn-row" style="gap:8px;">
            <button class="btn btn-secondary" onclick="prevStep()">Back</button>
            <button class="btn" style="background:var(--bg-input); color:var(--text-dim);" onclick="nextStep()">Skip</button>
            <button class="btn" style="background:var(--success); color:#000;" onclick="testAllCredentials()">Test All</button>
            <button class="btn btn-primary" onclick="validateCredentials()">Next</button>
        </div>
    </div>

    <!-- Step 6: Security -->
    <div class="step-content" data-step="6">
        <div class="step-header">
            <h2>Security</h2>
            <p>Set an owner PIN and optional encryption</p>
        </div>
        <div id="error-security" class="error-box"></div>
        <div class="form-group">
            <label>Owner PIN (4-8 digits)</label>
            <div class="input-wrapper">
                <input type="text" id="owner-pin" maxlength="8"
                       pattern="[0-9]*" inputmode="numeric" placeholder="e.g. 1234">
                <button class="toggle-vis" onclick="toggleVis('owner-pin')">show</button>
            </div>
            <div class="hint">
                This PIN lets you claim ownership when messaging your bot for the first time.
                Send it as your first message after /start.
            </div>
        </div>
        <div style="margin-top: 24px;">
            <div class="toggle-row">
                <div>
                    <div class="label">Encrypt data at rest</div>
                    <div class="sublabel">Conversations, tasks, contacts encrypted on disk</div>
                </div>
                <label class="toggle">
                    <input type="checkbox" id="encrypt-toggle" checked onchange="toggleEncryption()">
                    <span class="slider"></span>
                </label>
            </div>
        </div>
        <div id="encryption-config" class="conditional">
            <div class="form-group">
                <label>Encryption Passphrase</label>
                <div class="input-wrapper">
                    <input type="text" id="encryption-passphrase" placeholder="Auto-generated if blank">
                    <button class="toggle-vis" onclick="toggleVis('encryption-passphrase')">show</button>
                </div>
                <div class="hint">If you lose this passphrase, your data is unrecoverable.</div>
            </div>
        </div>
        <div class="btn-row">
            <button class="btn btn-secondary" onclick="prevStep()">Back</button>
            <button class="btn btn-primary" onclick="validateSecurity()">Next</button>
        </div>
    </div>

    <!-- Step 7: Briefing -->
    <div class="step-content" data-step="7">
        <div class="step-header">
            <h2>Morning Briefing</h2>
            <p>Get a daily summary of your calendar, tasks, and urgent items</p>
        </div>
        <div id="error-briefing" class="error-box"></div>
        <div class="toggle-row">
            <div>
                <div class="label">Enable daily briefing</div>
                <div class="sublabel">Familiar sends you a summary each morning</div>
            </div>
            <label class="toggle">
                <input type="checkbox" id="briefing-toggle" checked onchange="toggleBriefing()">
                <span class="slider"></span>
            </label>
        </div>
        <div id="briefing-config">
            <div class="form-group">
                <label>Briefing Time</label>
                <input type="time" id="briefing-time" value="08:00">
            </div>
            <div class="hint" style="margin-bottom: 12px;">
                Briefing works best with Google Calendar.
                Run: <code>python -m familiar --setup-google</code>
            </div>
            <div class="toggle-row">
                <div>
                    <div class="label">Proactive check-ins</div>
                    <div class="sublabel">Nudge about overdue tasks and urgent items during the day</div>
                </div>
                <label class="toggle">
                    <input type="checkbox" id="heartbeat-toggle" checked>
                    <span class="slider"></span>
                </label>
            </div>
        </div>
        <div class="btn-row">
            <button class="btn btn-secondary" onclick="prevStep()">Back</button>
            <button class="btn btn-primary" onclick="validateBriefing()">Next</button>
        </div>
    </div>

    <!-- Step 8: Review -->
    <div class="step-content" data-step="8">
        <div class="step-header">
            <h2>Review</h2>
            <p>Confirm your configuration</p>
        </div>
        <table class="summary-table" id="summary-table"></table>
        <div class="btn-row">
            <button class="btn btn-secondary" onclick="prevStep()">Back</button>
            <button class="btn btn-primary btn-big" onclick="activate()" id="activate-btn">
                Activate
            </button>
        </div>
    </div>

    <!-- Step 9: Activation -->
    <div class="step-content" data-step="9">
        <div class="step-header" id="activation-header">
            <h2>Setting up...</h2>
        </div>
        <div class="activation-progress"><div class="fill" id="activation-fill"></div></div>
        <div class="activation-log" id="activation-log"></div>
        <div id="complete-section" style="display:none">
            <div class="complete-icon">✅</div>
            <div class="step-header">
                <h2 id="complete-title">Ready!</h2>
                <p id="complete-subtitle">Your assistant is configured and ready to go.</p>
            </div>
            <table class="summary-table" id="complete-summary"></table>
            <div style="text-align:center">
                <p style="color:var(--text-dim); margin-bottom:16px; font-size:0.9rem" id="complete-instructions"></p>
                <button class="btn btn-primary btn-big" onclick="location.href='/'">
                    Open Dashboard
                </button>
            </div>
        </div>
    </div>
</div>

<script>
// ── State ────────────────────────────────────────────────────────
let currentStep = 0;
const totalSteps = 10;
let state = {
    provider: '', apiKey: '',
    channels: [], channelTokens: {},
    name: 'Familiar', persona: 'hospitality',
    profile: {orgName: '', orgType: '', email: '', location: ''},
    credentials: {},
    pin: '',
    encryptEnabled: true, passphrase: '',
    briefingEnabled: true, briefingTime: '08:00', heartbeatEnabled: true
};
let detectedProviders = {};

// ── Init ─────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
    updateProgress();
    detectProviders();
    // Generate default PIN
    const pin = String(Math.floor(Math.random() * 10000)).padStart(4, '0');
    document.getElementById('owner-pin').value = pin;
    state.pin = pin;
    // Show encryption config since encryption defaults ON
    document.getElementById('encryption-config').classList.add('show');
    // Restore saved wizard state
    restoreState();
});

// ── Save & Resume ────────────────────────────────────────────────
const STORAGE_KEY = 'familiar_onboard_state';

function saveState() {
    try {
        const saved = {step: currentStep, state: state};
        localStorage.setItem(STORAGE_KEY, JSON.stringify(saved));
    } catch(e) { /* storage unavailable */ }
    // Also save server-side for cross-browser/device resume
    fetch('/api/onboard/save-state', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({step: currentStep, state: state})
    }).catch(() => {});
}

function restoreState() {
    // Try server-side first, fall back to localStorage
    fetch('/api/onboard/load-state')
        .then(r => r.json())
        .then(saved => {
            if (saved.found) {
                _applyRestoredState(saved);
            } else {
                _restoreFromLocalStorage();
            }
        })
        .catch(() => _restoreFromLocalStorage());
}

function _restoreFromLocalStorage() {
    try {
        const raw = localStorage.getItem(STORAGE_KEY);
        if (!raw) return;
        const saved = JSON.parse(raw);
        _applyRestoredState(saved);
    } catch(e) { /* ignore corrupt data */ }
}

function _applyRestoredState(saved) {
    if (saved.state) {
        Object.assign(state, saved.state);
        // Restore form fields
        if (state.name) document.getElementById('assistant-name').value = state.name;
        if (state.pin) document.getElementById('owner-pin').value = state.pin;
        if (state.encryptEnabled) {
            document.getElementById('encrypt-toggle').checked = true;
            document.getElementById('encryption-config').classList.add('show');
        }
        if (state.briefingEnabled !== undefined) {
            document.getElementById('briefing-toggle').checked = state.briefingEnabled;
        }
        if (state.briefingTime) document.getElementById('briefing-time').value = state.briefingTime;
    }
    if (saved.step && saved.step > 0 && saved.step < 7) {
        showStep(saved.step);
    }
}

function clearSavedState() {
    try { localStorage.removeItem(STORAGE_KEY); } catch(e) {}
    fetch('/api/onboard/clear-state', {method: 'POST'}).catch(() => {});
}

// ── Progress Bar ─────────────────────────────────────────────────
function updateProgress() {
    document.querySelectorAll('.progress-bar .step').forEach(el => {
        const s = parseInt(el.dataset.step);
        el.classList.toggle('active', s === currentStep);
        el.classList.toggle('done', s < currentStep);
    });
}

// ── Navigation ───────────────────────────────────────────────────
function showStep(n) {
    document.querySelectorAll('.step-content').forEach(el => {
        el.classList.toggle('active', parseInt(el.dataset.step) === n);
    });
    currentStep = n;
    updateProgress();
    window.scrollTo(0, 0);
}

function nextStep() { showStep(currentStep + 1); saveState(); }
function prevStep() { showStep(currentStep - 1); saveState(); }

// ── Error display ────────────────────────────────────────────────
function showError(id, msg) {
    const el = document.getElementById(id);
    if (el) { el.textContent = msg; el.classList.add('show'); }
}
function clearError(id) {
    const el = document.getElementById(id);
    if (el) { el.textContent = ''; el.classList.remove('show'); }
}

// ── Toggle visibility ────────────────────────────────────────────
function toggleVis(inputId) {
    const inp = document.getElementById(inputId);
    const btn = inp.parentElement.querySelector('.toggle-vis');
    if (inp.type === 'password') {
        inp.type = 'text'; btn.textContent = 'hide';
    } else {
        inp.type = 'password'; btn.textContent = 'show';
    }
}

// ── Step 1: Detect Providers ─────────────────────────────────────
async function detectProviders() {
    try {
        const resp = await fetch('/api/onboard/detect');
        const data = await resp.json();
        detectedProviders = data.providers || {};
        buildProviderCards();
    } catch(e) {
        buildProviderCards();
    }
}

function buildProviderCards() {
    const providers = [
        {key: 'anthropic', icon: '🧠', label: 'Anthropic Claude', desc: 'Best quality cloud AI', url: 'https://console.anthropic.com/'},
        {key: 'openai', icon: '🤖', label: 'OpenAI GPT', desc: 'GPT-4o and GPT-4o-mini', url: 'https://platform.openai.com/api-keys'},
        {key: 'gemini', icon: '💎', label: 'Google Gemini', desc: 'Gemini 2.5 Flash', url: 'https://aistudio.google.com/apikey'},
        {key: 'ollama', icon: '🏠', label: 'Ollama (Local)', desc: 'Free, private, runs on your machine', url: 'https://ollama.ai/'}
    ];
    const container = document.getElementById('provider-cards');
    container.innerHTML = '';
    providers.forEach(p => {
        const detected = detectedProviders[p.key];
        const card = document.createElement('div');
        card.className = 'card' + (detected ? ' detected' : '');
        card.onclick = () => selectProvider(card, p.key, p.url);
        card.innerHTML = `
            <div class="icon">${p.icon}</div>
            <div class="card-body">
                <h4>${p.label}</h4>
                <p>${detected ? detected.label : p.desc}</p>
            </div>
            ${detected ? '<span class="badge">Detected</span>' : ''}
        `;
        container.appendChild(card);
    });

    // Auto-select first detected
    const autoKey = Object.keys(detectedProviders)[0];
    if (autoKey) {
        const cards = container.querySelectorAll('.card');
        const idx = providers.findIndex(p => p.key === autoKey);
        if (idx >= 0) selectProvider(cards[idx], autoKey, providers[idx].url);
    }
}

let ollamaBundles = null;

function selectProvider(el, key, url) {
    document.querySelectorAll('#provider-cards .card').forEach(c => c.classList.remove('selected'));
    el.classList.add('selected');
    state.provider = key;
    const keySection = document.getElementById('provider-key-section');
    const ollamaSection = document.getElementById('ollama-model-section');
    if (key === 'ollama') {
        keySection.classList.remove('show');
        ollamaSection.classList.add('show');
        loadOllamaBundles();
    } else {
        keySection.classList.add('show');
        ollamaSection.classList.remove('show');
        const detected = detectedProviders[key];
        if (detected && detected.key) {
            document.getElementById('provider-key').value = detected.key;
        } else {
            document.getElementById('provider-key').value = '';
        }
        document.getElementById('provider-key-label').textContent = key.charAt(0).toUpperCase() + key.slice(1) + ' API Key';
        document.getElementById('provider-key-link').href = url;
    }
}

async function loadOllamaBundles() {
    if (ollamaBundles) { renderOllamaBundles(); return; }
    try {
        const resp = await fetch('/api/onboard/ollama-bundles');
        ollamaBundles = await resp.json();
        renderOllamaBundles();
    } catch(e) { /* ignore */ }
}

function renderOllamaBundles() {
    if (!ollamaBundles) return;
    const hint = document.getElementById('ollama-ram-hint');
    hint.textContent = ollamaBundles.ram_gb.toFixed(0) + ' GB RAM detected';
    const container = document.getElementById('ollama-bundle-cards');
    const rec = ollamaBundles.recommended;
    state.ollamaMode = 'bundle';
    container.innerHTML = `
        <div class="card selected" onclick="selectOllamaMode(this, 'bundle')">
            <div class="icon">📦</div>
            <div class="card-body">
                <h4>${rec.label} (Recommended)</h4>
                <p>${rec.desc}</p>
            </div>
        </div>
        <div class="card" onclick="selectOllamaMode(this, 'custom')">
            <div class="icon">🔧</div>
            <div class="card-body">
                <h4>Custom</h4>
                <p>Choose individual models</p>
            </div>
        </div>
        <div class="card" onclick="selectOllamaMode(this, 'skip')">
            <div class="icon">⏭️</div>
            <div class="card-body">
                <h4>Skip</h4>
                <p>I'll manage models myself</p>
            </div>
        </div>
    `;
}

function selectOllamaMode(el, mode) {
    document.querySelectorAll('#ollama-bundle-cards .card').forEach(c => c.classList.remove('selected'));
    el.classList.add('selected');
    state.ollamaMode = mode;
}

async function validateProvider() {
    clearError('error-provider');
    state.apiKey = document.getElementById('provider-key').value.trim();
    if (!state.provider) {
        showError('error-provider', 'Please select a provider'); return;
    }
    const payload = {provider: state.provider, api_key: state.apiKey};
    // Ollama model selection
    if (state.provider === 'ollama' && ollamaBundles) {
        const rec = ollamaBundles.recommended;
        if (state.ollamaMode === 'bundle') {
            payload.ollama_model = rec.models[0];
            if (rec.models.length >= 3) payload.lightweight_model = rec.models[1];
        } else if (state.ollamaMode === 'custom') {
            payload.ollama_model = rec.models[0]; // default
        }
    }
    try {
        const resp = await fetch('/api/onboard/validate/provider', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(payload)
        });
        const data = await resp.json();
        if (data.success) { nextStep(); }
        else { showError('error-provider', data.errors.join('. ')); }
    } catch(e) { showError('error-provider', 'Connection error'); }
}

// ── Step 2: Channels ─────────────────────────────────────────────
function toggleChannel(el, key) {
    el.classList.toggle('selected');
    const idx = state.channels.indexOf(key);
    if (idx >= 0) { state.channels.splice(idx, 1); }
    else { state.channels.push(key); }

    // Show/hide config sections
    ['telegram', 'discord', 'matrix', 'whatsapp', 'signal', 'imessage', 'teams'].forEach(ch => {
        const cfg = document.getElementById(ch + '-config');
        if (cfg) cfg.classList.toggle('show', state.channels.includes(ch));
    });
}

async function validateChannels() {
    clearError('error-channel');
    const payload = {channels: state.channels};
    if (state.channels.includes('telegram'))
        payload.telegram_token = document.getElementById('telegram-token').value;
    if (state.channels.includes('discord'))
        payload.discord_token = document.getElementById('discord-token').value;
    if (state.channels.includes('matrix')) {
        payload.matrix_homeserver = document.getElementById('matrix-homeserver').value;
        payload.matrix_user = document.getElementById('matrix-user').value;
        payload.matrix_password = document.getElementById('matrix-password').value;
    }
    if (state.channels.includes('whatsapp'))
        payload.whatsapp_port = document.getElementById('whatsapp-port').value;
    if (state.channels.includes('signal'))
        payload.signal_phone = document.getElementById('signal-phone').value;
    if (state.channels.includes('teams')) {
        payload.teams_app_id = document.getElementById('teams-app-id').value;
        payload.teams_app_password = document.getElementById('teams-app-password').value;
        payload.teams_tenant_id = document.getElementById('teams-tenant-id').value;
    }
    state.channelTokens = payload;

    try {
        const resp = await fetch('/api/onboard/validate/channels', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(payload)
        });
        const data = await resp.json();
        if (data.success) { nextStep(); }
        else { showError('error-channel', data.errors.join('. ')); }
    } catch(e) { showError('error-channel', 'Connection error'); }
}

// ── Step 3: Identity ─────────────────────────────────────────────
function selectPersona(el, key) {
    document.querySelectorAll('#persona-cards .card').forEach(c => c.classList.remove('selected'));
    el.classList.add('selected');
    state.persona = key;
}

async function validateIdentity() {
    clearError('error-identity');
    state.name = document.getElementById('assistant-name').value.trim() || 'Familiar';
    try {
        const resp = await fetch('/api/onboard/validate/identity', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({name: state.name, persona: state.persona})
        });
        const data = await resp.json();
        if (data.success) { nextStep(); }
        else { showError('error-identity', data.errors.join('. ')); }
    } catch(e) { showError('error-identity', 'Connection error'); }
}

// ── Step 4: Profile (optional) ───────────────────────────────────
async function validateProfile() {
    clearError('error-profile');
    state.profile = {
        orgName: document.getElementById('profile-org-name').value.trim(),
        orgType: document.getElementById('profile-org-type').value,
        email: document.getElementById('profile-email').value.trim(),
        location: document.getElementById('profile-location').value.trim(),
    };
    try {
        const resp = await fetch('/api/onboard/validate/profile', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                org_name: state.profile.orgName,
                org_type: state.profile.orgType,
                owner_email: state.profile.email,
                location: state.profile.location,
            })
        });
        const data = await resp.json();
        if (data.success) {
            if (data.data && data.data.recommended_services) {
                renderCredentialsList(data.data.recommended_services);
            }
            nextStep();
        } else {
            showError('error-profile', data.errors.join('. '));
        }
    } catch(e) { showError('error-profile', 'Connection error'); }
}

// ── Step 5: API Credentials (optional) ──────────────────────────
const SERVICE_INFO = {};  // populated by renderCredentialsList

function renderCredentialsList(serviceKeys) {
    const container = document.getElementById('credentials-list');
    container.innerHTML = '';
    if (!serviceKeys || serviceKeys.length === 0) {
        container.innerHTML = '<p style="color:var(--text-dim); text-align:center;">No recommended services for your profile. You can add them later.</p>';
        return;
    }
    serviceKeys.forEach(key => {
        const div = document.createElement('div');
        div.style.cssText = 'background:var(--bg-card); border:1px solid var(--border); border-radius:8px; padding:16px;';
        div.innerHTML = `
            <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
                <strong style="text-transform:capitalize;">${key.replace(/_/g, ' ')}</strong>
                <span class="cred-status" id="cred-status-${key}" style="font-size:0.85rem; color:var(--text-dim);">Not set</span>
            </div>
            <div class="input-wrapper">
                <input type="password" id="cred-${key}" placeholder="API key" style="width:100%;">
                <button class="toggle-vis" onclick="toggleVis('cred-${key}')">show</button>
            </div>
        `;
        container.appendChild(div);
    });
}

async function testAllCredentials() {
    const creds = gatherCredentials();
    if (Object.keys(creds).length === 0) return;
    try {
        const resp = await fetch('/api/onboard/validate/credentials', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({credentials: creds, skip_test: false})
        });
        const data = await resp.json();
        if (data.data && data.data.test_results) {
            for (const [key, result] of Object.entries(data.data.test_results)) {
                const el = document.getElementById('cred-status-' + key);
                if (el) {
                    el.textContent = result.ok ? 'Connected' : 'Failed';
                    el.style.color = result.ok ? 'var(--success)' : 'var(--error)';
                }
            }
        }
        if (data.data && data.data.results) {
            for (const [key, result] of Object.entries(data.data.results)) {
                if (result.saved) {
                    const el = document.getElementById('cred-status-' + key);
                    if (el && el.textContent === 'Not set') {
                        el.textContent = 'Saved';
                        el.style.color = 'var(--warning)';
                    }
                }
            }
        }
    } catch(e) { showError('error-credentials', 'Connection error'); }
}

function gatherCredentials() {
    const creds = {};
    document.querySelectorAll('[id^="cred-"]').forEach(input => {
        if (input.tagName !== 'INPUT') return;
        const key = input.id.replace('cred-', '');
        const val = input.value.trim();
        if (val) {
            // Map to env var name — use uppercase KEY + common suffixes
            creds[key] = val;
        }
    });
    return creds;
}

async function validateCredentials() {
    clearError('error-credentials');
    const rawCreds = gatherCredentials();
    // Convert simple key:value to {service: {ENV_VAR: value}} format
    // The backend will look up the right env var from the catalog
    const creds = {};
    for (const [key, val] of Object.entries(rawCreds)) {
        if (val) creds[key] = val;
    }
    state.credentials = creds;

    if (Object.keys(creds).length === 0) {
        nextStep();
        return;
    }

    try {
        // Build proper credential map with env var names
        const resp = await fetch('/api/onboard/validate/credentials', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({credentials: _buildCredentialMap(creds), skip_test: false})
        });
        const data = await resp.json();
        if (data.success) {
            nextStep();
        } else {
            showError('error-credentials', data.errors.join('. '));
        }
    } catch(e) { showError('error-credentials', 'Connection error'); }
}

function _buildCredentialMap(simpleCreds) {
    // Maps service key -> {ENV_VAR: value} using known conventions
    const ENV_MAP = {
        anthropic: {var: 'ANTHROPIC_API_KEY'},
        openai: {var: 'OPENAI_API_KEY'},
        gemini: {var: 'GEMINI_API_KEY'},
        hubspot: {var: 'HUBSPOT_ACCESS_TOKEN'},
        todoist: {var: 'TODOIST_API_TOKEN'},
        stripe: {var: 'STRIPE_API_KEY'},
        square: {var: 'SQUARE_ACCESS_TOKEN'},
        mailerlite: {var: 'MAILERLITE_API_KEY'},
        shippo: {var: 'SHIPPO_API_KEY'},
        calcom: {var: 'CALCOM_API_KEY'},
        brave_search: {var: 'BRAVE_SEARCH_API_KEY'},
        spoonacular: {var: 'SPOONACULAR_API_KEY'},
        etsy: {var: 'ETSY_API_KEY'},
        clockify: {var: 'CLOCKIFY_API_KEY'},
        joplin: {var: 'JOPLIN_TOKEN'},
        github: {var: 'GITHUB_TOKEN'},
    };
    const result = {};
    for (const [key, val] of Object.entries(simpleCreds)) {
        const mapping = ENV_MAP[key];
        if (mapping) {
            result[key] = {[mapping.var]: val};
        } else {
            // Fallback: KEY_API_KEY or KEY_TOKEN
            result[key] = {[key.toUpperCase() + '_API_KEY']: val};
        }
    }
    return result;
}

// ── Step 6: Security ─────────────────────────────────────────────
function toggleEncryption() {
    state.encryptEnabled = document.getElementById('encrypt-toggle').checked;
    document.getElementById('encryption-config').classList.toggle('show', state.encryptEnabled);
}

async function validateSecurity() {
    clearError('error-security');
    state.pin = document.getElementById('owner-pin').value.trim();
    state.passphrase = document.getElementById('encryption-passphrase').value.trim();

    // Validate PIN
    let resp = await fetch('/api/onboard/validate/owner_pin', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({pin: state.pin})
    });
    let data = await resp.json();
    if (!data.success) {
        showError('error-security', data.errors.join('. ')); return;
    }

    // Validate encryption
    resp = await fetch('/api/onboard/validate/encryption', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({enabled: state.encryptEnabled, passphrase: state.passphrase})
    });
    data = await resp.json();
    if (data.success) {
        if (data.data && data.data.passphrase) {
            state.passphrase = data.data.passphrase;
        }
        nextStep();
    } else {
        showError('error-security', data.errors.join('. '));
    }
}

// ── Step 7: Briefing ─────────────────────────────────────────────
function toggleBriefing() {
    state.briefingEnabled = document.getElementById('briefing-toggle').checked;
    document.getElementById('briefing-config').style.display = state.briefingEnabled ? 'block' : 'none';
}

async function validateBriefing() {
    clearError('error-briefing');
    state.briefingTime = document.getElementById('briefing-time').value || '08:00';
    state.heartbeatEnabled = document.getElementById('heartbeat-toggle').checked;

    const resp = await fetch('/api/onboard/validate/briefing', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({
            enabled: state.briefingEnabled,
            time: state.briefingTime,
            heartbeat: state.heartbeatEnabled
        })
    });
    const data = await resp.json();
    if (data.success) { buildSummary(); nextStep(); }
    else { showError('error-briefing', data.errors.join('. ')); }
}

// ── Step 6: Summary ──────────────────────────────────────────────
function buildSummary() {
    const t = document.getElementById('summary-table');
    const providerLabels = {anthropic:'Anthropic Claude', openai:'OpenAI GPT', gemini:'Google Gemini', ollama:'Ollama (local)'};
    const personaLabels = {hospitality:'Warm & professional', efficient:'Efficient & concise', friendly:'Casual & friendly', formal:'Formal & thorough'};
    const rows = [
        ['Provider', providerLabels[state.provider] || state.provider],
        ['Channels', state.channels.join(', ')],
        ['Name', state.name],
        ['Persona', personaLabels[state.persona] || state.persona],
        ['Owner PIN', '•'.repeat(state.pin.length) + ' (' + state.pin.length + ' digits)'],
        ['Encryption', state.encryptEnabled ? 'Enabled' : 'Off'],
        ['Briefing', state.briefingEnabled ? 'Daily at ' + state.briefingTime : 'Off'],
    ];
    if (state.heartbeatEnabled && state.briefingEnabled) {
        rows.push(['Check-ins', 'Every 4 hours']);
    }
    t.innerHTML = rows.map(r => `<tr><td>${r[0]}</td><td>${r[1]}</td></tr>`).join('');
}

// ── Step 7: Activation ───────────────────────────────────────────
async function activate() {
    showStep(7);
    const log = document.getElementById('activation-log');
    const fill = document.getElementById('activation-fill');
    log.innerHTML = '';

    function addLine(msg, cls) {
        const div = document.createElement('div');
        div.className = 'line' + (cls ? ' ' + cls : '');
        div.textContent = msg;
        log.appendChild(div);
        log.scrollTop = log.scrollHeight;
    }

    addLine('Starting activation...');
    fill.style.width = '10%';

    try {
        // Use SSE for progress
        const evtSource = new EventSource('/api/onboard/activate/stream');
        let progress = 10;

        evtSource.onmessage = (event) => {
            const data = JSON.parse(event.data);
            if (data.message) {
                addLine(data.message, data.type === 'error' ? 'error' : 'ok');
            }
            if (data.progress) {
                progress = data.progress;
                fill.style.width = progress + '%';
            }
            if (data.done) {
                evtSource.close();
                fill.style.width = '100%';
                if (data.success) {
                    // Send test message
                    fetch('/api/onboard/test-message', {method:'POST'})
                        .then(r => r.json())
                        .then(d => {
                            if (d.success) addLine('Test message sent!', 'ok');
                            else if (d.warnings) d.warnings.forEach(w => addLine(w));
                        }).catch(() => {});
                    clearSavedState();
                    document.getElementById('activation-header').style.display = 'none';
                    document.getElementById('complete-section').style.display = 'block';
                    document.getElementById('complete-title').textContent = state.name + ' is ready!';
                    // Fetch and render checklist
                    fetch('/api/onboard/checklist')
                        .then(r => r.json())
                        .then(items => {
                            const rows = items.map((item, i) =>
                                `<tr><td>${i + 1}.</td><td><strong>${item.title}</strong><br><span style="color:var(--text-dim)">${item.detail}</span></td></tr>`
                            ).join('');
                            document.getElementById('complete-summary').innerHTML = rows;
                        }).catch(() => {
                            // Fallback to basic summary
                            const providerLabels = {anthropic:'Anthropic Claude', openai:'OpenAI GPT', gemini:'Google Gemini', ollama:'Ollama (local)'};
                            const summaryRows = [
                                ['Provider', providerLabels[state.provider] || state.provider],
                                ['Channels', state.channels.join(', ')],
                            ];
                            document.getElementById('complete-summary').innerHTML =
                                summaryRows.map(r => `<tr><td>${r[0]}</td><td>${r[1]}</td></tr>`).join('');
                        });
                    document.getElementById('complete-subtitle').textContent = 'Next steps:';
                    document.getElementById('complete-instructions').textContent = '';
                } else {
                    addLine('Activation failed. Check errors above.', 'error');
                }
            }
        };

        evtSource.onerror = () => {
            evtSource.close();
            addLine('Connection lost. Try again.', 'error');
        };

    } catch(e) {
        addLine('Error: ' + e.message, 'error');
    }
}
</script>
</body>
</html>
