Instruction: Create an AUTARCH web template for the 'Phishmail' page. It should extend base.html, have a page header, and use AUTARCH's CSS variables and UI patterns (sections, tab bars, data tables, buttons).

Input:
Template: phishmail.html
Sections: 0
Tabs: 0
Forms/API calls: 31
Has JavaScript: True

Output:
{% extends "base.html" %}
{% block title %}Gone Fishing — AUTARCH{% endblock %}
{% block content %}
<h1>Gone Fishing Mail Service</h1>
<div class="card" style="background:var(--bg-input);border-left:4px solid var(--accent);padding:0.75rem 1rem;margin-bottom:1.25rem">
    <strong>Demo Version</strong> &mdash; This is a demo of the mail module. A full version is available by request and verification of the user's intent.
</div>
<p style="color:var(--text-secondary);margin-bottom:1.5rem">
    Local network phishing simulator &mdash; sender spoofing, campaigns, tracking, certificate generation.
    <span style="color:var(--danger);font-weight:600">Local network only.</span>
</p>

<!-- Tabs -->
<div class="tabs" style="display:flex;gap:0;border-bottom:2px solid var(--border);margin-bottom:1.5rem">
    <button class="tab-btn active" onclick="switchTab('compose',this)">Compose</button>
    <button class="tab-btn" onclick="switchTab('campaigns',this)">Campaigns</button>
    <button class="tab-btn" onclick="switchTab('templates',this)">Templates</button>
    <button class="tab-btn" onclick="switchTab('landing',this)">Landing Pages</button>
    <button class="tab-btn" onclick="switchTab('captures',this)">Captures</button>
    <button class="tab-btn" onclick="switchTab('dkim',this)">DKIM / DNS</button>
    <button class="tab-btn" onclick="switchTab('evasion',this)">Evasion</button>
    <button class="tab-btn" onclick="switchTab('server',this)">Server &amp; Certs</button>
</div>

<!-- ═══════════════════ COMPOSE TAB ═══════════════════ -->
<div id="tab-compose" class="tab-pane">
    <div class="card" style="padding:1.25rem">
        <h3 style="margin-bottom:1rem">Compose Email</h3>
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:0.75rem">
            <div>
                <label class="form-label">From Name</label>
                <input id="c-from-name" class="form-input" value="IT Department" placeholder="Display name">
            </div>
            <div>
                <label class="form-label">From Address</label>
                <input id="c-from-addr" class="form-input" value="it@company.local" placeholder="sender@domain">
            </div>
            <div style="grid-column:span 2">
                <label class="form-label">To (comma-separated, local network only)</label>
                <input id="c-to" class="form-input" placeholder="user@192.168.1.10, admin@mail.local">
                <span id="c-to-status" style="font-size:0.8rem"></span>
            </div>
            <div style="grid-column:span 2">
                <label class="form-label">Subject</label>
                <input id="c-subject" class="form-input" placeholder="Email subject">
            </div>
        </div>

        <!-- Template selector -->
        <div style="margin:1rem 0 0.5rem">
            <label class="form-label">Template</label>
            <select id="c-template" class="form-input" onchange="loadTemplate(this.value)">
                <option value="">— Custom —</option>
            </select>
        </div>

        <!-- Body -->
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:0.75rem;margin-top:0.75rem">
            <div>
                <label class="form-label">HTML Body</label>
                <textarea id="c-html" class="form-input" rows="10" style="font-family:monospace;font-size:0.8rem" placeholder="<html>...</html>"></textarea>
            </div>
            <div>
                <label class="form-label">Preview</label>
                <div id="c-preview" style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;min-height:200px;background:#fff;color:#333;font-size:0.85rem;overflow:auto;max-height:300px"></div>
            </div>
        </div>
        <div style="margin-top:0.5rem">
            <label class="form-label">Plain Text Fallback</label>
            <textarea id="c-text" class="form-input" rows="3" placeholder="Plain text version (optional)"></textarea>
        </div>

        <!-- SMTP settings -->
        <details style="margin-top:1rem">
            <summary style="cursor:pointer;color:var(--text-secondary);font-size:0.85rem">SMTP Settings</summary>
            <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:0.75rem;margin-top:0.5rem">
                <div>
                    <label class="form-label">SMTP Host</label>
                    <input id="c-smtp-host" class="form-input" value="127.0.0.1">
                </div>
                <div>
                    <label class="form-label">SMTP Port</label>
                    <input id="c-smtp-port" class="form-input" value="25" type="number">
                </div>
                <div>
                    <label class="form-label">TLS</label>
                    <select id="c-tls" class="form-input">
                        <option value="0">No TLS</option>
                        <option value="1">STARTTLS</option>
                    </select>
                </div>
                <div>
                    <label class="form-label">Reply-To</label>
                    <input id="c-reply-to" class="form-input" placeholder="Same as From">
                </div>
                <div>
                    <label class="form-label">X-Mailer</label>
                    <input id="c-xmailer" class="form-input" value="Microsoft Outlook 16.0">
                </div>
                <div>
                    <label class="form-label">TLS Cert CN</label>
                    <input id="c-cert-cn" class="form-input" placeholder="For spoofed cert">
                </div>
            </div>
        </details>

        <div style="margin-top:1rem;display:flex;gap:0.75rem;align-items:center">
            <button class="btn btn-primary" onclick="sendEmail()">Send Email</button>
            <button class="btn" onclick="validateRecipients()">Validate Recipients</button>
            <span id="c-status" style="font-size:0.85rem"></span>
        </div>
    </div>
</div>

<!-- ═══════════════════ CAMPAIGNS TAB ═══════════════════ -->
<div id="tab-campaigns" class="tab-pane" style="display:none">
    <div style="display:grid;grid-template-columns:1fr 1fr;gap:1.25rem">
        <!-- Create Campaign -->
        <div class="card" style="padding:1.25rem">
            <h3 style="margin-bottom:1rem">Create Campaign</h3>
            <label class="form-label">Campaign Name</label>
            <input id="camp-name" class="form-input" placeholder="Q1 Security Test">

            <label class="form-label" style="margin-top:0.5rem">Template</label>
            <select id="camp-template" class="form-input"></select>

            <label class="form-label" style="margin-top:0.5rem">Targets (one per line)</label>
            <textarea id="camp-targets" class="form-input" rows="5" placeholder="user1@mail.local&#10;user2@192.168.1.10&#10;admin@intranet.lan"></textarea>
            <div style="display:flex;gap:0.5rem;margin-top:0.35rem;align-items:center">
                <label class="btn btn-small" style="cursor:pointer;margin:0;font-size:0.78rem">
                    Import CSV
                    <input type="file" accept=".csv,.txt" style="display:none" onchange="importTargetCSV(this)">
                </label>
                <span id="camp-import-status" style="font-size:0.78rem;color:var(--text-muted)"></span>
            </div>

            <div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;margin-top:0.5rem">
                <div>
                    <label class="form-label">From Address</label>
                    <input id="camp-from-addr" class="form-input" value="it@company.local">
                </div>
                <div>
                    <label class="form-label">From Name</label>
                    <input id="camp-from-name" class="form-input" value="IT Department">
                </div>
            </div>
            <label class="form-label" style="margin-top:0.5rem">Subject</label>
            <input id="camp-subject" class="form-input" placeholder="Leave blank to use template subject">

            <div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;margin-top:0.5rem">
                <div>
                    <label class="form-label">SMTP Host</label>
                    <input id="camp-smtp-host" class="form-input" value="127.0.0.1">
                </div>
                <div>
                    <label class="form-label">SMTP Port</label>
                    <input id="camp-smtp-port" class="form-input" value="25" type="number">
                </div>
            </div>
            <button class="btn btn-primary" style="margin-top:1rem;width:100%" onclick="createCampaign()">Create Campaign</button>
        </div>

        <!-- Campaign List -->
        <div class="card" style="padding:1.25rem">
            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem">
                <h3>Campaigns</h3>
                <button class="btn btn-small" onclick="loadCampaigns()">Refresh</button>
            </div>
            <div id="camp-list" style="font-size:0.85rem">Loading...</div>
        </div>
    </div>

    <!-- Campaign Detail (shown when viewing a campaign) -->
    <div id="camp-detail" class="card" style="padding:1.25rem;margin-top:1.25rem;display:none">
        <div style="display:flex;justify-content:space-between;align-items:center">
            <h3 id="camp-detail-name">Campaign Details</h3>
            <div style="display:flex;gap:0.5rem">
                <a id="camp-export-link" class="btn btn-small" href="#" target="_blank">Export CSV</a>
                <button class="btn btn-small" onclick="viewCampaign(_currentCampaign)">Refresh</button>
            </div>
        </div>
        <div id="camp-detail-stats" style="display:flex;gap:1.5rem;margin:1rem 0;font-size:0.9rem"></div>
        <table style="width:100%;font-size:0.82rem;border-collapse:collapse">
            <thead>
                <tr style="border-bottom:2px solid var(--border);text-align:left">
                    <th style="padding:6px">Email</th>
                    <th style="padding:6px">Status</th>
                    <th style="padding:6px">Sent</th>
                    <th style="padding:6px">Opened</th>
                    <th style="padding:6px">Clicked</th>
                </tr>
            </thead>
            <tbody id="camp-detail-targets"></tbody>
        </table>
    </div>
</div>

<!-- ═══════════════════ TEMPLATES TAB ═══════════════════ -->
<div id="tab-templates" class="tab-pane" style="display:none">
    <div style="display:grid;grid-template-columns:250px 1fr;gap:1.25rem">
        <!-- Template list -->
        <div class="card" style="padding:1rem">
            <h3 style="margin-bottom:0.75rem">Templates</h3>
            <div id="tpl-list" style="font-size:0.85rem">Loading...</div>
            <hr style="border-color:var(--border);margin:0.75rem 0">
            <label class="form-label">New Template Name</label>
            <input id="tpl-new-name" class="form-input" placeholder="My Template">
            <button class="btn btn-primary btn-small" style="margin-top:0.5rem;width:100%" onclick="createTemplate()">Create</button>
        </div>

        <!-- Template editor -->
        <div class="card" style="padding:1rem">
            <h3 id="tpl-editor-title" style="margin-bottom:0.75rem">Select a template</h3>
            <label class="form-label">Subject</label>
            <input id="tpl-subject" class="form-input" placeholder="Subject line">
            <div style="display:grid;grid-template-columns:1fr 1fr;gap:0.75rem;margin-top:0.5rem">
                <div>
                    <label class="form-label">HTML Body</label>
                    <textarea id="tpl-html" class="form-input" rows="14" style="font-family:monospace;font-size:0.78rem"></textarea>
                </div>
                <div>
                    <label class="form-label">Preview</label>
                    <div id="tpl-preview" style="border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;min-height:280px;background:#fff;color:#333;font-size:0.85rem;overflow:auto;max-height:350px"></div>
                </div>
            </div>
            <label class="form-label" style="margin-top:0.5rem">Plain Text</label>
            <textarea id="tpl-text" class="form-input" rows="3" style="font-family:monospace;font-size:0.78rem"></textarea>
            <div style="margin-top:0.5rem;color:var(--text-muted);font-size:0.78rem">
                Variables: <code>{{name}}</code> <code>{{email}}</code> <code>{{company}}</code>
                <code>{{date}}</code> <code>{{link}}</code> <code>{{tracking_pixel}}</code>
            </div>
            <div style="margin-top:0.75rem;display:flex;gap:0.5rem">
                <button class="btn btn-primary" onclick="saveTemplate()">Save Template</button>
                <button class="btn btn-danger" onclick="deleteTemplate()">Delete</button>
            </div>
        </div>
    </div>
</div>

<!-- ═══════════════════ LANDING PAGES TAB ═══════════════════ -->
<div id="tab-landing" class="tab-pane" style="display:none">
    <div style="display:grid;grid-template-columns:1fr 1fr;gap:1.25rem">
        <!-- Create/Edit Landing Page -->
        <div class="card" style="padding:1.25rem">
            <h3 style="margin-bottom:1rem">Landing Pages</h3>
            <p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.75rem">
                Credential harvesting pages. When a target clicks a link and submits the form, credentials are captured and logged.
            </p>

            <label class="form-label">Built-in Templates</label>
            <select id="lp-builtin" class="form-input" onchange="loadLPTemplate(this.value)">
                <option value="">— Select Built-in —</option>
                <option value="Office 365 Login">Office 365 Login</option>
                <option value="Google Login">Google Login</option>
                <option value="Generic Login">Generic Login</option>
                <option value="VPN Login">VPN Login</option>
            </select>

            <hr style="border-color:var(--border);margin:1rem 0">
            <label class="form-label">Page Name</label>
            <input id="lp-name" class="form-input" placeholder="My Phishing Page">
            <label class="form-label" style="margin-top:0.5rem">Redirect URL (after capture)</label>
            <input id="lp-redirect" class="form-input" placeholder="https://real-login-page.com (leave blank for generic success)">
            <label class="form-label" style="margin-top:0.5rem">HTML</label>
            <textarea id="lp-html" class="form-input" rows="10" style="font-family:monospace;font-size:0.78rem" placeholder="Landing page HTML..."></textarea>
            <div style="font-size:0.78rem;color:var(--text-muted);margin-top:0.25rem">
                Variables: <code>{{campaign_id}}</code> <code>{{target_id}}</code> <code>{{email}}</code>
            </div>

            <div style="display:flex;gap:0.5rem;margin-top:0.75rem">
                <button class="btn btn-primary" onclick="saveLandingPage()">Save Page</button>
                <button class="btn" onclick="previewLP()">Preview</button>
            </div>
            <div id="lp-status" style="font-size:0.82rem;margin-top:0.5rem"></div>
        </div>

        <!-- Existing Pages -->
        <div class="card" style="padding:1.25rem">
            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem">
                <h3>Saved Pages</h3>
                <button class="btn btn-small" onclick="loadLandingPages()">Refresh</button>
            </div>
            <div id="lp-list" style="font-size:0.85rem">Loading...</div>

            <hr style="border-color:var(--border);margin:1rem 0">
            <h4>How to Use</h4>
            <ol style="font-size:0.82rem;color:var(--text-secondary);padding-left:1.2rem;margin-top:0.5rem">
                <li>Create a landing page above (or use a built-in template)</li>
                <li>In your email template, set <code>{{link}}</code> to point to:
                    <code style="display:block;margin:0.25rem 0;padding:0.3rem;background:var(--bg-input);border-radius:3px">/phishmail/lp/PAGE_ID?c=CAMPAIGN_ID&t=TARGET_ID&e=EMAIL</code>
                </li>
                <li>When targets submit the form, credentials appear in the <strong>Captures</strong> tab</li>
            </ol>
        </div>
    </div>
</div>

<!-- ═══════════════════ CAPTURES TAB ═══════════════════ -->
<div id="tab-captures" class="tab-pane" style="display:none">
    <div class="card" style="padding:1.25rem">
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;flex-wrap:wrap;gap:0.5rem">
            <h3>Captured Credentials</h3>
            <div style="display:flex;gap:0.5rem;align-items:center">
                <select id="cap-filter-campaign" class="form-input" style="width:auto;padding:0.3rem 0.5rem;font-size:0.8rem" onchange="loadCaptures()">
                    <option value="">All Campaigns</option>
                </select>
                <button class="btn btn-small" onclick="loadCaptures()">Refresh</button>
                <a id="cap-export-link" class="btn btn-small" href="/phishmail/captures/export" target="_blank">Export CSV</a>
                <button class="btn btn-small btn-danger" onclick="clearCaptures()">Clear All</button>
            </div>
        </div>
        <div id="cap-count" style="font-size:0.78rem;color:var(--text-muted);margin-bottom:0.75rem"></div>
        <div style="overflow-x:auto">
            <table style="width:100%;font-size:0.82rem;border-collapse:collapse">
                <thead>
                    <tr style="border-bottom:2px solid var(--border);text-align:left">
                        <th style="padding:6px">Time</th>
                        <th style="padding:6px">Page</th>
                        <th style="padding:6px">Target</th>
                        <th style="padding:6px">IP</th>
                        <th style="padding:6px">Credentials</th>
                        <th style="padding:6px">User Agent</th>
                    </tr>
                </thead>
                <tbody id="cap-table"></tbody>
            </table>
        </div>
    </div>
</div>

<!-- ═══════════════════ DKIM / DNS TAB ═══════════════════ -->
<div id="tab-dkim" class="tab-pane" style="display:none">
    <div style="display:grid;grid-template-columns:1fr 1fr;gap:1.25rem">
        <!-- DKIM Key Generation -->
        <div class="card" style="padding:1.25rem">
            <h3 style="margin-bottom:0.75rem">DKIM Key Generation</h3>
            <p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.75rem">
                Generate RSA keypairs for DKIM email signing. Adds the public key as a DNS TXT record automatically if the DNS service is running.
            </p>
            <label class="form-label">Domain</label>
            <input id="dkim-domain" class="form-input" placeholder="spoofed-domain.local">
            <button class="btn btn-primary" style="margin-top:0.75rem;width:100%" onclick="generateDKIM()">Generate DKIM Keys</button>
            <div id="dkim-result" style="font-size:0.82rem;margin-top:0.5rem"></div>

            <hr style="border-color:var(--border);margin:1rem 0">
            <h4>DKIM Keys</h4>
            <div id="dkim-list" style="font-size:0.82rem;margin-top:0.5rem">Loading...</div>
        </div>

        <!-- DNS Auto-Setup -->
        <div class="card" style="padding:1.25rem">
            <h3 style="margin-bottom:0.75rem">DNS Auto-Setup</h3>
            <div id="dns-integration-status" style="font-size:0.85rem;margin-bottom:1rem">Checking DNS service...</div>
            <p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.75rem">
                Auto-create the zone, MX, SPF, DKIM, and DMARC records for a spoofed domain using the AUTARCH DNS service.
            </p>
            <label class="form-label">Domain</label>
            <input id="dns-setup-domain" class="form-input" placeholder="spoofed-domain.com">
            <label class="form-label" style="margin-top:0.5rem">Mail Server Hostname</label>
            <input id="dns-setup-mx" class="form-input" placeholder="mail.spoofed-domain.com (auto if blank)">
            <label class="form-label" style="margin-top:0.5rem">SPF Allow</label>
            <input id="dns-setup-spf" class="form-input" value="ip4:127.0.0.1" placeholder="ip4:YOUR_IP">
            <button class="btn btn-primary" style="margin-top:0.75rem;width:100%" onclick="setupDNS()">Auto-Configure DNS</button>
            <div id="dns-setup-result" style="font-size:0.82rem;margin-top:0.5rem"></div>

            <hr style="border-color:var(--border);margin:1rem 0">
            <h4>Authentication Records Checklist</h4>
            <div style="font-size:0.82rem;color:var(--text-secondary)">
                <div style="padding:3px 0"><strong>MX</strong> — Mail exchange record pointing to your server</div>
                <div style="padding:3px 0"><strong>SPF</strong> — <code>v=spf1 a mx ip4:YOUR_IP -all</code></div>
                <div style="padding:3px 0"><strong>DKIM</strong> — Public key TXT record at <code>default._domainkey</code></div>
                <div style="padding:3px 0"><strong>DMARC</strong> — <code>v=DMARC1; p=none;</code> at <code>_dmarc</code></div>
                <div style="padding:3px 0"><strong>PTR</strong> — Reverse DNS for your IP (requires ISP cooperation)</div>
            </div>
        </div>
    </div>
</div>

<!-- ═══════════════════ EVASION TAB ═══════════════════ -->
<div id="tab-evasion" class="tab-pane" style="display:none">
    <div style="display:grid;grid-template-columns:1fr 1fr;gap:1.25rem">
        <!-- Text Evasion -->
        <div class="card" style="padding:1.25rem">
            <h3 style="margin-bottom:0.75rem">Content Evasion</h3>
            <p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.75rem">
                Techniques to bypass email content filters and keyword scanners.
            </p>

            <label class="form-label">Technique</label>
            <select id="ev-mode" class="form-input">
                <option value="homoglyph">Unicode Homoglyphs (replaces chars with Cyrillic lookalikes)</option>
                <option value="zero_width">Zero-Width Characters (breaks keyword matching)</option>
                <option value="html_entity">HTML Entity Encoding (encodes chars as &#entities)</option>
            </select>

            <label class="form-label" style="margin-top:0.75rem">Input Text</label>
            <textarea id="ev-input" class="form-input" rows="4" placeholder="Enter text to transform...">Reset your password immediately to secure your account</textarea>

            <button class="btn btn-primary" style="margin-top:0.75rem;width:100%" onclick="previewEvasion()">Transform</button>

            <label class="form-label" style="margin-top:0.75rem">Output</label>
            <textarea id="ev-output" class="form-input" rows="4" style="font-family:monospace;font-size:0.78rem" readonly></textarea>
            <button class="btn btn-small" style="margin-top:0.35rem" onclick="navigator.clipboard.writeText(document.getElementById('ev-output').value)">Copy</button>
        </div>

        <!-- Header Evasion -->
        <div class="card" style="padding:1.25rem">
            <h3 style="margin-bottom:0.75rem">Header Evasion</h3>
            <p style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.75rem">
                Randomize email headers and generate spoofed Received chains.
            </p>

            <button class="btn" onclick="randomizeHeaders()">Generate Random Headers</button>
            <div id="ev-headers" style="margin-top:0.75rem;font-family:monospace;font-size:0.8rem;background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;display:none;white-space:pre-wrap"></div>

            <hr style="border-color:var(--border);margin:1.25rem 0">
            <h4 style="margin-bottom:0.5rem">Spoofed Received Chain</h4>
            <p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:0.5rem">
                Generates fake Received headers to simulate legitimate mail routing.
            </p>
            <label class="form-label">From Domain</label>
            <input id="ev-recv-domain" class="form-input" placeholder="company.com">
            <label class="form-label" style="margin-top:0.5rem">Hops</label>
            <input id="ev-recv-hops" class="form-input" type="number" value="2" min="1" max="5">
            <button class="btn" style="margin-top:0.5rem" onclick="generateReceivedChain()">Generate Chain</button>
            <div id="ev-recv-output" style="margin-top:0.5rem;font-family:monospace;font-size:0.78rem;display:none;background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;white-space:pre-wrap"></div>

            <hr style="border-color:var(--border);margin:1.25rem 0">
            <h4 style="margin-bottom:0.5rem">Tips</h4>
            <ul style="font-size:0.82rem;color:var(--text-secondary);padding-left:1.2rem">
                <li>Use homoglyphs to bypass keyword filters on "password", "urgent", "click"</li>
                <li>Zero-width chars break pattern matching without affecting visual appearance</li>
                <li>Randomize X-Mailer to avoid fingerprinting</li>
                <li>Spoofed Received headers make emails look like they traversed real mail infrastructure</li>
                <li>Use DKIM signing + SPF + DMARC to pass authentication checks</li>
                <li>Generate certs matching the spoofed domain for STARTTLS</li>
            </ul>
        </div>
    </div>
</div>

<!-- ═══════════════════ SERVER & CERTS TAB ═══════════════════ -->
<div id="tab-server" class="tab-pane" style="display:none">
    <div style="display:grid;grid-template-columns:1fr 1fr;gap:1.25rem">
        <!-- SMTP Relay -->
        <div class="card" style="padding:1.25rem">
            <h3 style="margin-bottom:1rem">SMTP Relay Server</h3>
            <div id="relay-status" style="font-size:0.9rem;margin-bottom:1rem">
                Status: <span style="color:var(--text-muted)">Checking...</span>
            </div>
            <div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem">
                <div>
                    <label class="form-label">Bind Host</label>
                    <input id="relay-host" class="form-input" value="0.0.0.0">
                </div>
                <div>
                    <label class="form-label">Port</label>
                    <input id="relay-port" class="form-input" value="2525" type="number">
                </div>
            </div>
            <div style="margin-top:0.75rem;display:flex;gap:0.5rem">
                <button class="btn btn-primary" onclick="startRelay()">Start Relay</button>
                <button class="btn btn-danger" onclick="stopRelay()">Stop Relay</button>
            </div>

            <hr style="border-color:var(--border);margin:1.25rem 0">
            <h4 style="margin-bottom:0.75rem">Test SMTP Connection</h4>
            <div style="display:grid;grid-template-columns:2fr 1fr auto;gap:0.5rem;align-items:end">
                <div>
                    <label class="form-label">Host</label>
                    <input id="test-host" class="form-input" placeholder="mail.local">
                </div>
                <div>
                    <label class="form-label">Port</label>
                    <input id="test-port" class="form-input" value="25" type="number">
                </div>
                <button class="btn" onclick="testSmtp()">Test</button>
            </div>
            <div id="test-result" style="font-size:0.82rem;margin-top:0.5rem"></div>
        </div>

        <!-- Certificate Generator -->
        <div class="card" style="padding:1.25rem">
            <h3 style="margin-bottom:1rem">Certificate Generator</h3>
            <p style="color:var(--text-secondary);font-size:0.82rem;margin-bottom:0.75rem">
                Generate spoofed self-signed TLS certificates for STARTTLS.
            </p>
            <div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem">
                <div>
                    <label class="form-label">Common Name (CN)</label>
                    <input id="cert-cn" class="form-input" value="mail.google.com">
                </div>
                <div>
                    <label class="form-label">Organization (O)</label>
                    <input id="cert-org" class="form-input" value="Google LLC">
                </div>
                <div>
                    <label class="form-label">Org Unit (OU)</label>
                    <input id="cert-ou" class="form-input" placeholder="Optional">
                </div>
                <div>
                    <label class="form-label">Country (C)</label>
                    <input id="cert-country" class="form-input" value="US">
                </div>
            </div>
            <button class="btn btn-primary" style="margin-top:0.75rem;width:100%" onclick="generateCert()">Generate Certificate</button>
            <div id="cert-result" style="font-size:0.82rem;margin-top:0.5rem"></div>

            <hr style="border-color:var(--border);margin:1rem 0">
            <h4>Generated Certificates</h4>
            <div id="cert-list" style="font-size:0.82rem;margin-top:0.5rem">Loading...</div>
        </div>
    </div>
</div>

<style>
.tab-btn{padding:0.6rem 1.2rem;background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:0.9rem;border-bottom:2px solid transparent;margin-bottom:-2px;transition:all 0.2s}
.tab-btn:hover{color:var(--text-primary)}
.tab-btn.active{color:var(--accent);border-bottom-color:var(--accent);font-weight:600}
.form-label{display:block;font-size:0.78rem;color:var(--text-secondary);margin-bottom:0.25rem;font-weight:600;text-transform:uppercase;letter-spacing:0.04em}
.form-input{width:100%;padding:0.5rem 0.65rem;background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius);color:var(--text-primary);font-size:0.85rem}
.form-input:focus{outline:none;border-color:var(--accent)}
select.form-input{cursor:pointer}
.btn-danger{background:var(--danger);color:#fff}
.btn-danger:hover{opacity:0.85}
.camp-card{background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius);padding:0.75rem;margin-bottom:0.5rem}
.camp-card:hover{border-color:var(--accent)}
.stat-pill{display:inline-block;padding:0.25rem 0.6rem;border-radius:12px;font-size:0.78rem;font-weight:600}
.tpl-item{padding:0.5rem 0.65rem;cursor:pointer;border-radius:var(--radius);margin-bottom:2px;font-size:0.85rem}
.tpl-item:hover{background:var(--bg-input)}
.tpl-item.active{background:var(--accent);color:#fff}
</style>

<script>
let _templates = {};
let _currentTemplate = '';
let _currentCampaign = '';

function switchTab(name, btn) {
    document.querySelectorAll('.tab-pane').forEach(p => p.style.display = 'none');
    document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
    document.getElementById('tab-' + name).style.display = '';
    btn.classList.add('active');
    if (name === 'campaigns') loadCampaigns();
    if (name === 'templates') loadTemplates();
    if (name === 'landing') loadLandingPages();
    if (name === 'captures') loadCaptures();
    if (name === 'dkim') { loadDKIMKeys(); checkDNSIntegration(); }
    if (name === 'server') { checkRelay(); loadCerts(); }
}

// ── Preview updates ──
document.addEventListener('DOMContentLoaded', function() {
    const htmlEl = document.getElementById('c-html');
    if (htmlEl) htmlEl.addEventListener('input', function() {
        document.getElementById('c-preview').innerHTML = this.value;
    });
    const tplHtml = document.getElementById('tpl-html');
    if (tplHtml) tplHtml.addEventListener('input', function() {
        document.getElementById('tpl-preview').innerHTML = this.value;
    });
    loadTemplateDropdowns();
});

// ── Template Dropdowns ──
function loadTemplateDropdowns() {
    fetch('/phishmail/templates').then(r => r.json()).then(d => {
        if (!d.ok) return;
        _templates = d.templates;
        const sel1 = document.getElementById('c-template');
        const sel2 = document.getElementById('camp-template');
        [sel1, sel2].forEach(sel => {
            if (!sel) return;
            const first = sel === sel1 ? '<option value="">— Custom —</option>' : '';
            sel.innerHTML = first + Object.keys(d.templates).map(n =>
                `<option value="${n}">${n}${d.templates[n].builtin ? ' (built-in)' : ''}</option>`
            ).join('');
        });
    });
}

function loadTemplate(name) {
    if (!name || !_templates[name]) return;
    const t = _templates[name];
    document.getElementById('c-html').value = t.html || '';
    document.getElementById('c-text').value = t.text || '';
    if (t.subject) document.getElementById('c-subject').value = t.subject;
    document.getElementById('c-preview').innerHTML = t.html || '';
}

// ── Compose: Send ──
function sendEmail() {
    const data = {
        from_name: document.getElementById('c-from-name').value,
        from_addr: document.getElementById('c-from-addr').value,
        to_addrs: document.getElementById('c-to').value,
        subject: document.getElementById('c-subject').value,
        html_body: document.getElementById('c-html').value,
        text_body: document.getElementById('c-text').value,
        smtp_host: document.getElementById('c-smtp-host').value,
        smtp_port: parseInt(document.getElementById('c-smtp-port').value),
        use_tls: document.getElementById('c-tls').value === '1',
        reply_to: document.getElementById('c-reply-to').value,
        x_mailer: document.getElementById('c-xmailer').value,
        cert_cn: document.getElementById('c-cert-cn').value,
    };
    const st = document.getElementById('c-status');
    st.textContent = 'Sending...';
    st.style.color = 'var(--text-secondary)';
    fetch('/phishmail/send', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(data)})
        .then(r => r.json()).then(d => {
            st.textContent = d.ok ? d.message : d.error;
            st.style.color = d.ok ? '#4ade80' : 'var(--danger)';
        }).catch(e => { st.textContent = e.message; st.style.color = 'var(--danger)'; });
}

function validateRecipients() {
    const addrs = document.getElementById('c-to').value.split(',').map(a => a.trim()).filter(Boolean);
    const st = document.getElementById('c-to-status');
    if (!addrs.length) { st.textContent = 'No addresses'; return; }
    let results = [];
    Promise.all(addrs.map(addr =>
        fetch('/phishmail/validate', {method:'POST', headers:{'Content-Type':'application/json'},
            body:JSON.stringify({address:addr})}).then(r => r.json()).then(d => {
            results.push(`${addr}: ${d.ok ? '✓ Local' : '✗ ' + d.message}`);
        })
    )).then(() => {
        st.innerHTML = results.map(r => r.includes('✓')
            ? `<span style="color:#4ade80">${r}</span>`
            : `<span style="color:var(--danger)">${r}</span>`
        ).join('<br>');
    });
}

// ── Campaigns ──
function loadCampaigns() {
    fetch('/phishmail/campaigns').then(r => r.json()).then(d => {
        const el = document.getElementById('camp-list');
        if (!d.ok || !d.campaigns.length) { el.textContent = 'No campaigns'; return; }
        el.innerHTML = d.campaigns.map(c => {
            const s = c.stats || {};
            return `<div class="camp-card">
                <div style="display:flex;justify-content:space-between;align-items:center">
                    <strong>${c.name}</strong>
                    <span class="stat-pill" style="background:${c.status==='sent'?'#22c55e20;color:#22c55e':'var(--bg-card);color:var(--text-secondary)'}">${c.status}</span>
                </div>
                <div style="margin-top:0.4rem;color:var(--text-secondary);font-size:0.8rem">
                    Targets: ${s.total||0} &bull; Sent: ${s.sent||0} &bull; Opened: ${s.opened||0} (${s.open_rate||'0%'}) &bull; Clicked: ${s.clicked||0} (${s.click_rate||'0%'})
                </div>
                <div style="margin-top:0.5rem;display:flex;gap:0.4rem">
                    <button class="btn btn-small btn-primary" onclick="executeCampaign('${c.id}')">Send</button>
                    <button class="btn btn-small" onclick="viewCampaign('${c.id}')">View</button>
                    <button class="btn btn-small btn-danger" onclick="deleteCampaign('${c.id}')">Delete</button>
                </div>
            </div>`;
        }).join('');
    });
}

function createCampaign() {
    const data = {
        name: document.getElementById('camp-name').value,
        template: document.getElementById('camp-template').value,
        targets: document.getElementById('camp-targets').value,
        from_addr: document.getElementById('camp-from-addr').value,
        from_name: document.getElementById('camp-from-name').value,
        subject: document.getElementById('camp-subject').value,
        smtp_host: document.getElementById('camp-smtp-host').value,
        smtp_port: parseInt(document.getElementById('camp-smtp-port').value),
    };
    fetch('/phishmail/campaigns', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(data)})
        .then(r => r.json()).then(d => {
            if (d.ok) { loadCampaigns(); document.getElementById('camp-name').value = ''; }
            else alert(d.error);
        });
}

function executeCampaign(cid) {
    if (!confirm('Send all campaign emails now?')) return;
    fetch(`/phishmail/campaigns/${cid}/send`, {method:'POST', headers:{'Content-Type':'application/json'},
        body:JSON.stringify({base_url: window.location.origin})})
        .then(r => r.json()).then(d => {
            alert(d.ok ? d.message : d.error);
            loadCampaigns();
        });
}

function viewCampaign(cid) {
    _currentCampaign = cid;
    document.getElementById('camp-export-link').href = `/phishmail/campaigns/${cid}/export`;
    fetch(`/phishmail/campaigns/${cid}`).then(r => r.json()).then(d => {
        if (!d.ok) return;
        const c = d.campaign;
        const det = document.getElementById('camp-detail');
        det.style.display = '';
        document.getElementById('camp-detail-name').textContent = c.name;
        const s = c.stats || {};
        document.getElementById('camp-detail-stats').innerHTML =
            `<span>Total: <strong>${s.total||0}</strong></span>
             <span>Sent: <strong>${s.sent||0}</strong></span>
             <span style="color:#4ade80">Opened: <strong>${s.opened||0}</strong> (${s.open_rate||'0%'})</span>
             <span style="color:var(--accent)">Clicked: <strong>${s.clicked||0}</strong> (${s.click_rate||'0%'})</span>`;
        document.getElementById('camp-detail-targets').innerHTML = (c.targets||[]).map(t =>
            `<tr style="border-bottom:1px solid var(--border)">
                <td style="padding:5px">${t.email}</td>
                <td style="padding:5px"><span class="stat-pill" style="background:${t.status==='sent'?'#22c55e20;color:#22c55e':t.status==='failed'?'#ef444420;color:#ef4444':'var(--bg-card);color:var(--text-muted)'}">${t.status}</span></td>
                <td style="padding:5px;font-size:0.78rem">${t.sent_at ? new Date(t.sent_at).toLocaleString() : '—'}</td>
                <td style="padding:5px;font-size:0.78rem">${t.opened_at ? new Date(t.opened_at).toLocaleString() : '—'}</td>
                <td style="padding:5px;font-size:0.78rem">${t.clicked_at ? new Date(t.clicked_at).toLocaleString() : '—'}</td>
            </tr>`
        ).join('');
    });
}

function deleteCampaign(cid) {
    if (!confirm('Delete this campaign?')) return;
    fetch(`/phishmail/campaigns/${cid}`, {method:'DELETE'}).then(r => r.json()).then(() => loadCampaigns());
}

// ── Templates ──
function loadTemplates() {
    fetch('/phishmail/templates').then(r => r.json()).then(d => {
        if (!d.ok) return;
        _templates = d.templates;
        const el = document.getElementById('tpl-list');
        el.innerHTML = Object.keys(d.templates).map(n => {
            const t = d.templates[n];
            return `<div class="tpl-item ${n===_currentTemplate?'active':''}" onclick="selectTemplate('${n.replace(/'/g,"\\'")}')">
                ${n} ${t.builtin ? '<span style="opacity:0.5;font-size:0.75rem">(built-in)</span>' : ''}
            </div>`;
        }).join('');
    });
}

function selectTemplate(name) {
    _currentTemplate = name;
    const t = _templates[name];
    if (!t) return;
    document.getElementById('tpl-editor-title').textContent = name;
    document.getElementById('tpl-subject').value = t.subject || '';
    document.getElementById('tpl-html').value = t.html || '';
    document.getElementById('tpl-text').value = t.text || '';
    document.getElementById('tpl-preview').innerHTML = t.html || '';
    loadTemplates(); // refresh active highlight
}

function createTemplate() {
    const name = document.getElementById('tpl-new-name').value.trim();
    if (!name) return;
    fetch('/phishmail/templates', {method:'POST', headers:{'Content-Type':'application/json'},
        body:JSON.stringify({name, html:'', text:'', subject:''})})
        .then(r => r.json()).then(d => {
            if (d.ok) { loadTemplates(); loadTemplateDropdowns(); selectTemplate(name); }
        });
    document.getElementById('tpl-new-name').value = '';
}

function saveTemplate() {
    if (!_currentTemplate) { alert('Select a template first'); return; }
    const data = {
        name: _currentTemplate,
        subject: document.getElementById('tpl-subject').value,
        html: document.getElementById('tpl-html').value,
        text: document.getElementById('tpl-text').value,
    };
    fetch('/phishmail/templates', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(data)})
        .then(r => r.json()).then(d => {
            if (d.ok) { loadTemplates(); loadTemplateDropdowns(); }
        });
}

function deleteTemplate() {
    if (!_currentTemplate) return;
    if (!confirm(`Delete template "${_currentTemplate}"?`)) return;
    fetch(`/phishmail/templates/${encodeURIComponent(_currentTemplate)}`, {method:'DELETE'})
        .then(r => r.json()).then(d => {
            if (d.ok) { _currentTemplate = ''; loadTemplates(); loadTemplateDropdowns(); }
            else alert(d.error);
        });
}

// ── Server & Certs ──
function checkRelay() {
    fetch('/phishmail/server/status').then(r => r.json()).then(d => {
        const el = document.getElementById('relay-status');
        if (d.running) {
            el.innerHTML = `Status: <span style="color:#4ade80;font-weight:600">RUNNING</span> on ${d.host}:${d.port} &bull; Received: ${d.received} messages`;
        } else {
            el.innerHTML = 'Status: <span style="color:var(--text-muted);font-weight:600">STOPPED</span>';
        }
    });
}

function startRelay() {
    const data = {
        host: document.getElementById('relay-host').value,
        port: parseInt(document.getElementById('relay-port').value),
    };
    fetch('/phishmail/server/start', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(data)})
        .then(r => r.json()).then(d => { checkRelay(); if (!d.ok) alert(d.error); });
}

function stopRelay() {
    fetch('/phishmail/server/stop', {method:'POST'}).then(() => checkRelay());
}

function testSmtp() {
    const host = document.getElementById('test-host').value;
    const port = parseInt(document.getElementById('test-port').value);
    const el = document.getElementById('test-result');
    el.textContent = 'Testing...';
    fetch('/phishmail/test', {method:'POST', headers:{'Content-Type':'application/json'},
        body:JSON.stringify({host, port})})
        .then(r => r.json()).then(d => {
            if (d.ok) {
                el.innerHTML = `<span style="color:#4ade80">✓ ${d.message}</span>` +
                    (d.banner ? `<br>Banner: <code>${d.banner.substring(0,200)}</code>` : '');
            } else {
                el.innerHTML = `<span style="color:var(--danger)">✗ ${d.error}</span>`;
            }
        });
}

function generateCert() {
    const data = {
        cn: document.getElementById('cert-cn').value,
        org: document.getElementById('cert-org').value,
        ou: document.getElementById('cert-ou').value,
        country: document.getElementById('cert-country').value,
    };
    const el = document.getElementById('cert-result');
    el.textContent = 'Generating...';
    fetch('/phishmail/cert/generate', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(data)})
        .then(r => r.json()).then(d => {
            if (d.ok) {
                el.innerHTML = `<span style="color:#4ade80">✓ ${d.message}</span>`;
                loadCerts();
            } else {
                el.innerHTML = `<span style="color:var(--danger)">✗ ${d.error}</span>`;
            }
        });
}

function loadCerts() {
    fetch('/phishmail/cert/list').then(r => r.json()).then(d => {
        const el = document.getElementById('cert-list');
        if (!d.ok || !d.certs.length) { el.textContent = 'No certificates generated'; return; }
        el.innerHTML = d.certs.map(c =>
            `<div style="padding:0.35rem 0;border-bottom:1px solid var(--border)">
                <strong>${c.name}</strong> &bull; ${c.cert} ${c.has_key ? ' + key' : ''}
            </div>`
        ).join('');
    });
}

// ── CSV Import ──
function importTargetCSV(input) {
    const file = input.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
        fetch('/phishmail/campaigns/import-targets', {method:'POST',
            headers:{'Content-Type':'application/json'},
            body:JSON.stringify({csv: reader.result})
        }).then(r => r.json()).then(d => {
            if (d.ok && d.emails) {
                const ta = document.getElementById('camp-targets');
                const existing = ta.value.trim();
                ta.value = existing ? existing + '\n' + d.emails.join('\n') : d.emails.join('\n');
                document.getElementById('camp-import-status').textContent = `Imported ${d.count} targets`;
            }
        });
    };
    reader.readAsText(file);
}

// ── Landing Pages ──
function loadLandingPages() {
    fetch('/phishmail/landing-pages').then(r => r.json()).then(d => {
        const el = document.getElementById('lp-list');
        if (!d.ok) { el.textContent = 'Error loading pages'; return; }
        const pages = d.pages || {};
        const entries = Object.entries(pages);
        if (!entries.length) { el.textContent = 'No landing pages created'; return; }
        el.innerHTML = entries.map(([name, page]) => {
            const pid = page.id || name;
            const tag = page.builtin ? '<span style="opacity:0.5;font-size:0.72rem">(built-in)</span>' : '';
            const url = `/phishmail/lp/${encodeURIComponent(pid)}`;
            return `<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid var(--border)">
                <div>
                    <strong>${name}</strong> ${tag}
                    <div style="font-size:0.72rem;color:var(--text-muted);font-family:monospace">${url}</div>
                </div>
                <div style="display:flex;gap:0.35rem">
                    <a class="btn btn-small" href="${url}?c=preview&t=preview&e=test@example.com" target="_blank">Preview</a>
                    ${page.builtin ? '' : `<button class="btn btn-small btn-danger" onclick="deleteLandingPage('${pid}')">Delete</button>`}
                </div>
            </div>`;
        }).join('');
    });
}

function loadLPTemplate(name) {
    if (!name) return;
    fetch(`/phishmail/landing-pages/${encodeURIComponent(name)}`).then(r => r.json()).then(d => {
        if (!d.ok) return;
        document.getElementById('lp-name').value = name + ' (custom)';
        document.getElementById('lp-html').value = d.page.html || '';
    });
}

function saveLandingPage() {
    const name = document.getElementById('lp-name').value.trim();
    const html = document.getElementById('lp-html').value;
    const redirect_url = document.getElementById('lp-redirect').value.trim();
    if (!name || !html) { document.getElementById('lp-status').innerHTML = '<span style="color:var(--danger)">Name and HTML required</span>'; return; }

    fetch('/phishmail/landing-pages', {method:'POST', headers:{'Content-Type':'application/json'},
        body:JSON.stringify({name, html, redirect_url})
    }).then(r => r.json()).then(d => {
        const el = document.getElementById('lp-status');
        if (d.ok) {
            el.innerHTML = `<span style="color:#4ade80">Page saved. URL: <code>/phishmail/lp/${d.id}</code></span>`;
            loadLandingPages();
        } else {
            el.innerHTML = `<span style="color:var(--danger)">${d.error}</span>`;
        }
    });
}

function deleteLandingPage(pid) {
    if (!confirm('Delete this landing page?')) return;
    fetch(`/phishmail/landing-pages/${pid}`, {method:'DELETE'}).then(() => loadLandingPages());
}

function previewLP() {
    const html = document.getElementById('lp-html').value;
    const win = window.open('', '_blank');
    win.document.write(html);
}

// ── Captures ──
function loadCaptures() {
    const campaign = document.getElementById('cap-filter-campaign').value;
    const params = campaign ? `?campaign=${campaign}` : '';
    document.getElementById('cap-export-link').href = `/phishmail/captures/export${params}`;
    fetch(`/phishmail/captures${params}`).then(r => r.json()).then(d => {
        const captures = d.captures || [];
        document.getElementById('cap-count').textContent = `${captures.length} captured credentials`;
        const el = document.getElementById('cap-table');
        if (!captures.length) {
            el.innerHTML = '<tr><td colspan="6" style="padding:10px;color:var(--text-muted)">No captures yet</td></tr>';
            return;
        }
        el.innerHTML = captures.slice().reverse().slice(0, 500).map(c => {
            const creds = c.credentials || {};
            const credsHtml = Object.entries(creds).map(([k, v]) =>
                `<span style="background:var(--bg-card);padding:1px 4px;border-radius:3px;margin-right:4px"><strong>${k}:</strong> ${esc(v)}</span>`
            ).join('');
            const time = c.timestamp ? new Date(c.timestamp).toLocaleString() : '—';
            const ua = (c.user_agent || '').substring(0, 50);
            return `<tr style="border-bottom:1px solid var(--border)">
                <td style="padding:5px;font-size:0.78rem;white-space:nowrap">${time}</td>
                <td style="padding:5px;font-size:0.78rem">${esc(c.page || '—')}</td>
                <td style="padding:5px;font-family:monospace;font-size:0.78rem">${esc(c.target || '—')}</td>
                <td style="padding:5px;font-family:monospace;font-size:0.78rem">${esc(c.ip || '—')}</td>
                <td style="padding:5px;font-size:0.78rem">${credsHtml}</td>
                <td style="padding:5px;font-size:0.72rem;color:var(--text-muted);max-width:150px;overflow:hidden;text-overflow:ellipsis" title="${esc(c.user_agent || '')}">${esc(ua)}</td>
            </tr>`;
        }).join('');
    });
}

function clearCaptures() {
    if (!confirm('Clear all captured credentials?')) return;
    const campaign = document.getElementById('cap-filter-campaign').value;
    fetch(`/phishmail/captures?campaign=${campaign}`, {method:'DELETE'}).then(() => loadCaptures());
}

// ── DKIM / DNS ──
function generateDKIM() {
    const domain = document.getElementById('dkim-domain').value.trim();
    if (!domain) return;
    const el = document.getElementById('dkim-result');
    el.innerHTML = '<span style="color:var(--text-muted)">Generating...</span>';
    fetch('/phishmail/dkim/generate', {method:'POST', headers:{'Content-Type':'application/json'},
        body:JSON.stringify({domain})
    }).then(r => r.json()).then(d => {
        if (d.ok) {
            el.innerHTML = `<span style="color:#4ade80">Keys generated for ${domain}</span>
            <div style="margin-top:0.5rem;font-family:monospace;font-size:0.75rem;background:var(--bg-input);padding:0.5rem;border-radius:var(--radius);word-break:break-all">
                <strong>DNS TXT Record</strong> (add at <code>default._domainkey.${domain}</code>):<br>${esc(d.dns_record)}
            </div>`;
            loadDKIMKeys();
        } else {
            el.innerHTML = `<span style="color:var(--danger)">${d.error}</span>`;
        }
    });
}

function loadDKIMKeys() {
    fetch('/phishmail/dkim/keys').then(r => r.json()).then(d => {
        const el = document.getElementById('dkim-list');
        if (!d.ok || !d.keys?.length) { el.textContent = 'No DKIM keys generated'; return; }
        el.innerHTML = d.keys.map(k =>
            `<div style="padding:4px 0;border-bottom:1px solid var(--border)">
                <strong>${k.domain}</strong> ${k.has_pub ? '(pub + priv)' : '(key only)'}
            </div>`
        ).join('');
    });
}

function checkDNSIntegration() {
    fetch('/phishmail/dns-status').then(r => r.json()).then(d => {
        const el = document.getElementById('dns-integration-status');
        if (d.available && d.running) {
            el.innerHTML = '<span style="color:#4ade80;font-weight:600">DNS Service: RUNNING</span> — auto-setup available';
        } else if (d.available) {
            el.innerHTML = '<span style="color:var(--text-muted)">DNS Service: STOPPED</span> — start it from the DNS page';
        } else {
            el.innerHTML = '<span style="color:var(--danger)">DNS Service: NOT AVAILABLE</span>';
        }
    });
}

function setupDNS() {
    const domain = document.getElementById('dns-setup-domain').value.trim();
    if (!domain) return;
    const el = document.getElementById('dns-setup-result');
    el.innerHTML = '<span style="color:var(--text-muted)">Configuring DNS...</span>';
    fetch('/phishmail/dns-setup', {method:'POST', headers:{'Content-Type':'application/json'},
        body:JSON.stringify({
            domain,
            mail_host: document.getElementById('dns-setup-mx').value.trim(),
            spf_allow: document.getElementById('dns-setup-spf').value.trim(),
        })
    }).then(r => r.json()).then(d => {
        el.innerHTML = d.ok
            ? `<span style="color:#4ade80">${d.message || 'DNS configured for ' + domain}</span>`
            : `<span style="color:var(--danger)">${d.error}</span>`;
    });
}

// ── Evasion ──
function previewEvasion() {
    const text = document.getElementById('ev-input').value;
    const mode = document.getElementById('ev-mode').value;
    fetch('/phishmail/evasion/preview', {method:'POST', headers:{'Content-Type':'application/json'},
        body:JSON.stringify({text, mode})
    }).then(r => r.json()).then(d => {
        document.getElementById('ev-output').value = d.result || '';
    });
}

function randomizeHeaders() {
    fetch('/phishmail/evasion/preview', {method:'POST', headers:{'Content-Type':'application/json'},
        body:JSON.stringify({text:'', mode:'random_headers'})
    }).then(r => r.json()).then(d => {
        const el = document.getElementById('ev-headers');
        el.style.display = '';
        const h = d.headers || {};
        el.textContent = Object.entries(h).map(([k, v]) => `${k}: ${v}`).join('\n');
    });
}

function generateReceivedChain() {
    const domain = document.getElementById('ev-recv-domain').value.trim();
    const hops = parseInt(document.getElementById('ev-recv-hops').value) || 2;
    if (!domain) return;
    // Client-side generation (mirrors the Python logic)
    const servers = ['mx', 'relay', 'gateway', 'edge', 'smtp', 'mail', 'mta'];
    const rnd = (n) => Math.floor(Math.random() * n);
    let prev = servers[rnd(servers.length)] + '.' + domain;
    let chain = [];
    for (let i = 0; i < hops; i++) {
        const next = servers[rnd(servers.length)] + (i+1) + '.' + domain;
        const ip = `10.${rnd(255)}.${rnd(255)}.${rnd(254)+1}`;
        const ts = new Date().toUTCString();
        chain.push(`Received: from ${prev} (${ip}) by ${next} with ESMTPS; ${ts}`);
        prev = next;
    }
    const el = document.getElementById('ev-recv-output');
    el.style.display = '';
    el.textContent = chain.join('\n');
}

function esc(s) {
    return s ? String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;') : '';
}
</script>
{% endblock %}


Instruction: Create an AUTARCH web template for the 'Pineapple' page. It should extend base.html, have a page header, and use AUTARCH's CSS variables and UI patterns (sections, tab bars, data tables, buttons).

Input:
Template: pineapple.html
Sections: 11
Tabs: 3
Forms/API calls: 1
Has JavaScript: True

Output:
{% extends "base.html" %}
{% block title %}AUTARCH — WiFi Pineapple{% endblock %}

{% block content %}
<div class="page-header">
    <h1>WiFi Pineapple / Rogue AP</h1>
    <p style="margin:0;font-size:0.85rem;color:var(--text-secondary)">
        Evil twin, captive portal, karma attack, DNS spoofing, and credential capture.
    </p>
</div>

<!-- Status Banner -->
<div id="pine-status-banner" class="section" style="display:none;padding:12px 18px;margin-bottom:16px;border:1px solid var(--border);border-radius:var(--radius)">
    <div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap">
        <span id="pine-status-dot" style="width:10px;height:10px;border-radius:50%;background:#ef4444;display:inline-block"></span>
        <strong id="pine-status-text">Stopped</strong>
        <span id="pine-status-ssid" style="color:var(--text-secondary);font-size:0.85rem"></span>
        <span id="pine-status-clients" style="color:var(--text-secondary);font-size:0.85rem"></span>
        <span id="pine-status-features" style="color:var(--text-secondary);font-size:0.85rem"></span>
    </div>
</div>

<!-- Tab Bar -->
<div class="tab-bar">
    <button class="tab active" data-tab-group="pine" data-tab="ap" onclick="showTab('pine','ap')">Rogue AP</button>
    <button class="tab" data-tab-group="pine" data-tab="portal" onclick="showTab('pine','portal')">Captive Portal</button>
    <button class="tab" data-tab-group="pine" data-tab="clients" onclick="showTab('pine','clients')">Clients</button>
    <button class="tab" data-tab-group="pine" data-tab="traffic" onclick="showTab('pine','traffic')">Traffic</button>
</div>

<!-- ==================== ROGUE AP TAB ==================== -->
<div class="tab-content active" data-tab-group="pine" data-tab="ap">

<div class="section">
    <h2>Access Point Configuration</h2>
    <p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
        Configure and launch a rogue access point using hostapd and dnsmasq.
    </p>
    <div class="form-row">
        <div class="form-group">
            <label>Wireless Interface</label>
            <select id="pine-iface">
                <option value="">-- loading --</option>
            </select>
        </div>
        <div class="form-group">
            <label>SSID</label>
            <input type="text" id="pine-ssid" placeholder="FreeWiFi" value="FreeWiFi">
        </div>
        <div class="form-group" style="max-width:120px">
            <label>Channel</label>
            <select id="pine-channel">
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
                <option value="4">4</option>
                <option value="5">5</option>
                <option value="6" selected>6</option>
                <option value="7">7</option>
                <option value="8">8</option>
                <option value="9">9</option>
                <option value="10">10</option>
                <option value="11">11</option>
                <option value="12">12</option>
                <option value="13">13</option>
                <option value="14">14</option>
            </select>
        </div>
    </div>
    <div class="form-row">
        <div class="form-group" style="max-width:160px">
            <label>Encryption</label>
            <select id="pine-enc" onchange="document.getElementById('pine-pass-group').style.display=this.value==='open'?'none':'block'">
                <option value="open">Open (No Password)</option>
                <option value="wpa2">WPA2-PSK</option>
            </select>
        </div>
        <div class="form-group" id="pine-pass-group" style="display:none">
            <label>Password</label>
            <input type="password" id="pine-pass" placeholder="Minimum 8 characters">
        </div>
        <div class="form-group">
            <label>Internet Interface (NAT)</label>
            <select id="pine-inet">
                <option value="">None (no internet)</option>
            </select>
        </div>
    </div>
    <div class="tool-actions">
        <button id="btn-pine-start" class="btn btn-primary" onclick="pineStartAP()">Start Rogue AP</button>
        <button id="btn-pine-stop" class="btn btn-danger" onclick="pineStopAP()">Stop AP</button>
        <button class="btn btn-small" onclick="pineRefreshIfaces()">Refresh Interfaces</button>
    </div>
    <pre class="output-panel" id="pine-ap-output"></pre>
</div>

<div class="section">
    <h2>Evil Twin Attack</h2>
    <p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
        Clone a target AP's SSID and channel. Optionally deauthenticates clients from the real AP.
    </p>
    <div class="form-row">
        <div class="form-group">
            <label>Target SSID</label>
            <input type="text" id="pine-twin-ssid" placeholder="TargetNetwork">
        </div>
        <div class="form-group">
            <label>Target BSSID</label>
            <input type="text" id="pine-twin-bssid" placeholder="AA:BB:CC:DD:EE:FF">
        </div>
        <div class="form-group">
            <label>Interface</label>
            <select id="pine-twin-iface">
                <option value="">-- select --</option>
            </select>
        </div>
    </div>
    <div class="tool-actions">
        <button id="btn-pine-twin" class="btn btn-danger" onclick="pineEvilTwin()">Clone & Start Evil Twin</button>
    </div>
    <pre class="output-panel" id="pine-twin-output"></pre>
</div>

<div class="section">
    <h2>Karma Attack</h2>
    <p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
        Respond to all client probe requests, impersonating any SSID they seek.
        Requires hostapd-mana or airbase-ng.
    </p>
    <div style="display:flex;align-items:center;gap:12px">
        <label class="toggle-switch">
            <input type="checkbox" id="pine-karma-toggle" onchange="pineToggleKarma(this.checked)">
            <span class="toggle-slider"></span>
        </label>
        <span id="pine-karma-label" style="font-size:0.85rem;color:var(--text-secondary)">Karma Disabled</span>
    </div>
    <pre class="output-panel" id="pine-karma-output" style="margin-top:8px"></pre>
</div>

</div>

<!-- ==================== CAPTIVE PORTAL TAB ==================== -->
<div class="tab-content" data-tab-group="pine" data-tab="portal">

<div class="section">
    <h2>Captive Portal</h2>
    <p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
        Redirect all HTTP/HTTPS traffic from connected clients to a fake login page.
    </p>
    <div class="form-row">
        <div class="form-group">
            <label>Portal Type</label>
            <select id="pine-portal-type" onchange="pinePortalTypeChanged(this.value)">
                <option value="hotel_wifi">Hotel WiFi Login</option>
                <option value="corporate">Corporate Network Auth</option>
                <option value="social_login">Social Media / Email Login</option>
                <option value="terms_accept">Terms & Conditions</option>
                <option value="custom">Custom HTML</option>
            </select>
        </div>
    </div>
    <div id="pine-custom-html-group" style="display:none;margin-top:8px">
        <label style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:4px;display:block">Custom Portal HTML</label>
        <textarea id="pine-custom-html" rows="10" style="width:100%;font-family:monospace;font-size:0.82rem;background:var(--bg-input);color:var(--text-primary);border:1px solid var(--border);border-radius:var(--radius);padding:10px;resize:vertical"
            placeholder="<html>&#10;<body>&#10;  <form method='POST' action='/portal/capture'>&#10;    <input name='username'>&#10;    <input name='password' type='password'>&#10;    <button type='submit'>Login</button>&#10;  </form>&#10;</body>&#10;</html>"></textarea>
    </div>
    <div class="tool-actions" style="margin-top:12px">
        <button id="btn-portal-start" class="btn btn-primary" onclick="pineStartPortal()">Start Portal</button>
        <button id="btn-portal-stop" class="btn btn-danger" onclick="pineStopPortal()">Stop Portal</button>
    </div>
    <div id="pine-portal-status" style="margin-top:8px;font-size:0.85rem;color:var(--text-secondary)"></div>
    <pre class="output-panel" id="pine-portal-output"></pre>
</div>

<div class="section">
    <h2>Captured Credentials</h2>
    <div class="tool-actions">
        <button class="btn btn-small" onclick="pineRefreshCaptures()">Refresh</button>
    </div>
    <table class="data-table">
        <thead>
            <tr>
                <th>Time</th>
                <th>Username / Email</th>
                <th>Password</th>
                <th>IP</th>
                <th>User-Agent</th>
            </tr>
        </thead>
        <tbody id="pine-captures-list">
            <tr><td colspan="5" class="empty-state">No captured credentials yet.</td></tr>
        </tbody>
    </table>
</div>

</div>

<!-- ==================== CLIENTS TAB ==================== -->
<div class="tab-content" data-tab-group="pine" data-tab="clients">

<div class="section">
    <h2>Connected Clients</h2>
    <div class="tool-actions" style="display:flex;align-items:center;gap:12px">
        <button class="btn btn-small" onclick="pineRefreshClients()">Refresh</button>
        <label style="font-size:0.82rem;color:var(--text-secondary);display:flex;align-items:center;gap:6px">
            <input type="checkbox" id="pine-auto-refresh" onchange="pineToggleAutoRefresh(this.checked)">
            Auto-refresh (5s)
        </label>
        <span id="pine-client-count" style="font-size:0.82rem;color:var(--text-muted)">0 clients</span>
    </div>
    <table class="data-table">
        <thead>
            <tr>
                <th>MAC Address</th>
                <th>IP Address</th>
                <th>Hostname</th>
                <th>OS</th>
                <th>First Seen</th>
                <th>Data Usage</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody id="pine-clients-list">
            <tr><td colspan="7" class="empty-state">No clients connected. Start a rogue AP first.</td></tr>
        </tbody>
    </table>
</div>

</div>

<!-- ==================== TRAFFIC TAB ==================== -->
<div class="tab-content" data-tab-group="pine" data-tab="traffic">

<div class="section">
    <h2>DNS Spoofing</h2>
    <p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
        Redirect specific domains to an IP address of your choice (e.g., AUTARCH server).
    </p>
    <div id="pine-dns-rows">
        <div class="form-row pine-dns-row" style="align-items:flex-end">
            <div class="form-group">
                <label>Domain</label>
                <input type="text" class="pine-dns-domain" placeholder="example.com">
            </div>
            <div class="form-group">
                <label>Redirect IP</label>
                <input type="text" class="pine-dns-ip" placeholder="10.0.0.1">
            </div>
            <button class="btn btn-danger btn-small" onclick="this.closest('.pine-dns-row').remove()" style="margin-bottom:8px">X</button>
        </div>
    </div>
    <div class="tool-actions">
        <button class="btn btn-small" onclick="pineAddDnsRow()">+ Add Domain</button>
        <button id="btn-dns-apply" class="btn btn-primary btn-small" onclick="pineApplyDns()">Apply DNS Spoofs</button>
        <button class="btn btn-danger btn-small" onclick="pineClearDns()">Clear All Spoofs</button>
    </div>
    <pre class="output-panel" id="pine-dns-output"></pre>
</div>

<div class="section">
    <h2>SSL Strip</h2>
    <p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
        Downgrade HTTPS connections to HTTP for traffic interception. Requires sslstrip installed.
    </p>
    <div style="display:flex;align-items:center;gap:12px">
        <label class="toggle-switch">
            <input type="checkbox" id="pine-ssl-toggle" onchange="pineToggleSslStrip(this.checked)">
            <span class="toggle-slider"></span>
        </label>
        <span id="pine-ssl-label" style="font-size:0.85rem;color:var(--text-secondary)">SSL Strip Disabled</span>
    </div>
    <pre class="output-panel" id="pine-ssl-output" style="margin-top:8px"></pre>
</div>

<div class="section">
    <h2>Traffic Statistics</h2>
    <div class="tool-actions">
        <button class="btn btn-small" onclick="pineRefreshTraffic()">Refresh Stats</button>
    </div>

    <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:12px">
        <div>
            <h3 style="font-size:0.9rem;margin-bottom:8px;color:var(--text-secondary)">Top Domains</h3>
            <table class="data-table">
                <thead><tr><th>Domain</th><th>Queries</th></tr></thead>
                <tbody id="pine-top-domains">
                    <tr><td colspan="2" class="empty-state">No data yet.</td></tr>
                </tbody>
            </table>
        </div>
        <div>
            <h3 style="font-size:0.9rem;margin-bottom:8px;color:var(--text-secondary)">Top Clients</h3>
            <table class="data-table">
                <thead><tr><th>MAC / IP</th><th>Usage</th></tr></thead>
                <tbody id="pine-top-clients">
                    <tr><td colspan="2" class="empty-state">No data yet.</td></tr>
                </tbody>
            </table>
        </div>
    </div>
</div>

<div class="section">
    <h2>Packet Capture</h2>
    <p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:12px">
        Capture network traffic from connected clients using tcpdump.
    </p>
    <div class="form-row">
        <div class="form-group" style="max-width:160px">
            <label>Duration (seconds)</label>
            <input type="number" id="pine-sniff-duration" value="60" min="5" max="600">
        </div>
        <div class="form-group">
            <label>BPF Filter (optional)</label>
            <input type="text" id="pine-sniff-filter" placeholder="e.g. port 80 or port 443">
        </div>
    </div>
    <div class="tool-actions">
        <button id="btn-sniff-start" class="btn btn-primary btn-small" onclick="pineStartSniff()">Start Capture</button>
        <button class="btn btn-danger btn-small" onclick="pineStopSniff()">Stop Capture</button>
    </div>
    <pre class="output-panel" id="pine-sniff-output"></pre>
</div>

</div>

<style>
.toggle-switch{position:relative;display:inline-block;width:44px;height:24px}
.toggle-switch input{opacity:0;width:0;height:0}
.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:var(--bg-input);border:1px solid var(--border);border-radius:24px;transition:.2s}
.toggle-slider:before{position:absolute;content:"";height:18px;width:18px;left:2px;bottom:2px;background:var(--text-secondary);border-radius:50%;transition:.2s}
.toggle-switch input:checked+.toggle-slider{background:var(--accent);border-color:var(--accent)}
.toggle-switch input:checked+.toggle-slider:before{transform:translateX(20px);background:#fff}
.pine-pass-reveal{cursor:pointer;color:var(--accent);font-size:0.75rem;margin-left:6px}
.pine-pass-reveal:hover{text-decoration:underline}
</style>

<script>
/* ── Pineapple Module JS ── */
var _pineAutoTimer = null;

function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }

/* ── Interface loading ── */
function pineRefreshIfaces() {
    fetchJSON('/pineapple/interfaces').then(function(data) {
        var ifaces = Array.isArray(data) ? data : (data.interfaces || []);
        var selAP = document.getElementById('pine-iface');
        var selInet = document.getElementById('pine-inet');
        var selTwin = document.getElementById('pine-twin-iface');

        selAP.innerHTML = '<option value="">-- select interface --</option>';
        selInet.innerHTML = '<option value="">None (no internet)</option>';
        selTwin.innerHTML = '<option value="">-- select interface --</option>';

        ifaces.forEach(function(ifc) {
            var label = ifc.name + ' (' + (ifc.mode || 'unknown') + ')';
            if (ifc.driver) label += ' [' + ifc.driver + ']';

            if (ifc.wireless !== false) {
                var opt1 = document.createElement('option');
                opt1.value = ifc.name;
                opt1.textContent = label;
                selAP.appendChild(opt1);

                var opt3 = document.createElement('option');
                opt3.value = ifc.name;
                opt3.textContent = label;
                selTwin.appendChild(opt3);
            }

            var opt2 = document.createElement('option');
            opt2.value = ifc.name;
            opt2.textContent = ifc.name + (ifc.wireless === false ? ' (wired)' : '');
            selInet.appendChild(opt2);
        });
    });
}

/* ── Status polling ── */
function pineRefreshStatus() {
    fetchJSON('/pineapple/status').then(function(s) {
        var banner = document.getElementById('pine-status-banner');
        var dot = document.getElementById('pine-status-dot');
        var text = document.getElementById('pine-status-text');
        var ssid = document.getElementById('pine-status-ssid');
        var clients = document.getElementById('pine-status-clients');
        var features = document.getElementById('pine-status-features');

        banner.style.display = 'block';

        if (s.running) {
            dot.style.background = '#4ade80';
            text.textContent = 'AP Running';
            ssid.textContent = 'SSID: ' + esc(s.ssid) + '  Ch: ' + s.channel;
            clients.textContent = 'Clients: ' + s.client_count;
            var feat = [];
            if (s.portal_active) feat.push('Portal:' + esc(s.portal_type));
            if (s.karma_active) feat.push('Karma');
            if (s.sslstrip_active) feat.push('SSLStrip');
            if (s.dns_spoof_active) feat.push('DNS Spoof');
            features.textContent = feat.length ? '[ ' + feat.join(' | ') + ' ]' : '';
        } else {
            dot.style.background = '#ef4444';
            text.textContent = 'Stopped';
            ssid.textContent = '';
            clients.textContent = '';
            features.textContent = '';
        }

        // Sync toggle states
        document.getElementById('pine-karma-toggle').checked = !!s.karma_active;
        document.getElementById('pine-karma-label').textContent = s.karma_active ? 'Karma Active' : 'Karma Disabled';
        document.getElementById('pine-ssl-toggle').checked = !!s.sslstrip_active;
        document.getElementById('pine-ssl-label').textContent = s.sslstrip_active ? 'SSL Strip Active' : 'SSL Strip Disabled';
    });
}

/* ── Start / Stop AP ── */
function pineStartAP() {
    var iface = document.getElementById('pine-iface').value;
    var ssid = document.getElementById('pine-ssid').value.trim();
    if (!iface) { alert('Select a wireless interface.'); return; }
    if (!ssid) { alert('Enter an SSID.'); return; }

    var enc = document.getElementById('pine-enc').value;
    var pass = document.getElementById('pine-pass').value;
    if (enc !== 'open' && pass.length < 8) { alert('WPA2 password must be at least 8 characters.'); return; }

    var btn = document.getElementById('btn-pine-start');
    setLoading(btn, true);
    postJSON('/pineapple/start', {
        ssid: ssid,
        interface: iface,
        channel: parseInt(document.getElementById('pine-channel').value) || 6,
        encryption: enc,
        password: enc !== 'open' ? pass : null,
        internet_interface: document.getElementById('pine-inet').value || null
    }).then(function(data) {
        setLoading(btn, false);
        renderOutput('pine-ap-output', data.message || data.error || JSON.stringify(data));
        pineRefreshStatus();
    }).catch(function() { setLoading(btn, false); });
}

function pineStopAP() {
    var btn = document.getElementById('btn-pine-stop');
    setLoading(btn, true);
    postJSON('/pineapple/stop', {}).then(function(data) {
        setLoading(btn, false);
        renderOutput('pine-ap-output', data.message || data.error || 'Stopped');
        pineRefreshStatus();
    }).catch(function() { setLoading(btn, false); });
}

/* ── Evil Twin ── */
function pineEvilTwin() {
    var ssid = document.getElementById('pine-twin-ssid').value.trim();
    var bssid = document.getElementById('pine-twin-bssid').value.trim();
    var iface = document.getElementById('pine-twin-iface').value;
    if (!ssid || !iface) { alert('Enter target SSID and select an interface.'); return; }

    var btn = document.getElementById('btn-pine-twin');
    setLoading(btn, true);
    postJSON('/pineapple/evil-twin', {
        target_ssid: ssid,
        target_bssid: bssid,
        interface: iface,
        internet_interface: document.getElementById('pine-inet').value || null
    }).then(function(data) {
        setLoading(btn, false);
        renderOutput('pine-twin-output', data.message || data.error || JSON.stringify(data));
        pineRefreshStatus();
    }).catch(function() { setLoading(btn, false); });
}

/* ── Karma ── */
function pineToggleKarma(enabled) {
    var url = enabled ? '/pineapple/karma/start' : '/pineapple/karma/stop';
    postJSON(url, {}).then(function(data) {
        renderOutput('pine-karma-output', data.message || data.error || '');
        document.getElementById('pine-karma-label').textContent = enabled ? 'Karma Active' : 'Karma Disabled';
        if (!data.ok) document.getElementById('pine-karma-toggle').checked = !enabled;
        pineRefreshStatus();
    });
}

/* ── Captive Portal ── */
function pinePortalTypeChanged(val) {
    document.getElementById('pine-custom-html-group').style.display = val === 'custom' ? 'block' : 'none';
}

function pineStartPortal() {
    var ptype = document.getElementById('pine-portal-type').value;
    var customHtml = null;
    if (ptype === 'custom') {
        customHtml = document.getElementById('pine-custom-html').value;
        if (!customHtml.trim()) { alert('Enter custom portal HTML.'); return; }
    }

    var btn = document.getElementById('btn-portal-start');
    setLoading(btn, true);
    postJSON('/pineapple/portal/start', {type: ptype, custom_html: customHtml}).then(function(data) {
        setLoading(btn, false);
        renderOutput('pine-portal-output', data.message || data.error || '');
        document.getElementById('pine-portal-status').textContent = data.ok ? 'Portal active (' + ptype + ')' : '';
        pineRefreshStatus();
    }).catch(function() { setLoading(btn, false); });
}

function pineStopPortal() {
    var btn = document.getElementById('btn-portal-stop');
    setLoading(btn, true);
    postJSON('/pineapple/portal/stop', {}).then(function(data) {
        setLoading(btn, false);
        renderOutput('pine-portal-output', data.message || data.error || '');
        document.getElementById('pine-portal-status').textContent = '';
        pineRefreshStatus();
    }).catch(function() { setLoading(btn, false); });
}

/* ── Captures ── */
function pineRefreshCaptures() {
    fetchJSON('/pineapple/portal/captures').then(function(data) {
        var captures = Array.isArray(data) ? data : (data.captures || []);
        var tbody = document.getElementById('pine-captures-list');
        if (!captures.length) {
            tbody.innerHTML = '<tr><td colspan="5" class="empty-state">No captured credentials yet.</td></tr>';
            return;
        }
        var html = '';
        captures.forEach(function(c) {
            var masked = c.password ? c.password.replace(/./g, '*') : '';
            var passId = 'pass-' + Math.random().toString(36).substr(2,8);
            html += '<tr>'
                + '<td style="white-space:nowrap;font-size:0.82rem">' + esc(c.timestamp ? c.timestamp.substring(0,19) : '--') + '</td>'
                + '<td>' + esc(c.username || c.email || '--') + '</td>'
                + '<td><code id="' + passId + '">' + esc(masked) + '</code>'
                + ' <span class="pine-pass-reveal" onclick="this.previousElementSibling.textContent='
                + "'" + esc(c.password || '') + "'" + ';this.textContent=\'\'">[show]</span></td>'
                + '<td>' + esc(c.ip || '--') + '</td>'
                + '<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;font-size:0.78rem">' + esc(c.user_agent || '--') + '</td>'
                + '</tr>';
        });
        tbody.innerHTML = html;
    });
}

/* ── Clients ── */
function pineRefreshClients() {
    fetchJSON('/pineapple/clients').then(function(data) {
        var clients = Array.isArray(data) ? data : (data.clients || []);
        var tbody = document.getElementById('pine-clients-list');
        var counter = document.getElementById('pine-client-count');
        counter.textContent = clients.length + ' client' + (clients.length !== 1 ? 's' : '');

        if (!clients.length) {
            tbody.innerHTML = '<tr><td colspan="7" class="empty-state">No clients connected.</td></tr>';
            return;
        }
        var html = '';
        clients.forEach(function(c) {
            var firstSeen = c.first_seen ? c.first_seen.substring(11,19) : '--';
            var usage = c.data_usage ? _formatBytes(c.data_usage) : '0 B';
            html += '<tr>'
                + '<td><code>' + esc(c.mac) + '</code></td>'
                + '<td>' + esc(c.ip || '--') + '</td>'
                + '<td>' + esc(c.hostname || '--') + '</td>'
                + '<td>' + esc(c.os || '--') + '</td>'
                + '<td style="font-size:0.82rem">' + esc(firstSeen) + '</td>'
                + '<td>' + esc(usage) + '</td>'
                + '<td><button class="btn btn-danger btn-small" onclick="pineKickClient(\'' + esc(c.mac) + '\')">Kick</button></td>'
                + '</tr>';
        });
        tbody.innerHTML = html;
    });
}

function pineKickClient(mac) {
    if (!confirm('Kick client ' + mac + '?')) return;
    postJSON('/pineapple/clients/' + encodeURIComponent(mac) + '/kick', {}).then(function(data) {
        if (data.ok || data.message) {
            pineRefreshClients();
        } else {
            alert(data.error || 'Failed to kick client');
        }
    });
}

function pineToggleAutoRefresh(enabled) {
    if (_pineAutoTimer) { clearInterval(_pineAutoTimer); _pineAutoTimer = null; }
    if (enabled) {
        _pineAutoTimer = setInterval(function() {
            pineRefreshClients();
            pineRefreshStatus();
        }, 5000);
    }
}

/* ── DNS Spoof ── */
function pineAddDnsRow() {
    var container = document.getElementById('pine-dns-rows');
    var row = document.createElement('div');
    row.className = 'form-row pine-dns-row';
    row.style.alignItems = 'flex-end';
    row.innerHTML = '<div class="form-group"><label>Domain</label>'
        + '<input type="text" class="pine-dns-domain" placeholder="example.com"></div>'
        + '<div class="form-group"><label>Redirect IP</label>'
        + '<input type="text" class="pine-dns-ip" placeholder="10.0.0.1"></div>'
        + '<button class="btn btn-danger btn-small" onclick="this.closest(\'.pine-dns-row\').remove()" style="margin-bottom:8px">X</button>';
    container.appendChild(row);
}

function pineApplyDns() {
    var rows = document.querySelectorAll('.pine-dns-row');
    var spoofs = {};
    rows.forEach(function(row) {
        var domain = row.querySelector('.pine-dns-domain').value.trim();
        var ip = row.querySelector('.pine-dns-ip').value.trim();
        if (domain && ip) spoofs[domain] = ip;
    });
    if (Object.keys(spoofs).length === 0) { alert('Add at least one domain/IP pair.'); return; }

    var btn = document.getElementById('btn-dns-apply');
    setLoading(btn, true);
    postJSON('/pineapple/dns-spoof', {spoofs: spoofs}).then(function(data) {
        setLoading(btn, false);
        renderOutput('pine-dns-output', data.message || data.error || '');
        pineRefreshStatus();
    }).catch(function() { setLoading(btn, false); });
}

function pineClearDns() {
    fetchJSON('/pineapple/dns-spoof', {method: 'DELETE'}).then(function(data) {
        renderOutput('pine-dns-output', data.message || data.error || 'DNS spoofs cleared');
        pineRefreshStatus();
    });
}

/* ── SSL Strip ── */
function pineToggleSslStrip(enabled) {
    var url = enabled ? '/pineapple/ssl-strip/start' : '/pineapple/ssl-strip/stop';
    postJSON(url, {}).then(function(data) {
        renderOutput('pine-ssl-output', data.message || data.error || '');
        document.getElementById('pine-ssl-label').textContent = enabled ? 'SSL Strip Active' : 'SSL Strip Disabled';
        if (!data.ok) document.getElementById('pine-ssl-toggle').checked = !enabled;
        pineRefreshStatus();
    });
}

/* ── Traffic ── */
function pineRefreshTraffic() {
    fetchJSON('/pineapple/traffic').then(function(data) {
        // Top domains
        var domBody = document.getElementById('pine-top-domains');
        var domains = data.top_domains || [];
        if (!domains.length) {
            domBody.innerHTML = '<tr><td colspan="2" class="empty-state">No DNS data yet.</td></tr>';
        } else {
            var html = '';
            domains.slice(0, 15).forEach(function(d) {
                html += '<tr><td style="font-size:0.82rem">' + esc(d.domain) + '</td>'
                    + '<td>' + d.queries + '</td></tr>';
            });
            domBody.innerHTML = html;
        }

        // Top clients
        var cliBody = document.getElementById('pine-top-clients');
        var topClients = data.top_clients || [];
        if (!topClients.length) {
            cliBody.innerHTML = '<tr><td colspan="2" class="empty-state">No client data yet.</td></tr>';
        } else {
            var html2 = '';
            topClients.slice(0, 15).forEach(function(c) {
                var label = c.hostname ? c.hostname + ' (' + c.ip + ')' : (c.mac + ' / ' + c.ip);
                html2 += '<tr><td style="font-size:0.82rem">' + esc(label) + '</td>'
                    + '<td>' + _formatBytes(c.data_usage || 0) + '</td></tr>';
            });
            cliBody.innerHTML = html2;
        }
    });
}

/* ── Packet Capture ── */
function pineStartSniff() {
    var duration = parseInt(document.getElementById('pine-sniff-duration').value) || 60;
    var filter = document.getElementById('pine-sniff-filter').value.trim();
    var btn = document.getElementById('btn-sniff-start');
    setLoading(btn, true);
    postJSON('/pineapple/sniff/start', {duration: duration, filter: filter || null}).then(function(data) {
        setLoading(btn, false);
        renderOutput('pine-sniff-output', data.message || data.error || '');
    }).catch(function() { setLoading(btn, false); });
}

function pineStopSniff() {
    postJSON('/pineapple/sniff/stop', {}).then(function(data) {
        renderOutput('pine-sniff-output', data.message || data.error || '');
    });
}

/* ── Helpers ── */
function _formatBytes(bytes) {
    if (bytes === 0) return '0 B';
    var k = 1024;
    var sizes = ['B', 'KB', 'MB', 'GB'];
    var i = Math.floor(Math.log(bytes) / Math.log(k));
    if (i >= sizes.length) i = sizes.length - 1;
    return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}

/* ── Init ── */
(function() {
    pineRefreshIfaces();
    pineRefreshStatus();
})();
</script>
{% endblock %}