DF ExtraButtons

Extra buttons for Dead Frontier

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         DF ExtraButtons
// @namespace    http://tampermonkey.net/
// @version      1.0.2
// @description  Extra buttons for Dead Frontier
// @author       D1N0
// @match        *://fairview.deadfrontier.com/onlinezombiemmo/*
// @icon         https://www.favicon.cc/favicon/336/1014/favicon.ico
// @run-at       document-start
// @grant        GM_setValue
// @grant        GM_getValue
// @license      GPL-3.0-or-later
// ==/UserScript==

/*
 * Created by D1N0 and inspired by community scripts
 * DFP: https://www.dfprofiler.com/profile/view/12191879
 *
 * ═══════════════════════════════════════════════════════════════════════════
 * DF ExtraButtons
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * WHAT THIS SCRIPT DOES:
 * ----------------------
 *
 * Replaces Dead Frontier's default navigation bar with a custom one that matches
 * the game's visual style. It adds quick-access buttons for all major in-game
 * pages (Marketplace, Bank, Storage, Crafting, etc.), a WIKI dropdown with links
 * to the community wiki, and compact icon buttons for Credit Shop, Help, and Logout.
 *
 * Buttons that require an Inner City trade zone (Clan, The Yard, Vendor) are automatically
 * disabled or enabled based on the player's current zone.
 *
 * The CITY button handles Inner City navigation correctly, since the game engine.
 *
 * Custom header and menu background images are also applied on every page load.
 *
 * All styles are fully scoped to avoid any conflict with the game's own CSS.
 *
 */


(() => {
    'use strict';

    // ─── Constants ───────────────────────────────────────────────────────
    const BASE_URL = 'https://fairview.deadfrontier.com/onlinezombiemmo/';
    const CLAN_ID_DEFAULT = '-1';

    // ─── Early: hide original nav immediately to prevent flash ─────────
    const earlyStyle = document.createElement('style');
    earlyStyle.textContent = '#outpostnavigationheaders, #outpostnavigationheaders * { display: none !important; }';
    (document.head || document.documentElement).appendChild(earlyStyle);

    // ─── State ───────────────────────────────────────────────────────────
    let clanId = CLAN_ID_DEFAULT;
    let clanName = '';

    // ─── Clan Cache (persisted via GM storage, keyed by user ID) ─────────
    const loadCachedClan = () => {
        const cached = GM_getValue('dfbm_clan', null);
        if (!cached) return;

        // Invalidate cache if user changed (different account)
        const currentUserId = unsafeWindow.userVars?.DFSTATS_id_member;
        if (currentUserId && cached.userId && cached.userId !== currentUserId) {
            GM_setValue('dfbm_clan', null);
            return;
        }

        clanId = cached.id ?? CLAN_ID_DEFAULT;
        clanName = cached.name ?? '';
        console.log('[DFBM] Clan loaded from cache:', clanId, clanName);
    };

    const saveClanCache = () => {
        const userId = unsafeWindow.userVars?.DFSTATS_id_member ?? null;
        GM_setValue('dfbm_clan', { id: clanId, name: clanName, userId });
    };

    // ─── URL Helpers ─────────────────────────────────────────────────────
    const getBasePath = () => window.location.href.replace(BASE_URL, '');

    const isHomePage = (path = getBasePath()) =>
        path === '' || path === '/' || /^index\.php(\?)?$/.test(path);

    const isActive = (href) => {
        if (!href) return false;
        const path = getBasePath();
        if (href === 'index.php') return isHomePage(path);
        if (href.includes('action=')) return path.includes(href.split('?')[1]);
        return path === href;
    };

    const isLoginPage = () => {
        const params = new URLSearchParams(window.location.search);
        return params.get('page') === '1' || params.get('action') === 'login';
    };

    // ─── DOM Helpers ─────────────────────────────────────────────────────
    const createElement = (tag, attrs = {}, children = []) => {
        const el = document.createElement(tag);
        for (const [key, val] of Object.entries(attrs)) {
            if (key === 'className') el.className = val;
            else if (key === 'textContent') el.textContent = val;
            else if (key === 'disabled') el.disabled = val;
            else if (key.startsWith('on')) el.addEventListener(key.slice(2).toLowerCase(), val);
            else el.setAttribute(key, val);
        }
        children.forEach(child => {
            if (typeof child === 'string') el.appendChild(document.createTextNode(child));
            else if (child) el.appendChild(child);
        });
        return el;
    };

    const makeButton = (label, extraAttrs = {}) =>
        createElement('button', { textContent: label, ...extraAttrs });

    const makeAnchor = (href, classes = [], external = false) => {
        const attrs = { className: ['dfbm-btn', ...classes].join(' ') };
        if (href) attrs.href = href;
        if (external) attrs.target = '_blank';
        return createElement('a', attrs);
    };

    // ─── Logout Href Extraction ──────────────────────────────────────────
    const extractLogoutHref = () => {
        const nav = document.getElementById('outpostnavigationheaders');
        if (!nav) return null;
        const anchor = [...nav.querySelectorAll('a')].find(a => a.href.includes('action=logout'));
        return anchor?.href ?? null;
    };

    // ─── Trade Zone Detection ────────────────────────────────────────────
    const resolveTradeZone = () => {
        const { userVars } = unsafeWindow;
        let tradeZone = GM_getValue('tradeZone', null);

        const freshZone = userVars?.DFSTATS_df_tradezone ?? userVars?.df_tradezone;
        if (freshZone) {
            tradeZone = freshZone;
            GM_setValue('tradeZone', tradeZone);
        }

        return tradeZone;
    };

    const isSpecialZone = (zone) => zone === '21' || zone === '22';

    // ─── Clan Data Fetch (background refresh) ──────────────────────────
    const fetchClanData = () => {
        const previousId = clanId;
        const previousName = clanName;

        return fetch(window.location.href, { cache: 'force-cache' })
            .then(r => r.text())
            .then(html => {
                clanId = html.match(/DFSTATS_df_clan_id=([^&]+)/)?.[1] ?? CLAN_ID_DEFAULT;
                clanName = html.match(/DFSTATS_df_clan_name=([^&]+)/)?.[1] ?? '';
                saveClanCache();

                const changed = clanId !== previousId || clanName !== previousName;
                if (changed) console.log('[DFBM] Clan data changed:', previousId, '→', clanId);
                return changed;
            });
    };

    const hasClan = () => clanId !== CLAN_ID_DEFAULT && clanName !== '';

    // ─── Button Configurations ───────────────────────────────────────────
    const WIKI_LINKS = [
        { label: 'WEAPONS', href: 'https://deadfrontier.fandom.com/wiki/Category:Weapons' },
        { label: 'ARMOUR', href: 'https://deadfrontier.fandom.com/wiki/Armour' },
        { label: 'IMPLANTS', href: 'https://deadfrontier.fandom.com/wiki/Implants' },
        { label: 'CLOTHING', href: 'https://deadfrontier.fandom.com/wiki/Clothing' },
        { label: 'MISSIONS', href: 'https://deadfrontier.fandom.com/wiki/Missions' },
        { label: 'CITY MAP', href: 'https://deadfrontier.fandom.com/wiki/Map' },
        { label: 'BESTIARY', href: 'https://deadfrontier.fandom.com/wiki/Bestiary' },
        { label: 'STORY', href: 'https://deadfrontier.fandom.com/wiki/Category:Background_Story' },
        { label: 'STATS & LEVEL', href: 'https://deadfrontier.fandom.com/wiki/Stats_and_Levels' },
        { label: 'BUILD GUIDE', href: 'https://deadfrontier.fandom.com/wiki/Character_Build_Guide' },
        { label: 'PROFESSIONS', href: 'https://deadfrontier.fandom.com/wiki/Professions' },
        { label: 'TIPS & TACTICS', href: 'https://deadfrontier.fandom.com/wiki/Tips_and_Tactics' },
        { label: 'DFPROFILER', href: 'https://www.dfprofiler.com/' },
    ];

    const CLAN_LINKS = [
        { label: 'STORAGE / BANK', href: 'index.php?page=89' },
        { label: 'MACHINES', href: 'index.php?page=93' },
    ];

    const buildLeftButtons = (specialZone, inClan) => [
        { label: 'OUTPOST', href: 'index.php' },
        { label: 'MARKETPLACE', href: 'index.php?page=35' },
        { label: 'BANK', href: 'index.php?page=15' },
        { label: 'STORAGE', href: 'index.php?page=50' },
        ...(inClan ? [{ label: 'CLAN', href: null, hasClanDropdown: true, disabled: !specialZone }] : []),
        { label: 'THE YARD', href: 'index.php?page=24', disabled: !specialZone },
        { label: 'CRAFTING', href: 'index.php?page=59' },
        { label: 'VENDOR', href: 'index.php?page=84', disabled: !specialZone },
        { label: 'CITY', href: null, isCity: true },
    ];

    const buildRightButtons = (logoutHref) => [
        { label: 'FORUM', href: 'index.php?action=forum' },
        { label: 'WIKI', href: 'https://deadfrontier.fandom.com/wiki/Dead_Frontier_Wiki', external: true, hasDropdown: true },
        { label: '$', href: 'index.php?page=28', tooltip: 'CREDIT SHOP', icon: true },
        { label: '?', href: 'index.php?page=53', tooltip: 'HELP', icon: true },
        { label: '✕', href: logoutHref, tooltip: 'LOGOUT', icon: true, isLogout: true },
    ];

    // ─── Dropdown Builders ───────────────────────────────────────────────
    const buildDropdownPanel = (links, className, options = {}) => {
        const panel = createElement('div', { className });
        links.forEach((item, i) => {
            const attrs = { href: item.href, textContent: item.label };
            if (options.external) attrs.target = '_blank';
            const link = createElement('a', attrs);
            if (options.lastClass && i === links.length - 1) link.classList.add(options.lastClass);
            panel.appendChild(link);
        });
        return panel;
    };

    const buildClanWrap = (btn) => {
        const wrap = createElement('div', { className: 'dfbm-clan-wrap' });
        if (btn.disabled) wrap.classList.add('dfbm-clan-disabled');

        const clanBtn = makeButton(btn.label, { disabled: btn.disabled });
        const panel = buildDropdownPanel(CLAN_LINKS, 'dfbm-clan-dropdown');

        const isClanActive = CLAN_LINKS.some(item => isActive(item.href));
        if (isClanActive && !btn.disabled) clanBtn.classList.add('dfbm-clan-active');

        if (!btn.disabled) {
            clanBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                wrap.classList.toggle('dfbm-clan-open');
            });
            document.addEventListener('click', () => wrap.classList.remove('dfbm-clan-open'));
        }

        wrap.append(clanBtn, panel);
        return wrap;
    };

    const buildWikiWrap = (btn) => {
        const wrap = createElement('div', { className: 'dfbm-wiki-wrap' });

        const a = createElement('a', { href: btn.href, target: '_blank' }, [makeButton(btn.label)]);
        wrap.appendChild(a);

        const panel = buildDropdownPanel(WIKI_LINKS, 'dfbm-wiki-dropdown', {
            external: true,
            lastClass: 'dfbm-wiki-more',
        });
        wrap.appendChild(panel);

        return wrap;
    };

    // ─── City Button Handler ─────────────────────────────────────────────
    const buildCityButton = () => {
        const a = makeAnchor(null, ['dfbm-city']);
        const button = createElement('button', {}, [
            createElement('span', { textContent: 'CITY' }),
            createElement('span', { className: 'dfbm-city-arrow', textContent: '›' }),
        ]);

        button.addEventListener('click', () => {
            unsafeWindow.playSound?.('outpost');
            if (isHomePage() && unsafeWindow.doPageChange) {
                setTimeout(() => unsafeWindow.doPageChange(21, 1, false), 1000);
            } else {
                GM_setValue('dfbm_redirectToCity', true);
                setTimeout(() => { window.location.href = `${BASE_URL}index.php`; }, 1000);
            }
        });

        a.appendChild(button);
        return a;
    };

    // ─── Logout Button Handler ──────────────────
    const buildLogoutAnchor = (btn) => {
        const classes = ['dfbm-icon', 'dfbm-logout'];
        const a = makeAnchor(null, classes);
        const button = makeButton(btn.label);

        if (btn.tooltip) {
            button.title = btn.tooltip;
            a.title = btn.tooltip;
        }

        a.addEventListener('click', (e) => {
            e.preventDefault();
            if (btn.href) {
                // Clear any script-specific GM state that could interfere on next login
                GM_setValue('dfbm_redirectToCity', false);
                window.location.assign(btn.href);
            }
        });

        a.appendChild(button);
        return a;
    };

    // ─── Build Left Side ─────────────────────────────────────────────────
    const populateLeftButtons = (container, buttons) => {
        for (const btn of buttons) {
            if (btn.hasClanDropdown) {
                container.appendChild(buildClanWrap(btn));
                continue;
            }

            if (btn.isCity) {
                container.appendChild(buildCityButton());
                continue;
            }

            const classes = [];
            if (btn.disabled) classes.push('dfbm-disabled');
            if (!btn.disabled && isActive(btn.href)) classes.push('dfbm-active');

            const a = makeAnchor(btn.href, classes);
            const button = makeButton(btn.label, { disabled: btn.disabled || false });
            a.appendChild(button);
            container.appendChild(a);
        }
    };

    // ─── Build Right Side ────────────────────────────────────────────────
    const populateRightButtons = (container, buttons) => {
        for (const btn of buttons) {
            if (btn.hasDropdown) {
                container.appendChild(buildWikiWrap(btn));
                continue;
            }

            if (btn.isLogout) {
                container.appendChild(buildLogoutAnchor(btn));
                continue;
            }

            const classes = [];
            if (btn.icon) classes.push('dfbm-icon');
            if (isActive(btn.href)) classes.push('dfbm-active');

            const a = makeAnchor(btn.href, classes, btn.external);
            const button = makeButton(btn.label);

            if (btn.tooltip) {
                button.title = btn.tooltip;
                a.title = btn.tooltip;
            }

            a.appendChild(button);
            container.appendChild(a);
        }
    };

    // ─── Post-insert DOM adjustments ─────────────────────────────────────
    const hideOriginalNav = (navEl) => {
        navEl.style.cssText = [
            'display:none !important',
            'visibility:hidden !important',
            'opacity:0 !important',
            'height:0 !important',
            'overflow:hidden !important',
            'position:absolute !important',
            'pointer-events:none !important',
        ].join(';');

        const navParent = navEl.parentNode;
        if (navParent && navParent.id !== 'sidebar' && navParent.tagName !== 'BODY') {
            navParent.style.overflow = 'visible';
        }
    };

    const fixOverflowAncestors = (startEl) => {
        let el = startEl;
        while (el && el !== document.body) {
            const { overflow, overflowX, overflowY } = window.getComputedStyle(el);
            if ([overflow, overflowX, overflowY].includes('hidden')) {
                el.style.overflow = 'visible';
            }
            el = el.parentElement;
        }
    };

    const injectPermanentHideStyle = () => {
        const style = createElement('style', {
            textContent: `
                #outpostnavigationheaders, #outpostnavigationheaders * {
                    display: none !important;
                    visibility: hidden !important;
                    height: 0 !important;
                    overflow: hidden !important;
                }
            `,
        });
        document.head.appendChild(style);
    };

    const replaceBackground = (selector, url) => {
        const el = document.querySelector(selector);
        if (!el) return;
        el.setAttribute('background', url);
        el.style.backgroundImage = `url("${url}")`;
    };

    // ─── City Redirect on Load ───────────────────────────────────────────
    const handleCityRedirect = () => {
        if (!GM_getValue('dfbm_redirectToCity', false) || !isHomePage()) return;

        GM_setValue('dfbm_redirectToCity', false);
        const interval = setInterval(() => {
            if (unsafeWindow.doPageChange) {
                clearInterval(interval);
                unsafeWindow.doPageChange(21, 1, false);
            }
        }, 200);
    };

    // ─── Clan Button Sync (add or remove based on fresh data) ──────────
    const syncClanButton = (clanChanged) => {
        const leftDiv = document.getElementById('dfbm-left');
        if (!leftDiv) return;

        const existingWrap = leftDiv.querySelector('.dfbm-clan-wrap');

        // If clan data didn't change and button is already correct, do nothing
        if (!clanChanged && existingWrap && hasClan()) return;
        if (!clanChanged && !existingWrap && !hasClan()) return;

        // Remove stale button if present
        if (existingWrap) existingWrap.remove();

        // Add button if currently in a clan
        if (!hasClan()) return;

        // Clan is disabled when NOT in a special zone (same as THE YARD / VENDOR)
        const tradeZone = GM_getValue('tradeZone', null);
        const clanDisabled = !isSpecialZone(tradeZone);

        const storageBtn = [...leftDiv.querySelectorAll('.dfbm-btn')]
            .find(b => b.querySelector('button')?.textContent === 'STORAGE');

        const wrap = buildClanWrap({ label: 'CLAN', disabled: clanDisabled });

        if (storageBtn) storageBtn.after(wrap);
        else leftDiv.appendChild(wrap);
    };

    // ─── CSS ─────────────────────────────────────────────────────────────
    const STYLES = `
        #dfbm-bar {
            display: flex;
            align-items: stretch;
            justify-content: space-between;
            width: 100%;
            max-width: 100%;
            box-sizing: border-box;
            overflow: visible;
            background: linear-gradient(180deg, #1a0a00 0%, #0d0500 40%, #120800 100%);
            border-bottom: 2px solid #5a1a00;
            border-top: 1px solid #3a1000;
            box-shadow: 0 3px 12px rgba(0,0,0,0.8), inset 0 1px 0 rgba(120,50,0,0.3);
            position: relative;
            z-index: 200;
            font-family: 'Arial Black', 'Arial Bold', Arial, sans-serif;
            min-height: 38px;
            padding: 0 4px;
        }

        #dfbm-bar::before {
            content: '';
            position: absolute;
            inset: 0;
            background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.04'/%3E%3C/svg%3E");
            pointer-events: none;
            z-index: 1;
        }

        #dfbm-left, #dfbm-right {
            display: flex;
            align-items: center;
            gap: 2px;
            position: relative;
            z-index: 2;
            flex-shrink: 1;
            min-width: 0;
            overflow: visible;
        }

        /* ── Base button ── */
        .dfbm-btn {
            position: relative;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            text-decoration: none !important;
            padding: 0;
            margin: 4px 1px;
            flex-shrink: 0;
        }

        .dfbm-btn button,
        .dfbm-wiki-wrap > a > button,
        .dfbm-clan-wrap > button {
            background: linear-gradient(180deg, #2a0e00 0%, #1a0800 50%, #200b00 100%);
            border: 1px solid #4a1500;
            border-top-color: #6a2500;
            border-bottom-color: #300800;
            color: #b85a20;
            font-family: 'Arial Black', 'Arial Bold', Arial, sans-serif;
            font-size: 9px;
            font-weight: 900;
            letter-spacing: 0.5px;
            padding: 6px 8px;
            cursor: pointer;
            white-space: nowrap;
            text-shadow: 0 0 8px rgba(200,80,0,0.5), 1px 1px 2px rgba(0,0,0,0.9);
            box-shadow: inset 0 1px 0 rgba(150,60,0,0.2), inset 0 -1px 0 rgba(0,0,0,0.4), 0 1px 3px rgba(0,0,0,0.6);
            transition: all 0.12s ease;
            min-width: 64px;
            height: 26px;
            position: relative;
            overflow: hidden;
            text-decoration: none;
        }

        .dfbm-btn button::before,
        .dfbm-wiki-wrap > a > button::before,
        .dfbm-clan-wrap > button::before {
            content: '';
            position: absolute;
            top: 0; left: 0; right: 0;
            height: 40%;
            background: linear-gradient(180deg, rgba(180,70,0,0.08), transparent);
            pointer-events: none;
        }

        .dfbm-btn button:hover,
        .dfbm-wiki-wrap:hover > a > button,
        .dfbm-wiki-wrap > a > button:hover,
        .dfbm-clan-wrap > button:hover,
        .dfbm-clan-wrap.dfbm-clan-open > button {
            background: linear-gradient(180deg, #3d1500 0%, #2a0d00 50%, #320f00 100%);
            border-color: #8a3000;
            border-top-color: #aa4500;
            color: #e87030;
            text-shadow: 0 0 12px rgba(255,120,0,0.8), 1px 1px 2px rgba(0,0,0,0.9);
            box-shadow: inset 0 1px 0 rgba(200,80,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.4), 0 1px 6px rgba(150,50,0,0.4);
        }

        .dfbm-btn.dfbm-active button,
        .dfbm-clan-wrap > button.dfbm-clan-active {
            background: linear-gradient(180deg, #4a1a00 0%, #3a1200 50%, #420f00 100%);
            border-color: #aa4500;
            border-top-color: #cc5500;
            color: #ff8c40;
            text-shadow: 0 0 10px rgba(255,140,50,0.9), 1px 1px 2px rgba(0,0,0,0.8);
            box-shadow: inset 0 1px 0 rgba(220,100,0,0.4), inset 0 -1px 0 rgba(0,0,0,0.5), 0 0 8px rgba(180,70,0,0.3);
        }

        /* ── Disabled state ── */
        .dfbm-btn.dfbm-disabled button,
        .dfbm-clan-wrap.dfbm-clan-disabled > button {
            opacity: 0.35;
            cursor: not-allowed;
            color: #5a2a10;
            text-shadow: none;
        }

        .dfbm-btn.dfbm-disabled button:hover,
        .dfbm-clan-wrap.dfbm-clan-disabled > button:hover {
            background: linear-gradient(180deg, #2a0e00 0%, #1a0800 50%, #200b00 100%) !important;
            border-color: #4a1500 !important;
            color: #5a2a10 !important;
            text-shadow: none !important;
            box-shadow: inset 0 1px 0 rgba(150,60,0,0.2), inset 0 -1px 0 rgba(0,0,0,0.4), 0 1px 3px rgba(0,0,0,0.6) !important;
        }

        /* ── Icon buttons ── */
        .dfbm-btn.dfbm-icon button {
            min-width: 26px;
            width: 26px;
            padding: 0;
            font-size: 13px;
            font-weight: 900;
            letter-spacing: 0;
        }

        /* ── City button ── */
        .dfbm-btn.dfbm-city button {
            min-width: 52px;
            color: #af9b6d;
        }

        .dfbm-btn.dfbm-city .dfbm-city-arrow {
            font-size: 12px;
            margin-left: 2px;
            opacity: 0.7;
            display: inline-block;
            transition: transform 0.15s ease, opacity 0.15s ease;
        }

        .dfbm-btn.dfbm-city button:hover .dfbm-city-arrow {
            transform: translateX(2px);
            opacity: 1;
        }

        /* ── Logout button ── */
        .dfbm-btn.dfbm-logout button {
            color: #8a1a1a;
            border-color: #3a0a0a;
            border-top-color: #5a1515;
        }

        .dfbm-btn.dfbm-logout button:hover {
            color: #cc3030;
            border-color: #6a1515;
            border-top-color: #8a2020;
            text-shadow: 0 0 12px rgba(255,50,50,0.7), 1px 1px 2px rgba(0,0,0,0.9);
            background: linear-gradient(180deg, #2a0a0a 0%, #1a0606 50%, #200808 100%);
        }

        /* ── Wiki dropdown ── */
        .dfbm-wiki-wrap {
            position: relative;
            display: inline-flex;
            margin: 4px 1px;
            flex-shrink: 0;
        }

        .dfbm-wiki-wrap > a {
            position: relative;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            text-decoration: none;
            padding: 0;
        }

        .dfbm-wiki-wrap > a > button::after {
            content: '▾';
            font-size: 8px;
            margin-left: 3px;
            opacity: 0.6;
            vertical-align: middle;
        }

        .dfbm-wiki-dropdown,
        .dfbm-clan-dropdown {
            display: none;
            position: absolute;
            top: 100%;
            min-width: 110px;
            background: linear-gradient(180deg, #1e0b00 0%, #110600 100%);
            border: 1px solid #5a1a00;
            border-top: none;
            box-shadow: 0 6px 20px rgba(0,0,0,0.9), inset 1px 0 0 rgba(100,40,0,0.2), inset -1px 0 0 rgba(100,40,0,0.2);
            z-index: 9999;
            flex-direction: column;
            overflow: hidden;
        }

        .dfbm-wiki-dropdown { right: 0; }
        .dfbm-clan-dropdown { left: 0; min-width: 130px; }

        .dfbm-wiki-dropdown::before,
        .dfbm-clan-dropdown::before {
            content: '';
            display: block;
            height: 1px;
            background: linear-gradient(90deg, transparent, #8a3000, transparent);
            margin: 0 8px;
        }

        .dfbm-wiki-wrap:hover .dfbm-wiki-dropdown { display: flex; }

        .dfbm-clan-wrap.dfbm-clan-open .dfbm-clan-dropdown,
        .dfbm-clan-wrap:hover .dfbm-clan-dropdown { display: flex; }

        .dfbm-clan-wrap.dfbm-clan-disabled .dfbm-clan-dropdown { display: none !important; }

        /* ── Dropdown links (shared) ── */
        .dfbm-wiki-dropdown a,
        .dfbm-clan-dropdown a {
            display: block;
            text-decoration: none;
            padding: 7px 12px;
            font-family: 'Arial Black', 'Arial Bold', Arial, sans-serif;
            font-size: 8px;
            font-weight: 900;
            letter-spacing: 0.6px;
            color: #8a3a10;
            text-shadow: 0 0 6px rgba(150,50,0,0.3), 1px 1px 2px rgba(0,0,0,0.9);
            border-bottom: 1px solid rgba(80,25,0,0.4);
            transition: all 0.1s ease;
            position: relative;
            white-space: nowrap;
        }

        .dfbm-wiki-dropdown a:last-child,
        .dfbm-clan-dropdown a:last-child { border-bottom: none; }

        .dfbm-wiki-dropdown a::before,
        .dfbm-clan-dropdown a::before {
            content: '';
            position: absolute;
            left: 0; top: 0; bottom: 0;
            width: 2px;
            background: #7a2800;
            opacity: 0;
            transition: opacity 0.1s ease;
        }

        .dfbm-wiki-dropdown a:hover,
        .dfbm-clan-dropdown a:hover {
            background: linear-gradient(90deg, rgba(60,20,0,0.8), rgba(40,12,0,0.4));
            color: #e07030;
            text-shadow: 0 0 10px rgba(220,100,0,0.7), 1px 1px 2px rgba(0,0,0,0.9);
            padding-left: 15px;
        }

        .dfbm-wiki-dropdown a:hover::before,
        .dfbm-clan-dropdown a:hover::before { opacity: 1; }

        .dfbm-wiki-dropdown a.dfbm-wiki-more {
            color: #8a1a1a;
            font-style: italic;
        }

        .dfbm-wiki-dropdown a.dfbm-wiki-more:hover { color: #cc5020; }

        /* ── Clan wrap ── */
        .dfbm-clan-wrap {
            position: relative;
            display: inline-flex;
            margin: 4px 1px;
            flex-shrink: 0;
        }

        .dfbm-clan-wrap > button::after {
            content: '▾';
            font-size: 8px;
            margin-left: 3px;
            opacity: 0.6;
            vertical-align: middle;
        }
    `;

    // ─── Main Init ───────────────────────────────────────────────────────
    const init = () => {
        if (!document.getElementById('sidebar')) return;

        // Don't run full init on login/logout pages to prevent any loop
        if (isLoginPage()) return;

        // Load cached clan data so the button appears instantly
        loadCachedClan();

        const tradeZone = resolveTradeZone();
        const specialZone = isSpecialZone(tradeZone);
        const inClan = hasClan();
        const logoutHref = extractLogoutHref();

        const leftButtons = buildLeftButtons(specialZone, inClan);
        const rightButtons = buildRightButtons(logoutHref);

        // Inject styles
        document.head.appendChild(createElement('style', { textContent: STYLES }));

        // Build bar
        const bar = createElement('div', { id: 'dfbm-bar' });
        const leftDiv = createElement('div', { id: 'dfbm-left' });
        const rightDiv = createElement('div', { id: 'dfbm-right' });

        populateLeftButtons(leftDiv, leftButtons);
        populateRightButtons(rightDiv, rightButtons);

        bar.append(leftDiv, rightDiv);

        // Insert bar & hide original nav
        const navEl = document.getElementById('outpostnavigationheaders');
        if (navEl) {
            hideOriginalNav(navEl);
            navEl.parentNode.insertBefore(bar, navEl);
        }

        fixOverflowAncestors(document.getElementById('dfbm-bar')?.parentElement);
        injectPermanentHideStyle();

        // City redirect
        handleCityRedirect();

        // Custom header images
        replaceBackground(
            'td[width="985"].design2010[background*="header.jpg"]',
            'https://i.ibb.co/1t6d7WZv/header.jpg'
        );
        replaceBackground(
            'td[width="911"].design2010[background*="menu_bottom.jpg"]',
            'https://i.ibb.co/wFLgpdbr/menu-bottom.jpg'
        );
    };

    // ─── Bootstrap ───────────────────────────────────────────────────────
    const waitForElement = (selector, timeout = 5000) => new Promise((resolve, reject) => {
        const existing = document.querySelector(selector);
        if (existing) return resolve(existing);

        const observer = new MutationObserver(() => {
            const el = document.querySelector(selector);
            if (el) {
                observer.disconnect();
                resolve(el);
            }
        });

        observer.observe(document.documentElement, { childList: true, subtree: true });

        setTimeout(() => {
            observer.disconnect();
            reject(new Error(`[DFBM] Timeout: "${selector}" not found after ${timeout}ms`));
        }, timeout);
    });

    document.addEventListener('DOMContentLoaded', async () => {
        try {
            // Wait for both key elements before initializing
            await Promise.all([
                waitForElement('#sidebar'),
                waitForElement('#outpostnavigationheaders'),
            ]);
            init();

            // Background refresh: fetch fresh clan data and sync UI if changed
            const clanChanged = await fetchClanData();
            syncClanButton(clanChanged);
        } catch (err) {
            console.warn(err.message);
            // Still try init even if one element times out (e.g. login page has no nav)
            init();
        }
    });

})();