Raw Source
Chokechai / IG MaxPland

// ==UserScript==
// @name         IG MaxPland
// @namespace    http://tampermonkey.net/
// @version      3.0.0
// @description  Instagram Relationship Scanner & Comprehensive Media Downloader (Clean Architecture v3.0.0)
// @author       P Choke & SORA
// @match        https://*.instagram.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=instagram.com
// @grant        GM_download
// @grant        GM_notification
// @grant        GM_registerMenuCommand
// @grant        GM_setClipboard
// @grant        unsafeWindow
// @connect      instagram.com
// @connect      cdninstagram.com
// @run-at       document-start
// @homepageURL  https://github.com/Stxyu-p/ig-maxpland
// @supportURL   https://github.com/Stxyu-p/ig-maxpland/issues
// @updateURL    https://raw.githubusercontent.com/Stxyu-p/ig-maxpland/main/ig_maxpland_en.user.js
// @downloadURL  https://raw.githubusercontent.com/Stxyu-p/ig-maxpland/main/ig_maxpland_en.user.js
// @license      MIT
// ==/UserScript==

(() => {
    'use strict';

    /* ==========================================================================
       APPLICATION RUNTIME - Global English (modules live in src/, covered by phase1-5)
       ========================================================================== */

    /* ==========================================================================
       APPLICATION RUNTIME & UI GLUE (Global English)
       ========================================================================== */


    /* ==========================================================================
       1. CONSTANTS & DESIGN TOKENS (Anti-Slop / Clean Minimal Precision)
       ========================================================================== */

    const APP_CONFIG = {
        APP_NAME: 'IG MaxPland',
        DB_NAME: 'IG_MAXPLAND_VAULT',
        DB_VERSION: 6,
        PAGE_SIZE: 50,
        INSTAGRAM_WEB_APP_ID: '936619743392459',
        FOLLOWING_PAGE_SAFETY_LIMIT: 60,
        FOLLOWERS_PAGE_SAFETY_LIMIT: 250,
        // Sweet spot (2026-09-24, same study as PACE_PRESETS): write actions restrict
        // accounts far faster than reads — 15-30 s randomized per unfollow replaces
        // the old 4.5-7.5 s (~500-800 actions/hour was well above safe guidance).
        UNFOLLOW_DELAY_MIN: 15000,
        UNFOLLOW_DELAY_MAX: 30000,
    };

    const ICONS = {
        LOGO: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="20" x="2" y="2" rx="5" ry="5"/><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/><line x1="17.5" x2="17.51" y1="6.5" y2="6.5"/></svg>`,
        CLOSE: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`,
        USERS: `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>`,
        DOWNLOAD: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`,
        DOWNLOAD_ALL: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 13v8l-4-4"/><path d="m12 21 4-4"/><path d="M4.393 15.269A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.436 8.284"/></svg>`,
        THUMBNAIL: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>`,
        STAR: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>`,
        STAR_FILLED: `<svg width="14" height="14" viewBox="0 0 24 24" fill="#fbbf24" stroke="#fbbf24" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>`,
        SEARCH: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`,
        EXTERNAL: `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>`,
        STOP: `<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>`,
        UNFOLLOW: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="17" y1="11" x2="23" y2="11"/></svg>`,
        COPY: `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>`,
        VERIFIED: `<svg width="13" height="13" viewBox="0 0 24 24" fill="#38bdf8"><path d="M12 2l2.4 2.4 3.4-.4 1.4 3.1 3.2 1.3-.4 3.4 2.4 2.4-2.4 2.4.4 3.4-3.2 1.3-1.4 3.1-3.4-.4L12 22l-2.4-2.4-3.4.4-1.4-3.1-3.2-1.3.4-3.4L-2 12l2.4-2.4-.4-3.4 3.2-1.3 1.4-3.1 3.4.4L12 2zm-1.5 13.5l5.5-5.5-1.4-1.4-4.1 4.1-2.1-2.1-1.4 1.4 3.5 3.5z"/></svg>`,
        LOCK: `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`,
        BACKUP: `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>`,
        RESTORE: `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`,
        AVATAR: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="8" r="5"/><path d="M20 21a8 8 0 1 0-16 0"/></svg>`,
        EYE: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>`,
        SHIELD: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>`,
        ANALYTICS: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="m19 9-5 5-4-4-3 3"/></svg>`,
        FEATURES: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/><rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/></svg>`,
        SETTINGS: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>`,
        CLOCK: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>`
    };

    const NATIVE_IG_CSS = `
        :root {
            --mp-bg-root: #0b0d10;
            --mp-bg-surface: #12151b;
            --mp-bg-panel: #171b23;
            --mp-bg-card: #1e232d;
            --mp-bg-elevated: #252b37;
            --mp-bg-hover: #2b3240;
            --mp-border-subtle: rgba(255, 255, 255, 0.08);
            --mp-border-card: #2e3544;
            --mp-border-active: rgba(255, 255, 255, 0.25);
            --mp-text-primary: #f8fafc;
            --mp-text-secondary: #94a3b8;
            --mp-text-muted: #64748b;
            --mp-blue: #0284c7;
            --mp-blue-hover: #0369a1;
            --mp-rose: #f43f5e;
            --mp-rose-bg: rgba(244, 63, 94, 0.14);
            --mp-emerald: #10b981;
            --mp-emerald-bg: rgba(16, 185, 129, 0.14);
            --mp-amber: #f59e0b;
            --mp-amber-bg: rgba(245, 158, 11, 0.14);
            --mp-cyan: #0ea5e9;
            --mp-cyan-bg: rgba(14, 165, 233, 0.14);
            --mp-shadow-modal: 0 12px 32px rgba(0, 0, 0, 0.6), 0 32px 80px rgba(0, 0, 0, 0.85);
            --mp-shadow-card: 0 1px 3px rgba(0, 0, 0, 0.3), 0 4px 12px rgba(0, 0, 0, 0.2);
        }

        #maxpland-studio-modal {
            position: fixed; inset: 50% auto auto 50%; transform: translate(-50%, -50%); z-index: 100000;
            width: min(1040px, 95vw); height: min(780px, 90dvh); max-height: 90dvh;
            display: grid; grid-template-columns: 190px minmax(0, 1fr); grid-template-rows: auto auto minmax(0, 1fr) auto;
            grid-template-areas: 'header header' 'nav progress' 'nav main' 'footer footer';
            background: var(--mp-bg-surface); color: var(--mp-text-primary); border: 1px solid var(--mp-border-subtle);
            border-radius: 12px; overflow: hidden; box-shadow: var(--mp-shadow-modal);
            font: 13px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
            text-align: left; color-scheme: dark;
        }
        #maxpland-studio-modal, #maxpland-studio-modal * { box-sizing: border-box; }
        #maxpland-studio-modal button, #maxpland-studio-modal input, #maxpland-studio-modal textarea { font: inherit; }
        #maxpland-studio-modal button { cursor: pointer; }
        #maxpland-studio-modal button:disabled { cursor: not-allowed; opacity: .5; }
        #maxpland-studio-modal :focus-visible, #maxpland-trigger-btn:focus-visible { outline: 2px solid var(--mp-blue); outline-offset: 2px; }

        #maxpland-studio-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.75); z-index: 99999; backdrop-filter: blur(2px); }

        /* Mini Floating Draggable Launcher */
        #maxpland-trigger-btn {
            position: fixed; right: 24px; bottom: 24px; z-index: 99998;
            width: 44px; height: 44px; border-radius: 50%; padding: 0;
            display: grid; place-items: center;
            appearance: none; -webkit-appearance: none;
            border: 1.5px solid #0284c7; background: #171b23; color: #38bdf8;
            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5), 0 0 12px rgba(2, 132, 199, 0.35);
            cursor: grab; user-select: none; touch-action: none;
            transition: border-color .15s ease, box-shadow .15s ease, transform .12s ease;
        }
        #maxpland-trigger-btn:hover {
            border-color: #38bdf8; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.6), 0 0 16px rgba(56, 189, 248, 0.5);
            transform: scale(1.05);
        }
        #maxpland-trigger-btn:active { cursor: grabbing; transform: scale(0.98); }

        /* Header */
        .maxpland-header {
            grid-area: header; height: 52px; border-bottom: 1px solid var(--mp-border-subtle);
            display: flex; align-items: center; justify-content: space-between; padding: 0 18px;
            background: var(--mp-bg-surface);
        }
        .maxpland-brand { display: flex; align-items: center; gap: 9px; font-weight: 700; font-size: 14px; }
        .maxpland-user-pill {
            font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 6px;
            background: var(--mp-bg-panel); color: #38bdf8; border: 1px solid var(--mp-border-card);
            display: inline-flex; align-items: center; gap: 5px;
        }
        .maxpland-close-btn {
            background: none; border: 1px solid transparent; color: var(--mp-text-secondary);
            padding: 6px; border-radius: 6px; display: flex; align-items: center; justify-content: center;
            transition: background-color .15s, color .15s;
        }
        .maxpland-close-btn:hover { background: var(--mp-bg-hover); color: var(--mp-text-primary); border-color: var(--mp-border-subtle); }

        /* Sidebar Navigation */
        .maxpland-tabs {
            grid-area: nav; border-right: 1px solid var(--mp-border-subtle); padding: 14px 10px;
            display: flex; flex-direction: column; gap: 6px; background: var(--mp-bg-panel);
        }
        .maxpland-tab-btn {
            display: flex; align-items: center; gap: 10px; padding: 9px 12px; border: 1px solid transparent; background: none;
            color: var(--mp-text-secondary); border-radius: 8px; text-align: left; font-weight: 600; font-size: 13px;
            transition: background-color .15s, color .15s, border-color .15s;
        }
        .maxpland-tab-btn:hover { background: var(--mp-bg-hover); color: var(--mp-text-primary); }
        .maxpland-tab-btn.active {
            background: var(--mp-bg-card); color: var(--mp-text-primary);
            border-color: var(--mp-border-card);
        }

        /* Scan Progress & Action Strip */
        .maxpland-progress-container {
            grid-area: progress; display: none; padding: 12px 18px; background: var(--mp-bg-panel);
            border-bottom: 1px solid var(--mp-border-subtle);
        }
        .maxpland-scan-row {
            display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; font-size: 12.5px;
        }
        .maxpland-scan-phase { font-weight: 600; color: #38bdf8; display: flex; align-items: center; gap: 6px; }
        .maxpland-scan-meta {
            font-variant-numeric: tabular-nums; color: var(--mp-text-secondary); font-size: 12px; display: flex; align-items: center; gap: 12px;
        }
        .maxpland-progress-track {
            height: 6px; background: #0b0d10; border-radius: 3px; overflow: hidden; border: 1px solid var(--mp-border-subtle);
        }
        .maxpland-progress-bar {
            height: 100%; width: 0; background: linear-gradient(90deg, var(--mp-blue), #6366f1);
            transition: width .2s ease-out; border-radius: 3px;
        }
        .maxpland-scan-notice {
            font-size: 11.5px; color: #fbbf24; margin-top: 6px; display: none; font-weight: 500;
        }

        /* Main Content Body */
        .maxpland-body { grid-area: main; overflow-y: auto; padding: 18px 20px; background: var(--mp-bg-surface); position: relative; }
        .maxpland-tab-content { display: none; }
        .maxpland-tab-content.active { display: block; }

        /* Stat Cards */
        .maxpland-stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; margin-bottom: 16px; }
        .maxpland-stat-card {
            background: var(--mp-bg-card); border: 1px solid var(--mp-border-card); border-radius: 8px;
            padding: 12px 14px; display: flex; flex-direction: column; gap: 4px; box-shadow: var(--mp-shadow-card);
            position: relative; overflow: hidden; cursor: pointer; transition: border-color .15s, transform .12s;
        }
        .maxpland-stat-card:hover { border-color: var(--mp-border-active); transform: translateY(-1px); }
        .maxpland-stat-card::before {
            content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
        }
        .maxpland-stat-card.card-not-following::before { background: var(--mp-rose); }
        .maxpland-stat-card.card-fans::before { background: var(--mp-cyan); }
        .maxpland-stat-card.card-mutual::before { background: var(--mp-emerald); }
        .maxpland-stat-card.card-lost::before { background: var(--mp-amber); }
        .maxpland-stat-card.card-ghost::before { background: #a855f7; }

        .maxpland-stat-label { font-size: 11px; color: var(--mp-text-muted); font-weight: 600; text-transform: uppercase; letter-spacing: .4px; }
        .maxpland-stat-value {
            font-size: 24px; font-weight: 800; color: var(--mp-text-primary); line-height: 1.1;
            font-variant-numeric: tabular-nums;
        }
        .maxpland-stat-hint { font-size: 11px; color: var(--mp-text-secondary); }

        /* Toolbars & Filters */
        .maxpland-toolbar {
            display: flex; align-items: center; justify-content: space-between; gap: 10px;
            margin-bottom: 12px; flex-wrap: wrap;
        }
        .maxpland-filter-group { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
        .maxpland-pill-btn {
            padding: 5px 12px; border-radius: 6px; border: 1px solid var(--mp-border-card);
            background: var(--mp-bg-panel); color: var(--mp-text-secondary); font-size: 12px; font-weight: 600;
            display: inline-flex; align-items: center; gap: 6px; transition: all .15s;
        }
        .maxpland-pill-btn:hover { background: var(--mp-bg-hover); color: var(--mp-text-primary); border-color: var(--mp-border-active); }
        .maxpland-pill-btn.active {
            background: var(--mp-text-primary); color: var(--mp-bg-root); border-color: var(--mp-text-primary);
        }
        .maxpland-pill-count {
            font-size: 10.5px; opacity: .85; font-variant-numeric: tabular-nums;
        }

        /* Sub-filter chips */
        .maxpland-subfilters {
            display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; font-size: 11.5px; color: var(--mp-text-secondary);
        }
        .maxpland-export-actions {
            display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 6px; margin-bottom: 12px;
        }
        .maxpland-chip-toggle {
            display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 5px;
            background: var(--mp-bg-panel); border: 1px solid var(--mp-border-card); cursor: pointer; user-select: none;
            transition: all .12s;
        }
        .maxpland-chip-toggle:hover { border-color: var(--mp-border-active); color: var(--mp-text-primary); }
        .maxpland-chip-toggle.active {
            background: #1e293b; border-color: #38bdf8; color: #38bdf8;
        }

        /* Search Input */
        .maxpland-search-box {
            display: flex; align-items: center; gap: 8px; background: var(--mp-bg-panel); border: 1px solid var(--mp-border-card);
            border-radius: 6px; padding: 6px 10px; color: var(--mp-text-muted); min-width: 220px;
        }
        .maxpland-search-box:focus-within { border-color: var(--mp-blue); color: var(--mp-text-primary); }
        .maxpland-search-box input {
            background: none; border: none; outline: none; color: var(--mp-text-primary); font-size: 12px; width: 100%;
        }

        /* Action Buttons */
        .maxpland-btn-primary {
            background: var(--mp-blue); color: #fff; border: 1px solid transparent; border-radius: 6px;
            padding: 7px 14px; font-weight: 600; font-size: 12.5px; display: inline-flex; align-items: center; gap: 7px;
            transition: background-color .15s;
        }
        .maxpland-btn-primary:hover { background: var(--mp-blue-hover); }
        .maxpland-btn-secondary {
            background: var(--mp-bg-card); color: var(--mp-text-primary); border: 1px solid var(--mp-border-card);
            border-radius: 6px; padding: 7px 12px; font-weight: 600; font-size: 12.5px; display: inline-flex; align-items: center; gap: 7px;
            transition: background-color .15s, border-color .15s;
        }
        .maxpland-btn-secondary:hover { background: var(--mp-bg-hover); border-color: var(--mp-border-active); }
        .maxpland-btn-danger {
            background: var(--mp-rose-bg); color: #fda4af; border: 1px solid rgba(244, 63, 94, 0.3);
            border-radius: 6px; padding: 5px 11px; font-weight: 600; font-size: 11.5px; display: inline-flex; align-items: center; gap: 5px;
            transition: background-color .15s;
        }
        .maxpland-btn-danger:hover { background: rgba(244, 63, 94, 0.28); }

        /* Floating / Sticky Bulk Action Bar */
        .maxpland-bulk-bar {
            display: none; align-items: center; justify-content: space-between; padding: 8px 14px;
            background: #1e293b; border: 1px solid #38bdf8; border-radius: 8px; margin-bottom: 12px;
            box-shadow: 0 4px 16px rgba(0,0,0,0.4); animation: mpFadeIn .15s ease-out;
        }
        .maxpland-bulk-info { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 12.5px; color: #f8fafc; }
        .maxpland-bulk-actions { display: flex; align-items: center; gap: 8px; }

        /* User List */
        .maxpland-user-list {
            background: var(--mp-bg-panel); border: 1px solid var(--mp-border-card); border-radius: 8px;
            overflow: hidden; max-height: 420px; overflow-y: auto; position: relative;
        }
        .maxpland-user-row {
            display: flex; align-items: center; justify-content: space-between; padding: 8px 14px;
            border-bottom: 1px solid var(--mp-border-subtle); gap: 12px; transition: background-color .12s;
        }
        .maxpland-user-row:last-child { border-bottom: none; }
        .maxpland-user-row:hover { background: var(--mp-bg-hover); }
        .maxpland-user-left { display: flex; align-items: center; gap: 11px; min-width: 0; }
        
        .maxpland-checkbox {
            appearance: none; -webkit-appearance: none; width: 16px; height: 16px; border: 1.5px solid var(--mp-border-card);
            border-radius: 4px; background: var(--mp-bg-card); cursor: pointer; display: grid; place-items: center;
            flex: 0 0 16px; transition: all .12s;
        }
        .maxpland-checkbox:checked {
            background: var(--mp-blue); border-color: var(--mp-blue);
        }
        .maxpland-checkbox:checked::after {
            content: ''; width: 4px; height: 8px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg) translate(-1px, -1px);
        }
        .maxpland-checkbox:disabled { cursor: not-allowed; opacity: .3; }

        .maxpland-avatar {
            width: 36px; height: 36px; border-radius: 50%; object-fit: cover;
            background: var(--mp-bg-elevated); border: 1px solid var(--mp-border-subtle);
            display: grid; place-items: center; font-weight: 700; flex: 0 0 36px; color: var(--mp-text-secondary);
        }
        .maxpland-user-names { display: flex; flex-direction: column; min-width: 0; }
        .maxpland-username-wrap { display: flex; align-items: center; gap: 6px; }
        .maxpland-username {
            font-weight: 650; font-size: 13px; color: var(--mp-text-primary); text-decoration: none;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
        }
        .maxpland-username:hover { text-decoration: underline; color: #38bdf8; }
        .maxpland-fullname { font-size: 11.5px; color: var(--mp-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

        .maxpland-status-tag {
            font-size: 10px; font-weight: 600; padding: 2px 6px; border-radius: 4px;
        }
        .maxpland-status-tag.not_following { background: var(--mp-rose-bg); color: #fda4af; border: 1px solid rgba(244, 63, 94, 0.25); }
        .maxpland-status-tag.fan { background: var(--mp-cyan-bg); color: #7dd3fc; border: 1px solid rgba(14, 165, 233, 0.25); }
        .maxpland-status-tag.mutual { background: var(--mp-emerald-bg); color: #6ee7b7; border: 1px solid rgba(16, 185, 129, 0.25); }
        .maxpland-status-tag.lost { background: var(--mp-amber-bg); color: #fde68a; border: 1px solid rgba(245, 158, 11, 0.25); }
        .maxpland-status-tag.ghost { background: rgba(168, 85, 247, 0.14); color: #c084fc; border: 1px solid rgba(168, 85, 247, 0.25); }

        .maxpland-user-actions { display: flex; align-items: center; gap: 6px; }
        .maxpland-icon-action {
            background: none; border: none; cursor: pointer; padding: 6px; color: var(--mp-text-muted);
            border-radius: 5px; display: flex; align-items: center; justify-content: center;
            transition: color .15s, background-color .15s;
        }
        .maxpland-icon-action:hover { color: var(--mp-text-primary); background: var(--mp-bg-elevated); }
        .maxpland-row-unfollow-btn {
            background: var(--mp-bg-elevated); color: #fda4af; border: 1px solid rgba(244, 63, 94, 0.2);
            border-radius: 5px; padding: 4px 8px; font-size: 11px; font-weight: 600; display: inline-flex; align-items: center; gap: 4px;
            transition: all .12s;
        }
        .maxpland-row-unfollow-btn:hover { background: var(--mp-rose-bg); border-color: rgba(244, 63, 94, 0.4); color: #fff; }

        /* Toast Notification */
        .maxpland-toast {
            position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
            background: #1e293b; color: #f8fafc; border: 1px solid #38bdf8;
            padding: 8px 16px; border-radius: 8px; font-size: 12px; font-weight: 600;
            box-shadow: 0 8px 24px rgba(0,0,0,0.5); z-index: 100005; pointer-events: none;
            opacity: 0; transition: opacity .2s ease, transform .2s ease;
        }
        .maxpland-toast.show { opacity: 1; transform: translate(-50%, -6px); }

        /* Footer */
        .maxpland-footer {
            grid-area: footer; height: 38px; border-top: 1px solid var(--mp-border-subtle); background: var(--mp-bg-surface);
            padding: 0 18px; display: flex; align-items: center; justify-content: space-between; font-size: 11.5px; color: var(--mp-text-muted);
        }
        .maxpland-footer kbd {
            background: var(--mp-bg-panel); border: 1px solid var(--mp-border-card); border-radius: 4px;
            padding: 2px 5px; font-size: 10px; font-family: ui-monospace, monospace; color: var(--mp-text-secondary);
        }

        /* In-feed Native Action Bar Integration (Zero Vertical Space Waste) */
        .maxpland-action-wrap {
            display: inline-flex;
            align-items: center;
            position: relative;
            z-index: 50;
        }
        .maxpland-action-btn {
            background: none;
            border: none;
            padding: 8px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            color: inherit;
            border-radius: 50%;
            transition: transform .12s ease, opacity .12s ease, color .12s ease;
            line-height: 1;
        }
        .maxpland-action-btn:hover {
            opacity: 0.75;
            transform: scale(1.08);
            color: #38bdf8;
        }
        .maxpland-action-btn:active {
            transform: scale(0.95);
        }
        .maxpland-action-btn svg {
            width: 24px;
            height: 24px;
        }
        .maxpland-action-btn.loading {
            animation: mpPulse 1s infinite alternate;
            pointer-events: none;
            opacity: 0.5;
        }

        /* Native-integrated Glassmorphism Popover Menu */
        .maxpland-action-menu {
            position: absolute;
            bottom: calc(100% + 8px);
            right: 0;
            background: rgba(18, 21, 27, 0.96);
            backdrop-filter: blur(14px);
            border: 1px solid rgba(255, 255, 255, 0.12);
            border-radius: 10px;
            padding: 6px;
            display: none;
            flex-direction: column;
            gap: 2px;
            min-width: 200px;
            box-shadow: 0 10px 32px rgba(0, 0, 0, 0.75);
            z-index: 1000;
            animation: mpFadeIn .14s ease-out;
        }
        .maxpland-action-menu.show {
            display: flex;
        }
        .maxpland-menu-item {
            background: none;
            border: none;
            color: #f8fafc;
            padding: 7px 11px;
            border-radius: 6px;
            font-size: 12px;
            font-weight: 600;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 9px;
            text-align: left;
            transition: background-color .12s, color .12s;
            white-space: nowrap;
        }
        .maxpland-menu-item:hover {
            background: #0284c7;
            color: #ffffff;
        }
        .maxpland-menu-item svg {
            width: 14px;
            height: 14px;
            flex-shrink: 0;
        }
        .maxpland-menu-divider {
            height: 1px;
            background: rgba(255, 255, 255, 0.08);
            margin: 3px 0;
        }

        @keyframes mpPulse {
            from { transform: scale(1); opacity: 0.6; }
            to { transform: scale(1.15); opacity: 1; color: #38bdf8; }
        }

        /* Floating Story Tools */
        .maxpland-story-tools {
            position: fixed; top: 18px; right: 80px; z-index: 10001;
            display: flex; align-items: center; gap: 6px; background: rgba(18, 21, 27, 0.85);
            backdrop-filter: blur(8px); border: 1px solid var(--mp-border-subtle); border-radius: 20px; padding: 4px 10px;
        }
        .maxpland-story-btn {
            background: none; border: none; color: #f8fafc; font-size: 12px; font-weight: 600; cursor: pointer;
            display: inline-flex; align-items: center; gap: 5px; padding: 4px 8px; border-radius: 12px;
            transition: background .15s;
        }
        .maxpland-story-btn:hover { background: rgba(255,255,255,0.12); color: #38bdf8; }

        /* Profile Avatar HD Download Badge */
        .maxpland-avatar-badge {
            display: inline-flex; align-items: center; gap: 5px; margin-left: 10px;
            background: var(--mp-bg-panel); border: 1px solid var(--mp-border-card); border-radius: 6px;
            padding: 4px 10px; font-size: 11.5px; font-weight: 600; color: #38bdf8; cursor: pointer;
            transition: all .12s; vertical-align: middle;
        }
        .maxpland-avatar-badge:hover { background: var(--mp-bg-hover); border-color: #38bdf8; }

        @keyframes mpFadeIn {
            from { opacity: 0; transform: translateY(4px); }
            to { opacity: 1; transform: translateY(0); }
        }

        /* Feature Cards & Switch Controllers */
        .maxpland-feature-card {
            display: flex; align-items: center; justify-content: space-between; gap: 16px;
            padding: 14px 16px; background: var(--mp-bg-card); border: 1px solid var(--mp-border-card);
            border-radius: 8px; margin-bottom: 10px; transition: border-color .15s;
        }
        .maxpland-feature-card:hover { border-color: var(--mp-border-active); }
        .maxpland-feature-info { flex: 1; }
        .maxpland-feature-title { font-size: 13.5px; font-weight: 600; color: var(--mp-text-primary); display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
        .maxpland-feature-desc { font-size: 11.5px; color: var(--mp-text-secondary); line-height: 1.45; }
        .maxpland-badge-emerald { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: var(--mp-emerald-bg); color: var(--mp-emerald); font-weight: 700; text-transform: uppercase; }

        /* Modern Toggle Switch */
        .maxpland-switch { position: relative; display: inline-block; width: 42px; height: 22px; flex-shrink: 0; }
        .maxpland-switch input { opacity: 0; width: 0; height: 0; }
        .maxpland-slider {
            position: absolute; cursor: pointer; inset: 0; background-color: var(--mp-bg-hover);
            transition: .2s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 22px; border: 1px solid var(--mp-border-card);
        }
        .maxpland-slider:before {
            position: absolute; content: ""; height: 16px; width: 16px; left: 2px; bottom: 2px;
            background-color: var(--mp-text-secondary); transition: .2s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 50%;
        }
        .maxpland-switch input:checked + .maxpland-slider { background-color: var(--mp-blue); border-color: var(--mp-blue); }
        .maxpland-switch input:checked + .maxpland-slider:before { transform: translateX(20px); background-color: #fff; }

        /* Settings Panels */
        .maxpland-settings-group {
            background: var(--mp-bg-card); border: 1px solid var(--mp-border-card); border-radius: 8px;
            padding: 14px 16px; margin-bottom: 12px;
        }
        .maxpland-settings-group-title { font-size: 13px; font-weight: 700; color: var(--mp-text-primary); margin: 0 0 10px; display: flex; align-items: center; gap: 6px; }
        .maxpland-settings-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 10px 0; border-top: 1px solid var(--mp-border-subtle); }
        .maxpland-settings-row:first-of-type { border-top: none; padding-top: 0; }
        .maxpland-select {
            background: var(--mp-bg-panel); color: var(--mp-text-primary); border: 1px solid var(--mp-border-card);
            border-radius: 6px; padding: 5px 10px; font-size: 12px; outline: none; cursor: pointer;
        }

        /* Health Dashboard Elements */
        .maxpland-health-hero {
            display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; margin-bottom: 14px;
        }
        .maxpland-health-card {
            background: var(--mp-bg-card); border: 1px solid var(--mp-border-card); border-radius: 8px; padding: 14px 16px;
        }
        .maxpland-health-title { font-size: 11px; font-weight: 700; color: var(--mp-text-muted); text-transform: uppercase; margin-bottom: 6px; letter-spacing: 0.5px; }
        .maxpland-health-val { font-size: 22px; font-weight: 800; color: var(--mp-text-primary); font-variant-numeric: tabular-nums; }
        .maxpland-health-badge { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; padding: 3px 8px; border-radius: 5px; font-weight: 600; margin-top: 6px; }
        .maxpland-chart-box {
            background: var(--mp-bg-card); border: 1px solid var(--mp-border-card); border-radius: 8px; padding: 16px; margin-bottom: 14px;
        }
        .maxpland-sparkline { width: 100%; height: 130px; overflow: visible; }
    `;

    /* ==========================================================================
       2. DATABASE LAYER (IndexedDB with Whitelist Backup & Restore)
       ========================================================================== */

    class MaxPlandVault {
        static db = null;
        static opening = null;

        static async init() {
            if (this.db) return this.db;
            if (this.opening) return this.opening;
            this.opening = new Promise((resolve, reject) => {
                const req = indexedDB.open(APP_CONFIG.DB_NAME, APP_CONFIG.DB_VERSION);
                req.onupgradeneeded = (e) => {
                    const db = e.target.result;
                    if (!db.objectStoreNames.contains('snapshots')) {
                        db.createObjectStore('snapshots', { keyPath: 'timestamp' });
                    }
                    if (!db.objectStoreNames.contains('whitelist')) {
                        db.createObjectStore('whitelist', { keyPath: 'id' });
                    }
                    if (!db.objectStoreNames.contains('media_vault')) {
                        const vault = db.createObjectStore('media_vault', { keyPath: 'key' });
                        vault.createIndex('downloaded_at', 'downloaded_at', { unique: false });
                    }
                    if (!db.objectStoreNames.contains('user_activity')) {
                        db.createObjectStore('user_activity', { keyPath: 'id' });
                    }
                };
                req.onsuccess = (e) => {
                    this.db = e.target.result;
                    this.db.onversionchange = () => { this.db.close(); this.db = null; };
                    resolve(this.db);
                };
                req.onerror = () => reject(req.error || new Error('IndexedDB open failed'));
            }).finally(() => { this.opening = null; });
            return this.opening;
        }

        static async getWhitelist() {
            const db = await this.init();
            return new Promise((resolve) => {
                const tx = db.transaction('whitelist', 'readonly');
                const store = tx.objectStore('whitelist');
                const req = store.getAll();
                req.onsuccess = () => resolve(new Map((req.result || []).map(item => [String(item.id), item])));
                req.onerror = () => resolve(new Map());
            });
        }

        static async toggleWhitelist(user) {
            const db = await this.init();
            const uid = String(user.pk || user.pk_id || user.id || '');
            if (!uid) throw new Error('Missing user ID');
            return new Promise((resolve, reject) => {
                const tx = db.transaction('whitelist', 'readwrite');
                const store = tx.objectStore('whitelist');
                let added = false;
                const req = store.get(uid);
                req.onsuccess = () => {
                    added = !req.result;
                    if (added) {
                        store.put({
                            id: uid,
                            username: user.username,
                            full_name: user.full_name || '',
                            profile_pic_url: user.profile_pic_url || '',
                            is_verified: Boolean(user.is_verified),
                            is_private: Boolean(user.is_private),
                            added_at: Date.now()
                        });
                    } else {
                        store.delete(uid);
                    }
                };
                tx.oncomplete = () => resolve(added);
                tx.onerror = () => reject(tx.error);
            });
        }

        static async importWhitelist(usersList) {
            const db = await this.init();
            return new Promise((resolve, reject) => {
                const tx = db.transaction('whitelist', 'readwrite');
                const store = tx.objectStore('whitelist');
                let count = 0;
                for (const u of usersList) {
                    const uid = String(u.id || u.pk || u.pk_id || '');
                    if (uid) {
                        store.put({
                            id: uid,
                            username: u.username || 'unknown',
                            full_name: u.full_name || '',
                            profile_pic_url: u.profile_pic_url || '',
                            is_verified: Boolean(u.is_verified),
                            is_private: Boolean(u.is_private),
                            added_at: u.added_at || Date.now()
                        });
                        count++;
                    }
                }
                tx.oncomplete = () => resolve(count);
                tx.onerror = () => reject(tx.error);
            });
        }

        static async saveSnapshot(followers, following, accountId = null, signal = null) {
            if (followers.completed !== true || following.completed !== true) throw new Error('Cannot save snapshot from incomplete scan data');
            const db = await this.init();
            IgBridge.assertAccount(accountId);
            if (signal?.aborted) throw new DOMException('Scan aborted by user', 'AbortError');
            return new Promise((resolve, reject) => {
                const tx = db.transaction('snapshots', 'readwrite');
                const abort = () => { try { tx.abort(); } catch (_) {} };
                signal?.addEventListener('abort', abort, { once: true });
                const cleanup = () => signal?.removeEventListener('abort', abort);
                const store = tx.objectStore('snapshots');
                const snap = {
                    timestamp: Date.now(),
                    complete: true,
                    account_id: accountId ? String(accountId) : null,
                    follower_count: followers.length,
                    following_count: following.length,
                    follower_ids: followers.map(u => String(u.pk || u.pk_id || u.id)),
                    following_ids: following.map(u => String(u.pk || u.pk_id || u.id)),
                    // ponytail: separate id->username maps (not one fat blob) so lost-follower
                    // rows can show real names without any extra API call; add an index when queried.
                    follower_usernames: Object.fromEntries(followers.map(u => [String(u.pk || u.pk_id || u.id), String(u.username || '')]).filter(pair => pair[1])),
                    following_usernames: Object.fromEntries(following.map(u => [String(u.pk || u.pk_id || u.id), String(u.username || '')]).filter(pair => pair[1]))
                };
                store.put(snap);
                tx.oncomplete = () => { cleanup(); resolve(snap); };
                tx.onerror = () => { cleanup(); reject(tx.error || new Error('Snapshot write failed')); };
                tx.onabort = () => { cleanup(); reject(signal?.aborted ? new DOMException('Scan aborted by user', 'AbortError') : tx.error || new Error('Snapshot transaction aborted')); };
            });
        }

        static async getLatestSnapshot(accountId = null) {
            const db = await this.init();
            return new Promise((resolve) => {
                const tx = db.transaction('snapshots', 'readonly');
                const store = tx.objectStore('snapshots');
                const req = store.openCursor(null, 'prev');
                req.onsuccess = (e) => {
                    const cursor = e.target.result;
                    if (!cursor) { resolve(null); return; }
                    if (cursor.value.complete === true && (!accountId || String(cursor.value.account_id) === String(accountId))) {
                        resolve(cursor.value);
                    } else {
                        cursor.continue();
                    }
                };
                req.onerror = () => resolve(null);
            });
        }

        static async markMediaDownloaded(record) {
            const db = await this.init();
            return new Promise((resolve, reject) => {
                const tx = db.transaction('media_vault', 'readwrite');
                const store = tx.objectStore('media_vault');
                store.put({ ...record, downloaded_at: Date.now() });
                tx.oncomplete = () => resolve();
                tx.onerror = () => reject(tx.error);
            });
        }

        static async hasMedia(key) {
            const db = await this.init();
            return new Promise((resolve) => {
                const tx = db.transaction('media_vault', 'readonly');
                const req = tx.objectStore('media_vault').get(key);
                req.onsuccess = () => resolve(Boolean(req.result));
                req.onerror = () => resolve(false);
            });
        }

        static async getMediaVault(limit = 100) {
            const db = await this.init();
            return new Promise((resolve) => {
                const tx = db.transaction('media_vault', 'readonly');
                const store = tx.objectStore('media_vault');
                const index = store.index('downloaded_at');
                const results = [];
                const req = index.openCursor(null, 'prev');
                req.onsuccess = (e) => {
                    const cursor = e.target.result;
                    if (cursor && results.length < limit) {
                        results.push(cursor.value);
                        cursor.continue();
                    } else {
                        resolve(results);
                    }
                };
                req.onerror = () => resolve([]);
            });
        }

        static async getAllSnapshots(accountId = null, limit = 20) {
            const db = await this.init();
            return new Promise((resolve) => {
                const tx = db.transaction('snapshots', 'readonly');
                const store = tx.objectStore('snapshots');
                const req = store.openCursor(null, 'prev');
                const list = [];
                req.onsuccess = (e) => {
                    const cursor = e.target.result;
                    if (cursor && list.length < limit) {
                        if (cursor.value.complete === true && (!accountId || String(cursor.value.account_id) === String(accountId))) {
                            list.push(cursor.value);
                        }
                        cursor.continue();
                    } else {
                        resolve(list);
                    }
                };
                req.onerror = () => resolve([]);
            });
        }

        static async getUserActivity(userId) {
            const db = await this.init();
            return new Promise((resolve) => {
                const tx = db.transaction('user_activity', 'readonly');
                const req = tx.objectStore('user_activity').get(String(userId));
                req.onsuccess = () => resolve(req.result || null);
                req.onerror = () => resolve(null);
            });
        }

        static async saveUserActivity(record) {
            const db = await this.init();
            return new Promise((resolve, reject) => {
                const tx = db.transaction('user_activity', 'readwrite');
                tx.objectStore('user_activity').put(record);
                tx.oncomplete = () => resolve();
                tx.onerror = () => reject(tx.error);
            });
        }

        static async clearActivityCache() {
            const db = await this.init();
            return new Promise((resolve, reject) => {
                const tx = db.transaction('user_activity', 'readwrite');
                tx.objectStore('user_activity').clear();
                tx.oncomplete = () => resolve();
                tx.onerror = () => reject(tx.error);
            });
        }
    }

    /* ==========================================================================
       3. API TRANSPORT ENGINE (Fetch + GM_xhr fallback + CSRF + Unfollow)
       ========================================================================== */

    class IgBridge {
        static currentUserId = null;
        static currentUsername = null;
        static wwwClaim = null;

        static getAppId() {
            return APP_CONFIG.INSTAGRAM_WEB_APP_ID || '936619743392459';
        }

        static getCookie(name) {
            const match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)'));
            return match ? decodeURIComponent(match[3]) : null;
        }

        static async resolveCurrentUser() {
            // Level 1: Standard authentication cookie 'ds_user_id'
            const cookieId = this.getCookie('ds_user_id');
            if (!cookieId) { this.currentUserId = null; this.currentUsername = null; return { id: null, username: null }; }
            if (cookieId) {
                if (this.currentUserId !== cookieId) {
                    this.currentUsername = null;
                }
                this.currentUserId = cookieId;
            }

            // Level 2: window globals (_sharedData, __initialData) with viewer validation
            try {
                const page = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
                const sharedViewer = page._sharedData?.config?.viewer;
                const sharedViewerId = page._sharedData?.config?.viewerId || sharedViewer?.id;
                if (sharedViewerId) {
                    const svIdStr = String(sharedViewerId);
                    if (!this.currentUserId) this.currentUserId = svIdStr;
                    if (this.currentUserId === svIdStr && sharedViewer?.username) {
                        this.currentUsername = sharedViewer.username;
                    }
                }

                const initialViewer = page.__initialData?.data?.viewer;
                if (initialViewer?.id) {
                    const ivIdStr = String(initialViewer.id);
                    if (!this.currentUserId) this.currentUserId = ivIdStr;
                    if (this.currentUserId === ivIdStr && initialViewer.username) {
                        this.currentUsername = initialViewer.username;
                    }
                }
            } catch (_) {}

            // Level 3: Scan inline JSON script tags strictly tied to currentUserId
            try {
                const scripts = document.querySelectorAll('script[type="application/json"], script:not([src])');
                for (const s of scripts) {
                    const txt = s.textContent || '';
                    if (!this.currentUserId) {
                        const m = txt.match(/"(?:viewerId|actorID|userId)":\s*"(\d+)"/) 
                               || txt.match(/"viewer":\s*\{\s*"id":\s*"(\d+)"/);
                        if (m && m[1]) this.currentUserId = m[1];
                    }

                    if (this.currentUserId && !this.currentUsername && txt.includes('username')) {
                        const uid = this.currentUserId;
                        // Match username in tight proximity to user ID (JSON object level)
                        const m1 = txt.match(new RegExp(`"id"\\s*:\\s*"${uid}"[^{}]{0,300}"username"\\s*:\\s*"([a-zA-Z0-9._]+)"`));
                        const m2 = txt.match(new RegExp(`"username"\\s*:\\s*"([a-zA-Z0-9._]+)"[^{}]{0,300}"id"\\s*:\\s*"${uid}"`));
                        const m3 = txt.match(new RegExp(`"(?:viewerId|actorID)"\\s*:\\s*"${uid}"[^{}]{0,300}"username"\\s*:\\s*"([a-zA-Z0-9._]+)"`));
                        const m4 = txt.match(new RegExp(`"username"\\s*:\\s*"([a-zA-Z0-9._]+)"[^{}]{0,300}"(?:viewerId|actorID)"\\s*:\\s*"${uid}"`));
                        const found = m1?.[1] || m2?.[1] || m3?.[1] || m4?.[1];
                        if (found && !['null', 'undefined'].includes(found)) {
                            this.currentUsername = found;
                            break;
                        }
                    }
                }
            } catch (_) {}

            // Level 4: Navigation Bar ONLY (strictly desktop sidebar or mobile nav bar, NEVER feed/posts)
            if (!this.currentUsername) {
                try {
                    const navContainer = document.querySelector('nav, [role="navigation"], aside');
                    if (navContainer) {
                        const navAnchors = Array.from(navContainer.querySelectorAll('a[href^="/"]'));
                        const reserved = new Set([
                            '', 'explore', 'reels', 'direct', 'stories', 'your_activity', 
                            'settings', 'accounts', 'api', 'legal', 'about', 'p', 'reel', 'tv'
                        ]);

                        // 4a. Profile link with explicit aria-label or SVG Profile icon in nav
                        const profileLink = navAnchors.find(a => {
                            const svg = a.querySelector('svg[aria-label="Profile"], svg[aria-label="โปรไฟล์"]');
                            const aria = a.getAttribute('aria-label') || '';
                            return Boolean(svg || aria.includes('Profile') || aria.includes('โปรไฟล์'));
                        });

                        if (profileLink) {
                            const parts = (profileLink.getAttribute('href') || '').replace(/^\/|\/$/g, '').split('/');
                            if (parts.length === 1 && !reserved.has(parts[0].toLowerCase())) {
                                this.currentUsername = parts[0];
                            }
                        }

                        // 4b. Nav link that contains an avatar img inside the navigation container
                        if (!this.currentUsername) {
                            for (const a of navAnchors) {
                                const parts = (a.getAttribute('href') || '').replace(/^\/|\/$/g, '').split('/');
                                if (parts.length === 1 && !reserved.has(parts[0].toLowerCase())) {
                                    if (a.querySelector('img')) {
                                        this.currentUsername = parts[0];
                                        break;
                                    }
                                }
                            }
                        }
                    }
                } catch (_) {}
            }

            return { id: this.currentUserId, username: this.currentUsername };
        }

        static cooldownUntil = 0;
        static cooldownAccount = null;

        // Story viewer analytics: who viewed MY story media (private web endpoint).
        static async fetchStoryViewers(mediaId) {
            if (!/^\d+$/.test(String(mediaId || ''))) throw new Error('Invalid story media id');
            const data = await this.request(`/api/v1/media/${String(mediaId)}/list_reel_media_viewer/`);
            if (data?.status === 'ok' || Array.isArray(data?.users)) return Array.isArray(data.users) ? data.users : [];
            return [];
        }

        static error(message, code, status = 0) {
            return Object.assign(new Error(message), { code, status });
        }

        static assertAccount(accountId) {
            if (!accountId || this.getCookie('ds_user_id') !== String(accountId)) {
                throw this.error('Account changed or logged out. Please refresh and restart.', 'ACCOUNT_CHANGED');
            }
        }

        static isSessionError(err) {
            return ['AUTH', 'CHECKPOINT', 'RATE_LIMIT', 'ACCOUNT_CHANGED'].includes(err?.code);
        }

        static async request(url, options = {}) {
            const target = new URL(url, 'https://www.instagram.com');
            if (target.protocol !== 'https:' || !['www.instagram.com', 'instagram.com'].includes(target.hostname) || target.port || target.username || target.password) {
                throw this.error('Unsupported API origin', 'ORIGIN');
            }
            const accountId = options.accountId || this.getCookie('ds_user_id');
            this.assertAccount(accountId);
            if (this.cooldownAccount === accountId && Date.now() < this.cooldownUntil) {
                const seconds = Math.ceil((this.cooldownUntil - Date.now()) / 1000);
                throw this.error(`Instagram rate limit reached. Please wait ${seconds} seconds.`, 'RATE_LIMIT', 429);
            }
            const page = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
            const fetchFn = typeof page.fetch === 'function' ? page.fetch.bind(page) : fetch;
            const method = String(options.method || 'GET').toUpperCase();
            const csrf = this.getCookie('csrftoken');
            // ponytail: align headers with real Instagram Web client (no X-Requested-With, add X-ASBD-ID and X-IG-WWW-Claim)
            const headers = {
                'X-IG-App-ID': this.getAppId(),
                'X-ASBD-ID': '129477',
                'X-IG-WWW-Claim': this.wwwClaim || '0',
                'Accept': '*/*',
                ...(csrf ? { 'X-CSRFToken': csrf } : {}),
                ...options.headers
            };
            const attempts = method === 'GET' ? 3 : 1;
            for (let attempt = 0; attempt < attempts; attempt++) {
                this.assertAccount(accountId);
                if (options.signal?.aborted) throw new DOMException('Operation aborted by user', 'AbortError');
                const controller = new AbortController();
                const abort = () => controller.abort();
                options.signal?.addEventListener('abort', abort, { once: true });
                let timedOut = false;
                const timeout = setTimeout(() => { timedOut = true; controller.abort(); }, 30000);
                try {
                    const res = await fetchFn(target.href, { method, headers, credentials: 'include',
                        body: options.body ?? options.data, signal: controller.signal });
                    const newClaim = res.headers?.get('x-ig-www-claim');
                    if (newClaim) this.wwwClaim = newClaim;
                    const text = await res.text();
                    this.assertAccount(accountId);
                    if (options.signal?.aborted) throw new DOMException('Operation aborted by user', 'AbortError');
                    let data = null;
                    try { data = JSON.parse(text); } catch (_) {}
                    const message = String(data?.message || data?.error_type || '');
                    const redirected = res.url || '';
                    if (/challenge|checkpoint|consent_required/i.test(message + redirected) || data?.challenge || data?.checkpoint_url) {
                        throw this.error('Instagram requires checkpoint verification. Please solve challenge on Instagram web.', 'CHECKPOINT', res.status);
                    }
                    if (res.status === 401 || /login_required|\/accounts\/login/i.test(message + redirected)) {
                        throw this.error('Session expired. Please log into Instagram and refresh.', 'AUTH', res.status);
                    }
                    if (res.status === 429 || /feedback_required|please wait|try again later|rate.limit/i.test(message)) {
                        const retry = res.headers?.get('Retry-After');
                        const delay = retry && /^\d+(\.\d+)?$/.test(retry) ? Number(retry) * 1000 : Date.parse(retry) - Date.now();
                        this.cooldownUntil = Date.now() + Math.max(60000, Number.isFinite(delay) ? delay : 60000);
                        this.cooldownAccount = accountId;
                        // Surface the cooldown once at the moment it starts (all flows: scan/unfollow/media).
                        showToast(`⏳ Instagram rate limit — waiting ${Math.max(1, Math.ceil((this.cooldownUntil - Date.now()) / 1000))}s before the next request`, 4000);
                        throw this.error('Instagram rate limit active. Please rest before trying again.', 'RATE_LIMIT', res.status);
                    }
                    if (res.status === 403) throw this.error('Instagram forbidden (403). Check account status on web.', 'AUTH', 403);
                    if (/^\s*</.test(text)) throw this.error('Instagram returned HTML instead of JSON. Check login status.', 'HTML_RESPONSE', res.status);
                    if (!res.ok) throw this.error(`Instagram responded with HTTP ${res.status}`, 'HTTP', res.status);
                    if (!data || typeof data !== 'object') throw this.error('Instagram returned invalid payload format', 'PAYLOAD', res.status);
                    if (data.status === 'fail' || data.errors?.length) throw this.error('Instagram rejected data request', 'API', res.status);
                    return data;
                } catch (err) {
                    if (options.signal?.aborted) throw new DOMException('Operation aborted by user', 'AbortError');
                    this.assertAccount(accountId);
                    if (timedOut) throw this.error('Instagram request timed out after 30s. Please retry.', 'TIMEOUT');
                    const transient = err.status >= 500 || (!err.code && err.name === 'TypeError');
                    if (!transient || attempt + 1 >= attempts) throw err;
                } finally {
                    clearTimeout(timeout);
                    options.signal?.removeEventListener('abort', abort);
                }
                await sleep(3000 * (attempt + 1)); // 3/6/9 s: a fast retry storm reads as a bot
            }
        }

        static async unfollowUser(userId) {
            const uid = String(userId || '');
            if (!/^\d+$/.test(uid)) throw new Error('Invalid User ID to unfollow');
            const user = STATE.following.find(u => String(u.id || u.pk_id || u.pk || '') === uid);
            if (isProtectedUser(uid, user?.username)) throw new Error('This account is in the Whitelist');
            const accountId = STATE.relationshipAccountId || this.getCookie('ds_user_id');
            this.assertAccount(accountId);
            const csrf = this.getCookie('csrftoken');
            if (!csrf) throw this.error('CSRF token missing. Please refresh Instagram.', 'AUTH');

            const candidateRoutes = [
                { url: `/web/friendships/${uid}/unfollow/`, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
                { url: `/api/v1/web/friendships/${uid}/unfollow/`, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
                {
                    url: `/api/v1/friendships/destroy/${uid}/`,
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: new URLSearchParams({ user_id: uid, _uid: accountId, _csrftoken: csrf }).toString()
                }
            ];

            let lastError = null;
            for (let i = 0; i < candidateRoutes.length; i++) {
                this.assertAccount(accountId);
                if (i > 0) await sleep(1000);
                const route = candidateRoutes[i];
                try {
                    const res = await this.request(route.url, {
                        method: 'POST',
                        accountId,
                        headers: route.headers,
                        body: route.body
                    });
                    if (res?.friendship_status?.following === false || (res?.status === 'ok' && res.friendship_status === undefined)) {
                        return true;
                    }
                    throw this.error('Instagram did not confirm the unfollow. Check the profile before retrying', 'UNCONFIRMED');
                } catch (err) {
                    lastError = err;
                    // Never repeat an ambiguous write or a rejected session on another route.
                    if (err.code !== 'HTTP' || ![404, 405].includes(err.status)) throw err;
                }
            }
            throw lastError || new Error('Instagram did not confirm unfollow. Check profile on web.');
        }

        static async fetchRelationshipPage(endpoint, userId, cursor = null, options = {}) {
            const uid = String(userId || '');
            if (!/^\d+$/.test(uid) || !['followers', 'following'].includes(endpoint)) throw new Error('Invalid relationship target');
            this.assertAccount(uid);
            const count = Math.min(50, Math.max(1, Number(APP_CONFIG.PAGE_SIZE) || 50));
            if (options.transport === 'graphql') {
                // Compatibility route used by Instaloader's Profile.get_followers/get_followees.
                if (!window.__mpGqlTransportWarned) {
                    window.__mpGqlTransportWarned = true;
                    console.warn('[MaxPland] Legacy GraphQL relationship transport active (query_hash can rotate; REST remains primary)');
                }
                const hash = endpoint === 'followers' ? '37479f2b8209594dde7facb0d904896a' : '58712303d941c6855d4e888c5f0cd22f';
                const variables = { id: uid, first: count, ...(cursor ? { after: cursor } : {}) };
                const qs = new URLSearchParams({ query_hash: hash, variables: JSON.stringify(variables) });
                const data = await this.request(`/graphql/query/?${qs}`, { ...options, accountId: uid });
                const connection = data?.data?.user?.[endpoint === 'followers' ? 'edge_followed_by' : 'edge_follow'];
                if (!Array.isArray(connection?.edges) || typeof connection.page_info?.has_next_page !== 'boolean') throw new Error('Invalid GraphQL relationship page');
                return { users: connection.edges.map(edge => edge?.node),
                    has_more: connection.page_info.has_next_page,
                    next_max_id: connection.page_info.has_next_page ? connection.page_info.end_cursor : null };
            }
            const qs = new URLSearchParams({ count: String(count), search_surface: 'follow_list_page' });
            if (cursor) qs.set('max_id', cursor);
            return this.request(`/api/v1/friendships/${uid}/${endpoint}/?${qs}`, { ...options, accountId: uid });
        }

        static async fetchAllRelationships(endpoint, userId, pageSafetyLimit = 250, onProgress, speedMode = 'A') {
            const all = [];
            all.completed = false;
            all.lastError = null;
            all.pagesFetched = 0;
            const seenUsers = new Set(), seenCursors = new Set();
            let cursor = null, transport = 'rest';
            // Scan resume: a stopped/paused scan persists cursor+users per page; cleared on completion.
            const RESUME_KEY = `mp_scan_resume_${endpoint}`;
            let savedResume = null;
            try { savedResume = JSON.parse(localStorage.getItem(RESUME_KEY) || 'null'); } catch (_) {}
            if (savedResume && String(savedResume.userId) === String(userId) && Array.isArray(savedResume.users) && savedResume.cursor) {
                for (const u of savedResume.users) {
                    if (!seenUsers.has(String(u.id))) { seenUsers.add(String(u.id)); all.push(u); }
                }
                cursor = String(savedResume.cursor);
                transport = savedResume.transport === 'graphql' ? 'graphql' : 'rest';
                all.pagesFetched = Number(savedResume.pagesFetched) || 0;
                onProgress?.(all.length, all.pagesFetched, `Resumed saved scan at page ${all.pagesFetched} (${all.length.toLocaleString()} users kept)`);
            }
            const signal = STATE.scanController?.signal;
            const limit = Math.max(1, Math.min(250, Number(pageSafetyLimit) || 250));
            const fetchStats = { requestMs: 0, waitMs: 0 };
            // Sweet spot (2026-09-24): field studies (instaloader RateController practice,
            // Bellingcat toolkit guidance) + own incident log — sustained metronome
            // pagination at 2-3 s/page tripped IG automation detection; serial single
            // streams with randomized intervals and burst-then-rest patterns did not.
            // Combined request rate is what detection sees: never fetch lists concurrently.
            const PACE_PRESETS = {
                A: { page: [6000, 12000], restEvery: 30, rest: [60000, 120000] },
                B: { page: [3000, 6000], restEvery: 40, rest: [45000, 90000] },
                C: { page: [1500, 3000], restEvery: 50, rest: [30000, 60000] }
            };
            const PACE = PACE_PRESETS[speedMode] || PACE_PRESETS.A;
            try {
                while (!STATE.stopScanFlag) {
                    while (STATE.scanPaused && !STATE.stopScanFlag) await sleep(250);
                    if (STATE.stopScanFlag) break;
                    this.assertAccount(String(userId));
                    let data;
                    try {
                        const t0 = performance.now();
                        data = await this.fetchRelationshipPage(endpoint, userId, cursor, { transport, signal });
                        fetchStats.requestMs += performance.now() - t0;
                    } catch (err) {
                        // Only first-page endpoint incompatibility can switch routes. Never mix cursors,
                        // bypass session challenges, or repeat a rejected request unchanged.
                        if (all.pagesFetched || transport !== 'rest' || err.code !== 'HTTP' || ![400,404].includes(err.status)) throw err;
                        transport = 'graphql';
                        onProgress?.(0, 0, 'Switching to fallback query endpoint...');
                        if (STATE.stopScanFlag) break;
                        const t1 = performance.now();
                        data = await this.fetchRelationshipPage(endpoint, userId, null, { transport, signal });
                        fetchStats.requestMs += performance.now() - t1;
                    }
                    this.assertAccount(String(userId));
                    if (STATE.stopScanFlag) break;
                    if (!Array.isArray(data?.users)) throw new Error('Invalid relationship page');
                    const next = data.next_max_id == null ? '' : String(data.next_max_id);
                    if ((data.has_more === true || data.more_available === true) && !next) throw new Error('Missing relationship cursor');
                    if (next && (data.has_more === false || data.more_available === false)) throw new Error('Conflicting relationship cursor');
                    if (!data.users.length && next) throw new Error('Empty relationship page with cursor');
                    const before = all.length;
                    for (const user of data.users) {
                        const id = String(user?.id || user?.pk_id || user?.pk || '');
                        if (!/^\d+$/.test(id)) continue;
                        if (!seenUsers.has(id)) {
                            seenUsers.add(id);
                            all.push({
                                id,
                                username: user.username || '',
                                full_name: user.full_name || '',
                                profile_pic_url: user.profile_pic_url || '',
                                is_verified: Boolean(user.is_verified),
                                is_private: Boolean(user.is_private),
                                has_anonymous_profile_picture: Boolean(user.has_anonymous_profile_picture)
                            });
                        }
                    }
                    all.pagesFetched++;
                    onProgress?.(all.length, all.pagesFetched, null);
                    this.assertAccount(String(userId));
                    if (STATE.stopScanFlag) break;
                    if (!next) { all.completed = true; try { localStorage.removeItem(RESUME_KEY); } catch (_) {} break; }
                    if (seenCursors.has(next)) throw new Error('Repeated relationship cursor');
                    if (before === all.length) throw new Error('Relationship pagination made no progress');
                    if (all.pagesFetched >= limit) throw new Error(`Safety page limit reached (${limit} pages)`);
                    seenCursors.add(next);
                    cursor = next;
                    try {
                        localStorage.setItem(RESUME_KEY, JSON.stringify({ userId: String(userId), cursor, transport, pagesFetched: all.pagesFetched, users: all }));
                    } catch (_) { /* quota exceeded: resume survives only while storage allows */ }
                    // Yield between pages: jittered per-mode delay + periodic long rest
                    // (honor Stop/pause throughout). PAGE_SIZE/headers/transports untouched.
                    const pause = PACE.page[0] + Math.floor(Math.random() * (PACE.page[1] - PACE.page[0]));
                    const tw = performance.now();
                    for (let ms = 0; ms < pause && !STATE.stopScanFlag && !signal?.aborted; ms += 250) await sleep(250);
                    if (PACE.restEvery && next && all.pagesFetched % PACE.restEvery === 0 && !STATE.stopScanFlag && !signal?.aborted) {
                        const rest = PACE.rest[0] + Math.floor(Math.random() * (PACE.rest[1] - PACE.rest[0]));
                        onProgress?.(all.length, all.pagesFetched, `Breathing rest ${Math.round(rest / 1000)}s — keeps request rate under the detection radar...`);
                        for (let ms = 0; ms < rest && !STATE.stopScanFlag && !signal?.aborted; ms += 250) await sleep(250);
                    }
                    fetchStats.waitMs += performance.now() - tw;
                    if (STATE.stopScanFlag || signal?.aborted) break;
                }
            } catch (err) {
                all.lastError = err;
                onProgress?.(all.length, all.pagesFetched, err.message);
            }
            all.fetchStats = fetchStats;
            return all;
        }

        static shortcodeToMediaId(shortcode) {
            if (!/^[A-Za-z0-9_-]+$/.test(String(shortcode || ''))) throw new Error('Invalid shortcode');
            const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
            let value = 0n;
            for (const ch of String(shortcode || '')) {
                const index = alphabet.indexOf(ch);
                if (index < 0) throw new Error('Invalid shortcode');
                value = value * 64n + BigInt(index);
            }
            return value.toString();
        }

        static async fetchMediaInfo(shortcode) {
            const raw = String(shortcode || '').trim();
            const mediaId = /^\d+$/.test(raw) ? raw : this.shortcodeToMediaId(raw);
            const data = await this.request(`/api/v1/media/${mediaId}/info/`);
            const item = (data.items || [])[0];
            if (!item) throw new Error('Could not fetch media data from Instagram API');
            return item;
        }

        static bestImage(item) {
            const c = item?.image_versions2?.candidates || [];
            return [...c].sort((a,b) => ((b.width||0)*(b.height||0))-((a.width||0)*(a.height||0)))[0]?.url || null;
        }

        static bestProgressiveVideo(item) {
            const v = item?.video_versions || [];
            return [...v].sort((a,b) => ((b.width||0)*(b.height||0))-((a.width||0)*(a.height||0)))[0]?.url || null;
        }

        static async resolveMedia(shortcode) {
            const item = await this.fetchMediaInfo(shortcode);
            const nodes = Array.isArray(item.carousel_media) && item.carousel_media.length ? item.carousel_media : [item];
            return {
                item, shortcode,
                username: item?.user?.username || '',
                caption: item?.caption?.text || '',
                nodes: nodes.map((node, index) => ({
                    index,
                    id: String(node.pk || node.id || item.pk || item.id || shortcode),
                    mediaType: node.media_type === 2 ? 'video' : 'image',
                    imageUrl: this.bestImage(node),
                    progressiveVideoUrl: this.bestProgressiveVideo(node)
                }))
            };
        }

        static async fetchUserProfileHD(username) {
            try {
                const data = await this.request(`/api/v1/users/web_profile_info/?username=${encodeURIComponent(username)}`);
                const user = data?.data?.user;
                if (user) {
                    return user.profile_pic_url_hd || user.profile_pic_url || null;
                }
            } catch (_) {}
            return null;
        }

        static async fetchUserLastPost(userId, options = {}) {
            const uid = String(userId || '');
            if (!/^\d+$/.test(uid)) throw new Error('Invalid user ID');
            const accountId = STATE.relationshipAccountId || this.getCookie('ds_user_id');
            this.assertAccount(accountId);
            const data = await this.request(`/api/v1/feed/user/${uid}/?count=1`, { ...options, accountId });
            const item = (data?.items || [])[0];
            return {
                has_posts: Boolean(item),
                last_taken_at: item?.taken_at || null,
                total_posts: data?.num_results ?? (item ? 1 : 0)
            };
        }
    }

    /* ==========================================================================
       4. CONTROLLER & STATE
       ========================================================================== */

    const DEFAULT_PREFS = {
        stealthStory: true,
        cleanFeed: true,
        quickDownloadFeed: true,
        quickDownloadStory: true,
        inactiveThresholdDays: 180,
        scanSpeed: 'A'
    };

    function loadPrefs() {
        try {
            const raw = localStorage.getItem('maxpland_prefs');
            if (raw) return { ...DEFAULT_PREFS, ...JSON.parse(raw) };
        } catch (_) {}
        return { ...DEFAULT_PREFS };
    }

    function savePrefs(prefs) {
        try {
            localStorage.setItem('maxpland_prefs', JSON.stringify(prefs));
        } catch (_) {}
    }

    const STATE = {
        currentUser: { id: null, username: null },
        followers: [],
        following: [],
        notFollowingBack: [],
        fans: [],
        mutual: [],
        lostFollowers: [],
        ghostFollowers: [],
        inactiveFollowing: [],
        isScanningInactive: false,
        stopInactiveScanFlag: false,
        whitelist: new Map(),
        selectedIds: new Set(),
        isScanning: false,
        stopScanFlag: false,
        isUnfollowing: false,
        stopUnfollowFlag: false,
        activeTab: 'relationship',
        relationshipFilter: 'not_following_back',
        searchQuery: '',
        relationshipLimit: 100,
        subFilters: {
            excludeVerified: false,
            excludePrivate: false,
            excludeNoAvatar: false,
            excludeWhitelist: false,
            onlyFollowing: false,
            onlyNotFollowed: false
        },
        scanStartTime: 0,
        prefs: loadPrefs()
    };

    // STEALTH STORY SEEN INTERCEPTOR (Ponytail: multi-channel stealth — fetch, XHR, sendBeacon masked)
    let stealthSeenCount = 0;
    function isStorySeenRequest(url, body) {
        if (!STATE.prefs?.stealthStory) return false;
        const urlStr = String(url || '');
        const bodyStr = typeof body === 'string' ? body : (body ? JSON.stringify(body) : '');

        // Safety Guard: never block read queries (drawing story rings, viewing profiles, feed)
        const isRead = /web_profile_info|users\/web_profile|PolarisProfile|ProfilePage|[A-Za-z0-9_]*Query\b/i.test(urlStr) ||
                       /operationName["']?\s*[:=]\s*["']?[A-Za-z0-9_]*Query\b/i.test(bodyStr);
        if (isRead) return false;

        // 1. REST endpoint: /stories/reel/seen or /api/v1/stories/reel/seen
        if (/\/(?:api\/v1\/)?stories\/reel\/seen/i.test(urlStr)) return true;

        // 2. viewSeenAt timestamp in query or body
        if (/viewSeenAt/i.test(urlStr) || /viewSeenAt/i.test(bodyStr)) return true;

        // 3. GraphQL seen mutations
        if (/reels?_?media_?seen|Stor(?:y|ies)Seen/i.test(urlStr) || /reels?_?media_?seen|Stor(?:y|ies)Seen/i.test(bodyStr)) return true;

        return false;
    }

    // ponytail: StoryMediaRegistry caches reels_media and GraphQL story payloads intercepted at document-start
    // skipped: IndexedDB persistent cross-session media blob cache, add when offline reel playback is required
    const StoryMediaRegistry = {
        items: new Map(),
        cacheItem(item) {
            if (!item || typeof item !== 'object') return;
            const id = String(item.pk || item.id || '').split('_')[0];
            const isVideo = Boolean(item.video_versions && item.video_versions.length > 0) || item.media_type === 2 || Boolean(item.is_video);
            const videoVersions = Array.isArray(item.video_versions) ? [...item.video_versions] : [];
            const imageCandidates = Array.isArray(item.image_versions2?.candidates) ? [...item.image_versions2.candidates] : [];
            videoVersions.sort((a, b) => ((b.width || 0) * (b.height || 0)) - ((a.width || 0) * (a.height || 0)));
            imageCandidates.sort((a, b) => ((b.width || 0) * (b.height || 0)) - ((a.width || 0) * (a.height || 0)));

            const videoUrl = videoVersions[0]?.url || item.video_url || item.videoUrl || null;
            const imageUrl = imageCandidates[0]?.url || item.display_url || item.src || null;
            const username = item.user?.username || item.owner?.username || '';

            const entry = { id, isVideo, videoUrl, imageUrl, username };
            if (id) this.items.set(id, entry);
        },
        parseAndCache(json) {
            if (!json || typeof json !== 'object') return;
            try {
                if (Array.isArray(json.reels_media)) {
                    for (const reel of json.reels_media) {
                        if (Array.isArray(reel?.items)) {
                            for (const it of reel.items) this.cacheItem(it);
                        }
                    }
                }
                if (json.reels && typeof json.reels === 'object') {
                    for (const k in json.reels) {
                        const reel = json.reels[k];
                        if (Array.isArray(reel?.items)) {
                            for (const it of reel.items) this.cacheItem(it);
                        }
                    }
                }
                if (Array.isArray(json.items)) {
                    for (const it of json.items) this.cacheItem(it);
                }
                if (json.data && typeof json.data === 'object') {
                    this.parseAndCache(json.data);
                }
            } catch (_) {}
        },
        scanDomScripts() {
            try {
                const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
                const doc = win.document || document;
                for (const s of doc.querySelectorAll('script[type="application/json"]')) {
                    const text = s.textContent || '';
                    if (text.includes('video_versions') || text.includes('image_versions2')) {
                        try {
                            const data = JSON.parse(text);
                            this.parseAndCache(data);
                        } catch (_) {}
                    }
                }
            } catch (_) {}
        }
    };

    function installStorySeenInterceptor() {
        try {
            const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
            const hookSym = Symbol.for('mp_seen_hooked');
            if (win[hookSym]) return;
            win[hookSym] = true;

            const FAKE_JSON = JSON.stringify({ status: 'ok' });
            const isStoryDataUrl = (u) => Boolean(u && typeof u === 'string' && (/reels?_?media|graphql\/query|\/api\/v1\/media\//i.test(u)));

            // 1. Intercept fetch
            if (typeof win.fetch === 'function') {
                const rawFetch = win.fetch;
                const stealthFetch = function(resource, init = {}) {
                    let url = '';
                    let body = init?.body || null;

                    if (typeof resource === 'string') {
                        url = resource;
                    } else if (resource && typeof resource.url === 'string') {
                        url = resource.url;
                    }

                    if (isStorySeenRequest(url, body)) {
                        stealthSeenCount++;
                        console.info(`[MaxPland Stealth] Blocked story seen ping #${stealthSeenCount}:`, url);
                        return Promise.resolve(new Response(FAKE_JSON, {
                            status: 200,
                            statusText: 'OK',
                            headers: { 'Content-Type': 'application/json' }
                        }));
                    }
                    const resPromise = rawFetch.apply(this, arguments);
                    try {
                        if (isStoryDataUrl(url)) {
                            resPromise.then(res => {
                                try {
                                    if (typeof res?.clone === 'function') {
                                        res.clone().json().then(data => StoryMediaRegistry.parseAndCache(data)).catch(() => {});
                                    }
                                } catch (_) {}
                            }).catch(() => {});
                        }
                    } catch (_) {}
                    return resPromise;
                };

                try {
                    Object.defineProperty(stealthFetch, 'name', { value: rawFetch.name || 'fetch' });
                    Object.defineProperty(stealthFetch, 'length', { value: rawFetch.length || 1 });
                    const origToString = Function.prototype.toString;
                    stealthFetch.toString = function() {
                        return this === stealthFetch ? origToString.call(rawFetch) : origToString.call(this);
                    };
                } catch (_) {}

                win.fetch = stealthFetch;
            }

            // 2. Intercept XMLHttpRequest
            if (win.XMLHttpRequest && win.XMLHttpRequest.prototype) {
                const rawOpen = win.XMLHttpRequest.prototype.open;
                const rawSend = win.XMLHttpRequest.prototype.send;

                win.XMLHttpRequest.prototype.open = function(method, url, ...rest) {
                    try {
                        this.__mpSeenUrl = typeof url === 'string' ? url : String(url || '');
                    } catch (_) {}
                    return rawOpen.apply(this, [method, url, ...rest]);
                };

                win.XMLHttpRequest.prototype.send = function(body) {
                    try {
                        const url = this.__mpSeenUrl || '';
                        if (isStorySeenRequest(url, body)) {
                            stealthSeenCount++;
                            console.info(`[MaxPland Stealth] Blocked XHR story seen ping #${stealthSeenCount}:`, url);
                            const xhr = this;
                            const define = (prop, val) => {
                                try { Object.defineProperty(xhr, prop, { value: val, configurable: true }); } catch (_) {}
                            };
                            define('readyState', 4);
                            define('status', 200);
                            define('statusText', 'OK');
                            define('responseText', FAKE_JSON);
                            define('response', FAKE_JSON);
                            setTimeout(() => {
                                try {
                                    if (typeof xhr.onreadystatechange === 'function') xhr.onreadystatechange();
                                    xhr.dispatchEvent(new Event('readystatechange'));
                                    xhr.dispatchEvent(new Event('load'));
                                    xhr.dispatchEvent(new Event('loadend'));
                                } catch (_) {}
                            }, 0);
                            return;
                        }
                        if (isStoryDataUrl(url)) {
                            const self = this;
                            const origOnLoad = self.onload;
                            self.onload = function(...args) {
                                try {
                                    if (self.status === 200 && self.responseText) {
                                        const data = JSON.parse(self.responseText);
                                        StoryMediaRegistry.parseAndCache(data);
                                    }
                                } catch (_) {}
                                if (typeof origOnLoad === 'function') return origOnLoad.apply(this, args);
                            };
                        }
                    } catch (_) {}
                    return rawSend.apply(this, arguments);
                };
            }

            // 3. Intercept navigator.sendBeacon
            if (win.navigator && typeof win.navigator.sendBeacon === 'function') {
                const rawSendBeacon = win.navigator.sendBeacon.bind(win.navigator);
                win.navigator.sendBeacon = function(url, data) {
                    try {
                        if (isStorySeenRequest(url, data)) {
                            stealthSeenCount++;
                            console.info(`[MaxPland Stealth] Blocked Beacon story seen ping #${stealthSeenCount}:`, url);
                            return true;
                        }
                    } catch (_) {}
                    return rawSendBeacon(url, data);
                };
            }
        } catch (_) {}
    }
    installStorySeenInterceptor();

    // CLEAN FEED MODE CONTROLLER
    let cleanFeedObserver = null;
    function applyCleanFeedMode() {
        const isEnabled = Boolean(STATE.prefs?.cleanFeed);
        let styleTag = document.getElementById('maxpland-clean-feed-style');

        if (!isEnabled) {
            if (styleTag) styleTag.remove();
            if (cleanFeedObserver) {
                cleanFeedObserver.disconnect();
                cleanFeedObserver = null;
            }
            document.querySelectorAll('article[data-mp-hidden-ad="true"]').forEach(el => {
                delete el.dataset.mpHiddenAd;
            });
            return;
        }

        if (!styleTag) {
            styleTag = document.createElement('style');
            styleTag.id = 'maxpland-clean-feed-style';
            // ponytail: avoid display:none layout collapse which causes browser scroll-anchor to jump to top (0, 0)
            styleTag.textContent = `
                article:has(a[href*="/ads/ig_redirect/"]),
                article:has(a[href*="/ads/about/"]),
                article:has(a[href*="facebook.com/ads/"]),
                article[data-mp-hidden-ad="true"] {
                    visibility: hidden !important;
                    height: 0 !important;
                    min-height: 0 !important;
                    max-height: 0 !important;
                    margin: 0 !important;
                    padding: 0 !important;
                    border: none !important;
                    overflow: hidden !important;
                    overflow-anchor: none !important;
                    pointer-events: none !important;
                    opacity: 0 !important;
                }
            `;
            (document.head || document.documentElement).appendChild(styleTag);
        }

        const scanArticles = () => {
            if (!STATE.prefs?.cleanFeed) return;
            const AD_KEYWORDS = ['sponsored', 'ได้รับการสนับสนุน', 'suggested for you', 'แนะนำสำหรับคุณ'];
            const articles = document.querySelectorAll('article:not([data-mp-hidden-ad])');
            for (const article of articles) {
                const header = article.querySelector('header');
                const text = (header ? header.textContent : (article.textContent || '').slice(0, 300)).toLowerCase();
                const isAd = AD_KEYWORDS.some(kw => text.includes(kw));
                if (isAd) {
                    article.dataset.mpHiddenAd = 'true';
                }
            }
        };

        scanArticles();

        if (!cleanFeedObserver) {
            let debounceTimer = null;
            cleanFeedObserver = new MutationObserver(() => {
                if (debounceTimer) return;
                debounceTimer = setTimeout(() => {
                    debounceTimer = null;
                    scanArticles();
                }, 150);
            });
            // ponytail: scope to <main> (the feed always lives there) with a body fallback —
            // stops reacting to every sidebar/dialog/header mutation IG performs.
            const feedRoot = document.querySelector('main') || document.body || document.documentElement;
            cleanFeedObserver.observe(feedRoot, { childList: true, subtree: true });
        }
    }

    /* ==========================================================================
       5. UI BUILDER (Mini Draggable Circle & Clean Precision Modal)
       ========================================================================== */

    function createUI() {
        const stylesheet = document.createElement('style');
        stylesheet.id = 'maxpland-styles';
        stylesheet.textContent = NATIVE_IG_CSS;
        (document.head || document.documentElement).append(stylesheet);

        // Mini Circular Floating Launcher Button
        const triggerBtn = document.createElement('button');
        triggerBtn.type = 'button';
        triggerBtn.setAttribute('aria-label', 'Open IG MaxPland');
        triggerBtn.id = 'maxpland-trigger-btn';
        triggerBtn.title = 'MaxPland · Alt+Shift+M (Draggable)';
        triggerBtn.innerHTML = ICONS.LOGO;

        // Restore saved position
        try {
            const savedPos = JSON.parse(localStorage.getItem('maxpland_launcher_pos') || 'null');
            if (savedPos && typeof savedPos.x === 'number' && typeof savedPos.y === 'number') {
                const clampX = Math.min(window.innerWidth - 50, Math.max(10, savedPos.x));
                const clampY = Math.min(window.innerHeight - 50, Math.max(10, savedPos.y));
                triggerBtn.style.left = `${clampX}px`;
                triggerBtn.style.top = `${clampY}px`;
                triggerBtn.style.right = 'auto';
                triggerBtn.style.bottom = 'auto';
            }
        } catch (_) {}

        document.body.appendChild(triggerBtn);

        // Overlay & Modal Container
        const overlay = document.createElement('div');
        overlay.id = 'maxpland-studio-overlay';
        overlay.style.display = 'none';

        const modal = document.createElement('div');
        modal.id = 'maxpland-studio-modal';
        modal.style.display = 'none';
        modal.setAttribute('role', 'dialog');
        modal.setAttribute('aria-modal', 'true');
        modal.setAttribute('aria-label', 'IG MaxPland');

        modal.innerHTML = `
            <div class="maxpland-header">
                <div class="maxpland-brand">
                    ${ICONS.LOGO}
                    <span>MaxPland</span>
                    <span id="maxpland-account-badge" class="maxpland-user-pill">Checking account...</span>
                </div>
                <button class="maxpland-close-btn" id="maxpland-close-btn" aria-label="Close MaxPland">${ICONS.CLOSE}</button>
            </div>

            <nav class="maxpland-tabs" role="tablist" aria-label="MaxPland Navigation">
                <button class="maxpland-tab-btn active" data-tab="relationship">${ICONS.USERS} Relationships</button>
                <button class="maxpland-tab-btn" data-tab="health">${ICONS.ANALYTICS} Account Health</button>
                <button class="maxpland-tab-btn" data-tab="features">${ICONS.FEATURES} Features</button>
                <button class="maxpland-tab-btn" data-tab="settings">${ICONS.SETTINGS} Settings</button>
                <button class="maxpland-tab-btn" data-tab="vault">${ICONS.DOWNLOAD} Media Vault</button>
            </nav>

            <div class="maxpland-progress-container" id="maxpland-global-progress">
                <div class="maxpland-scan-row">
                    <span class="maxpland-scan-phase" id="maxpland-scan-phase">[1/2] Preparing scan...</span>
                    <div class="maxpland-scan-meta">
                        <span id="maxpland-scan-stat-count">Users: 0</span>
                        <span id="maxpland-scan-stat-page">Pages: 0</span>
                        <span id="maxpland-scan-stat-timer">⏱️ 00:00</span>
                        <button type="button" class="maxpland-btn-secondary" id="maxpland-btn-pause-scan" style="display:none" title="Pause the running scan; progress is saved page by page">⏸ Pause</button>
                        <button type="button" class="maxpland-btn-danger" id="maxpland-btn-stop-scan">${ICONS.STOP} Stop</button>
                    </div>
                </div>
                <div class="maxpland-progress-track">
                    <div class="maxpland-progress-bar" id="maxpland-progress-fill"></div>
                </div>
                <div id="maxpland-scan-notice" class="maxpland-scan-notice"></div>
            </div>

            <div class="maxpland-body">
                <!-- TAB 1: Relationship & Unfollowers -->
                <div class="maxpland-tab-content active" id="tab-relationship">
                    <!-- Metric Cards -->
                    <div class="maxpland-stats-grid">
                        <div class="maxpland-stat-card card-not-following" data-target-filter="not_following_back">
                            <span class="maxpland-stat-label">Not Following Back</span>
                            <span class="maxpland-stat-value" id="stat-not-following-back">-</span>
                            <span class="maxpland-stat-hint">You follow them, they don't</span>
                        </div>
                        <div class="maxpland-stat-card card-fans" data-target-filter="fans">
                            <span class="maxpland-stat-label">Fans</span>
                            <span class="maxpland-stat-value" id="stat-fans">-</span>
                            <span class="maxpland-stat-hint">They follow you, you don't</span>
                        </div>
                        <div class="maxpland-stat-card card-mutual" data-target-filter="mutual">
                            <span class="maxpland-stat-label">Mutuals</span>
                            <span class="maxpland-stat-value" id="stat-mutual">-</span>
                            <span class="maxpland-stat-hint">Follow each other</span>
                        </div>
                        <div class="maxpland-stat-card card-lost" data-target-filter="lost">
                            <span class="maxpland-stat-label">Recently Lost</span>
                            <span class="maxpland-stat-value" id="stat-lost">-</span>
                            <span class="maxpland-stat-hint">Unfollowed since last scan</span>
                        </div>
                        <div class="maxpland-stat-card card-ghost" data-target-filter="ghost">
                            <span class="maxpland-stat-label">👻 Ghost Accounts</span>
                            <span class="maxpland-stat-value" id="stat-ghost">-</span>
                            <span class="maxpland-stat-hint">No profile picture</span>
                        </div>
                    </div>

                    <!-- Category Toolbar -->
                    <div class="maxpland-toolbar">
                        <div class="maxpland-filter-group">
                            <button class="maxpland-pill-btn active" data-filter="not_following_back">
                                Not Following Back <span class="maxpland-pill-count" id="pill-count-not">-</span>
                            </button>
                            <button class="maxpland-pill-btn" data-filter="fans">
                                Fans <span class="maxpland-pill-count" id="pill-count-fans">-</span>
                            </button>
                            <button class="maxpland-pill-btn" data-filter="mutual">
                                Mutual <span class="maxpland-pill-count" id="pill-count-mutual">-</span>
                            </button>
                            <button class="maxpland-pill-btn" data-filter="lost">
                                Recently Lost <span class="maxpland-pill-count" id="pill-count-lost">-</span>
                            </button>
                            <button class="maxpland-pill-btn" data-filter="ghost">
                                👻 Ghost <span class="maxpland-pill-count" id="pill-count-ghost">-</span>
                            </button>
                            <button class="maxpland-pill-btn" data-filter="renamed">
                                ✏️ Renamed <span class="maxpland-pill-count" id="pill-count-renamed">-</span>
                            </button>
                            <button class="maxpland-pill-btn" data-filter="inactive">
                                ⏱️ Inactive <span class="maxpland-pill-count" id="pill-count-inactive">-</span>
                            </button>
                            <button class="maxpland-pill-btn" data-filter="whitelist">
                                ⭐ Whitelist <span class="maxpland-pill-count" id="pill-count-white">-</span>
                            </button>
                        </div>
                        <div class="maxpland-search-box">
                            ${ICONS.SEARCH}
                            <input type="text" placeholder="Search username or full name..." id="maxpland-user-search">
                        </div>
                    </div>

                    <!-- Sub-filters & Quick Toggles -->
                    <div class="maxpland-subfilters">
                        <span style="font-weight:600;color:var(--mp-text-muted);">Filters:</span>
                        <div class="maxpland-chip-toggle" id="toggle-filter-whitelist" title="Hide starred whitelist accounts">
                            ${ICONS.STAR} Hide Whitelist
                        </div>
                        <div class="maxpland-chip-toggle" id="toggle-filter-verified" title="Hide verified badge accounts">
                            ${ICONS.VERIFIED} Hide Verified
                        </div>
                        <div class="maxpland-chip-toggle" id="toggle-filter-private" title="Hide private accounts">
                            ${ICONS.LOCK} Hide Private
                        </div>
                        <div class="maxpland-chip-toggle" id="toggle-filter-noavatar" title="Hide accounts without avatar">
                            Hide No-Avatar
                        </div>
                        <div class="maxpland-chip-toggle" id="toggle-filter-following" title="Show only accounts we already follow (present in our Following list)">
                            Followed
                        </div>
                        <div class="maxpland-chip-toggle" id="toggle-filter-notfollowing" title="Show only accounts we have not followed yet">
                            Not followed yet
                        </div>
                    </div>
                    <div class="maxpland-export-actions">
                        <button class="maxpland-btn-secondary" id="maxpland-btn-copy-usernames" style="padding:4px 9px;font-size:11.5px;" title="Copy all visible usernames to clipboard">
                            ${ICONS.COPY} Copy Usernames
                        </button>
                        <button class="maxpland-btn-secondary" id="maxpland-btn-export-csv" style="padding:4px 9px;font-size:11.5px;">
                            ${ICONS.DOWNLOAD} CSV
                        </button>
                        <button class="maxpland-btn-secondary" id="maxpland-btn-export-json" style="padding:4px 9px;font-size:11.5px;">
                            JSON
                        </button>
                    </div>

                    <!-- Action Bar -->
                    <div class="maxpland-toolbar" style="margin-bottom:10px;">
                        <div style="display:flex;gap:8px;align-items:center;">
                            <button class="maxpland-btn-primary" id="maxpland-btn-scan-relationships">
                                ${ICONS.USERS} Scan Relationships
                            </button>
                            <button class="maxpland-btn-secondary" id="maxpland-btn-scan-inactive" title="Scan Following accounts for inactivity beyond threshold" style="padding:6px 10px;font-size:12px;">
                                ${ICONS.CLOCK} Inactive Radar
                            </button>
                            <div style="display:flex;gap:6px;" id="maxpland-whitelist-tools">
                                <button class="maxpland-btn-secondary" id="maxpland-btn-backup-whitelist" title="Export starred accounts whitelist as JSON file" style="padding:6px 10px;font-size:12px;">
                                    ${ICONS.BACKUP} Backup Whitelist
                                </button>
                                <button class="maxpland-btn-secondary" id="maxpland-btn-restore-whitelist" title="Import starred accounts from backup JSON file" style="padding:6px 10px;font-size:12px;">
                                    ${ICONS.RESTORE} Restore Whitelist
                                </button>
                                <input type="file" id="maxpland-whitelist-file-input" accept=".json" style="display:none;">
                            </div>
                        </div>
                        <span id="maxpland-scan-status-summary" style="font-size:12px;color:var(--mp-text-muted);font-variant-numeric:tabular-nums;"></span>
                    </div>

                    <!-- Floating Bulk Action Bar (Visible when users selected) -->
                    <div class="maxpland-bulk-bar" id="maxpland-bulk-bar">
                        <div class="maxpland-bulk-info">
                            <span id="maxpland-bulk-count-label">0 accounts selected</span>
                            <button type="button" class="maxpland-btn-secondary" id="maxpland-btn-select-all" style="padding:3px 8px;font-size:11px;">Select page</button>
                            <button type="button" class="maxpland-btn-secondary" id="maxpland-btn-deselect-all" style="padding:3px 8px;font-size:11px;">Deselect all</button>
                        </div>
                        <div class="maxpland-bulk-actions">
                            <button type="button" class="maxpland-btn-danger" id="maxpland-btn-batch-unfollow">
                                ${ICONS.UNFOLLOW} Unfollow Selected
                            </button>
                        </div>
                    </div>

                    <!-- User List -->
                    <div class="maxpland-user-list" id="maxpland-relationship-list">
                        <div style="padding: 48px; text-align: center; color: var(--mp-text-muted);">
                            Click <b>"Scan Relationships"</b> to compare your Following & Followers.
                        </div>
                    </div>
                </div>

                <!-- TAB 2: Account Health Dashboard -->
                <div class="maxpland-tab-content" id="tab-health">
                    <div style="margin-bottom:14px;">
                        <h2 style="font-size:15px;font-weight:700;margin:0 0 4px;">Account Health & Relationship Analytics</h2>
                        <p style="font-size:12px;color:var(--mp-text-muted);margin:0;">Computed from local snapshot database with zero network overhead.</p>
                    </div>

                    <div class="maxpland-health-hero">
                        <div class="maxpland-health-card">
                            <div class="maxpland-health-title">Follower Ratio</div>
                            <div class="maxpland-health-val" id="health-ratio-val">-</div>
                            <div id="health-ratio-badge" class="maxpland-health-badge" style="background:var(--mp-cyan-bg);color:var(--mp-cyan);">Awaiting scan</div>
                        </div>
                        <div class="maxpland-health-card">
                            <div class="maxpland-health-title">Mutual Rate</div>
                            <div class="maxpland-health-val" id="health-mutual-val">-</div>
                            <div id="health-mutual-badge" class="maxpland-health-badge" style="background:var(--mp-emerald-bg);color:var(--mp-emerald);">Reciprocal follow rate</div>
                        </div>
                        <div class="maxpland-health-card">
                            <div class="maxpland-health-title">Not Following Back</div>
                            <div class="maxpland-health-val" id="health-notback-val">-</div>
                            <div id="health-notback-badge" class="maxpland-health-badge" style="background:var(--mp-rose-bg);color:var(--mp-rose);">Unrequited follows</div>
                        </div>
                        <div class="maxpland-health-card">
                            <div class="maxpland-health-title">Ghost & Inactive Impact</div>
                            <div class="maxpland-health-val" id="health-ghost-val">-</div>
                            <div id="health-ghost-badge" class="maxpland-health-badge" style="background:var(--mp-amber-bg);color:var(--mp-amber);">No profile picture</div>
                        </div>
                    </div>

                    <!-- Historical Sparkline Chart -->
                    <div class="maxpland-chart-box">
                        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
                            <div>
                                <span style="font-size:13px;font-weight:700;">📈 Follower Count Trend (Historical Snapshots)</span>
                                <div style="font-size:11.5px;color:var(--mp-text-muted);">Archived locally in IndexedDB after each scan</div>
                            </div>
                            <span id="health-drift-delta" style="font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;"></span>
                        </div>
                        <div id="health-chart-container" style="min-height:130px;display:grid;place-items:center;color:var(--mp-text-muted);font-size:12px;">
                            Run at least 1 relationship scan to begin plotting history trend.
                        </div>
                    </div>

                    <!-- Health Advice Box -->
                    <div style="background:var(--mp-bg-card);border:1px solid var(--mp-border-card);border-radius:8px;padding:14px 16px;">
                        <span style="font-size:13px;font-weight:700;color:#38bdf8;">💡 Strategic Account Health Advice:</span>
                        <div id="health-advice-text" style="font-size:12px;color:var(--mp-text-secondary);margin-top:6px;line-height:1.5;">
                            Run a relationship scan in the first tab to receive tailored account structure insights.
                        </div>
                    </div>
                </div>

                <!-- TAB 3: Features Control Panel (Switches) -->
                <div class="maxpland-tab-content" id="tab-features">
                    <div style="margin-bottom:16px;">
                        <h2 style="font-size:15px;font-weight:700;margin:0 0 4px;">Feature Controller & Master Toggles</h2>
                        <p style="font-size:12px;color:var(--mp-text-muted);margin:0;">Enable or disable capabilities on demand with instant persistence.</p>
                    </div>

                    <div class="maxpland-feature-card">
                        <div class="maxpland-feature-info">
                            <div class="maxpland-feature-title">
                                ${ICONS.EYE} Stealth Story Viewer (Anonymous)
                                <span class="maxpland-badge-emerald">Recommended</span>
                            </div>
                            <div class="maxpland-feature-desc">
                                Blocks seen telemetry beacons to Instagram servers 100%. View any story completely anonymously without appearing on the viewer list.
                            </div>
                        </div>
                        <label class="maxpland-switch" title="Toggle Stealth Story Viewer">
                            <input type="checkbox" id="pref-switch-stealth-story">
                            <span class="maxpland-slider"></span>
                        </label>
                    </div>

                    <div class="maxpland-feature-card">
                        <div class="maxpland-feature-info">
                            <div class="maxpland-feature-title">
                                ${ICONS.SHIELD} Clean Feed Mode (Ad & Suggestion Blocker)
                            </div>
                            <div class="maxpland-feature-desc">
                                Automatically hides sponsored ads and suggested posts from your home feed so you only see content from accounts you actually follow.
                            </div>
                        </div>
                        <label class="maxpland-switch" title="Toggle Clean Feed Mode">
                            <input type="checkbox" id="pref-switch-clean-feed">
                            <span class="maxpland-slider"></span>
                        </label>
                    </div>

                    <div class="maxpland-feature-card">
                        <div class="maxpland-feature-info">
                            <div class="maxpland-feature-title">
                                ${ICONS.DOWNLOAD} In-Feed Media Download Buttons
                            </div>
                            <div class="maxpland-feature-desc">
                                Displays direct full-resolution download buttons next to the bookmark icon on every feed post.
                            </div>
                        </div>
                        <label class="maxpland-switch" title="Toggle In-Feed Download Buttons">
                            <input type="checkbox" id="pref-switch-feed-download">
                            <span class="maxpland-slider"></span>
                        </label>
                    </div>

                    <div class="maxpland-feature-card">
                        <div class="maxpland-feature-info">
                            <div class="maxpland-feature-title">
                                ${ICONS.THUMBNAIL} Story Toolbar & Downloader
                            </div>
                            <div class="maxpland-feature-desc">
                                Floating utility toolbar in the top-right corner of story views for instant video, cover photo downloads, and stealth toggle.
                            </div>
                        </div>
                        <label class="maxpland-switch" title="Toggle Story Toolbar">
                            <input type="checkbox" id="pref-switch-story-toolbar">
                            <span class="maxpland-slider"></span>
                        </label>
                    </div>
                </div>

                <!-- TAB 4: Settings & Configuration -->
                <div class="maxpland-tab-content" id="tab-settings">
                    <div style="margin-bottom:16px;">
                        <h2 style="font-size:15px;font-weight:700;margin:0 0 4px;">Settings & Safety Configuration</h2>
                        <p style="font-size:12px;color:var(--mp-text-muted);margin:0;">Configure detection thresholds, safety delay parameters, and backups.</p>
                    </div>

                    <div class="maxpland-settings-group">
                        <h3 class="maxpland-settings-group-title">${ICONS.CLOCK} Inactive Following Radar Threshold</h3>
                        <div class="maxpland-settings-row">
                            <div>
                                <div style="font-weight:600;font-size:13px;">Inactivity Duration Threshold</div>
                                <div style="font-size:11.5px;color:var(--mp-text-muted);">Calculated from the timestamp of each user's latest feed post</div>
                            </div>
                            <select id="pref-setting-inactive-threshold" class="maxpland-select">
                                <option value="90">90 Days (3 Months)</option>
                                <option value="180">180 Days (6 Months - Recommended)</option>
                                <option value="365">365 Days (1 Year)</option>
                            </select>
                        </div>
                    </div>

                    <div class="maxpland-settings-group">
                        <h3 class="maxpland-settings-group-title">🛡️ Anti-Detection Guard & Pacing</h3>
                        <div class="maxpland-settings-row">
                            <div>
                                <div style="font-weight:600;font-size:13px;">Unfollow Jitter Delay</div>
                                <div style="font-size:11.5px;color:var(--mp-text-muted);">Randomized pacing interval between unfollow actions to emulate natural human behavior and prevent rate limits.</div>
                            </div>
                            <span style="font-size:12px;font-weight:600;color:var(--mp-emerald);">${APP_CONFIG.UNFOLLOW_DELAY_MIN.toLocaleString()} - ${APP_CONFIG.UNFOLLOW_DELAY_MAX.toLocaleString()} ms (Randomized)</span>
                        </div>
                        <div class="maxpland-settings-row">
                            <div>
                                <div style="font-weight:600;font-size:13px;">Scan Speed</div>
                                <div style="font-size:11.5px;color:var(--mp-text-muted);">A Safe 6–12 s/page · B ~2x faster 3–6 s/page · C fastest 1.5–3 s/page — all modes serial with jittered delays + breathing rests</div>
                            </div>
                            <select id="pref-setting-scan-speed" class="maxpland-select" aria-label="Scan speed" title="A: Safe (6-12 s/page + rests, recommended) · B: ~2x faster (3-6 s/page) · C: Fastest (1.5-3 s/page, higher risk)">
                                <option value="A" selected>A — Safe (recommended)</option>
                                <option value="B">B — Fast ~2x</option>
                                <option value="C">C — Fastest ~4x (high risk)</option>
                            </select>
                        </div>
                        <div class="maxpland-settings-row">
                            <div>
                                <div style="font-weight:600;font-size:13px;">Following / Followers Page Limit</div>
                                <div style="font-size:11.5px;color:var(--mp-text-muted);">Safety ceiling per scan session to prevent session throttling.</div>
                            </div>
                            <span style="font-size:12px;font-weight:600;color:var(--mp-blue);">60 pages (3,000 users) / 250 pages (12,500 users)</span>
                        </div>
                    </div>

                    <div class="maxpland-settings-group">
                        <h3 class="maxpland-settings-group-title">💾 Data Vault & Cache Management</h3>
                        <div class="maxpland-settings-row">
                            <div>
                                <div style="font-weight:600;font-size:13px;">Backup & Restore Starred Whitelist</div>
                                <div style="font-size:11.5px;color:var(--mp-text-muted);">Export or import protected accounts whitelist as JSON file.</div>
                            </div>
                            <div style="display:flex;gap:6px;">
                                <button type="button" class="maxpland-btn-secondary" id="setting-btn-backup-whitelist" style="padding:5px 10px;font-size:12px;">${ICONS.BACKUP} Backup Whitelist</button>
                                <button type="button" class="maxpland-btn-secondary" id="setting-btn-restore-whitelist" style="padding:5px 10px;font-size:12px;">${ICONS.RESTORE} Restore Whitelist</button>
                            </div>
                        </div>
                        <div class="maxpland-settings-row">
                            <div>
                                <div style="font-weight:600;font-size:13px;">Activity Radar Cache</div>
                                <div style="font-size:11.5px;color:var(--mp-text-muted);">Purge stored last-post timestamps from IndexedDB to force fresh scanning.</div>
                            </div>
                            <button type="button" class="maxpland-btn-danger" id="setting-btn-clear-cache" style="padding:5px 10px;font-size:12px;">Purge Radar Cache</button>
                        </div>
                    </div>
                </div>

                <!-- TAB 5: Media Vault -->
                <div class="maxpland-tab-content" id="tab-vault">
                    <div style="margin-bottom:12px;">
                        <h2 style="font-size:15px;font-weight:700;margin:0 0 4px;">Direct Media Downloader & Vault</h2>
                        <p style="font-size:12px;color:var(--mp-text-muted);margin:0;">Paste post or Reel links to download full-resolution assets or browse historical vault.</p>
                    </div>

                    <textarea id="maxpland-media-queue-input" placeholder="Paste post or Reel URLs (one per line):&#10;https://www.instagram.com/p/ABC123xyz/&#10;https://www.instagram.com/reel/XYZ789abc/" style="width:100%;min-height:100px;resize:vertical;background:var(--mp-bg-panel);border:1px solid var(--mp-border-card);color:var(--mp-text-primary);border-radius:6px;padding:10px;font-family:ui-monospace,monospace;font-size:12px;"></textarea>
                    
                    <div class="maxpland-toolbar" style="margin-top:10px;">
                        <div style="display:flex;gap:8px;flex-wrap:wrap;">
                            <button class="maxpland-btn-primary" id="maxpland-btn-run-media-queue">${ICONS.DOWNLOAD} Download New</button>
                            <button class="maxpland-btn-secondary" id="maxpland-btn-run-media-all">Re-download All</button>
                            <button class="maxpland-btn-secondary" id="maxpland-btn-refresh-vault">Refresh Vault</button>
                        </div>
                        <span id="maxpland-media-queue-status" style="font-size:12px;color:var(--mp-text-secondary);font-variant-numeric:tabular-nums;"></span>
                    </div>
                    <div class="maxpland-user-list" id="maxpland-vault-list">
                        <div style="padding:48px;text-align:center;color:var(--mp-text-muted);">No download history yet</div>
                    </div>
                </div>

                <!-- Toast Element -->
                <div class="maxpland-toast" id="maxpland-toast">Notification</div>
            </div>

            <div class="maxpland-footer">
                <span>MaxPland v3.0.0 · Clean Minimal Precision (Anti-Slop)</span>
                <span>Toggle <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></span>
            </div>
        `;

        document.body.appendChild(overlay);
        document.body.appendChild(modal);

        bindUIEvents(triggerBtn, overlay, modal);
    }

    function renderViewerPanel(anchor, viewers) {
        let panel = document.getElementById('maxpland-viewer-panel');
        if (!panel) {
            panel = document.createElement('div');
            panel.id = 'maxpland-viewer-panel';
            panel.style.cssText = 'position:fixed;z-index:99999;min-width:240px;max-width:300px;max-height:320px;overflow:auto;background:var(--mp-bg-panel,#14161a);border:1px solid var(--mp-border-card,#262a30);border-radius:10px;padding:10px 12px;color:#e6edf3;font-size:12.5px;box-shadow:0 8px 30px rgba(0,0,0,.45);';
            document.body.appendChild(panel);
            document.addEventListener('click', e => {
                const p = document.getElementById('maxpland-viewer-panel');
                if (p && !p.contains(e.target) && !e.target.closest?.('.maxpland-story-btn')) p.remove();
            });
        }
        const rect = anchor.getBoundingClientRect();
        panel.style.left = Math.max(8, Math.min(window.innerWidth - 308, rect.left)) + 'px';
        panel.style.top = Math.max(8, rect.top - 8) + 'px';
        panel.style.transform = 'translateY(-100%)';
        if (!viewers.length) {
            panel.innerHTML = '<div style="color:var(--mp-text-muted,#8b949e);">No viewer data (story may not be yours, expired, or private).</div>';
            return;
        }
        const rows = viewers.slice(0, 100).map(u => {
            // API response is a trust boundary: strip anything outside IG's username charset.
            const name = String(u.username || u.pk || 'unknown').replace(/[^A-Za-z0-9._-]/g, '').slice(0, 40) || 'unknown';
            const when = u.timestamp ? new Date(u.timestamp * 1000).toLocaleString('en-GB', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' }) : '';
            return `<div style="display:flex;justify-content:space-between;gap:8px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.05);"><span>@${name}</span><span style="color:var(--mp-text-muted,#8b949e);white-space:nowrap;">${when}</span></div>`;
        }).join('');
        panel.innerHTML = `<div style="font-weight:600;margin-bottom:6px;">👁 ${viewers.length.toLocaleString()} viewer${viewers.length === 1 ? '' : 's'}</div>${rows}`;
    }

    function showToast(message, duration = 2500) {
        const toast = document.getElementById('maxpland-toast');
        if (!toast) return;
        toast.textContent = message;
        toast.classList.add('show');
        setTimeout(() => toast.classList.remove('show'), duration);
    }

    /* ==========================================================================
       6. UI EVENTS & MODULE HANDLERS
       ========================================================================== */

    function bindUIEvents(triggerBtn, overlay, modal) {
        const openModal = async () => {
            overlay.style.display = 'block';
            modal.style.display = 'grid';
            triggerBtn.style.visibility = 'hidden';
            document.getElementById('maxpland-close-btn').focus();

            // Resolve and show active account
            const user = await IgBridge.resolveCurrentUser();
            const badge = document.getElementById('maxpland-account-badge');
            if (user && user.username) {
                badge.textContent = `@${user.username}`;
                STATE.currentUser = user;
            } else if (user && user.id) {
                badge.textContent = `UID: ${user.id}`;
                STATE.currentUser = user;
            } else {
                badge.textContent = 'Account not found (Log in)';
            }

            syncFeaturesTab();
            syncSettingsTab();
            if (STATE.activeTab === 'health') renderHealthDashboard();
        };

        const closeModal = () => {
            if (STATE.isScanning || STATE.isUnfollowing || STATE.isScanningInactive) {
                if (!confirm('A task is currently in progress. Do you want to close?')) return;
            }
            overlay.style.display = 'none';
            modal.style.display = 'none';
            triggerBtn.style.visibility = 'visible';
            triggerBtn.focus();
        };

        // Expose open/close functions globally for feed buttons & commands
        window.openMaxPlandStudio = openModal;
        window.closeMaxPlandStudio = closeModal;

        // DRAGGABLE LOGIC FOR LAUNCHER BUTTON
        let isPointerDown = false;
        let isDragging = false;
        let startX = 0, startY = 0;
        let startLeft = 0, startTop = 0;

        triggerBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            if (!isDragging) {
                openModal();
            }
        });

        triggerBtn.addEventListener('pointerdown', (e) => {
            isPointerDown = true;
            isDragging = false;
            startX = e.clientX;
            startY = e.clientY;
            const rect = triggerBtn.getBoundingClientRect();
            startLeft = rect.left;
            startTop = rect.top;
            triggerBtn.setPointerCapture(e.pointerId);
        });

        triggerBtn.addEventListener('pointermove', (e) => {
            if (!isPointerDown) return;
            const dist = Math.hypot(e.clientX - startX, e.clientY - startY);
            if (dist > 4) {
                isDragging = true;
                const newLeft = Math.min(window.innerWidth - 48, Math.max(8, startLeft + (e.clientX - startX)));
                const newTop = Math.min(window.innerHeight - 48, Math.max(8, startTop + (e.clientY - startY)));
                triggerBtn.style.left = `${newLeft}px`;
                triggerBtn.style.top = `${newTop}px`;
                triggerBtn.style.right = 'auto';
                triggerBtn.style.bottom = 'auto';
            }
        });

        const handlePointerEnd = (e) => {
            if (!isPointerDown) return;
            isPointerDown = false;
            try { triggerBtn.releasePointerCapture(e.pointerId); } catch (_) {}
            if (isDragging) {
                const rect = triggerBtn.getBoundingClientRect();
                localStorage.setItem('maxpland_launcher_pos', JSON.stringify({ x: Math.round(rect.left), y: Math.round(rect.top) }));
            } else {
                openModal();
            }
        };

        triggerBtn.addEventListener('pointerup', handlePointerEnd);
        triggerBtn.addEventListener('pointercancel', handlePointerEnd);

        modal.addEventListener('keydown', e => {
            if (e.key === 'Escape') { e.preventDefault(); closeModal(); }
        });

        document.addEventListener('keydown', e => {
            if (e.altKey && e.shiftKey && e.code === 'KeyM' && !e.repeat) {
                e.preventDefault();
                if (modal.style.display === 'none') openModal(); else closeModal();
            }
        });
        overlay.addEventListener('click', closeModal);
        document.getElementById('maxpland-close-btn').addEventListener('click', closeModal);

        // Tab Switching
        const tabs = modal.querySelectorAll('.maxpland-tab-btn');
        tabs.forEach(tab => {
            tab.addEventListener('click', () => {
                tabs.forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); });
                modal.querySelectorAll('.maxpland-tab-content').forEach(c => c.classList.remove('active'));

                tab.classList.add('active');
                tab.setAttribute('aria-selected', 'true');
                const target = document.getElementById(`tab-${tab.dataset.tab}`);
                if (target) target.classList.add('active');
                STATE.activeTab = tab.dataset.tab;
                if (tab.dataset.tab === 'vault') renderMediaVault();
                if (tab.dataset.tab === 'health') renderHealthDashboard();
                if (tab.dataset.tab === 'features') syncFeaturesTab();
                if (tab.dataset.tab === 'settings') syncSettingsTab();
            });
        });

        // Filter Switching (Pills)
        const filterBtns = document.getElementById('tab-relationship').querySelectorAll('.maxpland-pill-btn');
        filterBtns.forEach(btn => {
            btn.addEventListener('click', () => {
                filterBtns.forEach(b => b.classList.remove('active'));
                btn.classList.add('active');
                STATE.relationshipFilter = btn.dataset.filter;
                STATE.selectedIds.clear();
                updateBulkActionBar();
                renderRelationshipList();
            });
        });

        // Stat Card Quick Clicks
        modal.querySelectorAll('.maxpland-stat-card').forEach(card => {
            card.addEventListener('click', () => {
                const target = card.dataset.targetFilter;
                const pill = modal.querySelector(`.maxpland-pill-btn[data-filter="${target}"]`);
                if (pill) pill.click();
            });
        });

        // Sub-filter Chips Toggles
        const toggleWhitelist = document.getElementById('toggle-filter-whitelist');
        toggleWhitelist.addEventListener('click', () => {
            STATE.subFilters.excludeWhitelist = !STATE.subFilters.excludeWhitelist;
            toggleWhitelist.classList.toggle('active', STATE.subFilters.excludeWhitelist);
            renderRelationshipList();
        });

        const toggleVerified = document.getElementById('toggle-filter-verified');
        toggleVerified.addEventListener('click', () => {
            STATE.subFilters.excludeVerified = !STATE.subFilters.excludeVerified;
            toggleVerified.classList.toggle('active', STATE.subFilters.excludeVerified);
            renderRelationshipList();
        });

        const togglePrivate = document.getElementById('toggle-filter-private');
        togglePrivate.addEventListener('click', () => {
            STATE.subFilters.excludePrivate = !STATE.subFilters.excludePrivate;
            togglePrivate.classList.toggle('active', STATE.subFilters.excludePrivate);
            renderRelationshipList();
        });

        const toggleNoAvatar = document.getElementById('toggle-filter-noavatar');
        toggleNoAvatar.addEventListener('click', () => {
            STATE.subFilters.excludeNoAvatar = !STATE.subFilters.excludeNoAvatar;
            toggleNoAvatar.classList.toggle('active', STATE.subFilters.excludeNoAvatar);
            renderRelationshipList();
        });

        document.getElementById('toggle-filter-following').addEventListener('click', () => setFollowStateChip('onlyFollowing'));

        document.getElementById('toggle-filter-notfollowing').addEventListener('click', () => setFollowStateChip('onlyNotFollowed'));

        // Search Input
        document.getElementById('maxpland-user-search').addEventListener('input', (e) => {
            STATE.searchQuery = e.target.value.trim().toLowerCase();
            renderRelationshipList();
        });

        // Scan Actions
        document.getElementById('maxpland-btn-scan-relationships').addEventListener('click', runRelationshipScan);
        document.getElementById('maxpland-btn-stop-scan').addEventListener('click', () => {
            STATE.stopScanFlag = true;
            STATE.scanController?.abort();
            STATE.stopInactiveScanFlag = true;
            STATE.inactiveController?.abort();
            STATE.stopUnfollowFlag = true;
            document.getElementById('maxpland-scan-phase').textContent = 'Stopping scan as requested...';
        });
        document.getElementById('maxpland-btn-pause-scan').addEventListener('click', () => {
            STATE.scanPaused = !STATE.scanPaused;
            const pb = document.getElementById('maxpland-btn-pause-scan');
            pb.textContent = STATE.scanPaused ? '▶ Resume' : '⏸ Pause';
            document.getElementById('maxpland-scan-phase').textContent = STATE.scanPaused ? 'Scan paused — progress saved page by page' : 'Fetching relationship pages...';
        });

        // Whitelist Backup & Restore
        document.getElementById('maxpland-btn-backup-whitelist').addEventListener('click', exportWhitelistBackup);
        const restoreInput = document.getElementById('maxpland-whitelist-file-input');
        document.getElementById('maxpland-btn-restore-whitelist').addEventListener('click', () => restoreInput.click());
        restoreInput.addEventListener('change', handleWhitelistRestoreFile);

        // Bulk Selection Actions
        document.getElementById('maxpland-btn-select-all').addEventListener('click', selectAllVisible);
        document.getElementById('maxpland-btn-deselect-all').addEventListener('click', () => {
            STATE.selectedIds.clear();
            updateBulkActionBar();
            renderRelationshipList();
        });
        document.getElementById('maxpland-btn-batch-unfollow').addEventListener('click', runBatchUnfollow);

        // Export Actions
        document.getElementById('maxpland-btn-copy-usernames').addEventListener('click', copyVisibleUsernames);
        document.getElementById('maxpland-btn-export-csv').addEventListener('click', exportRelationshipCSV);
        document.getElementById('maxpland-btn-export-json').addEventListener('click', exportRelationshipJSON);

        // Media Downloader Actions
        document.getElementById('maxpland-btn-run-media-queue').addEventListener('click', () => runMediaQueue({ skipExisting: true }));
        document.getElementById('maxpland-btn-run-media-all').addEventListener('click', () => runMediaQueue({ skipExisting: false }));
        document.getElementById('maxpland-btn-refresh-vault').addEventListener('click', renderMediaVault);

        // Feature Switches (Master Toggles)
        const swStealth = document.getElementById('pref-switch-stealth-story');
        if (swStealth) {
            swStealth.addEventListener('change', (e) => {
                STATE.prefs.stealthStory = e.target.checked;
                savePrefs(STATE.prefs);
                showToast(e.target.checked ? 'Stealth Story Viewer enabled (Seen blocked)' : 'Stealth Story Viewer disabled');
            });
        }
        const swCleanFeed = document.getElementById('pref-switch-clean-feed');
        if (swCleanFeed) {
            swCleanFeed.addEventListener('change', (e) => {
                STATE.prefs.cleanFeed = e.target.checked;
                savePrefs(STATE.prefs);
                applyCleanFeedMode();
                showToast(e.target.checked ? 'Clean Feed Mode enabled (Ads hidden)' : 'Clean Feed Mode disabled');
            });
        }
        const swFeedDl = document.getElementById('pref-switch-feed-download');
        if (swFeedDl) {
            swFeedDl.addEventListener('change', (e) => {
                STATE.prefs.quickDownloadFeed = e.target.checked;
                savePrefs(STATE.prefs);
                showToast(e.target.checked ? 'In-feed download buttons enabled' : 'In-feed download buttons disabled');
            });
        }
        const swStoryTb = document.getElementById('pref-switch-story-toolbar');
        if (swStoryTb) {
            swStoryTb.addEventListener('change', (e) => {
                STATE.prefs.quickDownloadStory = e.target.checked;
                savePrefs(STATE.prefs);
                if (!e.target.checked) {
                    const bar = document.getElementById('maxpland-story-bar');
                    if (bar) bar.remove();
                }
                showToast(e.target.checked ? 'Story toolbar enabled' : 'Story toolbar disabled');
            });
        }

        // Settings Threshold & Actions
        const selInactive = document.getElementById('pref-setting-inactive-threshold');
        if (selInactive) {
            selInactive.addEventListener('change', (e) => {
                STATE.prefs.inactiveThresholdDays = Number(e.target.value);
                savePrefs(STATE.prefs);
                showToast(`Inactive threshold updated to ${e.target.value} days`);
            });
        }
        const selSpeed = document.getElementById('pref-setting-scan-speed');
        if (selSpeed) {
            selSpeed.addEventListener('change', (e) => {
                STATE.prefs.scanSpeed = e.target.value;
                savePrefs(STATE.prefs);
                showToast(`Scan speed set to mode ${e.target.value}`);
            });
        }
        const btnSettingBackup = document.getElementById('setting-btn-backup-whitelist');
        if (btnSettingBackup) btnSettingBackup.addEventListener('click', exportWhitelistBackup);
        const btnSettingRestore = document.getElementById('setting-btn-restore-whitelist');
        if (btnSettingRestore) btnSettingRestore.addEventListener('click', () => restoreInput.click());
        const btnClearCache = document.getElementById('setting-btn-clear-cache');
        if (btnClearCache) {
            btnClearCache.addEventListener('click', async () => {
                if (confirm('Do you want to purge all cached account activity timestamps?')) {
                    await MaxPlandVault.clearActivityCache();
                    STATE.inactiveFollowing = [];
                    const countEl = document.getElementById('pill-count-inactive');
                    if (countEl) countEl.textContent = '-';
                    showToast('Activity radar cache purged successfully');
                }
            });
        }

        // Inactive Radar Scan Trigger
        const btnScanInactive = document.getElementById('maxpland-btn-scan-inactive');
        if (btnScanInactive) btnScanInactive.addEventListener('click', runInactiveScan);

        // ponytail: Native Event Delegation for Relationship List (Zero Memory Leaks & 60fps Search)
        const relList = document.getElementById('maxpland-relationship-list');
        if (relList) {
            relList.addEventListener('change', (e) => {
                const box = e.target.closest('.user-select-checkbox');
                if (!box) return;
                const uid = box.dataset.id;
                if (box.checked) STATE.selectedIds.add(uid);
                else STATE.selectedIds.delete(uid);
                updateBulkActionBar();
            });

            relList.addEventListener('click', async (e) => {
                // 1. Star Toggle
                const star = e.target.closest('.maxpland-star-btn');
                if (star && !star.disabled) {
                    const uid = star.dataset.id;
                    const pool = getFilteredUsers();
                    const user = pool.find(u => String(u.id || u.pk || u.pk_id) === uid) || STATE.whitelist.get(uid);
                    if (user) {
                        star.disabled = true;
                        try {
                            const added = await MaxPlandVault.toggleWhitelist(user);
                            STATE.whitelist = await MaxPlandVault.getWhitelist();
                            const whiteCountEl = document.getElementById('pill-count-white');
                            if (whiteCountEl) whiteCountEl.textContent = STATE.whitelist.size.toLocaleString();
                            if (added) STATE.selectedIds.delete(uid);
                            showToast(added ? `Added @${user.username} to Whitelist` : `Removed @${user.username} from Whitelist`);
                            updateBulkActionBar();
                            renderRelationshipList();
                        } catch (err) { alert(err.message || String(err)); }
                        finally { star.disabled = false; }
                    }
                    return;
                }

                // 2. Single Unfollow
                const btn = e.target.closest('.maxpland-row-unfollow-btn');
                if (btn && !btn.disabled) {
                    if (STATE.isScanning || STATE.isUnfollowing || STATE.isScanningInactive || STATE.scanIncomplete) return;
                    const uid = btn.dataset.id;
                    const uname = btn.dataset.user;
                    if (!confirm(`Are you sure you want to unfollow @${uname}?`)) return;

                    STATE.isUnfollowing = true;
                    btn.disabled = true;
                    btn.textContent = 'Unfollowing...';
                    try {
                        await IgBridge.unfollowUser(uid);
                        showToast(`Unfollowed @${uname} successfully`);
                        applyUnfollowResult(uid, uname);
                        STATE.selectedIds.delete(uid);
                        const notBackEl = document.getElementById('stat-not-following-back');
                        if (notBackEl) notBackEl.textContent = STATE.notFollowingBack.length.toLocaleString();
                        const pillNotEl = document.getElementById('pill-count-not');
                        if (pillNotEl) pillNotEl.textContent = STATE.notFollowingBack.length.toLocaleString();
                        updateBulkActionBar();
                        renderRelationshipList();
                    } catch (err) {
                        alert(`Unfollow failed: ${err.message || err}`);
                        btn.disabled = false;
                        btn.innerHTML = `${ICONS.UNFOLLOW} Unfollow`;
                    } finally {
                        STATE.isUnfollowing = false;
                    }
                    return;
                }
            });
        }
    }

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function formatTime(seconds) {
        const m = Math.floor(seconds / 60).toString().padStart(2, '0');
        const s = (seconds % 60).toString().padStart(2, '0');
        return `${m}:${s}`;
    }

    function escapeHtml(value) {
        return String(value ?? '').replace(/[&<>"']/g, ch => ({
            '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
        })[ch]);
    }

    function csvCell(value) {
        let s = String(value ?? '');
        if (/^[\s\x00-\x1f]*[=+\-@]/.test(s)) s = "'" + s;
        return `"${s.replace(/"/g, '""')}"`;
    }

    function isProtectedUser(uid, username = '') {
        const id = String(uid);
        const name = String(username || '').toLowerCase();
        if (!name) return STATE.whitelist.has(id);
        if (STATE.whitelistByNameRef !== STATE.whitelist) { // rebuild once per whitelist instance, not per row
            STATE.whitelistByNameRef = STATE.whitelist;
            STATE.whitelistByName = new Map([...STATE.whitelist.values()]
                .filter(w => w.username)
                .map(w => [String(w.username).toLowerCase(), w]));
        }
        return STATE.whitelistByName.has(name) || STATE.whitelist.has(id);
    }

    function hasNoAvatar(user) {
        if (typeof IgRelationship !== 'undefined' && typeof IgRelationship.hasNoAvatar === 'function') {
            return IgRelationship.hasNoAvatar(user);
        }
        if (!user || !user.profile_pic_url) return true;
        if (user.has_anonymous_profile_picture === true) return true;
        // ponytail: patterns inlined at this single live site (config copy removed — identical values); hoist back to config when a second live site appears
        const patterns = [
            '44884218_345707102882519_2446069589734326272_n',
            '464760996_1254146839119862_3605321457742435801_n'
        ];
        return patterns.some(p => typeof user.profile_pic_url === 'string' && user.profile_pic_url.includes(p));
    }

    function extractShortcodesFromText(text) {
        const raw = String(text || '');
        const codes = new Set();
        const urlRx = /(?:https?:\/\/)?(?:www\.)?instagram\.com\/(?:p|reel|reels|tv)\/([A-Za-z0-9_-]+)/gi;
        let match;
        while ((match = urlRx.exec(raw))) codes.add(match[1]);
        raw.split(/\r?\n/).forEach(line => {
            const clean = line.trim().replace(/^["']+|["']+$/g, '');
            if (/^[A-Za-z0-9_-]{5,}$/.test(clean)) codes.add(clean);
        });
        return [...codes];
    }

    function shortcodeFromArticle(article) {
        const links = [...article.querySelectorAll('a[href*="/p/"],a[href*="/reel/"],a[href*="/tv/"]')];
        for (const a of links) {
            const m = (a.href || a.getAttribute('href') || '').match(/instagram\.com\/(?:p|reel|reels|tv)\/([A-Za-z0-9_-]+)/i);
            if (m) return m[1];
        }
        const m = location.href.match(/instagram\.com\/(?:p|reel|reels|tv)\/([A-Za-z0-9_-]+)/i);
        return m ? m[1] : null;
    }

    function safeFilename(value) {
        return String(value || 'instagram')
            .replace(/[<>:"/\\|?*\x00-\x1F]/g, '_')
            .replace(/\s+/g, '_')
            .replace(/_+/g, '_')
            .slice(0, 150);
    }

    function extensionFromUrl(url, fallback) {
        try {
            const ext = new URL(url).pathname.match(/\.([a-zA-Z0-9]{2,5})$/)?.[1]?.toLowerCase();
            if (ext && ['jpg','jpeg','png','webp','mp4','m4a','mp3','aac','webm'].includes(ext)) return ext;
        } catch (_) {}
        return fallback;
    }

    function gmDownload(url, name) {
        return new Promise((resolve, reject) => {
            GM_download({
                url,
                name,
                saveAs: false,
                timeout: 120000,
                ontimeout: () => reject(new Error('Download timed out')),
                onabort: () => reject(new Error('Download aborted')),
                onload: () => resolve(name),
                onerror: err => reject(err)
            });
        });
    }

    function downloadBlob(blob, filename) {
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
        a.remove();
        setTimeout(() => URL.revokeObjectURL(url), 30000);
        return filename;
    }

    /* ==========================================================================
       7. RELATIONSHIP SCANNER LOGIC
       ========================================================================== */

    async function runRelationshipScan() {
        if (STATE.isScanning || STATE.isUnfollowing || STATE.isScanningInactive) return;
        if (STATE.prefs?.scanSpeed === 'C'
            && !confirm('⚠️ Mode C pages at 1.5-3 s (fastest tier, still serial + periodic rests)\nStill higher detection risk than A/B — prefer off-peak hours\nProceed with the high-speed scan?')) return;
        STATE.isScanning = true;
        STATE.stopScanFlag = false;
        STATE.scanController = new AbortController();
        STATE.scanStartTime = Date.now();
        clearTimeout(STATE.progressHideTimer);
        const el = id => document.getElementById(id);
        const btn = el('maxpland-btn-scan-relationships');
        const progress = el('maxpland-global-progress');
        const phase = el('maxpland-scan-phase');
        const notice = el('maxpland-scan-notice');
        const summary = el('maxpland-scan-status-summary');
        STATE.scanIncomplete = true;
        for (const key of ['followers','following','notFollowingBack','fans','mutual','lostFollowers','ghostFollowers','renamed']) STATE[key] = [];
        STATE.selectedIds.clear();
        updateBulkActionBar();
        for (const id of ['stat-not-following-back','stat-fans','stat-mutual','stat-lost','stat-ghost','pill-count-not','pill-count-fans','pill-count-mutual','pill-count-lost','pill-count-ghost','pill-count-renamed']) {
            if (el(id)) el(id).textContent = '-';
        }
        renderRelationshipList();
        const timer = setInterval(() => { el('maxpland-scan-stat-timer').textContent = `⏱️ ${formatTime(Math.floor((Date.now() - STATE.scanStartTime) / 1000))}`; }, 1000);
        try {
            btn.disabled = true;
            progress.style.display = 'block';
            STATE.scanPaused = false;
            const pauseBtn = document.getElementById('maxpland-btn-pause-scan');
            pauseBtn.style.display = '';
            pauseBtn.textContent = '⏸ Pause';
            phase.textContent = 'Verifying account...';
            notice.style.display = 'none';
            summary.textContent = '';
            el('maxpland-progress-fill').style.width = '0%';
            el('maxpland-scan-stat-count').textContent = 'Users: 0';
            el('maxpland-scan-stat-page').textContent = 'Pages: 0';
            el('maxpland-scan-stat-timer').textContent = '⏱️ 00:00';
            const currentUser = await IgBridge.resolveCurrentUser();
            IgBridge.assertAccount(currentUser.id);
            STATE.currentUser = currentUser;
            STATE.relationshipAccountId = currentUser.id;
            el('maxpland-account-badge').textContent = currentUser.username ? `@${currentUser.username}` : `UID: ${currentUser.id}`;
            const lists = {};
            const speedMode = ['B', 'C'].includes(STATE.prefs?.scanSpeed) ? STATE.prefs.scanSpeed : 'A';
            const startList = endpoint => {
                if (STATE.stopScanFlag) throw new DOMException('Scan aborted by user', 'AbortError');
                phase.textContent = endpoint === 'followers' ? 'Fetching Followers...' : 'Fetching Following...';
                return IgBridge.fetchAllRelationships(endpoint, currentUser.id,
                    endpoint === 'followers' ? APP_CONFIG.FOLLOWERS_PAGE_SAFETY_LIMIT : APP_CONFIG.FOLLOWING_PAGE_SAFETY_LIMIT,
                    (count, page, message) => {
                        el('maxpland-scan-stat-count').textContent = `${endpoint}: ${count.toLocaleString()}`;
                        el('maxpland-scan-stat-page').textContent = `Pages: ${page}`;
                        el('maxpland-progress-fill').style.width = `${Math.min(95, Math.min(45, page * 3) + (lists.followers ? 45 : 0) + (lists.following ? 45 : 0))}%`;
                        if (message) { notice.textContent = message; notice.style.display = 'block'; }
                    }, speedMode);
            };
            const finishList = (endpoint, result) => {
                if (STATE.stopScanFlag) throw new DOMException('Scan aborted by user', 'AbortError');
                if (!result.completed) throw result.lastError || new Error(`Incomplete fetch for ${endpoint}`);
                lists[endpoint] = result;
            };
            // Every speed mode fetches serially: concurrent double-streams doubled the
            // combined request rate — the signal automation detection watches for.
            // Mode speed now comes purely from PACE_PRESETS inside fetchAllRelationships.
            for (const endpoint of ['followers', 'following']) {
                finishList(endpoint, await startList(endpoint));
            }
            const { followers, following } = lists;
            const getUid = u => String(u?.id || u?.pk_id || u?.pk || '').trim();
            const getUname = u => String(u?.username || '').trim().toLowerCase();

            const followerIdSet = new Set();
            const followerUsernameSet = new Set();
            for (const u of followers) {
                const uid = getUid(u);
                const uname = getUname(u);
                if (uid) followerIdSet.add(uid);
                if (uname) followerUsernameSet.add(uname);
            }

            const followingIdSet = new Set();
            const followingUsernameSet = new Set();
            for (const u of following) {
                const uid = getUid(u);
                const uname = getUname(u);
                if (uid) followingIdSet.add(uid);
                if (uname) followingUsernameSet.add(uname);
            }

            const isFollower = u => {
                if (u?.friendship_status?.followed_by === true) return true;
                const uid = getUid(u);
                const uname = getUname(u);
                return (Boolean(uid) && followerIdSet.has(uid)) || (Boolean(uname) && followerUsernameSet.has(uname));
            };

            const isFollowing = u => {
                if (u?.friendship_status?.following === true) return true;
                const uid = getUid(u);
                const uname = getUname(u);
                return (Boolean(uid) && followingIdSet.has(uid)) || (Boolean(uname) && followingUsernameSet.has(uname));
            };

            const prev = await MaxPlandVault.getLatestSnapshot(currentUser.id);
            const whitelist = await MaxPlandVault.getWhitelist();
            IgBridge.assertAccount(currentUser.id);
            if (STATE.stopScanFlag) throw new DOMException('Scan aborted by user', 'AbortError');
            const idxMap = prev?.user_index ? new Map(Object.entries(prev.user_index)) : new Map();
            const prevUsernames = { ...(prev?.follower_usernames || {}), ...(prev?.usernames || {}) };
            const lostFollowers = (prev?.follower_ids || []).filter(id => !followerIdSet.has(String(id))).map(id => {
                const sid = String(id);
                const uname = prevUsernames[sid] || idxMap.get(sid) || `user_${sid}`;
                // Block detector: gone from followers AND from our following list -> blocked us
                // or deactivated. Still in following -> plain unfollow.
                const gone = !followingIdSet.has(sid) && !followingUsernameSet.has(String(uname).toLowerCase());
                return {
                    pk: sid, id: sid, username: uname, profile_pic_url: '',
                    full_name: gone ? 'Gone — blocked us or deactivated' : 'Unfollowed since the previous scan'
                };
            });
            // Rename detector: same follower ids whose username changed since the previous scan.
            const renamed = [];
            if (prev && typeof prevUsernames === 'object') {
                const currentById = new Map(followers.map(f => [String(f.id || f.pk_id || f.pk || ''), f]));
                for (const [sid, oldName] of Object.entries(prevUsernames)) {
                    if (!oldName) continue;
                    const now = currentById.get(sid);
                    const newName = String(now?.username || '');
                    if (now && newName && newName.toLowerCase() !== oldName.toLowerCase()) {
                        renamed.push({
                            pk: sid, id: sid, username: newName,
                            full_name: `Renamed from @${oldName}`,
                            profile_pic_url: now.profile_pic_url || '',
                            is_verified: Boolean(now.is_verified), is_private: Boolean(now.is_private),
                            has_anonymous_profile_picture: Boolean(now.has_anonymous_profile_picture)
                        });
                    }
                }
            }
            await MaxPlandVault.saveSnapshot(followers, following, currentUser.id, STATE.scanController.signal);
            IgBridge.assertAccount(currentUser.id);
            if (STATE.stopScanFlag) throw new DOMException('Scan aborted by user', 'AbortError');
            Object.assign(STATE, { followers, following, whitelist, lostFollowers, renamed, scanIncomplete: false,
                notFollowingBack: following.filter(u => !isFollower(u)),
                fans: followers.filter(u => !isFollowing(u)),
                mutual: following.filter(u => isFollower(u)),
                ghostFollowers: followers.filter(u => hasNoAvatar(u)) });
            for (const [stat, pill, key] of [['not-following-back','not','notFollowingBack'],['fans','fans','fans'],['mutual','mutual','mutual'],['lost','lost','lostFollowers'],['ghost','ghost','ghostFollowers'],['renamed','renamed','renamed']]) {
                if (el(`stat-${stat}`)) el(`stat-${stat}`).textContent = STATE[key].length.toLocaleString();
                if (el(`pill-count-${pill}`)) el(`pill-count-${pill}`).textContent = STATE[key].length.toLocaleString();
            }
            el('pill-count-white').textContent = whitelist.size.toLocaleString();
            el('maxpland-progress-fill').style.width = '100%';
            phase.textContent = 'Scan completed';
            notice.style.display = 'none';
            const fs = lists.followers.fetchStats || { requestMs: 0, waitMs: 0 };
            const fw = lists.following.fetchStats || { requestMs: 0, waitMs: 0 };
            STATE.scanStatus = { requestMs: fs.requestMs + fw.requestMs, waitMs: fs.waitMs + fw.waitMs, wallMs: Date.now() - STATE.scanStartTime };
            const fmtMs = ms => { const s = Math.round(ms / 1000); return s >= 60 ? `${Math.floor(s / 60)} min ${s % 60} s` : `${s} s`; };
            summary.textContent = `Followers ${followers.length.toLocaleString()} · Following ${following.length.toLocaleString()} · Fetch ${fmtMs(STATE.scanStatus.requestMs)} · Pacing ${fmtMs(STATE.scanStatus.waitMs)} · Total ${fmtMs(STATE.scanStatus.wallMs)}`;
            showToast('Scan completed successfully');
        } catch (err) {
            if (err?.code === 'RATE_LIMIT') showToast(`⏳ ${err.message} — retry after the cooldown ends.`, 5000);
            phase.textContent = STATE.stopScanFlag || err.name === 'AbortError' ? 'Scan stopped' : 'Scan failed';
            notice.textContent = `⚠️ ${err.message} · Relationships not updated`;
            notice.style.display = 'block';
            summary.textContent = phase.textContent;
        } finally {
            clearInterval(timer);
            btn.disabled = false;
            STATE.isScanning = false;
            STATE.scanController = null;
            STATE.scanPaused = false;
            const pauseBtnEnd = document.getElementById('maxpland-btn-pause-scan');
            pauseBtnEnd.style.display = 'none';
            pauseBtnEnd.textContent = '⏸ Pause';
            renderRelationshipList();
            // Keep failed scan diagnostics visible until the next action.
            if (!STATE.scanIncomplete) STATE.progressHideTimer = setTimeout(() => { progress.style.display = 'none'; }, 2500);
        }
    }

    /* ==========================================================================
       8. FILTER & RENDER ENGINE
       ========================================================================== */

    function getFilteredUsers() {
        let pool = [];
        if (STATE.relationshipFilter === 'not_following_back') pool = STATE.notFollowingBack;
        else if (STATE.relationshipFilter === 'fans') pool = STATE.fans;
        else if (STATE.relationshipFilter === 'mutual') pool = STATE.mutual;
        else if (STATE.relationshipFilter === 'lost') pool = STATE.lostFollowers;
        else if (STATE.relationshipFilter === 'ghost') pool = STATE.ghostFollowers;
        else if (STATE.relationshipFilter === 'renamed') pool = STATE.renamed || [];
        else if (STATE.relationshipFilter === 'inactive') pool = STATE.inactiveFollowing;
        else if (STATE.relationshipFilter === 'whitelist') pool = Array.from(STATE.whitelist.values());

        const followedIds = new Set(STATE.following.map(x => String(x.id || x.pk_id || x.pk || '').trim()));
        return pool.filter(u => {
            const uid = String(u.id || u.pk_id || u.pk || '');
            const uname = String(u.username || '').toLowerCase();
            const isFollowedByUs = uid !== '' && followedIds.has(uid);
            if (STATE.subFilters.onlyFollowing && !isFollowedByUs) return false;
            if (STATE.subFilters.onlyNotFollowed && isFollowedByUs) return false;
            const isWhitelisted = isProtectedUser(uid, uname);
            if (STATE.subFilters.excludeWhitelist && isWhitelisted) return false;
            if (STATE.subFilters.excludeVerified && u.is_verified) return false;
            if (STATE.subFilters.excludePrivate && u.is_private) return false;
            if (STATE.subFilters.excludeNoAvatar && hasNoAvatar(u)) return false;
            if (STATE.searchQuery) {
                const q = STATE.searchQuery;
                const matchUser = u.username && u.username.toLowerCase().includes(q);
                const matchName = u.full_name && u.full_name.toLowerCase().includes(q);
                if (!matchUser && !matchName) return false;
            }
            return true;
        });
    }

    function setFollowStateChip(which) {
        const next = !STATE.subFilters[which];
        STATE.subFilters.onlyFollowing = which === 'onlyFollowing' && next;
        STATE.subFilters.onlyNotFollowed = which === 'onlyNotFollowed' && next;
        document.getElementById('toggle-filter-following').classList.toggle('active', STATE.subFilters.onlyFollowing);
        document.getElementById('toggle-filter-notfollowing').classList.toggle('active', STATE.subFilters.onlyNotFollowed);
        renderRelationshipList();
    }

    function renderRelationshipList() {
        const listEl = document.getElementById('maxpland-relationship-list');
        const currentPool = getFilteredUsers();

        let tagClass = 'not_following';
        let tagText = 'Not Following Back';
        if (STATE.relationshipFilter === 'fans') { tagClass = 'fan'; tagText = 'Fan'; }
        else if (STATE.relationshipFilter === 'mutual') { tagClass = 'mutual'; tagText = 'Mutual'; }
        else if (STATE.relationshipFilter === 'lost') { tagClass = 'lost'; tagText = 'Lost'; }
        else if (STATE.relationshipFilter === 'ghost') { tagClass = 'ghost'; tagText = '👻 Ghost'; }
        else if (STATE.relationshipFilter === 'inactive') { tagClass = 'ghost'; tagText = '⏱️ Inactive'; }
        else if (STATE.relationshipFilter === 'whitelist') { tagClass = 'mutual'; tagText = '⭐ Whitelist'; }

        if (STATE.scanIncomplete && STATE.followers.length === 0 && (STATE.relationshipFilter === 'not_following_back' || STATE.relationshipFilter === 'mutual' || STATE.relationshipFilter === 'fans')) {
            listEl.innerHTML = `
                <div style="padding: 48px 24px; text-align: center; color: var(--mp-rose); line-height: 1.6;">
                    <div style="font-size: 15px; font-weight: 600; margin-bottom: 8px;">${STATE.isScanning ? 'Scanning relationships...' : 'No complete scan results yet'}</div>
                    <div style="color: var(--mp-text-secondary); font-size: 13px; max-width: 500px; margin: 0 auto;">
                        ${STATE.isScanning ? 'Please wait for followers and following to finish fetching.' : 'Check scan status above or click Scan Relationships to start.'}
                    </div>
                </div>
            `;
            return;
        }

        if (currentPool.length === 0) {
            listEl.innerHTML = `<div style="padding: 48px; text-align: center; color: var(--mp-text-muted);">No accounts match current filter</div>`;
            return;
        }

        const visibleUsers = currentPool.slice(0, STATE.relationshipLimit || 100);
        let html = '';

        visibleUsers.forEach(user => {
            const uid = String(user.id || user.pk_id || user.pk || '');
            const uname = String(user.username || '').toLowerCase();
            const isWhitelisted = isProtectedUser(uid, uname);
            const isChecked = STATE.selectedIds.has(uid);
            const avatar = user.profile_pic_url || '';
            const initial = String(user.username || '?').slice(0, 1).toUpperCase();
            let displayTag = tagText;
            if (STATE.relationshipFilter === 'inactive' && user.dormant_days !== undefined) {
                displayTag = typeof user.dormant_days === 'number' ? `⏱️ Inactive ${user.dormant_days}d` : `⏱️ ${user.dormant_days}`;
            }

            html += `
                <div class="maxpland-user-row" data-id="${escapeHtml(uid)}">
                    <div class="maxpland-user-left">
                        <input type="checkbox" class="maxpland-checkbox user-select-checkbox" data-id="${escapeHtml(uid)}" aria-label="Select @${escapeHtml(user.username || uid)}" ${isChecked ? 'checked' : ''} ${isWhitelisted ? 'disabled title="In Whitelist (protected from unfollow)"' : ''}>
                        ${avatar ? `<img class="maxpland-avatar" src="${escapeHtml(avatar)}" loading="lazy" alt="">` : `<span class="maxpland-avatar">${escapeHtml(initial)}</span>`}
                        <div class="maxpland-user-names">
                            <div class="maxpland-username-wrap">
                                <a href="https://www.instagram.com/${encodeURIComponent(user.username || '')}/" target="_blank" rel="noopener noreferrer" class="maxpland-username">${escapeHtml(user.username || 'unknown')}</a>
                                ${user.is_verified ? `<span title="Verified">${ICONS.VERIFIED}</span>` : ''}
                                ${user.is_private ? `<span title="Private Account" style="color:var(--mp-text-muted);display:flex;align-items:center;">${ICONS.LOCK}</span>` : ''}
                                <span class="maxpland-status-tag ${tagClass}">${escapeHtml(displayTag)}</span>
                            </div>
                            <span class="maxpland-fullname">${escapeHtml(user.full_name || '')}</span>
                        </div>
                    </div>
                    <div class="maxpland-user-actions">
                        <a href="https://www.instagram.com/${encodeURIComponent(user.username || '')}/" target="_blank" rel="noopener noreferrer" class="maxpland-icon-action" title="Open Instagram Profile">
                            ${ICONS.EXTERNAL}
                        </a>
                        <button type="button" class="maxpland-icon-action maxpland-star-btn" data-id="${escapeHtml(uid)}" title="${isWhitelisted ? 'Remove from Whitelist' : 'Add to Whitelist'}">
                            ${isWhitelisted ? ICONS.STAR_FILLED : ICONS.STAR}
                        </button>
                        ${STATE.relationshipFilter === 'not_following_back' || STATE.relationshipFilter === 'mutual' || STATE.relationshipFilter === 'inactive' ? `
                            <button type="button" class="maxpland-row-unfollow-btn" data-id="${escapeHtml(uid)}" data-user="${escapeHtml(user.username || '')}" ${isWhitelisted ? 'disabled title="Whitelist"' : ''}>
                                ${ICONS.UNFOLLOW} Unfollow
                            </button>
                        ` : ''}
                    </div>
                </div>
            `;
        });

        listEl.innerHTML = html;

        if (visibleUsers.length < currentPool.length) {
            const more = document.createElement('button');
            more.type = 'button';
            more.className = 'maxpland-btn-secondary';
            more.style.margin = '10px auto';
            more.style.display = 'block';
            more.textContent = `Show more (${visibleUsers.length.toLocaleString()} / ${currentPool.length.toLocaleString()})`;
            more.onclick = () => { STATE.relationshipLimit += 100; renderRelationshipList(); };
            listEl.append(more);
        }
    }

    function applyUnfollowResult(uid, username = '') {
        const idOf = u => String(u?.id || u?.pk_id || u?.pk || '');
        const nameOf = u => String(u?.username || '').toLowerCase();
        const targetName = String(username || '').toLowerCase();
        const isTarget = u => (uid && idOf(u) === uid) || (targetName && nameOf(u) === targetName);
        STATE.following = STATE.following.filter(u => !isTarget(u));
        STATE.notFollowingBack = STATE.notFollowingBack.filter(u => !isTarget(u));
        STATE.mutual = STATE.mutual.filter(u => !isTarget(u));
        const fan = STATE.followers.find(isTarget);
        if (fan && !STATE.fans.some(isTarget)) STATE.fans.push(fan);
        for (const key of ['fans', 'mutual']) {
            const stat = document.getElementById('stat-' + key), pill = document.getElementById('pill-count-' + key);
            if (stat) stat.textContent = STATE[key].length.toLocaleString();
            if (pill) pill.textContent = STATE[key].length.toLocaleString();
        }
    }

    function updateBulkActionBar() {
        const bar = document.getElementById('maxpland-bulk-bar');
        const countLabel = document.getElementById('maxpland-bulk-count-label');
        const count = STATE.selectedIds.size;

        if (count > 0) {
            bar.style.display = 'flex';
            countLabel.textContent = `${count.toLocaleString()} selected`;
        } else {
            bar.style.display = 'none';
        }
    }

    function selectAllVisible() {
        const visiblePool = getFilteredUsers().slice(0, STATE.relationshipLimit || 100);
        visiblePool.forEach(u => {
            const uid = String(u.id || u.pk_id || u.pk || '');
            const uname = String(u.username || '').toLowerCase();
            const isWhitelisted = isProtectedUser(uid, uname);
            if (!isWhitelisted) {
                STATE.selectedIds.add(uid);
            }
        });
        updateBulkActionBar();
        renderRelationshipList();
    }

    /* ==========================================================================
       9. UNFOLLOW ENGINE (Batch with Safe Random Pacing)
       ========================================================================== */

    async function runBatchUnfollow() {
        if (STATE.isUnfollowing || STATE.isScanning || STATE.isScanningInactive || STATE.scanIncomplete) return;
        const count = STATE.selectedIds.size;
        if (count === 0) {
            alert('Please select at least one account to unfollow.');
            return;
        }

        const estSeconds = Math.round(count * ((APP_CONFIG.UNFOLLOW_DELAY_MIN + APP_CONFIG.UNFOLLOW_DELAY_MAX) / 2000));
        const proceed = confirm(`⚠️ Safety warning:\nYou are about to unfollow ${count} accounts\nEstimated duration: ${Math.ceil(estSeconds / 60)} minutes (random delay ${APP_CONFIG.UNFOLLOW_DELAY_MIN / 1000}-${APP_CONFIG.UNFOLLOW_DELAY_MAX / 1000} seconds per account)\n\nStart now?`);
        if (!proceed) return;

        STATE.isUnfollowing = true;
        STATE.stopUnfollowFlag = false;
        clearTimeout(STATE.progressHideTimer);

        const progressCont = document.getElementById('maxpland-global-progress');
        const progressFill = document.getElementById('maxpland-progress-fill');
        const phaseEl = document.getElementById('maxpland-scan-phase');
        const statCount = document.getElementById('maxpland-scan-stat-count');
        const statPage = document.getElementById('maxpland-scan-stat-page');
        const noticeEl = document.getElementById('maxpland-scan-notice');

        progressCont.style.display = 'block';
        phaseEl.textContent = 'Preparing safe unfollow batch...';
        noticeEl.style.display = 'none';

        const idsToUnfollow = Array.from(STATE.selectedIds);
        let successCount = 0;
        let failCount = 0;

        try {
            for (let i = 0; i < idsToUnfollow.length; i++) {
                if (STATE.stopUnfollowFlag) {
                    phaseEl.textContent = 'Unfollow batch stopped by user';
                    break;
                }

                const uid = idsToUnfollow[i];
                const userObj = STATE.notFollowingBack.find(u => String(u.id || u.pk_id || u.pk || '') === uid)
                    || STATE.following.find(u => String(u.id || u.pk_id || u.pk || '') === uid);
                if (isProtectedUser(uid, userObj?.username)) { STATE.selectedIds.delete(uid); continue; }
                const uname = userObj ? `@${userObj.username}` : `UID ${uid}`;

                phaseEl.textContent = `Unfollowing [${i + 1}/${idsToUnfollow.length}]: ${uname}`;
                statCount.textContent = `Success: ${successCount} | Failed: ${failCount}`;
                statPage.textContent = `Remaining: ${idsToUnfollow.length - i}`;
                progressFill.style.width = `${Math.round(((i + 1) / idsToUnfollow.length) * 100)}%`;

                try {
                    await IgBridge.unfollowUser(uid);
                    successCount++;
                    STATE.selectedIds.delete(uid);
                    applyUnfollowResult(uid, userObj?.username);
                } catch (err) {
                    console.error('[Unfollow Error]', uid, err);
                    failCount++;
                    noticeEl.textContent = err.message || 'Unfollow failed';
                    noticeEl.style.display = 'block';
                    STATE.stopUnfollowFlag = true;
                    break;
                }

                if (i < idsToUnfollow.length - 1 && !STATE.stopUnfollowFlag) {
                    const delay = Math.floor(Math.random() * (APP_CONFIG.UNFOLLOW_DELAY_MAX - APP_CONFIG.UNFOLLOW_DELAY_MIN + 1)) + APP_CONFIG.UNFOLLOW_DELAY_MIN;
                    for (let s = Math.ceil(delay / 1000); s > 0; s--) {
                        if (STATE.stopUnfollowFlag) break;
                        phaseEl.textContent = `Safety delay... ${s}s (${uname} done)`;
                        await sleep(1000);
                    }
                }
            }

            document.getElementById('stat-not-following-back').textContent = STATE.notFollowingBack.length.toLocaleString();
            document.getElementById('pill-count-not').textContent = STATE.notFollowingBack.length.toLocaleString();
            updateBulkActionBar();
            renderRelationshipList();
            showToast(`Unfollow completed: ${successCount} succeeded, ${failCount} failed`);
        } finally {
            STATE.isUnfollowing = false;
            if (!STATE.stopUnfollowFlag) STATE.progressHideTimer = setTimeout(() => { progressCont.style.display = 'none'; }, 2500);
        }
    }

    /* ==========================================================================
       10. EXPORT & BACKUP UTILITIES
       ========================================================================== */

    async function copyVisibleUsernames() {
        const pool = getFilteredUsers();
        if (!pool.length) {
            alert('No usernames found under current filter to copy.');
            return;
        }
        const text = pool.map(u => u.username || '').filter(Boolean).join('\n');
        try {
            await navigator.clipboard.writeText(text);
            showToast(`Copied ${pool.length.toLocaleString()} usernames to clipboard`);
        } catch (_) {
            alert('Failed to copy to clipboard. Please retry.');
        }
    }

    function exportRelationshipCSV() {
        const pool = getFilteredUsers();
        if (!pool.length) {
            alert('No data in this category to export.');
            return;
        }
        const rows = [['User ID', 'Username', 'Full Name', 'Is Verified', 'Is Private']];
        pool.forEach(u => rows.push([
            u.pk || u.pk_id || u.id || '',
            u.username || '',
            u.full_name || '',
            u.is_verified ? 'Yes' : 'No',
            u.is_private ? 'Yes' : 'No'
        ]));
        const csv = '\uFEFF' + rows.map(r => r.map(csvCell).join(',')).join('\r\n');
        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
        downloadBlob(blob, `IG_MaxPland_${STATE.relationshipFilter}_${new Date().toISOString().slice(0,10)}.csv`);
    }

    function exportRelationshipJSON() {
        const pool = getFilteredUsers();
        if (!pool.length) {
            alert('No data in this category to export.');
            return;
        }
        const data = JSON.stringify(pool, null, 2);
        const blob = new Blob([data], { type: 'application/json;charset=utf-8;' });
        downloadBlob(blob, `IG_MaxPland_${STATE.relationshipFilter}_${new Date().toISOString().slice(0,10)}.json`);
    }

    function exportWhitelistBackup() {
        const whitelistArr = Array.from(STATE.whitelist.values());
        if (!whitelistArr.length) {
            alert('No starred accounts in whitelist to export.');
            return;
        }
        const json = JSON.stringify(whitelistArr, null, 2);
        const blob = new Blob([json], { type: 'application/json;charset=utf-8;' });
        downloadBlob(blob, `IG_MaxPland_Whitelist_Backup_${new Date().toISOString().slice(0,10)}.json`);
        showToast('Starred whitelist exported successfully');
    }

    async function handleWhitelistRestoreFile(e) {
        const file = e.target.files?.[0];
        if (!file) return;
        try {
            const text = await file.text();
            const data = JSON.parse(text);
            if (!Array.isArray(data)) throw new Error('Invalid backup file (Must be JSON array)');
            const count = await MaxPlandVault.importWhitelist(data);
            STATE.whitelist = await MaxPlandVault.getWhitelist();
            document.getElementById('pill-count-white').textContent = STATE.whitelist.size.toLocaleString();
            showToast(`Restored ${count} accounts to whitelist`);
            renderRelationshipList();
        } catch (err) {
            alert(`Import failed: ${err.message || err}`);
        } finally {
            e.target.value = '';
        }
    }

    /* ==========================================================================
       11. MEDIA DOWNLOADER ENGINE
       ========================================================================== */

    async function downloadResolvedMedia(resolved, { allCarousel = false, skipExisting = false } = {}) {
        const accountId = IgBridge.getCookie('ds_user_id');
        IgBridge.assertAccount(accountId);
        const completed = [];
        const nodesToDownload = allCarousel ? resolved.nodes : [resolved.nodes[0]];

        for (const node of nodesToDownload) {
            IgBridge.assertAccount(accountId);
            const key = `${resolved.shortcode}:${node.id || node.index}:media`;
            if (skipExisting && await MaxPlandVault.hasMedia(key)) {
                completed.push({ key, status: 'skipped' });
                continue;
            }

            IgBridge.assertAccount(accountId);
            const base = safeFilename(`${resolved.username || 'instagram'}_${resolved.shortcode}_${node.index + 1}`);
            const files = [];

            if (node.mediaType === 'image') {
                if (!node.imageUrl) throw new Error(`No image URL for ${resolved.shortcode}`);
                const ext = extensionFromUrl(node.imageUrl, 'jpg');
                files.push(await gmDownload(node.imageUrl, `${base}.${ext}`));
            } else if (node.progressiveVideoUrl) {
                const ext = extensionFromUrl(node.progressiveVideoUrl, 'mp4');
                files.push(await gmDownload(node.progressiveVideoUrl, `${base}.${ext}`));
            } else if (node.imageUrl) {
                // Video stream unavailable: the thumbnail is stored under its own type
                // so the next run still retries the real video instead of skipping it.
                const thumbType = node.mediaType === 'video' ? 'video_thumb' : node.mediaType;
                const ext = extensionFromUrl(node.imageUrl, node.mediaType === 'video' ? 'jpg' : 'jpg');
                files.push(await gmDownload(node.imageUrl, `${base}.${ext}`));
                node.mediaType = thumbType;
            } else {
                throw new Error(`No downloadable stream for ${resolved.shortcode}`);
            }

            IgBridge.assertAccount(accountId);
            await MaxPlandVault.markMediaDownloaded({
                key,
                shortcode: resolved.shortcode,
                username: resolved.username || '',
                item_index: node.index,
                media_type: node.mediaType,
                files
            });
            completed.push({ key, status: 'done', files });
        }
        return completed;
    }

    async function downloadByShortcode(shortcode, options = {}) {
        const resolved = await IgBridge.resolveMedia(shortcode);
        return downloadResolvedMedia(resolved, options);
    }

    /* ==========================================================================
       9. HEALTH DASHBOARD, INACTIVE RADAR & SETTINGS SYNC
       ========================================================================== */

    function syncFeaturesTab() {
        const el = id => document.getElementById(id);
        const p = STATE.prefs || {};
        if (el('pref-switch-stealth-story')) el('pref-switch-stealth-story').checked = Boolean(p.stealthStory);
        if (el('pref-switch-clean-feed')) el('pref-switch-clean-feed').checked = Boolean(p.cleanFeed);
        if (el('pref-switch-feed-download')) el('pref-switch-feed-download').checked = Boolean(p.quickDownloadFeed);
        if (el('pref-switch-story-toolbar')) el('pref-switch-story-toolbar').checked = Boolean(p.quickDownloadStory);
    }

    function syncSettingsTab() {
        const el = id => document.getElementById(id);
        const p = STATE.prefs || {};
        if (el('pref-setting-inactive-threshold')) el('pref-setting-inactive-threshold').value = String(p.inactiveThresholdDays || 180);
        if (el('pref-setting-scan-speed')) el('pref-setting-scan-speed').value = ['B', 'C'].includes(p.scanSpeed) ? p.scanSpeed : 'A';
    }

    async function runInactiveScan() {
        if (STATE.isScanning || STATE.isUnfollowing || STATE.isScanningInactive) return;
        if (STATE.scanIncomplete || !STATE.following.length) {
            alert('Complete a relationship scan before checking activity.');
            return;
        }
        const accountId = STATE.relationshipAccountId;
        const thresholdSeconds = Number(STATE.prefs?.inactiveThresholdDays || 180) * 86400;
        const nowSec = Math.floor(Date.now() / 1000);
        const pool = [...STATE.following];
        STATE.isScanningInactive = true;
        STATE.stopInactiveScanFlag = false;
        STATE.stopScanFlag = false;
        STATE.inactiveController = new AbortController();
        const signal = STATE.inactiveController.signal;
        STATE.inactiveFollowing = [];
        clearTimeout(STATE.progressHideTimer);
        const el = id => document.getElementById(id);
        const progress = el('maxpland-global-progress');
        const phase = el('maxpland-scan-phase');
        const btn = el('maxpland-btn-scan-inactive');
        btn.disabled = true;
        progress.style.display = 'block';
        phase.textContent = '[Inactive Radar] Starting scan...';
        el('maxpland-scan-notice').style.display = 'none';
        el('maxpland-scan-stat-count').textContent = 'Inactive found: 0';
        el('maxpland-scan-stat-page').textContent = `Checked: 0/${pool.length}`;
        el('maxpland-scan-stat-timer').textContent = '⏱️ 00:00';
        el('maxpland-progress-fill').style.width = '0%';
        const started = Date.now();
        const timer = setInterval(() => {
            el('maxpland-scan-stat-timer').textContent = `⏱️ ${formatTime(Math.floor((Date.now() - started) / 1000))}`;
        }, 1000);
        let checked = 0, failed = 0, newFetches = 0;
        try {
            IgBridge.assertAccount(accountId);
            for (const user of pool) {
                if (signal.aborted) break;
                IgBridge.assertAccount(accountId);
                const uid = String(user.id || user.pk_id || user.pk || '');
                const cached = await MaxPlandVault.getUserActivity(uid);
                if (signal.aborted) break;
                IgBridge.assertAccount(accountId);
                const cacheValid = cached?.checked_at && Date.now() - cached.checked_at < 14 * 86400 * 1000;
                // ponytail: preserve the 20-request ceiling; cached rows need no new request.
                if (!cacheValid && newFetches >= 20) break;
                phase.textContent = `[Inactive Radar] Checking @${user.username || uid} (${checked + 1}/${pool.length})...`;
                let lastTakenAt = cached?.last_post_taken_at;
                let hasPosts = cached?.has_posts !== false;
                let known = Boolean(cacheValid);
                if (!cacheValid) {
                    newFetches++;
                    try {
                        const info = await IgBridge.fetchUserLastPost(uid, { signal });
                        if (signal.aborted) break;
                        IgBridge.assertAccount(accountId);
                        lastTakenAt = info.last_taken_at;
                        hasPosts = info.has_posts;
                        known = true;
                        await MaxPlandVault.saveUserActivity({ id: uid, username: user.username || '',
                            last_post_taken_at: lastTakenAt, has_posts: hasPosts, checked_at: Date.now() });
                    } catch (err) {
                        if (signal.aborted || err.name === 'AbortError' || IgBridge.isSessionError(err)) throw err;
                        failed++;
                        known = false;
                    }
                }
                if (signal.aborted) break;
                IgBridge.assertAccount(accountId);
                checked++;
                if (known && (!hasPosts || (lastTakenAt && nowSec - lastTakenAt > thresholdSeconds))) {
                    STATE.inactiveFollowing.push({ ...user,
                        dormant_days: lastTakenAt ? Math.floor((nowSec - lastTakenAt) / 86400) : 'No recent posts found' });
                }
                el('maxpland-progress-fill').style.width = `${Math.round(checked / pool.length * 100)}%`;
                el('maxpland-scan-stat-page').textContent = `Checked: ${checked}/${pool.length}`;
                el('maxpland-scan-stat-count').textContent = `Inactive found: ${STATE.inactiveFollowing.length} · Unavailable: ${failed}`;
                if (!cacheValid && checked < pool.length && newFetches < 20) {
                    const jitter = 3500 + Math.floor(Math.random() * 2500);
                    for (let ms = 0; ms < jitter && !signal.aborted; ms += 250) await sleep(250);
                }
            }
            const state = signal.aborted ? 'Stopped by request' : checked < pool.length ? 'Paused — scan again to continue' : failed ? 'Finished with unavailable results' : 'Scan complete';
            phase.textContent = `[Inactive Radar] ${state} (${checked}/${pool.length}) · Found ${STATE.inactiveFollowing.length} · Unavailable ${failed}`;
            showToast(phase.textContent, 5000);
            if (!signal.aborted && checked === pool.length && !failed) {
                STATE.progressHideTimer = setTimeout(() => { progress.style.display = 'none'; }, 3000);
            }
        } catch (err) {
            phase.textContent = signal.aborted || err.name === 'AbortError'
                ? `[Inactive Radar] Stopped by request (${checked}/${pool.length})`
                : `[Inactive Radar] Stopped: ${err.message} (${checked}/${pool.length})`;
            showToast(phase.textContent, 4000);
        } finally {
            clearInterval(timer);
            btn.disabled = false;
            STATE.isScanningInactive = false;
            STATE.inactiveController = null;
            el('pill-count-inactive').textContent = STATE.inactiveFollowing.length;
            document.querySelector('.maxpland-pill-btn[data-filter="inactive"]')?.click();
        }
    }

    async function renderHealthDashboard() {
        const el = id => document.getElementById(id);
        const followerCount = STATE.followers.length;
        const followingCount = STATE.following.length;

        // 1. Ratio
        const ratio = followingCount > 0 ? (followerCount / followingCount) : 0;
        const ratioVal = el('health-ratio-val');
        const ratioBadge = el('health-ratio-badge');
        if (ratioVal && ratioBadge) {
            if (followerCount === 0 && followingCount === 0) {
                ratioVal.textContent = '-';
                ratioBadge.textContent = 'Please scan relationships first';
                ratioBadge.style.background = 'var(--mp-bg-hover)';
                ratioBadge.style.color = 'var(--mp-text-secondary)';
            } else {
                ratioVal.textContent = `${ratio.toFixed(2)}x`;
                if (ratio >= 1.5) {
                    ratioBadge.textContent = '⭐ Creator / Influencer';
                    ratioBadge.style.background = 'var(--mp-emerald-bg)';
                    ratioBadge.style.color = 'var(--mp-emerald)';
                } else if (ratio >= 0.8) {
                    ratioBadge.textContent = '⚖️ Healthy Balance';
                    ratioBadge.style.background = 'var(--mp-cyan-bg)';
                    ratioBadge.style.color = 'var(--mp-cyan)';
                } else {
                    ratioBadge.textContent = '🔍 Consumer heavy';
                    ratioBadge.style.background = 'var(--mp-amber-bg)';
                    ratioBadge.style.color = 'var(--mp-amber)';
                }
            }
        }

        // 2. Mutual Rate
        const mutualVal = el('health-mutual-val');
        const mutualBadge = el('health-mutual-badge');
        if (mutualVal && mutualBadge) {
            if (followingCount === 0) {
                mutualVal.textContent = '-';
                mutualBadge.textContent = 'No data';
            } else {
                const rate = Math.round((STATE.mutual.length / followingCount) * 100);
                mutualVal.textContent = `${rate}%`;
                mutualBadge.textContent = `${STATE.mutual.length} of ${followingCount} accounts`;
            }
        }

        // 3. Not Back Outbound
        const notbackVal = el('health-notback-val');
        const notbackBadge = el('health-notback-badge');
        if (notbackVal && notbackBadge) {
            if (followingCount === 0) {
                notbackVal.textContent = '-';
                notbackBadge.textContent = 'No data';
            } else {
                const rate = Math.round((STATE.notFollowingBack.length / followingCount) * 100);
                notbackVal.textContent = `${rate}%`;
                notbackBadge.textContent = `${STATE.notFollowingBack.length} not following back`;
            }
        }

        // 4. Ghost & Inactive Impact
        const ghostVal = el('health-ghost-val');
        const ghostBadge = el('health-ghost-badge');
        if (ghostVal && ghostBadge) {
            const totalGhost = STATE.ghostFollowers.length + STATE.inactiveFollowing.length;
            if (followerCount === 0 && followingCount === 0) {
                ghostVal.textContent = '-';
                ghostBadge.textContent = 'No data';
            } else {
                const rate = Math.round((totalGhost / (followerCount || 1)) * 100);
                ghostVal.textContent = `${totalGhost} accounts`;
                ghostBadge.textContent = `Ghost: ${STATE.ghostFollowers.length} / Inactive: ${STATE.inactiveFollowing.length}`;
            }
        }

        // 5. Historical Snapshots & Pure SVG Sparkline
        const chartBox = el('health-chart-container');
        const deltaLabel = el('health-drift-delta');
        try {
            const accountId = STATE.relationshipAccountId || STATE.currentUser?.id;
            const snapshots = await MaxPlandVault.getAllSnapshots(accountId, 8);
            if (snapshots.length >= 2) {
                const sorted = [...snapshots].reverse();
                const counts = sorted.map(s => s.follower_count || 0);
                const min = Math.min(...counts);
                const max = Math.max(...counts);
                const diff = counts[counts.length - 1] - counts[0];

                if (deltaLabel) {
                    deltaLabel.textContent = `${diff >= 0 ? '+' : ''}${diff} followers`;
                    deltaLabel.style.color = diff >= 0 ? 'var(--mp-emerald)' : 'var(--mp-rose)';
                }

                const w = 700, h = 110;
                const pad = 24;
                const range = (max - min) || 1;
                const points = counts.map((c, i) => {
                    const x = pad + (i / (counts.length - 1)) * (w - pad * 2);
                    const y = h - pad - ((c - min) / range) * (h - pad * 2);
                    return { x, y, val: c, date: new Date(sorted[i].timestamp).toLocaleDateString('en-GB') };
                });

                const polyline = points.map(p => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ');
                const dots = points.map(p => `
                    <circle cx="${p.x.toFixed(1)}" cy="${p.y.toFixed(1)}" r="4" fill="#38bdf8" stroke="#0b0d10" stroke-width="2">
                        <title>${p.date}: ${p.val} followers</title>
                    </circle>
                    <text x="${p.x.toFixed(1)}" y="${(p.y - 8).toFixed(1)}" font-size="10" fill="#94a3b8" text-anchor="middle">${p.val}</text>
                `).join('');

                chartBox.innerHTML = `
                    <svg viewBox="0 0 ${w} ${h}" class="maxpland-sparkline">
                        <defs>
                            <linearGradient id="mp-chart-grad" x1="0" y1="0" x2="0" y2="1">
                                <stop offset="0%" stop-color="#38bdf8" stop-opacity="0.3"/>
                                <stop offset="100%" stop-color="#38bdf8" stop-opacity="0.0"/>
                            </linearGradient>
                        </defs>
                        <polyline fill="none" stroke="#0284c7" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" points="${polyline}" />
                        ${dots}
                    </svg>
                `;
            } else if (snapshots.length === 1) {
                if (deltaLabel) deltaLabel.textContent = '1 snapshot recorded';
                chartBox.innerHTML = `<div style="padding:20px;text-align:center;color:var(--mp-text-muted);">First snapshot recorded (${snapshots[0].follower_count} followers). Run another scan to view growth comparison curve.</div>`;
            } else {
                if (deltaLabel) deltaLabel.textContent = '';
                chartBox.innerHTML = `<div style="padding:20px;text-align:center;color:var(--mp-text-muted);">No snapshot history. Run a relationship scan to begin tracking.</div>`;
            }
        } catch (_) {}

        // 6. Advice Box
        const advice = el('health-advice-text');
        if (advice) {
            if (STATE.notFollowingBack.length > 50) {
                advice.textContent = `You have ${STATE.notFollowingBack.length} accounts not following you back. Review in Relationships tab and safely unfollow non-whitelisted accounts to restore balance.`;
            } else if (ratio >= 1.0) {
                advice.textContent = `Your account structure is healthy with a strong follower ratio (${ratio.toFixed(2)}x) and solid mutual engagement.`;
            } else {
                advice.textContent = `You follow more accounts than follow you back. Use Inactive Radar to detect and prune accounts that haven't posted in over 6 months.`;
            }
        }
    }

    async function renderMediaVault() {
        const list = document.getElementById('maxpland-vault-list');
        if (!list) return;
        let rows;
        try { rows = await MaxPlandVault.getMediaVault(100); }
        catch (err) { list.textContent = `History unavailable: ${err.message || err}`; return; }
        if (!rows.length) {
            list.innerHTML = '<div style="padding:48px;text-align:center;color:var(--mp-text-muted);">No download history yet</div>';
            return;
        }
        list.innerHTML = rows.map(r => `
            <div class="maxpland-user-row">
                <div class="maxpland-user-names">
                    <span class="maxpland-username">${escapeHtml(r.username || 'instagram')} · ${escapeHtml(r.shortcode || '')}</span>
                    <span class="maxpland-fullname">${escapeHtml(r.media_type || '')}${r.audio_only ? ' · audio' : ''} · ${new Date(r.downloaded_at).toLocaleString()}</span>
                </div>
                <a class="maxpland-btn-secondary" target="_blank" rel="noopener noreferrer" href="https://www.instagram.com/p/${encodeURIComponent(r.shortcode || '')}/" style="text-decoration:none;padding:3px 8px;font-size:11px;">View</a>
            </div>`).join('');
    }

    async function runMediaQueue({ skipExisting = false } = {}) {
        if (STATE.isMediaQueueRunning) return;
        const input = document.getElementById('maxpland-media-queue-input');
        const status = document.getElementById('maxpland-media-queue-status');
        const codes = extractShortcodesFromText(input.value || '');
        if (!codes.length) { alert('Please paste Instagram post links or shortcodes first'); return; }
        const accountId = IgBridge.getCookie('ds_user_id');
        const key = JSON.stringify([accountId, codes, skipExisting]);
        if (STATE.mediaQueue?.key !== key) STATE.mediaQueue = { key, finished: new Set(), attempted: new Set() };
        const queue = STATE.mediaQueue;
        STATE.isMediaQueueRunning = true;
        const buttons = ['maxpland-btn-run-media-queue', 'maxpland-btn-run-media-all'].map(id => document.getElementById(id)).filter(Boolean);
        buttons.forEach(btn => { btn.disabled = true; });
        input.disabled = true;
        let done = 0, skipped = 0, failed = 0, stopped = false;
        try {
            IgBridge.assertAccount(accountId);
            for (let i = 0; i < codes.length; i++) {
                const code = codes[i];
                if (queue.finished.has(code)) continue;
                status.textContent = (i + 1) + '/' + codes.length + ' · Fetching ' + code + '...';
                try {
                    IgBridge.assertAccount(accountId);
                    const retry = queue.attempted.has(code);
                    queue.attempted.add(code);
                    const result = await downloadByShortcode(code, { skipExisting: skipExisting || retry, allCarousel: true });
                    IgBridge.assertAccount(accountId);
                    for (const item of result) {
                        if (item.status === 'done') done++;
                        else if (item.status === 'skipped') skipped++;
                    }
                    queue.finished.add(code);
                } catch (err) {
                    failed++;
                    if (IgBridge.isSessionError(err)) {
                        stopped = true;
                        status.textContent = 'Queue paused: ' + err.message + ' · ' + (codes.length - queue.finished.size) + ' remaining. Click download to resume.';
                        break;
                    }
                }
                if (i < codes.length - 1) await sleep(800);
            }
            if (!stopped) status.textContent = 'Downloaded: ' + done + ', Skipped: ' + skipped + ', Failed: ' + failed + ' · ' + queue.finished.size + '/' + codes.length + ' done';
        } catch (err) { status.textContent = err.message; }
        finally {
            STATE.isMediaQueueRunning = false;
            if (queue.finished.size === codes.length) STATE.mediaQueue = null;
            buttons.forEach(btn => { btn.disabled = false; });
            input.disabled = false;
            await renderMediaVault();
        }
    }

    // In-Feed Media Native Action Bar Integration (Zero Vertical Space Waste)
    function injectInFeedDownloadButtons(root = document) {
        if (STATE.prefs && STATE.prefs.quickDownloadFeed === false) {
            document.querySelectorAll('.maxpland-action-wrap').forEach(el => el.remove());
            return;
        }
        const articles = root instanceof Element && root.matches('article') ? [root] : root.querySelectorAll('article');
        articles.forEach(article => {
            const section = article.querySelector('section');
            if (!section) return;

            // Remove legacy banner if present
            const oldBanner = article.querySelector('.maxpland-feed-tools');
            if (oldBanner) oldBanner.remove();

            // Prevent duplicate insertion
            if (section.querySelector('.maxpland-action-wrap')) return;

            const wrap = document.createElement('div');
            wrap.className = 'maxpland-action-wrap';

            const btn = document.createElement('button');
            btn.type = 'button';
            btn.className = 'maxpland-action-btn';
            btn.setAttribute('aria-label', 'Download media (MaxPland)');
            btn.title = 'Download media (MaxPland) · Click for options';
            btn.innerHTML = ICONS.DOWNLOAD;

            const menu = document.createElement('div');
            menu.className = 'maxpland-action-menu';

            // 1. Single Media Download (Dynamic: Video or Photo)
            const dlSingle = document.createElement('button');
            dlSingle.type = 'button';
            dlSingle.className = 'maxpland-menu-item maxpland-menu-dl-single';
            dlSingle.innerHTML = ICONS.DOWNLOAD + '<span>Download (HD)</span>';
            dlSingle.onclick = (e) => {
                e.stopPropagation();
                closeAllMenus();
                runFeedAction(article, btn, { allCarousel: false });
            };

            // 2. Carousel All Download (only if multi-item post)
            const dlAll = document.createElement('button');
            dlAll.type = 'button';
            dlAll.className = 'maxpland-menu-item maxpland-menu-dl-all';
            dlAll.innerHTML = ICONS.DOWNLOAD_ALL + '<span>Download all media</span>';
            dlAll.onclick = (e) => {
                e.stopPropagation();
                closeAllMenus();
                runFeedAction(article, btn, { allCarousel: true });
            };

            // 3. Open in new tab
            const openTab = document.createElement('button');
            openTab.type = 'button';
            openTab.className = 'maxpland-menu-item';
            openTab.innerHTML = ICONS.EXTERNAL + '<span>Open media in new tab</span>';
            openTab.onclick = (e) => {
                e.stopPropagation();
                closeAllMenus();
                openMediaDirectLink(article);
            };

            // 4. Divider
            const divider = document.createElement('div');
            divider.className = 'maxpland-menu-divider';

            // 5. Open MaxPland Studio
            const openStudio = document.createElement('button');
            openStudio.type = 'button';
            openStudio.className = 'maxpland-menu-item';
            openStudio.innerHTML = ICONS.LOGO + '<span>Open MaxPland Studio</span>';
            openStudio.onclick = (e) => {
                e.preventDefault();
                e.stopPropagation();
                closeAllMenus();
                if (typeof window.openMaxPlandStudio === 'function') {
                    window.openMaxPlandStudio();
                } else {
                    const tb = document.getElementById('maxpland-trigger-btn');
                    if (tb) tb.click();
                }
            };

            menu.append(dlSingle, dlAll, openTab, divider, openStudio);

            btn.onclick = (e) => {
                e.stopPropagation();
                const isShown = menu.classList.contains('show');
                closeAllMenus();
                if (!isShown) {
                    const hasVideo = Boolean(article.querySelector('video'));
                    const isCarousel = Boolean(article.querySelector('ul li') || article.querySelectorAll('button[aria-label*="Next"], button[aria-label*="ต่อไป"]').length);

                    dlSingle.innerHTML = ICONS.DOWNLOAD + `<span>${hasVideo ? 'Download Video (HD)' : 'Download Image (HD)'}</span>`;
                    dlAll.style.display = isCarousel ? 'flex' : 'none';
                    menu.classList.add('show');
                }
            };

            btn.ondblclick = (e) => {
                e.preventDefault();
                e.stopPropagation();
                closeAllMenus();
                runFeedAction(article, btn, { allCarousel: false });
            };

            wrap.append(btn, menu);

            // Integrate into section alongside bookmark/save or at the end
            const bookmarkIcon = section.querySelector('svg[aria-label*="Save"], svg[aria-label*="บันทึก"], svg[aria-label*="Remove"], svg[aria-label*="ลบออก"], polygon');
            const bookmarkContainer = bookmarkIcon ? bookmarkIcon.closest('button, div[role="button"]') : null;
            if (bookmarkContainer && bookmarkContainer.parentElement) {
                bookmarkContainer.parentElement.insertBefore(wrap, bookmarkContainer);
            } else {
                const lastChild = section.lastElementChild;
                if (lastChild && lastChild !== section) {
                    lastChild.appendChild(wrap);
                } else {
                    section.appendChild(wrap);
                }
            }
        });
    }

    function closeAllMenus() {
        document.querySelectorAll('.maxpland-action-menu.show').forEach(m => m.classList.remove('show'));
    }

    async function runFeedAction(article, btn, options) {
        if (btn.classList.contains('loading')) return;
        btn.classList.add('loading');
        const origIcon = btn.innerHTML;
        btn.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#38bdf8" stroke-width="2.5"><circle cx="12" cy="12" r="9" stroke-dasharray="28" stroke-linecap="round"/></svg>`;
        try {
            await handleDirectMediaDownload(article, options);
        } catch (err) {
            alert(err.message || String(err));
        } finally {
            btn.classList.remove('loading');
            btn.innerHTML = origIcon;
        }
    }

    async function handleDirectMediaDownload(article, { allCarousel = false } = {}) {
        const accountId = IgBridge.getCookie('ds_user_id');
        IgBridge.assertAccount(accountId);
        const shortcode = shortcodeFromArticle(article);
        if (shortcode) {
            try {
                const results = await downloadByShortcode(shortcode, { allCarousel, skipExisting: false });
                const done = results.filter(x => x.status === 'done').length;
                if (!done) throw new Error('No downloadable media stream found');
                if (typeof GM_notification === 'function') GM_notification({ title: APP_CONFIG.APP_NAME, text: `Downloaded ${done} file(s) from ${shortcode}`, timeout: 2500 });
                return;
            } catch (err) {
                IgBridge.assertAccount(accountId);
                if (IgBridge.isSessionError(err) || allCarousel) throw err;
                console.warn('[IG MaxPland] API Resolver failed, trying DOM fallback');
            }
        }

        // DOM Fallback
        IgBridge.assertAccount(accountId);
        const video = article.querySelector('video');
        const img = article.querySelector('img[srcset]') || article.querySelector('img');
        const mediaUrl = video?.currentSrc || video?.src || img?.currentSrc || img?.src || null;
        if (!mediaUrl || !/^https?:\/\//i.test(mediaUrl)) {
            alert('No downloadable media URL found for this post');
            return;
        }
        const isVideo = Boolean(video && (video.currentSrc || video.src));
        const ext = extensionFromUrl(mediaUrl, isVideo ? 'mp4' : 'jpg');
        const filename = `IG_MaxPland_${Date.now()}.${ext}`;
        try { await gmDownload(mediaUrl, filename); }
        catch (_) { window.open(mediaUrl, '_blank', 'noopener,noreferrer'); }
    }

    function openMediaDirectLink(article) {
        const video = article.querySelector('video');
        const img = article.querySelector('img[srcset]') || article.querySelector('img');
        const mediaUrl = video?.currentSrc || video?.src || img?.currentSrc || img?.src || null;
        if (mediaUrl) window.open(mediaUrl, '_blank', 'noopener,noreferrer');
        else alert('No media URL found for this item');
    }

    // Story & Highlight Tools
    // ponytail: resolveCurrentStoryMedia extracts pristine image/poster directly from center active story section
    // skipped: video stream scraping and open-tab actions to prevent UI freezing and blob errors; add when Instagram opens unencrypted public streams
    function findCenterElement(selector, root = document) {
        const viewportCenterX = (window.innerWidth || 800) / 2;
        let best = null;
        let minDistance = Infinity;

        for (const el of root.querySelectorAll(selector)) {
            if (typeof el.checkVisibility === 'function' && !el.checkVisibility({ checkVisibilityCSS: true })) continue;
            if (typeof el.getBoundingClientRect !== 'function') continue;
            const r = el.getBoundingClientRect();
            if (r.width <= 0 || r.height <= 0) continue;
            if (r.bottom <= 0 || r.top >= (window.innerHeight || 600)) continue;

            const elCenterX = (r.left + r.right) / 2;
            const dist = Math.abs(elCenterX - viewportCenterX);
            if (dist < minDistance) {
                minDistance = dist;
                best = el;
            }
        }
        return best;
    }

    function getActiveStorySection() {
        const viewportCenterX = (window.innerWidth || 800) / 2;
        const sections = [...document.querySelectorAll('section')];
        let best = null;
        let minDistance = Infinity;

        for (const sec of sections) {
            if (typeof sec.checkVisibility === 'function' && !sec.checkVisibility({ checkVisibilityCSS: true })) continue;
            if (typeof sec.getBoundingClientRect !== 'function') continue;
            const r = sec.getBoundingClientRect();
            if (r.width < 50 || r.height < 50) continue;
            if (r.bottom <= 0 || r.top >= (window.innerHeight || 600)) continue;

            const secCenterX = (r.left + r.right) / 2;
            const dist = Math.abs(secCenterX - viewportCenterX);
            if (dist < minDistance) {
                minDistance = dist;
                best = sec;
            }
        }
        return best;
    }

    function visibleStoryElement(selector) {
        const activeSec = getActiveStorySection();
        if (activeSec && typeof activeSec.querySelector === 'function') {
            const inner = activeSec.querySelector(selector);
            if (inner) return inner;
        }
        return findCenterElement(selector);
    }

    function pickStoryMedia(selectorVideo, selectorImg) {
        const activeSection = getActiveStorySection();

        // 1. Video in active section or centered
        let video = (typeof activeSection?.querySelector === 'function' ? activeSection.querySelector('video') : null) || null;
        if (!video) {
            video = findCenterElement(selectorVideo || 'section video, video');
        }

        // 2. Story image in active section or centered (avatar-proof)
        let img = null;
        const imgCandidates = (activeSection && typeof activeSection.querySelectorAll === 'function')
            ? activeSection.querySelectorAll(selectorImg || 'img._aa63, img[crossorigin], img[referrerpolicy], img')
            : document.querySelectorAll(selectorImg || 'section img._aa63, section img[crossorigin], section img[referrerpolicy], img');

        let maxArea = 0;
        const viewportCenterX = (window.innerWidth || 800) / 2;

        for (const candidate of imgCandidates) {
            if (typeof candidate.checkVisibility === 'function' && !candidate.checkVisibility({ checkVisibilityCSS: true })) continue;
            if (typeof candidate.getBoundingClientRect !== 'function') continue;
            const r = candidate.getBoundingClientRect();
            if (r.width <= 0 || r.height <= 0) continue;
            if (r.bottom <= 0 || r.top >= (window.innerHeight || 600)) continue;

            const isAvatar = (r.width <= 64 && r.height <= 64) || (typeof candidate.closest === 'function' && candidate.closest('header'));
            if (isAvatar) continue;

            if (!activeSection) {
                const cX = (r.left + r.right) / 2;
                if (Math.abs(cX - viewportCenterX) > 350) continue;
            }

            const area = r.width * r.height;
            if (area > maxArea) {
                maxArea = area;
                img = candidate;
            }
        }

        if (!img && !video) {
            img = findCenterElement(selectorImg || 'img');
        }

        let mediaUrl = video?.getAttribute?.('poster') || video?.poster || img?.currentSrc || img?.src || '';
        if (!/^https?:\/\//i.test(mediaUrl)) mediaUrl = '';
        return { video, img, mediaUrl, activeSection };
    }

    // ponytail: extractMediaFromFiber walks React Fiber return chain with strict bounds (no recursion, zero UI freeze)
    // skipped: recursive fiber graph walking to prevent browser freeze; add when React moves item props off parent return chain
    function extractMediaFromFiber(el) {
        if (!el) return null;
        const keys = Object.keys(el);
        const fiberKey = keys.find(k => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'));
        const propsKey = keys.find(k => k.startsWith('__reactProps$'));

        if (propsKey && el[propsKey]?.item) {
            const item = el[propsKey].item;
            if (item.video_versions || item.image_versions2) {
                const isVideo = Boolean(item.video_versions && item.video_versions.length > 0);
                const videoUrl = item.video_versions?.slice()?.sort((a, b) => (b.width * b.height) - (a.width * a.height))[0]?.url || null;
                const imageUrl = item.image_versions2?.candidates?.slice()?.sort((a, b) => (b.width * b.height) - (a.width * a.height))[0]?.url || null;
                const url = isVideo ? (videoUrl || imageUrl) : (imageUrl || videoUrl);
                if (url) return { url, isVideo, id: item.id || item.pk, source: 'fiber-props' };
            }
        }

        if (fiberKey) {
            let curr = el[fiberKey];
            let depth = 0;
            while (curr && depth < 30) {
                const item = curr.memoizedProps?.item || curr.pendingProps?.item || curr.props?.item;
                if (item && (item.video_versions || item.image_versions2)) {
                    const isVideo = Boolean(item.video_versions && item.video_versions.length > 0);
                    const videoUrl = item.video_versions?.slice()?.sort((a, b) => (b.width * b.height) - (a.width * a.height))[0]?.url || null;
                    const imageUrl = item.image_versions2?.candidates?.slice()?.sort((a, b) => (b.width * b.height) - (a.width * a.height))[0]?.url || null;
                    const url = isVideo ? (videoUrl || imageUrl) : (imageUrl || videoUrl);
                    if (url) return { url, isVideo, id: item.id || item.pk, source: 'fiber' };
                }
                curr = curr.return;
                depth++;
            }
        }
        return null;
    }

    // ponytail: resolveCurrentStoryMedia extracts pristine 1080p story MP4/JPG via bounded Fiber, network registry, or DOM
    // skipped: third-party proxy fetchers to prevent auth leak and 429 checkpoint; add when client-side extraction fails entirely
    async function resolveCurrentStoryMedia(isThumb = false) {
        const activeSection = getActiveStorySection();
        const { video, img } = pickStoryMedia('section video', 'section img._aa63, section img[crossorigin], section img[referrerpolicy]');

        // 1. Direct video poster from active story if specifically requested
        if (isThumb) {
            const poster = video?.getAttribute?.('poster') || video?.poster;
            if (poster && /^https?:\/\//i.test(poster)) {
                return { url: poster, isVideo: false, source: 'dom-poster', isBlob: false };
            }
        }

        // 2. React Fiber extraction (lightning fast, bounded parent return loop, 0ms, zero-freeze)
        const fiberCandidates = [video, img, activeSection].filter(Boolean);
        for (const cand of fiberCandidates) {
            const fiber = extractMediaFromFiber(cand);
            if (fiber?.url) {
                if (isThumb && fiber.source.includes('fiber')) {
                    const poster = video?.getAttribute?.('poster') || video?.poster;
                    if (poster && /^https?:\/\//i.test(poster)) return { url: poster, isVideo: false, source: 'dom-poster', isBlob: false };
                }
                return fiber;
            }
        }

        // 3. Network Cache lookup (StoryMediaRegistry)
        let mediaId = null;
        if (activeSection && typeof activeSection.querySelector === 'function') {
            const storyLink = activeSection.querySelector('a[href*="/stories/"]');
            const hrefMatch = storyLink?.getAttribute?.('href')?.match(/\/stories\/[^\/]+\/(\d+)/);
            if (hrefMatch) mediaId = hrefMatch[1];
        }
        if (!mediaId) {
            const pathMatch = location.pathname.match(/\/stories\/[^\/]+\/(\d+)/);
            if (pathMatch) mediaId = pathMatch[1];
        }

        if (mediaId && StoryMediaRegistry.items.has(mediaId)) {
            const cached = StoryMediaRegistry.items.get(mediaId);
            if (isThumb && cached.imageUrl) return { url: cached.imageUrl, isVideo: false, source: 'cache', isBlob: false };
            const isVideo = Boolean(cached.isVideo && cached.videoUrl);
            const url = isVideo ? cached.videoUrl : (cached.imageUrl || cached.videoUrl);
            if (url) return { url, isVideo, source: 'cache', isBlob: false };
        }

        // 4. Native DOM fallback
        if (!isThumb && video) {
            const vSrc = video.currentSrc || video.src || '';
            if (vSrc && /^https?:\/\//i.test(vSrc) && !vSrc.startsWith('blob:')) {
                return { url: vSrc, isVideo: true, source: 'dom-video', isBlob: false };
            }
            const srcEl = typeof video.querySelector === 'function' ? video.querySelector('source') : null;
            if (srcEl?.src && /^https?:\/\//i.test(srcEl.src) && !srcEl.src.startsWith('blob:')) {
                return { url: srcEl.src, isVideo: true, source: 'dom-source', isBlob: false };
            }
        }
        if (img) {
            const imgUrl = img.currentSrc || img.src || '';
            if (imgUrl && /^https?:\/\//i.test(imgUrl)) {
                return { url: imgUrl, isVideo: false, source: 'dom-img', isBlob: false };
            }
        }

        // 5. API Fallback (IgBridge.fetchMediaInfo)
        if (mediaId) {
            try {
                const item = await IgBridge.fetchMediaInfo(mediaId);
                const isVideo = Boolean(item.video_versions && item.video_versions.length > 0);
                const videoUrl = IgBridge.bestProgressiveVideo(item);
                const imageUrl = IgBridge.bestImage(item);
                if (isThumb && imageUrl) return { url: imageUrl, isVideo: false, source: 'api', isBlob: false };
                const url = isVideo ? (videoUrl || imageUrl) : (imageUrl || videoUrl);
                if (url) return { url, isVideo, source: 'api', isBlob: false };
            } catch (err) {
                console.warn('[MaxPland] Story API info unavailable', err);
            }
        }

        // 6. Video poster fallback if video blob could not be resolved
        const poster = video?.getAttribute?.('poster') || video?.poster;
        if (poster && /^https?:\/\//i.test(poster)) {
            return { url: poster, isVideo: false, source: 'dom-poster', isBlob: false };
        }

        return { url: null, isVideo: Boolean(video), isBlob: Boolean(video?.currentSrc?.startsWith('blob:')), source: 'none' };
    }

    // ponytail: Story toolbar: Stealth Seen toggle + Open Raw + Viewers (3 controls)
    // skipped: story media downloads removed per user direction due to unstable Instagram MSE blob stream encryption; add when a non-fragile public media API is available
    function injectStoryBar() {
        if (!location.pathname.startsWith('/stories/')) {
            const existing = document.getElementById('maxpland-story-bar');
            if (existing) existing.remove();
            return;
        }

        if (STATE.prefs && STATE.prefs.quickDownloadStory === false) {
            const existing = document.getElementById('maxpland-story-bar');
            if (existing) existing.remove();
            return;
        }

        StoryMediaRegistry.scanDomScripts();

        if (document.getElementById('maxpland-story-bar')) return;

        const storyContainer = visibleStoryElement('section');
        if (!storyContainer) return;

        const bar = document.createElement('div');
        bar.id = 'maxpland-story-bar';
        bar.className = 'maxpland-story-tools';

        const stealthBtn = document.createElement('button');
        stealthBtn.type = 'button';
        stealthBtn.id = 'maxpland-story-stealth-toggle';
        stealthBtn.className = 'maxpland-story-btn maxpland-story-stealth-btn';
        const isStealth = STATE.prefs?.stealthStory !== false;
        stealthBtn.style.background = isStealth ? 'rgba(16, 185, 129, 0.2)' : 'rgba(239, 68, 68, 0.2)';
        stealthBtn.style.border = `1px solid ${isStealth ? 'rgba(16, 185, 129, 0.4)' : 'rgba(239, 68, 68, 0.4)'}`;
        stealthBtn.innerHTML = `<span>${isStealth ? '👁️ Stealth: ON' : '👁️ Stealth: OFF'}</span>`;
        stealthBtn.title = isStealth ? 'Stealth Story Viewer active (Seen beacon blocked)' : 'Stealth Story Viewer off (Seen beacon sent normally)';
        stealthBtn.onclick = () => {
            const next = !(STATE.prefs?.stealthStory !== false);
            STATE.prefs.stealthStory = next;
            savePrefs();
            stealthBtn.style.background = next ? 'rgba(16, 185, 129, 0.2)' : 'rgba(239, 68, 68, 0.2)';
            stealthBtn.style.border = `1px solid ${next ? 'rgba(16, 185, 129, 0.4)' : 'rgba(239, 68, 68, 0.4)'}`;
            stealthBtn.innerHTML = `<span>${next ? '👁️ Stealth: ON' : '👁️ Stealth: OFF'}</span>`;
            stealthBtn.title = next ? 'Stealth Story Viewer active (Seen beacon blocked)' : 'Stealth Story Viewer off (Seen beacon sent normally)';
            const modalSwitch = document.getElementById('pref-switch-stealth-story');
            if (modalSwitch) modalSwitch.checked = next;
            showToast(next ? 'Stealth Story Viewer enabled (Seen blocked)' : 'Stealth Story Viewer disabled');
        };

        // ponytail: story DOWNLOAD buttons stay removed (fragile per v2.7.2), but "Open Raw"
        // only hands the resolved URL to a new tab — no download pipeline to break.
        // Technique adapted from navchandar/Instagram_Story_Saver, re-written around our
        // 5-layer resolver instead of blind parentNode climbing.
        const openBtn = document.createElement('button');
        openBtn.type = 'button';
        openBtn.id = 'maxpland-story-open-btn';
        openBtn.className = 'maxpland-story-btn';
        openBtn.innerHTML = '<span>↗ Open Raw</span>'; // literal-only markup, no user-controlled data (same pattern as stealthBtn)
        openBtn.title = 'Open the current story media URL in a new tab (save from there manually)';
        const viewersBtn = document.createElement('button');
        viewersBtn.type = 'button';
        viewersBtn.id = 'maxpland-story-viewers-btn';
        viewersBtn.className = 'maxpland-story-btn';
        viewersBtn.innerHTML = '<span>👁 Viewers</span>'; // literal-only markup, same pattern as siblings
        viewersBtn.title = 'Who viewed this story (your own stories only)';
        viewersBtn.onclick = async () => {
            viewersBtn.disabled = true;
            try {
                const mediaId = (() => {
                    const active = typeof getActiveStorySection === 'function' ? getActiveStorySection() : null;
                    const link = active?.querySelector?.('a[href*="/stories/"]');
                    const m = (link?.getAttribute?.('href') || location.pathname).match(/\/stories\/[^\/]+\/(\d+)/);
                    return m ? m[1] : null;
                })();
                if (!mediaId) { showToast('Open a specific story first (no media id in view)'); return; }
                showToast('Loading story viewers...');
                const viewers = await IgBridge.fetchStoryViewers(mediaId);
                renderViewerPanel(viewersBtn, viewers);
            } catch (err) {
                console.warn('[MaxPland] Story viewers failed', err);
                showToast('Viewers unavailable — works on your own stories only');
            } finally {
                viewersBtn.disabled = false;
            }
        };

        openBtn.onclick = async () => {
            // Open the tab synchronously inside the click gesture to dodge popup blockers,
            // then fill in the real URL once the resolver settles.
            const win = window.open('about:blank', '_blank', 'noopener');
            try {
                const media = await resolveCurrentStoryMedia();
                if (win && media?.url && !media.isBlob) {
                    win.location.href = media.url;
                    return;
                }
                if (win) win.close();
                showToast('Raw media URL not available for this story');
            } catch (err) {
                console.warn('[MaxPland] Open raw story URL failed', err);
                if (win) win.close();
                showToast('Could not resolve story media URL');
            }
        };

        bar.append(stealthBtn, openBtn, viewersBtn);
        document.body.appendChild(bar);
    }

    // Profile HD Avatar Downloader
    function injectProfileAvatarBadge() {
        const pathParts = location.pathname.split('/').filter(Boolean);
        if (pathParts.length !== 1 || ['explore','stories','reels','direct','accounts'].includes(pathParts[0])) {
            const b = document.getElementById('maxpland-profile-avatar-btn');
            if (b) b.remove();
            return;
        }

        if (document.getElementById('maxpland-profile-avatar-btn')) return;

        const headerActions = document.querySelector('header section') || document.querySelector('header h2')?.parentElement;
        if (!headerActions) return;

        const uname = pathParts[0];
        const btn = document.createElement('button');
        btn.id = 'maxpland-profile-avatar-btn';
        btn.type = 'button';
        btn.className = 'maxpland-avatar-badge';
        btn.title = `Download HD profile picture of @${uname}`;
        btn.innerHTML = ICONS.AVATAR + '<span>HD Avatar</span>';
        btn.onclick = async () => {
            btn.disabled = true;
            btn.textContent = 'Fetching HD...';
            try {
                const hdUrl = await IgBridge.fetchUserProfileHD(uname);
                const fallbackImg = document.querySelector('header img[alt*="profile"]');
                const finalUrl = hdUrl || fallbackImg?.currentSrc || fallbackImg?.src;
                if (!finalUrl) throw new Error('Profile avatar not found');
                const filename = `${uname}_profile_hd_${Date.now()}.jpg`;
                await gmDownload(finalUrl, filename);
                showToast(`HD Avatar of @${uname} downloaded successfully`);
            } catch (err) {
                alert(`Failed to download profile avatar: ${err.message || err}`);
            } finally {
                btn.disabled = false;
                btn.innerHTML = ICONS.AVATAR + '<span>HD Avatar</span>';
            }
        };

        headerActions.appendChild(btn);
    }

    function startPageObserver() {
        let timer = null;
        const observer = new MutationObserver(() => {
            if (timer) return;
            timer = setTimeout(() => {
                timer = null;
                const path = location.pathname;
                injectStoryBar();
                if (!path.startsWith('/stories/')) {
                    injectInFeedDownloadButtons();
                    injectProfileAvatarBadge();
                }
            }, 300);
        });
        // ponytail: story bar / feed buttons / avatar badge all live under <main>;
        // the profile header badge lives under <header>. Observe both roots and
        // fall back to body when either is missing (degrades to the old scope).
        const pageRoots = [document.querySelector('main'), document.querySelector('header')].filter(Boolean);
        for (const root of (pageRoots.length ? pageRoots : [document.body])) {
            observer.observe(root, { childList: true, subtree: true });
        }
    }

    /* ==========================================================================
       12. INITIALIZATION
       ========================================================================== */

    async function init() {
        if (document.getElementById('maxpland-trigger-btn')) return;
        createUI();
        if (typeof GM_registerMenuCommand === 'function') {
            GM_registerMenuCommand('Open IG MaxPland', () => {
                if (typeof window.openMaxPlandStudio === 'function') {
                    window.openMaxPlandStudio();
                } else {
                    document.getElementById('maxpland-trigger-btn')?.click();
                }
            });
        }
        try {
            await MaxPlandVault.init();
            STATE.whitelist = await MaxPlandVault.getWhitelist();
            const whiteCountEl = document.getElementById('pill-count-white');
            if (whiteCountEl) whiteCountEl.textContent = STATE.whitelist.size.toLocaleString();
        } catch (err) {
            console.warn('[MaxPland] Local vault unavailable', err);
        }

        installStorySeenInterceptor();
        applyCleanFeedMode();

        injectInFeedDownloadButtons();
        injectStoryBar();
        injectProfileAvatarBadge();
        startPageObserver();
        document.addEventListener('click', () => closeAllMenus());
    }

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        init();
    } else {
        window.addEventListener('DOMContentLoaded', init);
    }

})();