crwflags tool

tool for crwflags.com

// ==UserScript==
// @name         crwflags tool
// @license MIT
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  tool for crwflags.com
// @author       Derus
// @match        *://www.crwflags.com/fotw/*
// @grant        GM_addStyle
// @require      https://unpkg.com/[email protected]/dist/fuse.min.js
// @run-at       document-idle
// ==/UserScript==

(async function () {
    'use strict';

    const Config = {
        BASE_URL: 'https://www.crwflags.com/fotw/flags/',
        DB_NAME: 'crwflags_tool_v7',
        STORE_NAME: 'flags',
        FAV_KEY: 'fotw_favs_v7',
        SESSION_CONTEXT_KEY: 'fotw_nav_context_v7',
        SESSION_HISTORY_KEY: 'fotw_history_v9',
        INSPECTOR_STATE_KEY: 'fotw_inspector_minimized_v9',
        HISTORY_STATE_KEY: 'fotw_history_minimized_v9',
        FETCH_TIMEOUT_MS: 12000,
        KEYWORD_SCRAPE_DELAY_MS: 100,
        LONG_NAME_MIN_LENGTH: 40,
        SEARCH_RESULT_LIMIT: 80,
        DEBOUNCE_DELAY_MS: 300,
        HISTORY_MAX_SIZE: 15,
    };

    const State = {
        fuse: null,
        masterList: [],
        db: null,
        isRebuilding: false,
        favorites: new Set(),
        currentList: [],
        currentIndex: -1,
        currentItem: null,
        flagPreviewCache: new Map(),
        debounceTimer: null,
        historyList: [],
        inspectorFlags: [],
        isViewingFavorites: false,
    };

    const JUNK_PAGE_IDS = new Set([
        'index.html', 'host.html', 'fe_writ.html', 'mailme.html',
        'disclaim.html', 'search.html', 'mirror.html', 'mirrors.html'
    ]);

    const UI = {
        init() {
            GM_addStyle(`
                #fotwPanel { position:fixed; top:48px; right:12px; width:360px; max-height:85vh; overflow-y:auto; background:#003399; padding:14px; border-radius:10px; font-family:Arial,sans-serif; color:#fff; box-shadow:0 0 18px rgba(0,0,0,0.7); z-index:2147483647; }
                #fotwHeader { font-size:18px; font-weight:700; margin-bottom:10px; text-align:center; user-select:none; }
                #fotwSearchInput, #fotwCategoryFilter { width:100%; padding:8px; font-size:14px; border-radius:6px; border:none; margin-bottom:8px; box-sizing:border-box; }
                #fotwButtonsRow { display:grid; grid-template-columns:repeat(3, 1fr); gap:6px; justify-items:stretch; margin-bottom:8px; }
                #fotwButtonsRow button { background:#0055cc; border:none; border-radius:6px; padding:8px 6px; color:#fff; font-size:13px; cursor:pointer; transition: background-color 0.2s; }
                #fotwButtonsRow button:hover:not([disabled]) { background:#0077ff; }
                #fotwSearchResults { background:#fff; color:#000; max-height:260px; overflow-y:auto; border-radius:6px; display:none; font-size:13px; margin-bottom:8px; }
                #fotwSearchResults div, .fotw-sub-content div { padding:8px 10px; border-bottom:1px solid #ddd; cursor:pointer; }
                #fotwSearchResults div:hover, .fotw-sub-content div:hover { background:#f0f0f0; }
                #fotwFlagInfo { background:#002266; border-radius:8px; padding:10px; font-size:13px; min-height:60px; line-height:1.4em; word-break:break-word; margin-bottom: 8px; }
                button[disabled] { opacity:0.6; cursor:not-allowed; }
                #fotwPreviewPanel { position: fixed; z-index: 2147483647; background: rgba(255, 255, 255, 0.95); border: 1px solid #ccc; box-shadow: 0 4px 12px rgba(0,0,0,0.4); padding: 8px; border-radius: 6px; pointer-events: none; max-width: 250px; text-align: center; color: #333; font-size: 12px; }
                #fotwPreviewPanel img { max-width: 100%; max-height: 150px; display: block; }
                .fotw-sub-panel { margin-top: 8px; background: #002266; border-radius: 8px; }
                .fotw-sub-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; font-weight: bold; user-select: none; }
                .fotw-sub-toggle { font-family: monospace; cursor: pointer; padding: 2px 6px; border-radius: 4px; background: #0055cc; }
                .fotw-sub-content { background:#fff; color:#000; max-height:200px; overflow-y:auto; border-radius: 0 0 6px 6px; font-size:13px; }
                #copyLinkBtn { cursor: pointer; margin-left: 8px; font-size: 16px; vertical-align: middle; }
            `);

            const panel = document.createElement('div');
            panel.id = 'fotwPanel';
            panel.innerHTML = `
                <div id="fotwHeader">⚑ FOTW Tool v9.2.0</div>
                <input id="fotwSearchInput" type="search" placeholder="Search flags..." disabled>
                <select id="fotwCategoryFilter" disabled>
                  <option value="all">All Categories</option><option value="country">Countries</option><option value="historical">Historical</option><option value="regional">Regional/Subnational</option><option value="maritime">Maritime</option><option value="obscure">Obscure</option>
                </select>
                <div id="fotwButtonsRow">
                  <button id="btnRandom" title="Random from entire database" disabled>🎲 Random</button>
                  <button id="btnRandomHistorical" title="Random historical flag" disabled>🏛 Historical</button>
                  <button id="btnRandomObscure" title="Random obscure flag" disabled>🏴 Obscure</button>
                  <button id="btnRandomLongName" title="Random long name" disabled>📝 Long Name</button>
                  <button id="btnRandomFav" title="Random favorite" disabled>🎯 Random Fav</button>
                  <button id="btnViewFavs" title="Show/Hide favorites" disabled>📂 View Favs</button>
                  <button id="btnFavToggle" title="Add/Remove favorite" disabled>⭐ Fav</button>
                  <button id="btnRebuild" title="Rebuild database" disabled>🔄 Rebuild DB</button>
                </div>
                <div id="fotwStatus" style="font-size:13px; min-height:18px; margin-bottom:6px; font-weight:bold;">Initializing...</div>
                <div id="fotwSearchResults"></div>
                <div id="fotwFlagInfo"></div>
                <div id="fotwInspectorPanel" class="fotw-sub-panel" style="display:none;">
                    <div class="fotw-sub-header">
                        <span id="fotwInspectorTitle">🔎 Flag Inspector (0)</span>
                        <button id="fotwInspectorToggle" class="fotw-sub-toggle">[–]</button>
                    </div>
                    <div id="fotwInspectorContent" class="fotw-sub-content"></div>
                </div>
                <div id="fotwHistoryPanel" class="fotw-sub-panel" style="display:none;">
                    <div class="fotw-sub-header">
                        <span id="fotwHistoryTitle">🕒 Session History (0)</span>
                        <button id="fotwHistoryToggle" class="fotw-sub-toggle">[–]</button>
                    </div>
                    <div id="fotwHistoryContent" class="fotw-sub-content"></div>
                </div>
            `;
            document.body.appendChild(panel);

            this.statusEl = document.getElementById('fotwStatus'); this.searchEl = document.getElementById('fotwSearchInput'); this.resultsEl = document.getElementById('fotwSearchResults'); this.infoEl = document.getElementById('fotwFlagInfo'); this.categoryFilterEl = document.getElementById('fotwCategoryFilter');
            this.btnFavToggle = document.getElementById('btnFavToggle'); this.btnRebuild = document.getElementById('btnRebuild');
            this.inspectorPanel = document.getElementById('fotwInspectorPanel'); this.inspectorTitle = document.getElementById('fotwInspectorTitle'); this.inspectorContent = document.getElementById('fotwInspectorContent');
            this.historyPanel = document.getElementById('fotwHistoryPanel'); this.historyTitle = document.getElementById('fotwHistoryTitle'); this.historyContent = document.getElementById('fotwHistoryContent');
        },
        setStatus(msg) { this.statusEl.textContent = msg; },
        enableAll() { document.querySelectorAll('#fotwPanel button, #fotwPanel input, #fotwPanel select').forEach(el => el.disabled = false); },
        disableAll() { document.querySelectorAll('#fotwPanel button, #fotwPanel input, #fotwPanel select').forEach(el => el.disabled = true); },
        escapeHtml(s) { return (s || '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c])); },
        renderList(element, items) { if (!items || items.length === 0) { element.innerHTML = ''; return; } element.innerHTML = items.map(it => ` <div data-id="${this.escapeHtml(it.id)}" data-url="${this.escapeHtml(it.url)}"> ${this.escapeHtml(it.name)} <span style="opacity:.6; font-size:11px">[${this.escapeHtml(it.category)}]</span> </div>`).join(''); },
        updateCurrentItemDisplay() { if (!State.currentItem) { this.infoEl.innerHTML = 'No item selected or not in DB.'; this.btnFavToggle.disabled = true; return; } this.infoEl.innerHTML = `<b>${this.escapeHtml(State.currentItem.name)}</b> <br><small style="opacity:.7;">[${this.escapeHtml(State.currentItem.category)}]</small> <br><a href="${this.escapeHtml(State.currentItem.url)}" target="_blank" rel="noopener noreferrer">Open page</a> <span id="copyLinkBtn" title="Copy Link">📋</span>`; this.updateFavButton(); this.btnFavToggle.disabled = false; document.getElementById('copyLinkBtn').addEventListener('click', Logic.copyCurrentLink); },
        updateFavButton() { const curId = State.currentItem ? State.currentItem.id : null; this.btnFavToggle.textContent = State.favorites.has(curId) ? '🌟 Unfav' : '⭐ Fav'; },
        applyStoredUIState() { const isInspectorMinimized = localStorage.getItem(Config.INSPECTOR_STATE_KEY) === 'true'; if (isInspectorMinimized) { this.inspectorContent.style.display = 'none'; document.getElementById('fotwInspectorToggle').textContent = '[+]'; } const isHistoryMinimized = localStorage.getItem(Config.HISTORY_STATE_KEY) === 'true'; if (isHistoryMinimized) { this.historyContent.style.display = 'none'; document.getElementById('fotwHistoryToggle').textContent = '[+]'; } }
    };

    const API = { async fetchWithTimeout(url, timeoutMs = Config.FETCH_TIMEOUT_MS) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(url, { signal: controller.signal }); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } return await response.text(); } finally { clearTimeout(timeoutId); } } };
    const DB = { open() { return new Promise((resolve, reject) => { const request = indexedDB.open(Config.DB_NAME, 1); request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains(Config.STORE_NAME)) { db.createObjectStore(Config.STORE_NAME, { keyPath: 'id' }); } }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); }, getAll(db) { return new Promise((resolve, reject) => { try { const transaction = db.transaction(Config.STORE_NAME, 'readonly'); const store = transaction.objectStore(Config.STORE_NAME); const request = store.getAll(); request.onsuccess = () => resolve(request.result || []); request.onerror = () => reject(request.error); } catch (e) { reject(e); } }); }, putBatch(db, items) { return new Promise((resolve, reject) => { try { const transaction = db.transaction(Config.STORE_NAME, 'readwrite'); const store = transaction.objectStore(Config.STORE_NAME); items.forEach(item => store.put(item)); transaction.oncomplete = () => resolve(); transaction.onerror = () => reject(transaction.error); } catch (e) { reject(e); } }); } };

    const Logic = {
        sleep: ms => new Promise(r => setTimeout(r, ms)),
        filenameFromId: id => id ? id.split('/').pop().split('?')[0].split('#')[0].toLowerCase() : '',
        isTwoLetterCountryId: id => /^[a-z]{2}(?:\.html?)?$/.test(Logic.filenameFromId(id)),
        isIndexPageId: id => /^keyword[a-z]\.html?$/.test(Logic.filenameFromId(id)),
        async getFlagImageUrl(pageUrl) { if (State.flagPreviewCache.has(pageUrl)) { return State.flagPreviewCache.get(pageUrl); } try { const html = await API.fetchWithTimeout(pageUrl); const doc = new DOMParser().parseFromString(html, 'text/html'); const flagImg = doc.querySelector('p img[src*="images/"]'); if (flagImg && flagImg.src) { const imageUrl = new URL(flagImg.src, pageUrl).href; State.flagPreviewCache.set(pageUrl, imageUrl); return imageUrl; } } catch (error) { console.warn(`Could not fetch or parse preview for ${pageUrl}:`, error); } State.flagPreviewCache.set(pageUrl, null); return null; },
        rebuildMasterList: async function() { if (State.isRebuilding) return; State.isRebuilding = true; UI.disableAll(); UI.btnRebuild.disabled = true; UI.setStatus('Rebuilding DB...'); const newMap = new Map(); try { const letters = 'abcdefghijklmnopqrstuvwxyz'; for (let i = 0; i < letters.length; i++) { const char = letters[i]; const path = `${Config.BASE_URL}keyword${char}.html`; let html = ''; try { html = await API.fetchWithTimeout(path); } catch (e) { console.warn(`Failed to fetch keyword page for "${char}":`, e); await Logic.sleep(Config.KEYWORD_SCRAPE_DELAY_MS); continue; } const doc = new DOMParser().parseFromString(html, 'text/html'); for (const a of doc.querySelectorAll('a[href]')) { if (!a.href) continue; const url = new URL(a.href, path).href; if (url.startsWith(Config.BASE_URL)) { const id = url.slice(Config.BASE_URL.length); const name = (a.textContent || '').trim() || id; if (!newMap.has(id)) newMap.set(id, { id, name, url, category: 'misc' }); } } UI.setStatus(`Rebuild: Scanned '${char.toUpperCase()}' (${i + 1}/${letters.length}) | Found: ${newMap.size}`); await Logic.sleep(Config.KEYWORD_SCRAPE_DELAY_MS); } let allItems = Array.from(newMap.values()); UI.setStatus(`Categorizing ${allItems.length} links...`); await Logic.sleep(100); const obscureKeywords = /\b(proposed|fictional|micronation|unofficial|club|football|society|organization|personal|local custom|movement|variant|political|party|separatist|ethnic|company|house flag|aspirant|cultural|sports|association|UFE|unidentified)\b/i; for (const item of allItems) { if (Logic.isTwoLetterCountryId(item.id)) { item.category = 'country'; } else if (Logic.isIndexPageId(item.id)) { item.category = 'index'; } else { const name = item.name.toLowerCase(); if (/\b(historical|history|former|formerly|obsolete)\b/i.test(name)) item.category = 'historical'; else if (/\b(ensign|naval|jack|pennant|burgee|yacht|ship|maritime|merchant)\b/i.test(name)) item.category = 'maritime'; else if (/\b(region|province|municipal|city|county|prefecture|oblast|krai|state|provincial)\b/i.test(name)) item.category = 'regional'; else if (obscureKeywords.test(name) || item.name.split(/\s+/).length > 4) item.category = 'obscure'; else item.category = 'regional'; } } State.masterList = allItems.filter(item => item.category !== 'index' && !JUNK_PAGE_IDS.has(item.id)); await DB.putBatch(State.db, State.masterList); State.fuse = new Fuse(State.masterList, { keys: ['name'], threshold: 0.3 }); UI.setStatus(`Rebuild complete: ${State.masterList.length} items.`); } catch (e) { UI.setStatus(`Rebuild failed: ${e.message}`); console.error("Rebuild failed:", e); } finally { State.isRebuilding = false; UI.enableAll(); } },
        goToItem(item) { if (item && item.url) { Logic.saveContextToSession(); location.href = item.url; } },
        saveContextToSession() { if (State.currentList && State.currentList.length > 0) { const context = { list: State.currentList.map(item => item.id), index: State.currentIndex, }; sessionStorage.setItem(Config.SESSION_CONTEXT_KEY, JSON.stringify(context)); } },
        loadContextFromSession() { const storedContext = sessionStorage.getItem(Config.SESSION_CONTEXT_KEY); if (!storedContext) return false; try { const context = JSON.parse(storedContext); if (context && Array.isArray(context.list) && context.list.length > 0) { const idToItemMap = new Map(State.masterList.map(item => [item.id, item])); State.currentList = context.list.map(id => idToItemMap.get(id)).filter(Boolean); State.currentIndex = context.index; return true; } } catch (e) { console.error("Failed to parse session context:", e); sessionStorage.removeItem(Config.SESSION_CONTEXT_KEY); } return false; },
        loadFavorites() { State.favorites = new Set(JSON.parse(localStorage.getItem(Config.FAV_KEY) || '[]')); },
        saveFavorites() { localStorage.setItem(Config.FAV_KEY, JSON.stringify(Array.from(State.favorites))); },
        toggleFavorite() { if (!State.currentItem) return; const id = State.currentItem.id; if (State.favorites.has(id)) { State.favorites.delete(id); } else { State.favorites.add(id); } this.saveFavorites(); UI.updateFavButton(); },
        copyCurrentLink(event) { if (!State.currentItem) return; navigator.clipboard.writeText(State.currentItem.url).then(() => { const originalText = event.target.textContent; event.target.textContent = 'Copied!'; setTimeout(() => { event.target.textContent = originalText; }, 1500); }).catch(err => console.error('Failed to copy link:', err)); },
        updateInspector() { const masterMap = new Map(State.masterList.map(item => [item.id, item])); const foundIds = new Set(); for (const a of document.links) { if (a.href.startsWith(Config.BASE_URL)) { const id = a.href.slice(Config.BASE_URL.length); if (masterMap.has(id) && id !== (State.currentItem?.id) && !JUNK_PAGE_IDS.has(id)) { foundIds.add(id); } } } State.inspectorFlags = Array.from(foundIds).map(id => masterMap.get(id)); if (State.inspectorFlags.length > 0) { UI.inspectorPanel.style.display = 'block'; UI.inspectorTitle.textContent = `🔎 Flag Inspector (${State.inspectorFlags.length})`; UI.renderList(UI.inspectorContent, State.inspectorFlags); } else { UI.inspectorPanel.style.display = 'none'; } },
        loadHistory() { const storedHistory = sessionStorage.getItem(Config.SESSION_HISTORY_KEY); if (storedHistory) { try { const historyIds = JSON.parse(storedHistory); const masterMap = new Map(State.masterList.map(item => [item.id, item])); State.historyList = historyIds.map(id => masterMap.get(id)).filter(Boolean); } catch(e) { console.error("Could not parse history:", e); State.historyList = []; } } },
        updateHistory() { if (!State.currentItem) return; const currentId = State.currentItem.id; const existingIndex = State.historyList.findIndex(item => item.id === currentId); if (existingIndex > -1) State.historyList.splice(existingIndex, 1); State.historyList.unshift(State.currentItem); if (State.historyList.length > Config.HISTORY_MAX_SIZE) { State.historyList = State.historyList.slice(0, Config.HISTORY_MAX_SIZE); } sessionStorage.setItem(Config.SESSION_HISTORY_KEY, JSON.stringify(State.historyList.map(item => item.id))); if (State.historyList.length > 0) { UI.historyPanel.style.display = 'block'; UI.historyTitle.textContent = `🕒 Session History (${State.historyList.length})`; UI.renderList(UI.historyContent, State.historyList); } else { UI.historyPanel.style.display = 'none'; } },
        bindEvents() {
            const { searchEl, resultsEl, categoryFilterEl, btnRebuild, btnFavToggle } = UI;
            searchEl.addEventListener('input', () => { clearTimeout(State.debounceTimer); State.debounceTimer = setTimeout(() => { State.isViewingFavorites = false; const term = (searchEl.value || '').trim(); if (!term) { UI.renderList(UI.resultsEl, []); resultsEl.style.display = 'none'; return; } const category = categoryFilterEl.value; let results = State.fuse.search(term, { limit: Config.SEARCH_RESULT_LIMIT }).map(r => r.item); if (category && category !== 'all') { results = results.filter(it => it.category === category); } UI.renderList(UI.resultsEl, results); resultsEl.style.display = 'block'; State.currentList = results; }, Config.DEBOUNCE_DELAY_MS); });
            let previewPanel = null;
            document.getElementById('fotwPanel').addEventListener('mouseover', async (e) => { const resultDiv = e.target.closest('div[data-url]'); if (!resultDiv) return; if (!previewPanel) { previewPanel = document.createElement('div'); previewPanel.id = 'fotwPreviewPanel'; document.body.appendChild(previewPanel); } previewPanel.innerHTML = 'Loading...'; previewPanel.style.display = 'block'; const imageUrl = await Logic.getFlagImageUrl(resultDiv.dataset.url); if (previewPanel.style.display === 'none') return; if (imageUrl) { previewPanel.innerHTML = `<img src="${UI.escapeHtml(imageUrl)}" alt="Flag preview">`; } else { previewPanel.innerHTML = 'No preview'; } });
            document.getElementById('fotwPanel').addEventListener('mousemove', (e) => { if (previewPanel) { previewPanel.style.left = `${e.clientX + 20}px`; previewPanel.style.top = `${e.clientY + 20}px`; } });
            document.getElementById('fotwPanel').addEventListener('mouseout', (e) => { if (previewPanel) { if (e.target.closest('div[data-url]')) { previewPanel.style.display = 'none'; } } });
            document.getElementById('fotwPanel').addEventListener('click', e => { const div = e.target.closest('div[data-id]'); if (!div) return; const id = div.dataset.id; const isSearchResult = div.parentElement.id === 'fotwSearchResults'; if (isSearchResult) { State.currentIndex = State.currentList.findIndex(it => it.id === id); Logic.goToItem(State.currentList[State.currentIndex]); } else { const item = State.masterList.find(it => it.id === id); if (item) Logic.goToItem(item); } });
            categoryFilterEl.addEventListener('change', () => searchEl.dispatchEvent(new Event('input', { bubbles: true })));
            document.getElementById('btnRandom').addEventListener('click', () => { const pool = State.masterList; if (pool.length > 0) Logic.goToItem(pool[Math.floor(Math.random() * pool.length)]); });
            document.getElementById('btnRandomHistorical').addEventListener('click', () => { const pool = State.masterList.filter(it => it.category === 'historical'); if (pool.length > 0) Logic.goToItem(pool[Math.floor(Math.random() * pool.length)]); });
            document.getElementById('btnRandomObscure').addEventListener('click', () => { const pool = State.masterList.filter(it => it.category === 'obscure'); if (pool.length > 0) Logic.goToItem(pool[Math.floor(Math.random() * pool.length)]); });
            document.getElementById('btnRandomLongName').addEventListener('click', () => { const pool = State.masterList.filter(it => it.name.length >= Config.LONG_NAME_MIN_LENGTH); if (pool.length > 0) Logic.goToItem(pool[Math.floor(Math.random() * pool.length)]); });
            document.getElementById('btnViewFavs').addEventListener('click', () => { if (State.isViewingFavorites) { UI.resultsEl.style.display = 'none'; UI.renderList(UI.resultsEl, []); State.isViewingFavorites = false; return; } const favArray = State.masterList.filter(it => State.favorites.has(it.id)); if (!favArray.length) { alert('No favorites yet.'); return; } UI.searchEl.value = ''; State.currentList = favArray; State.currentIndex = -1; UI.renderList(UI.resultsEl, favArray); UI.resultsEl.style.display = 'block'; UI.setStatus(`Showing ${favArray.length} favorite(s).`); State.isViewingFavorites = true; });
            document.getElementById('btnRandomFav').addEventListener('click', () => { const favArray = State.masterList.filter(it => State.favorites.has(it.id)); if (!favArray.length) { alert('No favorites yet.'); return; } State.currentList = favArray; State.currentIndex = Math.floor(Math.random() * favArray.length); Logic.goToItem(favArray[State.currentIndex]); });
            btnFavToggle.addEventListener('click', () => Logic.toggleFavorite());
            btnRebuild.addEventListener('click', () => { if (confirm('This will rebuild the flag database from the website.\nThis can take a minute but is recommended for the best categories.\nContinue?')) { Logic.rebuildMasterList(); } });
            document.getElementById('fotwInspectorToggle').addEventListener('click', (e) => { const content = UI.inspectorContent; const isMinimized = content.style.display === 'none'; content.style.display = isMinimized ? 'block' : 'none'; e.target.textContent = isMinimized ? '[–]' : '[+]'; localStorage.setItem(Config.INSPECTOR_STATE_KEY, !isMinimized); });
            document.getElementById('fotwHistoryToggle').addEventListener('click', (e) => { const content = UI.historyContent; const isMinimized = content.style.display === 'none'; content.style.display = isMinimized ? 'block' : 'none'; e.target.textContent = isMinimized ? '[–]' : '[+]'; localStorage.setItem(Config.HISTORY_STATE_KEY, !isMinimized); });
        }
    };

    async function main() {
        UI.init();
        UI.disableAll();
        try {
            const s = Date.now();
            while (typeof window.Fuse === 'undefined') { if (Date.now() - s > 5000) throw new Error("Fuse.js failed to load"); await Logic.sleep(50); }
            UI.setStatus('Opening local DB...');
            State.db = await DB.open();
            State.masterList = await DB.getAll(State.db);
            if (State.masterList.length === 0) { UI.setStatus('No local DB found. Starting build...'); await Logic.rebuildMasterList(); }
            UI.setStatus(`Loaded ${State.masterList.length} flags. Initializing...`);
            State.fuse = new Fuse(State.masterList, { keys: ['name'], threshold: 0.3 });
            UI.applyStoredUIState();
            Logic.loadFavorites();
            Logic.loadHistory();
            const currentPageId = location.href.startsWith(Config.BASE_URL) ? location.href.slice(Config.BASE_URL.length) : null;
            State.currentItem = currentPageId ? State.masterList.find(it => it.id === currentPageId) : null;
            if (Logic.loadContextFromSession()) { UI.setStatus(`Context restored from session.`); if (currentPageId) { const restoredIndex = State.currentList.findIndex(it => it.id === currentPageId); if (restoredIndex !== -1) { State.currentIndex = restoredIndex; } } }
            else if (State.currentItem) { UI.setStatus(`No session context.`); State.currentList = State.masterList.filter(it => it.category === State.currentItem.category); State.currentIndex = State.currentList.findIndex(it => it.id === currentPageId); }
            UI.enableAll();
            UI.updateCurrentItemDisplay();
            Logic.updateHistory();
            Logic.updateInspector();
            Logic.bindEvents();
        } catch (e) {
            UI.setStatus(`Initialization Error: ${e.message}`);
            console.error("Fatal script error:", e);
            UI.btnRebuild.disabled = false;
        }
    }

    main();
})();