Booru Search Tag Enhancer (Universal)

Modernize and enhance search bar and tag input for booru sites, with modular site-specific configurations, dynamic cheat sheets with caching, e621-specific order syntax, and improved layout for rule34.xxx and e621.net

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Booru Search Tag Enhancer (Universal)
// @namespace    booru-tag-search
// @version      2.1
// @description  Modernize and enhance search bar and tag input for booru sites, with modular site-specific configurations, dynamic cheat sheets with caching, e621-specific order syntax, and improved layout for rule34.xxx and e621.net
// @author       Piperun
// @license      LGPL-3.0-or-later
// @match        *://*.booru.org/*
// @match        *://*.booru.com/*
// @match        *://*.booru.ca/*
// @match        *://*.booru.xxx/*
// @match        *://rule34.xxx/*
// @match        *://safebooru.org/*
// @match        *://danbooru.donmai.us/*
// @match        *://e621.net/*
// @grant        none
// ==/UserScript==

// --- Awesomplete hijack: always attach instance to input ---
(function hijackAwesomplete() {
    const origAwesomplete = window.Awesomplete;
    if (typeof origAwesomplete === "function") {
        window.Awesomplete = function AwesompleteHijack(input, options) {
            const instance = new origAwesomplete(input, options);
            input.awesomplete = instance;
            return instance;
        };
        // Copy prototype and static properties
        Object.setPrototypeOf(window.Awesomplete, origAwesomplete);
        window.Awesomplete.prototype = origAwesomplete.prototype;
    }
})();

(function main() {
    'use strict';

    // --- Configuration ---
    const SITE_CONFIG = {
        "e621.net": {
            placeholder: "Enter tags...",
            sortOptions: [
                { value: 'score', label: 'Score' },
                { value: 'favcount', label: 'Favorites' },
                { value: 'comment_count', label: 'Comments' },
                { value: 'id', label: 'ID' },
                { value: 'mpixels', label: 'Megapixels' },
                { value: 'filesize', label: 'File Size' },
                { value: 'landscape', label: 'Landscape' },
                { value: 'portrait', label: 'Portrait' },
                { value: 'created', label: 'Created' },
                { value: 'updated', label: 'Updated' },
                { value: 'tagcount', label: 'Tag Count' },
                { value: 'duration', label: 'Duration' },
                { value: 'random', label: 'Random' }
            ],
            ratings: [
                { value: 'safe', label: 'Safe' },
                { value: 'questionable', label: 'Questionable' },
                { value: 'explicit', label: 'Explicit' }
            ],
            metatagRegex: /^(order:|rating:|user:|parent:|score:|md5:|width:|height:|source:|id:|favcount:|comment_count:|type:|date:|status:|\( rating:)/,
            cheatSheetContent: `
                <section><h4>Basics</h4>
                    <ul>
                        <li><code>cat dog</code> — Search for posts that are tagged with both cat and dog. Tags are separated by spaces.</li>
                        <li><code>red_panda african_wild_dog</code> — Words within each tag are separated by underscores.</li>
                        <li><code>~cat ~dog</code> — Search for posts that are tagged either cat or dog (or both). May not work well when combined with other syntaxes.</li>
                        <li><code>-chicken</code> — Search for posts that don't have the chicken tag.</li>
                        <li><code>fox -chicken</code> — Search for posts that are tagged with fox and are not tagged with chicken.</li>
                        <li><code>african_*</code> — Search for posts with any tag that starts with african_, such as african_wild_dog or african_golden_cat. May not work well when combined with other syntaxes. Limit one wildcard per search.</li>
                        <li><code>( ~cat ~tiger ~leopard ) ( ~dog ~wolf )</code> — Search for posts that are tagged with one (or more) of cat, tiger or leopard, and one (or more) of dog or wolf.</li>
                    </ul>
                </section>
                <section><h4>Sorting</h4>
                    <ul>
                        <li><code>order:id</code> — Oldest to newest</li>
                        <li><code>order:id_desc</code> — Newest to oldest</li>
                        <li><code>order:score</code> — Highest score first</li>
                        <li><code>order:score_asc</code> — Lowest score first</li>
                        <li><code>order:favcount</code> — Most favorites first</li>
                        <li><code>order:favcount_asc</code> — Least favorites first</li>
                        <li><code>order:comment_count</code> — Most comments first</li>
                        <li><code>order:comment_count_asc</code> — Least comments first</li>
                        <li><code>order:mpixels</code> — Largest resolution first</li>
                        <li><code>order:mpixels_asc</code> — Smallest resolution first</li>
                        <li><code>order:filesize</code> — Largest file size first</li>
                        <li><code>order:filesize_asc</code> — Smallest file size first</li>
                        <li><code>order:landscape</code> — Wide and short to tall and thin</li>
                        <li><code>order:portrait</code> — Tall and thin to wide and short</li>
                        <li><code>order:duration</code> — Video duration longest to shortest</li>
                        <li><code>order:duration_asc</code> — Video duration shortest to longest</li>
                        <li><code>order:random</code> — Orders posts randomly</li>
                    </ul>
                </section>
                <section><h4>User Metatags</h4>
                    <ul>
                        <li><code>user:Bob</code> — Posts uploaded by Bob</li>
                        <li><code>fav:Bob</code> or <code>favoritedby:Bob</code> — Posts favorited by Bob</li>
                        <li><code>voted:anything</code> — Posts you voted on. Only works while logged in.</li>
                        <li><code>votedup:anything</code> or <code>upvote:anything</code> — Posts you upvoted. Only works while logged in.</li>
                        <li><code>voteddown:anything</code> or <code>downvote:anything</code> — Posts you downvoted. Only works while logged in.</li>
                        <li><code>approver:Bob</code> — Posts approved by Bob</li>
                        <li><code>commenter:Bob</code> or <code>comm:Bob</code> — Posts commented on by Bob</li>
                        <li><code>noter:Bob</code> — Posts with notes written by Bob</li>
                    </ul>
                </section>
                <section><h4>Post Metatags - Counts</h4>
                    <ul>
                        <li><code>id:100</code> — Post with an ID of 100</li>
                        <li><code>score:100</code> — Posts with a score of 100</li>
                        <li><code>favcount:100</code> — Posts with exactly 100 favorites</li>
                        <li><code>comment_count:100</code> — Posts with exactly 100 comments</li>
                        <li><code>tagcount:2</code> — Posts with exactly 2 tags</li>
                        <li><code>gentags:2</code> — Posts with exactly 2 general tags</li>
                        <li><code>arttags:2</code> — Posts with exactly 2 artist tags</li>
                        <li><code>chartags:2</code> — Posts with exactly 2 character tags</li>
                        <li><code>copytags:2</code> — Posts with exactly 2 copyright tags</li>
                        <li><code>spectags:2</code> — Posts with exactly 2 species tags</li>
                        <li><code>invtags:2</code> — Posts with exactly 2 invalid tags</li>
                        <li><code>lortags:2</code> — Posts with exactly 2 lore tags</li>
                        <li><code>metatags:2</code> — Posts with exactly 2 meta tags</li>
                    </ul>
                </section>
                <section><h4>Rating</h4>
                    <ul>
                        <li><code>rating:safe</code> or <code>rating:s</code> — Posts rated safe</li>
                        <li><code>rating:questionable</code> or <code>rating:q</code> — Posts rated questionable</li>
                        <li><code>rating:explicit</code> or <code>rating:e</code> — Posts rated explicit</li>
                    </ul>
                </section>
                <section><h4>File Types</h4>
                    <ul>
                        <li><code>type:jpg</code> — Posts that are JPG, a type of image</li>
                        <li><code>type:png</code> — Posts that are PNG, a type of image</li>
                        <li><code>type:gif</code> — Posts that are GIF, a type of image (may be animated)</li>
                        <li><code>type:swf</code> — Posts that are Flash, a format used for animation</li>
                        <li><code>type:webm</code> — Posts that are WebM, a type of video</li>
                    </ul>
                </section>
                <section><h4>Image Size</h4>
                    <ul>
                        <li><code>width:100</code> — Posts with a width of 100 pixels</li>
                        <li><code>height:100</code> — Posts with a height of 100 pixels</li>
                        <li><code>mpixels:1</code> — Posts that are 1 megapixel (a 1000x1000 image equals 1 megapixel)</li>
                        <li><code>ratio:1.33</code> — Search for posts with a ratio of 4:3. All ratios are rounded to two digits, therefore 1.33 will return posts with a ratio of 4:3.</li>
                        <li><code>filesize:200KB</code> — Posts with a file size of 200 kilobytes. File sizes within ±5% of the value are included.</li>
                        <li><code>filesize:2MB</code> — Posts with a file size of 2 megabytes. File sizes within ±5% of the value are included.</li>
                    </ul>
                </section>
                <section><h4>Post Status</h4>
                    <ul>
                        <li><code>status:pending</code> — Posts that are waiting to be approved or deleted</li>
                        <li><code>status:active</code> — Posts that have been approved</li>
                        <li><code>status:deleted</code> — Posts that have been deleted</li>
                        <li><code>status:flagged</code> — Posts that are flagged for deletion</li>
                        <li><code>status:modqueue</code> — Posts that are pending or flagged</li>
                        <li><code>status:any</code> — All active or deleted posts</li>
                    </ul>
                </section>
                <section><h4>Dates</h4>
                    <ul>
                        <li><code>date:2012-04-27</code> or <code>date:april/27/2012</code> — Search for posts uploaded on a specific date</li>
                        <li><code>date:today</code> — Posts from today</li>
                        <li><code>date:yesterday</code> — Posts from yesterday</li>
                        <li><code>date:week</code> — Posts from the last 7 days</li>
                        <li><code>date:month</code> — Posts from the last 30 days</li>
                        <li><code>date:year</code> — Posts from the last 365 days</li>
                        <li><code>date:5_days_ago</code> — Posts from within the last 5 days</li>
                        <li><code>date:5_weeks_ago</code> — Posts from within the last 5 weeks</li>
                        <li><code>date:5_months_ago</code> — Posts from within the last 5 months</li>
                        <li><code>date:5_years_ago</code> — Posts from within the last 5 years</li>
                        <li><code>date:yesterweek</code> — Posts from last week</li>
                        <li><code>date:yestermonth</code> — Posts from last month</li>
                        <li><code>date:yesteryear</code> — Posts from last year</li>
                    </ul>
                </section>
                <section><h4>Text Searching</h4>
                    <ul>
                        <li><code>source:*example.com</code> — Posts with a source that contains "example.com", prefix matched, use wildcards as needed</li>
                        <li><code>source:none</code> — Posts without a source</li>
                        <li><code>description:whatever</code> — Posts with a description that contains the text "whatever"</li>
                        <li><code>description:"hello there"</code> — Posts with a description that contains the text "hello there"</li>
                        <li><code>note:whatever</code> — Posts with a note that contains the text "whatever"</li>
                        <li><code>delreason:*whatever</code> — Deleted posts that contain a reason with the text "whatever", prefix matched, use wildcards as needed</li>
                    </ul>
                </section>
                <section><h4>Parents and Children</h4>
                    <ul>
                        <li><code>ischild:true</code> — Posts that are a child</li>
                        <li><code>ischild:false</code> — Posts that aren't a child</li>
                        <li><code>isparent:true</code> — Posts that are a parent</li>
                        <li><code>isparent:false</code> — Posts that aren't a parent</li>
                        <li><code>parent:1234</code> — Posts with a parent of 1234</li>
                        <li><code>parent:none</code> — Posts with no parent (same as ischild:false)</li>
                    </ul>
                </section>
                <section><h4>Other</h4>
                    <ul>
                        <li><code>hassource:true</code> — Posts with a source</li>
                        <li><code>hassource:false</code> — Posts without a source</li>
                        <li><code>hasdescription:true</code> — Posts with a description</li>
                        <li><code>hasdescription:false</code> — Posts without a description</li>
                        <li><code>inpool:true</code> — Posts that are in a pool</li>
                        <li><code>inpool:false</code> — Posts that aren't in a pool</li>
                        <li><code>pool:4</code> or <code>pool:fox_and_the_grapes</code> — Posts in the pool "Fox and the Grapes"</li>
                        <li><code>set:17</code> or <code>set:cute_rabbits</code> — Posts in the set with the short name "cute_rabbits"</li>
                        <li><code>md5:02dd0...</code> — Post with the given MD5 hash. MD5 hashes will never be shared by more than one image.</li>
                        <li><code>duration:>120</code> — Videos with a duration of at least 120 seconds</li>
                    </ul>
                </section>
                <section><h4>Range Syntax</h4>
                    <ul>
                        <li><code>id:100</code> — Post with an ID of exactly 100</li>
                        <li><code>date:year..month</code> — Posts uploaded between 30 days ago and 1 year ago</li>
                        <li><code>filesize:200KB..300KB</code> — Posts with a file size between 200 kilobytes and 300 kilobytes</li>
                        <li><code>score:25..50</code> — Posts with a score between 25 and 50</li>
                        <li><code>score:>=100</code> — Posts with a score of 100 or greater (100+)</li>
                        <li><code>score:>100</code> — Posts with a score greater than 100 (101+)</li>
                        <li><code>favcount:<=100</code> — Posts with 100 or less favorites (0-100)</li>
                        <li><code>favcount:<100</code> — Posts with less than 100 favorites (0-99)</li>
                    </ul>
                </section>
            `
        },
        "rule34.xxx": {
            placeholder: "Enter tags...",
            sortOptions: [
                { value: 'score', label: 'Score' },
                { value: 'updated', label: 'Updated' },
                { value: 'id', label: 'ID' },
                { value: 'rating', label: 'Rating' },
                { value: 'user', label: 'User' },
                { value: 'height', label: 'Height' },
                { value: 'width', label: 'Width' },
                { value: 'parent', label: 'Parent' },
                { value: 'source', label: 'Source' }
            ],
            ratings: [
                { value: 'safe', label: 'Safe' },
                { value: 'questionable', label: 'Questionable' },
                { value: 'explicit', label: 'Explicit' }
            ],
            metatagRegex: /^(sort:|rating:|user:|parent:|score:|md5:|width:|height:|source:|\( rating:)/,
            cheatSheetContent: `
                <section><h4>Basic Search</h4>
                    <ul>
                        <li><code>tag1 tag2</code> — Posts with <b>tag1</b> and <b>tag2</b></li>
                        <li><code>( tag1 ~ tag2 )</code> — Posts with <b>tag1</b> or <b>tag2</b></li>
                        <li><code>-tag1</code> — Posts without <b>tag1</b></li>
                        <li><code>ta*1</code> — Tags starting with <b>ta</b> and ending with <b>1</b></li>
                    </ul>
                </section>
                <section><h4>Metatags</h4>
                    <ul>
                        <li><code>user:bob</code> — Posts by user <b>bob</b></li>
                        <li><code>rating:questionable</code> — Rated <b>questionable</b></li>
                        <li><code>score:>=10</code> — Score 10 or higher</li>
                        <li><code>width:>=1000</code> — Width 1000px or more</li>
                        <li><code>height:>1000</code> — Height greater than 1000px</li>
                        <li><code>parent:1234</code> — Has parent <b>1234</b></li>
                        <li><code>md5:foo</code> — Posts with MD5 hash <b>foo</b></li>
                    </ul>
                </section>
                <section><h4>Sorting</h4>
                    <ul>
                        <li><code>sort:updated:desc</code> — Sort by updated (descending)</li>
                        <li><code>sort:score:asc</code> — Sort by score (ascending)</li>
                        <li>Other types: <code>id</code>, <code>rating</code>, <code>user</code>, <code>height</code>, <code>width</code>, <code>parent</code>, <code>source</code></li>
                    </ul>
                </section>
            `
        },
        "danbooru.donmai.us": {
            placeholder: "Enter tags...",
            sortOptions: [
                { value: 'score', label: 'Score' },
                { value: 'favcount', label: 'Favorites' },
                { value: 'comment_count', label: 'Comments' },
                { value: 'id', label: 'ID' },
                { value: 'mpixels', label: 'Megapixels' },
                { value: 'filesize', label: 'File Size' },
                { value: 'landscape', label: 'Landscape' },
                { value: 'portrait', label: 'Portrait' },
                { value: 'created_at', label: 'Created' },
                { value: 'updated_at', label: 'Updated' },
                { value: 'tagcount', label: 'Tag Count' },
                { value: 'duration', label: 'Duration' },
                { value: 'random', label: 'Random' }
            ],
            ratings: [
                { value: 'general', label: 'General' },
                { value: 'sensitive', label: 'Sensitive' },
                { value: 'questionable', label: 'Questionable' },
                { value: 'explicit', label: 'Explicit' }
            ],
            metatagRegex: /^(order:|rating:|user:|parent:|score:|md5:|width:|height:|source:|id:|favcount:|comment_count:|type:|date:|status:|pool:|set:|\( rating:)/,
            cheatSheetUrl: "https://danbooru.donmai.us/wiki_pages/help%3Acheatsheet"
        },
        "default": {
            placeholder: "Enter tags...",
            sortOptions: [
                { value: 'score', label: 'Score' },
                { value: 'updated', label: 'Updated' },
                { value: 'id', label: 'ID' },
                { value: 'rating', label: 'Rating' },
                { value: 'user', label: 'User' },
                { value: 'height', label: 'Height' },
                { value: 'width', label: 'Width' },
                { value: 'parent', label: 'Parent' },
                { value: 'source', label: 'Source' }
            ],
            ratings: [
                { value: 'safe', label: 'Safe' },
                { value: 'questionable', label: 'Questionable' },
                { value: 'explicit', label: 'Explicit' }
            ],
            metatagRegex: /^(sort:|rating:|user:|parent:|score:|md5:|width:|height:|source:|\( rating:)/,
            cheatSheetContent: `
                <section><h4>Basic Search</h4>
                    <ul>
                        <li><code>tag1 tag2</code> — Posts with <b>tag1</b> and <b>tag2</b></li>
                        <li><code>( tag1 ~ tag2 )</code> — Posts with <b>tag1</b> or <b>tag2</b></li>
                        <li><code>-tag1</code> — Posts without <b>tag1</b></li>
                        <li><code>ta*1</code> — Tags starting with <b>ta</b> and ending with <b>1</b></li>
                    </ul>
                </section>
                <section><h4>Metatags</h4>
                    <ul>
                        <li><code>user:bob</code> — Posts by user <b>bob</b></li>
                        <li><code>rating:questionable</code> — Rated <b>questionable</b></li>
                        <li><code>score:>=10</code> — Score 10 or higher</li>
                        <li><code>width:>=1000</code> — Width 1000px or more</li>
                        <li><code>height:>1000</code> — Height greater than 1000px</li>
                        <li><code>parent:1234</code> — Has parent <b>1234</b></li>
                        <li><code>md5:foo</code> — Posts with MD5 hash <b>foo</b></li>
                    </ul>
                </section>
                <section><h4>Sorting</h4>
                    <ul>
                        <li><code>sort:updated:desc</code> — Sort by updated (descending)</li>
                        <li><code>sort:score:asc</code> — Sort by score (ascending)</li>
                        <li>Other types: <code>id</code>, <code>rating</code>, <code>user</code>, <code>height</code>, <code>width</code>, <code>parent</code>, <code>source</code></li>
                    </ul>
                </section>
            `
        }
    };

    // --- State ---
    let tags = [];
    const siteConfig = SITE_CONFIG[location.hostname] || SITE_CONFIG['default'];
    const isE621 = location.hostname.endsWith('e621.net');
    const isRule34 = location.hostname.endsWith('rule34.xxx');
    const isDanbooru = location.hostname.endsWith('danbooru.donmai.us');
    const isWikiPage = location.pathname.includes('/wiki') || location.pathname.includes('/help') || document.title.includes('Wiki');

    // Define global site variable
    let site = '';
    if (isE621) site = 'e621';
    else if (isRule34) site = 'rule34';
    else if (isDanbooru) site = 'danbooru';

    // --- Settings State ---
    let settings = {
        showIncludeExclude: true,
        showMetatags: true,
        showAllTags: true
    };

    // Load settings from localStorage
    function loadSettings() {
        const saved = localStorage.getItem('booru-enhancer-settings');
        if (saved) {
            try {
                settings = { ...settings, ...JSON.parse(saved) };
            } catch (e) {
                console.warn('Failed to load settings:', e);
            }
        }
    }

    // Save settings to localStorage
    function saveSettings() {
        try {
            localStorage.setItem('booru-enhancer-settings', JSON.stringify(settings));
        } catch (e) {
            console.warn('Failed to save settings:', e);
        }
    }

    loadSettings();

    // --- Site Configuration Helper ---
    function addSiteConfig(hostname, config) {
        SITE_CONFIG[hostname] = config;
    }

    // Example usage to add a new site:
    // addSiteConfig('newsite.com', {
    //     placeholder: "Search tags...",
    //     sortOptions: [{ value: 'date', label: 'Date' }],
    //     ratings: [{ value: 'sfw', label: 'Safe for Work' }],
    //     metatagRegex: /^(category:|type:)/,
    //     cheatSheetContent: `<section><h4>Custom Help</h4>...</section>`
    // });

    // --- Color/Theme Helpers ---
    function getContrastYIQ(hexcolor) {
        hexcolor = hexcolor.replace('#', '').trim();
        // If rgb/rgba, convert to hex
        if (hexcolor.startsWith('rgb')) {
            const rgb = hexcolor.match(/\d+/g).map(Number);
            if (rgb.length >= 3) {
                hexcolor = rgb.slice(0, 3).map(x => x.toString(16).padStart(2, '0')).join('');
            }
        }
        if (hexcolor.length === 3) {
            hexcolor = hexcolor.split('').map(x => x + x).join('');
        }
        if (hexcolor.length !== 6) return '#222';
        var r = parseInt(hexcolor.substr(0, 2), 16);
        var g = parseInt(hexcolor.substr(2, 2), 16);
        var b = parseInt(hexcolor.substr(4, 2), 16);
        var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
        return (yiq >= 128) ? '#222' : '#fff';
    }

    function updateTagColors() {
        let bg, border;
        if (isE621) {
            const root = document.body;
            bg = getComputedStyle(root).getPropertyValue('--color-tag-general') || '#e6f7ff';
            border = getComputedStyle(root).getPropertyValue('--color-tag-general-alt') || '#7ecfff';
        } else if (isRule34) {
            bg = '#e6ffe6';
            border = '#7edc7e';
        } else if (isDanbooru) {
            bg = '#ffe6e6';
            border = '#ff7e7e';
        } else {
            bg = '#e0ffe0';
            border = '#b0d0b0';
        }
        document.querySelectorAll('.r34-tag-item').forEach(tag => {
            tag.style.background = bg.trim();
            tag.style.borderColor = border.trim();
            // Auto-contrast text color
            let color = getContrastYIQ(bg.trim());
            tag.style.color = color;
        });
    }

    // --- Tag Management ---
    function addTag(tag) {
        if (!tag || tags.includes(tag)) return;
        tags.push(tag);
        tags = Array.from(new Set(tags));
        renderTags(window.r34_tagList);
    }

    function getTagsFromURL() {
        const params = new URLSearchParams(window.location.search);
        let tagString = params.get('tags') || '';


        // Replace + with space
        tagString = tagString.replace(/\+/g, ' ');
        // Custom split: treat parenthesized groups and tags with parentheses as single tags
        const tags = [];
        let buffer = '';
        let parenLevel = 0;
        for (let i = 0; i < tagString.length; ++i) {
            const c = tagString[i];
            if (c === '(') {
                // Check if this looks like a grouping parenthesis (preceded by space or at start)
                // vs a tag name parenthesis (preceded by underscore or alphanumeric)
                const prevChar = i > 0 ? tagString[i - 1] : ' ';
                const isGroupingParen = prevChar === ' ' || i === 0;
                
                if (isGroupingParen && parenLevel === 0 && buffer.trim()) {
                    tags.push(buffer.trim());
                    buffer = '';
                }
                parenLevel++;
                buffer += c;
            } else if (c === ')' && parenLevel > 0) {
                buffer += c;
                parenLevel--;
                // Only treat as end of group if this was a grouping parenthesis
                const nextChar = i < tagString.length - 1 ? tagString[i + 1] : ' ';
                const isGroupingParen = nextChar === ' ' || i === tagString.length - 1;
                
                if (parenLevel === 0 && isGroupingParen) {
                    tags.push(buffer.trim());
                    buffer = '';
                }
            } else if (c === ' ' && parenLevel === 0) {
                if (buffer.trim()) {
                    tags.push(buffer.trim());
                    buffer = '';
                }
            } else {
                buffer += c;
            }
        }
        if (buffer.trim()) tags.push(buffer.trim());



        // Convert old sort syntax to new unified syntax for e621/danbooru
        if (isDanbooru || isE621) {
            const convertedTags = tags.map(tag => {
                // Convert sort:updated_at:desc to order:updated_at
                if (tag.startsWith('sort:') && tag.includes(':')) {
                    const parts = tag.split(':');
                    if (parts.length === 3) {
                        const [, field, direction] = parts;
                        const converted = direction === 'asc' ? `order:${field}_asc` : `order:${field}`;

                        return converted;
                    }
                }
                return tag;
            });

            return convertedTags.filter(Boolean);
        }


        return tags.filter(Boolean);
    }

    function renderTags(tagList) {
        // Always get from window to avoid ReferenceError
        const metatagList = window.r34_metatagList;
        const metatagRowWrap = window.r34_metatagRowWrap;
        const includeList = window.r34_includeList;
        const excludeList = window.r34_excludeList;
        const includeRowWrap = window.r34_includeRowWrap;
        const excludeRowWrap = window.r34_excludeRowWrap;

        // Defensive: ensure tags are unique before rendering
        const uniqueTags = Array.from(new Set(tags));
        // Use site-specific metatag regex or fallback to default
        const metatagRegex = siteConfig.metatagRegex || /^(sort:|rating:|user:|parent:|score:|md5:|width:|height:|source:|\( rating:)/;

        // --- Render include/exclude rows ---
        if (includeList) includeList.innerHTML = '';
        if (excludeList) excludeList.innerHTML = '';

        let hasIncludeTags = false;
        let hasExcludeTags = false;

        uniqueTags.forEach((tag, idx) => {
            if (metatagRegex.test(tag)) return; // skip metatags
            if (tag.startsWith('-')) {
                hasExcludeTags = true;
                // Exclude tag
                if (excludeList && settings.showIncludeExclude) {
                    const tagEl = document.createElement('span');
                    tagEl.className = 'r34-tag-item r34-exclude-item';
                    tagEl.textContent = tag;
                    const removeBtn = document.createElement('span');
                    removeBtn.className = 'r34-remove-tag';
                    removeBtn.textContent = '×';
                    removeBtn.onclick = () => {
                        const tagIdx = tags.indexOf(tag);
                        if (tagIdx !== -1) {
                            tags.splice(tagIdx, 1);
                            renderTags(tagList);
                        }
                    };
                    tagEl.appendChild(removeBtn);
                    excludeList.appendChild(tagEl);
                }
            } else {
                hasIncludeTags = true;
                // Include tag
                if (includeList && settings.showIncludeExclude) {
                    const tagEl = document.createElement('span');
                    tagEl.className = 'r34-tag-item r34-include-item';
                    tagEl.textContent = tag;
                    const removeBtn = document.createElement('span');
                    removeBtn.className = 'r34-remove-tag';
                    removeBtn.textContent = '×';
                    removeBtn.onclick = () => {
                        const tagIdx = tags.indexOf(tag);
                        if (tagIdx !== -1) {
                            tags.splice(tagIdx, 1);
                            renderTags(tagList);
                        }
                    };
                    tagEl.appendChild(removeBtn);
                    includeList.appendChild(tagEl);
                }
            }
        });

        // Show/hide include/exclude rows based on content and settings
        if (includeRowWrap) {
            includeRowWrap.style.display = hasIncludeTags ? '' : 'none';
        }
        if (excludeRowWrap) {
            excludeRowWrap.style.display = hasExcludeTags ? '' : 'none';
        }
        // --- Render metatag row ---
        if (metatagList && metatagRowWrap) {
            metatagList.innerHTML = '';
            const metatags = uniqueTags.filter(tag => metatagRegex.test(tag));
            if (metatags.length > 0 && settings.showMetatags) {
                metatagRowWrap.style.display = '';
                metatags.forEach((tag, idx) => {
                    const tagEl = document.createElement('span');
                    tagEl.className = 'r34-tag-item r34-metatag-item';
                    tagEl.textContent = tag;
                    const removeBtn = document.createElement('span');
                    removeBtn.className = 'r34-remove-tag';
                    removeBtn.textContent = '×';
                    removeBtn.onclick = () => {
                        // Remove from tags and update UI controls as before
                        const tagIdx = tags.indexOf(tag);
                        if (tagIdx !== -1) {
                            tags.splice(tagIdx, 1);
                            renderTags(tagList);
                        }
                    };
                    tagEl.appendChild(removeBtn);
                    metatagList.appendChild(tagEl);
                });
            } else {
                metatagRowWrap.style.display = 'none';
            }
        }
        // --- Bi-directional sync: update UI controls from metatags ---
        // Find sort and rating metatags
        let sortType = '';
        let sortOrder = 'desc';
        let foundSort = false;
        let ratingsSet = new Set();
        uniqueTags.forEach(tag => {
            // Handle unified order: format for e621/danbooru and sort: format for rule34
            let sortMatch;

            if (site === 'e621' || site === 'danbooru') {
                // Unified e621/danbooru format: order:score or order:score_asc
                sortMatch = tag.match(/^order:([a-z_]+)(_asc)?$/);
                if (sortMatch) {
                    sortType = sortMatch[1];
                    sortOrder = sortMatch[2] ? 'asc' : 'desc';
                    foundSort = true;

                }
            } else {
                // rule34 format: sort:score:desc or sort:score:asc
                sortMatch = tag.match(/^sort:([a-z_]+):(asc|desc)$/);
                if (sortMatch) {
                    sortType = sortMatch[1];
                    sortOrder = sortMatch[2];
                    foundSort = true;

                }
            }
            // rating:<value>
            const ratingMatch = tag.match(/^rating:(safe|questionable|explicit)$/);
            if (ratingMatch) {
                ratingsSet.add(ratingMatch[1]);
            }
            // ( rating:type ~ rating:type )
            const ratingOrMatch = tag.match(/^\(\s*([^)]+)\s*\)$/);
            if (ratingOrMatch) {
                // Split by ~ and extract rating values
                const parts = ratingOrMatch[1].split('~').map(s => s.trim());
                parts.forEach(part => {
                    const m = part.match(/^rating:(safe|questionable|explicit)$/);
                    if (m) ratingsSet.add(m[1]);
                });
            }
        });
        // Update sort dropdown
        if (typeof sortSelect !== 'undefined') {
            sortSelect.value = foundSort ? sortType : '';
            // Show/hide order switch
            if (foundSort) {
                orderSwitch.style.display = '';
                orderSwitch.dataset.state = sortOrder;
                orderSwitch.textContent = sortOrder === 'asc' ? 'Order: Ascend \u2191' : 'Order: Descend \u2193';
            } else {
                orderSwitch.style.display = 'none';
            }
        }
        // Update rating checkboxes
        if (typeof sortRow !== 'undefined') {
            sortRow.querySelectorAll('.r34-rating-checkbox').forEach(cb => {
                cb.checked = ratingsSet.has(cb.value);
            });
        }
        // --- Render only non-metatag tag pills in the main tag list ---
        const allTagsRowWrap = window.r34_allTagsRowWrap;
        const allTagsList = window.r34_allTagsList;

        if (allTagsList && allTagsRowWrap) {
            allTagsList.innerHTML = '';
            const mainTags = uniqueTags.filter(tag => !metatagRegex.test(tag));
            if (mainTags.length > 0 && settings.showAllTags) {
                allTagsRowWrap.style.display = '';
                mainTags.forEach((tag, idx) => {
                    const tagEl = document.createElement('span');
                    tagEl.className = 'r34-tag-item';
                    tagEl.textContent = tag;
                    const removeBtn = document.createElement('span');
                    removeBtn.className = 'r34-remove-tag';
                    removeBtn.textContent = '×';
                    removeBtn.onclick = () => {
                        // --- Bi-directional sync: update UI controls when metatag pill is removed ---
                        // If removing a sort or rating metatag, update UI controls
                        const isSortTag = (site === 'e621' || site === 'danbooru') ? /^order:[a-z_]+(_asc)?$/.test(tag) : /^sort:[a-z_]+:(asc|desc)$/.test(tag);
                        if (isSortTag) {
                            if (typeof sortSelect !== 'undefined') {
                                sortSelect.value = '';
                                orderSwitch.style.display = 'none';
                            }
                        }
                        if (/^rating:(safe|questionable|explicit)$/.test(tag)) {
                            if (typeof sortRow !== 'undefined') {
                                sortRow.querySelectorAll('.r34-rating-checkbox').forEach(cb => {
                                    if (cb.value === tag.split(':')[1]) cb.checked = false;
                                });
                            }
                        }
                        const tagIdx = tags.indexOf(tag);
                        if (tagIdx !== -1) {
                            tags.splice(tagIdx, 1);
                            renderTags(tagList);
                        }
                    };
                    tagEl.appendChild(removeBtn);
                    allTagsList.appendChild(tagEl);
                });
            } else {
                allTagsRowWrap.style.display = 'none';
            }
        }
        updateTagColors();
    }

    // --- Modal Creation ---
    function createModal(id, title, contentNode, actions) {
        // Remove any existing modal with this id
        const old = document.getElementById(id);
        if (old) old.remove();
        const modal = document.createElement('div');
        modal.className = 'modal';
        modal.id = id;
        modal.tabIndex = -1;
        modal.style.display = 'none';

        // Modal overlay
        const modalContent = document.createElement('div');
        modalContent.className = 'modal-content';

        // Close (×) button
        const closeX = document.createElement('button');
        closeX.className = 'modal-close';
        closeX.type = 'button';
        closeX.innerHTML = '&times;';
        closeX.title = 'Close';
        closeX.onclick = () => { modal.style.display = 'none'; };
        modalContent.appendChild(closeX);

        // Title
        const h3 = document.createElement('h3');
        h3.textContent = title;
        modalContent.appendChild(h3);

        modalContent.appendChild(contentNode);
        const actionsDiv = document.createElement('div');
        actionsDiv.className = 'modal-actions';
        actions.forEach(btn => actionsDiv.appendChild(btn));
        modalContent.appendChild(actionsDiv);
        modal.appendChild(modalContent);
        document.body.appendChild(modal);
        // Modal close on Esc/click outside
        function closeModal() { modal.style.display = 'none'; }
        modal.addEventListener('mousedown', e => { if (e.target === modal) closeModal(); });
        modal.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); });
        return { modal, closeModal };
    }

    // --- Export Modal ---
    function showExportModal() {
        // --- Export Data Preparation ---
        const website = location.hostname;
        const timestamp = new Date().toISOString();
        const rawTags = tags.join(' ');
        const jsonExport = JSON.stringify({ website, tags: [...tags], timestamp }, null, 2);

        // --- Modal Elements ---
        const tabWrap = document.createElement('div');
        tabWrap.className = 'modal-tabs';
        const rawTab = document.createElement('button');
        rawTab.textContent = 'Raw';
        rawTab.type = 'button';
        rawTab.className = 'modal-tab';
        const jsonTab = document.createElement('button');
        jsonTab.textContent = 'JSON';
        jsonTab.type = 'button';
        jsonTab.className = 'modal-tab';
        tabWrap.appendChild(rawTab);
        tabWrap.appendChild(jsonTab);

        const textarea = document.createElement('textarea');
        textarea.readOnly = true;
        textarea.className = 'modal-pastebin';
        textarea.value = rawTags;

        // --- Action Buttons ---
        const copyBtn = document.createElement('button');
        copyBtn.textContent = 'Copy';
        copyBtn.type = 'button';
        copyBtn.onclick = function () {
            textarea.select();
            document.execCommand('copy');
        };

        const exportBtn = document.createElement('button');
        exportBtn.textContent = 'Export to file';
        exportBtn.type = 'button';
        exportBtn.style.display = 'none';
        exportBtn.onclick = function () {
            const blob = new Blob([jsonExport], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = 'booru-tags.json';
            document.body.appendChild(a);
            a.click();
            setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100);
        };

        const closeBtn = document.createElement('button');
        closeBtn.textContent = 'Close';
        closeBtn.type = 'button';

        // --- Tab Switch Logic ---
        function showRaw() {
            textarea.value = rawTags;
            copyBtn.style.display = '';
            exportBtn.style.display = 'none';
            rawTab.classList.add('active');
            jsonTab.classList.remove('active');
        }
        function showJSON() {
            textarea.value = jsonExport;
            copyBtn.style.display = 'none';
            exportBtn.style.display = '';
            rawTab.classList.remove('active');
            jsonTab.classList.add('active');
        }
        rawTab.onclick = showRaw;
        jsonTab.onclick = showJSON;

        // --- Modal Content (Refactored) ---
        const formWrap = document.createElement('div');
        formWrap.className = 'modal-form';
        formWrap.appendChild(tabWrap);
        formWrap.appendChild(textarea);
        // The modal-actions div is handled by createModal, so do not add buttons here

        // --- Modal Actions ---
        const actions = [copyBtn, exportBtn, closeBtn];

        let modalObj = createModal('modal-export', 'Export Tags', formWrap, actions);
        closeBtn.onclick = modalObj.closeModal;
        modalObj.modal.style.display = 'flex';
        modalObj.modal.focus();
        // Default to raw view
        showRaw();
    }

    // --- Settings Modal ---
    function showSettingsModal() {
        const formWrap = document.createElement('div');
        formWrap.className = 'modal-form settings-form';

        // Include/Exclude toggle
        const includeExcludeRow = document.createElement('div');
        includeExcludeRow.className = 'settings-row';
        const includeExcludeLabel = document.createElement('label');
        includeExcludeLabel.className = 'settings-label';
        const includeExcludeCheckbox = document.createElement('input');
        includeExcludeCheckbox.type = 'checkbox';
        includeExcludeCheckbox.checked = settings.showIncludeExclude;
        includeExcludeLabel.appendChild(includeExcludeCheckbox);
        includeExcludeLabel.appendChild(document.createTextNode(' Show Include/Exclude Tags'));
        includeExcludeRow.appendChild(includeExcludeLabel);

        // Metatags toggle
        const metatagsRow = document.createElement('div');
        metatagsRow.className = 'settings-row';
        const metatagsLabel = document.createElement('label');
        metatagsLabel.className = 'settings-label';
        const metatagsCheckbox = document.createElement('input');
        metatagsCheckbox.type = 'checkbox';
        metatagsCheckbox.checked = settings.showMetatags;
        metatagsLabel.appendChild(metatagsCheckbox);
        metatagsLabel.appendChild(document.createTextNode(' Show Metatags'));
        metatagsRow.appendChild(metatagsLabel);

        // All Tags toggle
        const allTagsRow = document.createElement('div');
        allTagsRow.className = 'settings-row';
        const allTagsLabel = document.createElement('label');
        allTagsLabel.className = 'settings-label';
        const allTagsCheckbox = document.createElement('input');
        allTagsCheckbox.type = 'checkbox';
        allTagsCheckbox.checked = settings.showAllTags;
        allTagsLabel.appendChild(allTagsCheckbox);
        allTagsLabel.appendChild(document.createTextNode(' Show All Tags'));
        allTagsRow.appendChild(allTagsLabel);

        formWrap.appendChild(includeExcludeRow);
        formWrap.appendChild(metatagsRow);
        formWrap.appendChild(allTagsRow);

        // Action buttons
        const saveBtn = document.createElement('button');
        saveBtn.textContent = 'Save';
        saveBtn.type = 'button';
        saveBtn.onclick = function () {
            settings.showIncludeExclude = includeExcludeCheckbox.checked;
            settings.showMetatags = metatagsCheckbox.checked;
            settings.showAllTags = allTagsCheckbox.checked;
            saveSettings();
            renderTags(window.r34_tagList);
            modalObj.closeModal();
        };

        const cancelBtn = document.createElement('button');
        cancelBtn.textContent = 'Cancel';
        cancelBtn.type = 'button';

        const actions = [saveBtn, cancelBtn];

        let modalObj = createModal('modal-settings', 'Settings', formWrap, actions);
        cancelBtn.onclick = modalObj.closeModal;
        modalObj.modal.style.display = 'flex';
        modalObj.modal.focus();
    }

    // --- Dynamic Cheat Sheet Cache ---
    const CACHE_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds

    async function fetchCheatSheet(url) {
        try {
            const response = await fetch(url, {
                credentials: 'same-origin',
                headers: {
                    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
                }
            });

            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            const html = await response.text();
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');

            // Extract cheat sheet content based on site
            let content = '';
            if (isE621) {
                const cheatsheetSection = doc.querySelector('#c-help #a-show .styled-dtext');
                if (cheatsheetSection) {
                    // Clean up the content and convert to our format
                    content = extractE621CheatSheet(cheatsheetSection);
                }
            } else if (isRule34) {
                const cheatsheetSection = doc.querySelector('.content');
                if (cheatsheetSection) {
                    content = extractRule34CheatSheet(cheatsheetSection);
                }
            } else if (isDanbooru) {
                const cheatsheetSection = doc.querySelector('#c-wiki-pages #a-show .prose, .wiki-page-body, .dtext-container');
                if (cheatsheetSection) {
                    content = extractDanbooruCheatSheet(cheatsheetSection);
                }
            }

            return content;
        } catch (error) {
            console.error('Failed to fetch cheat sheet:', error);
            return null;
        }
    }

    function extractE621CheatSheet(element) {
        // First, convert HTML to structured JSON data
        const cheatSheetData = parseCheatSheetToJSON(element, 'e621');

        // Then convert JSON to standardized HTML format
        const html = formatCheatSheetFromJSON(cheatSheetData);

        return html;
    }

    function extractRule34CheatSheet(element) {
        // First, convert HTML to structured JSON data
        const cheatSheetData = parseCheatSheetToJSON(element, 'rule34');

        // Then convert JSON to standardized HTML format
        const html = formatCheatSheetFromJSON(cheatSheetData);

        return html;
    }

    function extractDanbooruCheatSheet(element) {
        // First, convert HTML to structured JSON data
        const cheatSheetData = parseCheatSheetToJSON(element, 'danbooru');

        // Then convert JSON to standardized HTML format
        const html = formatCheatSheetFromJSON(cheatSheetData);

        return html;
    }

    function parseCheatSheetToJSON(element, site) {
        // Clone the element to avoid modifying the original
        const clonedElement = element.cloneNode(true);

        // Remove Table of Contents sections
        const tocElements = clonedElement.querySelectorAll('h1, h2, h3, h4, h5, h6');
        tocElements.forEach(heading => {
            const headingText = heading.textContent.toLowerCase();
            if (headingText.includes('table of contents') || headingText.includes('contents')) {
                let nextElement = heading.nextElementSibling;
                heading.remove();
                while (nextElement && !/^H[1-6]$/.test(nextElement.tagName)) {
                    const toRemove = nextElement;
                    nextElement = nextElement.nextElementSibling;
                    toRemove.remove();
                }
            }
        });

        // Remove TOC lists
        const tocLists = clonedElement.querySelectorAll('ul, ol');
        tocLists.forEach(list => {
            const listText = list.textContent.toLowerCase();
            const hasMultipleSections = (listText.match(/\d+\./g) || []).length > 3;
            const hasTocKeywords = listText.includes('searching') && listText.includes('metatags');
            if (hasMultipleSections && hasTocKeywords) {
                list.remove();
            }
        });

        const cheatSheetData = {
            site: site,
            sections: []
        };

        const sections = clonedElement.querySelectorAll('h1, h2, h3, h4, h5, h6, p, ul, table, div');
        let currentSection = null;
        let skipSection = false;
        let orphanedEntries = []; // For content without clear section headers

        sections.forEach(el => {
            if (/^H[1-6]$/.test(el.tagName)) {
                const headingText = el.textContent.trim();
                if (headingText.toLowerCase().includes('table of contents') || headingText.toLowerCase().includes('contents')) {
                    skipSection = true;
                    return;
                } else {
                    skipSection = false;
                }

                // If we have orphaned entries, create a default section for them
                if (orphanedEntries.length > 0 && !currentSection) {
                    currentSection = {
                        title: 'Search Help',
                        entries: orphanedEntries
                    };
                    cheatSheetData.sections.push(currentSection);
                    orphanedEntries = [];
                }

                currentSection = {
                    title: headingText,
                    entries: []
                };
                cheatSheetData.sections.push(currentSection);
            } else if (!skipSection) {
                const entries = [];

                if (el.tagName === 'P') {
                    const text = el.textContent.trim();
                    if (text && !text.toLowerCase().includes('table of contents')) {
                        const entry = parseTextEntry(text);
                        if (entry) {
                            entries.push(entry);
                        }
                    }
                } else if (el.tagName === 'UL') {
                    el.querySelectorAll('li').forEach(li => {
                        const text = li.textContent.trim();
                        if (text && text.length > 3 && !text.toLowerCase().includes('table of contents')) {
                            const entry = parseTextEntry(text);
                            if (entry) {
                                entries.push(entry);
                            }
                        }
                    });
                } else if (el.tagName === 'TABLE') {
                    el.querySelectorAll('tbody tr, tr').forEach(row => {
                        const cells = row.querySelectorAll('td, th');
                        if (cells.length >= 2) {
                            const code = cells[0].textContent.trim();
                            const desc = cells[1].textContent.trim();
                            if (code && desc && !code.includes('Example') && !code.includes('Description')) {
                                const entry = {
                                    type: 'definition',
                                    code: code,
                                    description: desc
                                };
                                entries.push(entry);
                            }
                        }
                    });
                } else if (el.tagName === 'DIV' && site === 'rule34') {
                    // Special handling for Rule34's div-based content
                    const text = el.textContent.trim();
                    if (text && text.length > 10) {
                        // Split by line breaks and process each line
                        const lines = text.split('\n').filter(line => line.trim().length > 3);
                        lines.forEach(line => {
                            const entry = parseTextEntry(line.trim());
                            if (entry) {
                                entries.push(entry);
                            }
                        });
                    }
                }

                // Add entries to current section or orphaned list
                if (currentSection) {
                    currentSection.entries.push(...entries);
                } else {
                    orphanedEntries.push(...entries);
                }
            }
        });

        // Handle any remaining orphaned entries
        if (orphanedEntries.length > 0) {
            const defaultSection = {
                title: 'Search Help',
                entries: orphanedEntries
            };
            cheatSheetData.sections.push(defaultSection);
        }

        return cheatSheetData;
    }

    function parseTextEntry(text) {
        // Try to parse different formats of text entries
        if (text.includes('—')) {
            const parts = text.split('—').map(s => s.trim());
            if (parts.length >= 2 && parts[0] && parts[1]) {
                return {
                    type: 'definition',
                    code: parts[0].replace(/^[•\s]+/, ''),
                    description: parts.slice(1).join(' — ')
                };
            }
        }

        // Check for colon-separated format (common in metatags)
        if (text.includes(':') && !text.startsWith('http')) {
            const colonIndex = text.indexOf(':');
            const beforeColon = text.substring(0, colonIndex).trim();
            const afterColon = text.substring(colonIndex + 1).trim();

            // Only treat as code:description if the part before colon looks like a tag/command
            if (beforeColon.length > 0 && beforeColon.length < 30 && !beforeColon.includes(' ')) {
                return {
                    type: 'definition',
                    code: beforeColon,
                    description: afterColon || 'No description available'
                };
            }
        }

        // Check for parenthetical descriptions
        const parenMatch = text.match(/^([^(]+)\s*\(([^)]+)\)/);
        if (parenMatch) {
            return {
                type: 'definition',
                code: parenMatch[1].trim(),
                description: parenMatch[2].trim()
            };
        }

        // Default to description-only entry
        return {
            type: 'description',
            description: text
        };
    }

    function formatCheatSheetFromJSON(cheatSheetData) {
        let html = '<div class="dynamic-cheatsheet">';

        cheatSheetData.sections.forEach(section => {
            if (section.entries.length > 0) {
                html += `<section><h4>${section.title}</h4><ul>`;

                section.entries.forEach(entry => {
                    if (entry.type === 'definition' && entry.code) {
                        html += `<li><code>${entry.code}</code> — ${entry.description}</li>`;
                    } else {
                        html += `<li>${entry.description}</li>`;
                    }
                });

                html += '</ul></section>';
            }
        });

        html += '</div>';
        return html;
    }

    async function getCachedCheatSheet(site) {
        const cacheKey = `booru-cheatsheet-${site}`;
        const cached = localStorage.getItem(cacheKey);

        if (cached) {
            try {
                const data = JSON.parse(cached);
                const now = new Date().getTime();
                const age = now - data.timestamp;

                // Check if cache is still valid
                if (data.timestamp && (now - data.timestamp) < CACHE_DURATION) {
                    return data.content;
                }
            } catch (e) {
                console.error('Failed to parse cached cheat sheet:', e);
            }
        }

        // Fetch fresh data
        let url = '';
        if (site === 'e621') {
            url = 'https://e621.net/help/cheatsheet';
        } else if (site === 'rule34') {
            url = 'https://rule34.xxx/index.php?page=help&topic=cheatsheet';
        } else if (site === 'danbooru') {
            url = 'https://danbooru.donmai.us/wiki_pages/help%3Acheatsheet';
        }

        if (url) {
            const content = await fetchCheatSheet(url);
            if (content) {
                // Cache the result
                const cacheData = {
                    content: content,
                    timestamp: new Date().getTime(),
                    url: url
                };
                try {
                    localStorage.setItem(cacheKey, JSON.stringify(cacheData));
                } catch (e) {
                    console.error('Failed to cache cheat sheet:', e);
                }
                return content;
            }
        }

        // Fallback to static content
        return null;
    }

    function clearCheatSheetCache() {
        const sites = ['e621', 'rule34', 'danbooru'];
        sites.forEach(site => {
            const cacheKey = `booru-cheatsheet-${site}`;
            localStorage.removeItem(cacheKey);
        });
    }

    // --- Cheat Sheet Modal ---
    async function showCheatSheetModal() {
        // Show loading state
        const loadingDiv = document.createElement('div');
        loadingDiv.className = 'modal-doc';
        loadingDiv.innerHTML = '<p style="text-align: center;">Loading cheat sheet...</p>';

        const closeBtn = document.createElement('button');
        closeBtn.textContent = 'Close';
        closeBtn.type = 'button';

        let modalObj = createModal('modal-cheat', 'Cheat Sheet', loadingDiv, [closeBtn]);
        closeBtn.onclick = modalObj.closeModal;
        modalObj.modal.style.display = 'flex';
        modalObj.modal.focus();

        // Determine which site we're on
        let site = '';
        if (isE621) site = 'e621';
        else if (isRule34) site = 'rule34';
        else if (isDanbooru) site = 'danbooru';

        // Try to get dynamic content
        let cheatSheetContent = null;
        if (site) {
            cheatSheetContent = await getCachedCheatSheet(site);
        }

        // If dynamic fetch failed, use static content
        if (!cheatSheetContent) {
            cheatSheetContent = siteConfig.cheatSheetContent || `
          <section><h4>Basic Search</h4>
            <ul>
              <li><code>tag1 tag2</code> — Posts with <b>tag1</b> and <b>tag2</b></li>
              <li><code>( tag1 ~ tag2 )</code> — Posts with <b>tag1</b> or <b>tag2</b></li>
              <li><code>night~</code> — Fuzzy search for <b>night</b> (e.g. <b>night</b>, <b>fight</b>, <b>bright</b>)</li>
              <li><code>-tag1</code> — Exclude posts with <b>tag1</b></li>
              <li><code>ta*1</code> — Tags starting with <b>ta</b> and ending with <b>1</b></li>
            </ul>
          </section>
          <section><h4>Metatags</h4>
            <ul>
              <li><code>user:bob</code> — Uploaded by user <b>bob</b></li>
              <li><code>md5:foo</code> — Posts with MD5 <b>foo</b></li>
              <li><code>md5:foo*</code> — MD5 starts with <b>foo</b></li>
              <li><code>rating:questionable</code> — Rated <b>questionable</b></li>
              <li><code>-rating:questionable</code> — Not rated <b>questionable</b></li>
              <li><code>parent:1234</code> — Has parent <b>1234</b> (includes 1234)</li>
              <li><code>width:>=1000 height:>1000</code> — Width ≥ 1000, Height > 1000</li>
              <li><code>score:>=10</code> — Score ≥ 10</li>
            </ul>
          </section>
          <section><h4>Sorting</h4>
            <ul>
              <li><code>sort:updated:desc</code> — Sort by <b>updated</b> (descending)</li>
              <li>Other sortable types: <code>id</code>, <code>score</code>, <code>rating</code>, <code>user</code>, <code>height</code>, <code>width</code>, <code>parent</code>, <code>source</code>, <code>updated</code></li>
              <li>Can be sorted by both <b>asc</b> or <b>desc</b></li>
            </ul>
          </section>
          <section><h4>Notes</h4>
            <ul>
              <li>Combining the same metatags (with colons) usually does not work.</li>
              <li>You can combine different metatags (e.g. <code>rating:questionable parent:100</code>).</li>
            </ul>
          </section>
        `;
        }

        // Update modal content
        const docWrap = document.createElement('div');
        docWrap.className = 'modal-doc';
        docWrap.innerHTML = cheatSheetContent;

        // Add reference link and cache info
        const infoBar = document.createElement('div');
        infoBar.className = 'cheatsheet-info-bar';
        infoBar.style.cssText = 'margin-top: 16px; padding: 12px; background: #f0f4fa; border-radius: 8px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px;';

        const refLink = document.createElement('a');
        refLink.href = site === 'e621' ? 'https://e621.net/help/cheatsheet' :
            site === 'rule34' ? 'https://rule34.xxx/index.php?page=help&topic=cheatsheet' :
                site === 'danbooru' ? 'https://danbooru.donmai.us/wiki_pages/help%3Acheatsheet' : '#';
        refLink.target = '_blank';
        refLink.textContent = 'View on site →';
        refLink.style.cssText = 'color: #4a90e2; text-decoration: none; font-weight: 500;';
        refLink.onmouseover = () => { refLink.style.textDecoration = 'underline'; };
        refLink.onmouseout = () => { refLink.style.textDecoration = 'none'; };

        const cacheInfo = document.createElement('span');
        cacheInfo.style.cssText = 'font-size: 0.9em; color: #666;';

        // Check cache status
        const cacheKey = `booru-cheatsheet-${site}`;
        const cached = localStorage.getItem(cacheKey);
        if (cached) {
            try {
                const data = JSON.parse(cached);
                const age = new Date().getTime() - data.timestamp;
                const days = Math.floor(age / (24 * 60 * 60 * 1000));
                const hours = Math.floor((age % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000));
                cacheInfo.textContent = `Cached: ${days}d ${hours}h ago`;
            } catch (e) {
                cacheInfo.textContent = 'Using static content';
            }
        } else {
            cacheInfo.textContent = 'Using static content';
        }

        const flushBtn = document.createElement('button');
        flushBtn.textContent = 'Refresh Cache';
        flushBtn.type = 'button';
        flushBtn.style.cssText = 'border-radius: 12px; padding: 6px 16px; font-size: 0.9em; border: 1.5px solid #b0d0b0; background: #f8fff8; cursor: pointer; transition: all 0.2s;';
        flushBtn.onmouseover = () => { flushBtn.style.borderColor = '#4a90e2'; flushBtn.style.background = '#e0f7fa'; };
        flushBtn.onmouseout = () => { flushBtn.style.borderColor = '#b0d0b0'; flushBtn.style.background = '#f8fff8'; };
        flushBtn.onclick = async () => {
            clearCheatSheetCache();
            modalObj.closeModal();
            await showCheatSheetModal(); // Reload the modal
        };

        infoBar.appendChild(refLink);
        infoBar.appendChild(cacheInfo);
        infoBar.appendChild(flushBtn);

        docWrap.appendChild(infoBar);

        // Update modal with new content
        const modalContent = modalObj.modal.querySelector('.modal-content');
        const oldDoc = modalContent.querySelector('.modal-doc');
        if (oldDoc) {
            oldDoc.replaceWith(docWrap);
        }
    }

    // --- Search Bar Creation ---
    function createSearchSection(site) {
        const centerWrap = document.createElement('div');
        centerWrap.className = 'r34-center-wrap';

        const searchForm = document.createElement('form');
        searchForm.className = 'r34-search-form';
        searchForm.method = 'GET';
        searchForm.action = '';

        const searchBarContainer = document.createElement('div');
        searchBarContainer.className = 'r34-modern-searchbar';

        let searchInput;
        if (site === 'e621') {
            searchInput = document.createElement('textarea');
            searchInput.rows = 1;
            searchInput.id = 'tags';
            searchInput.name = 'tags';
            searchInput.setAttribute('data-autocomplete', 'tag-query');
            searchInput.placeholder = siteConfig.placeholder || 'Enter tags...';
            searchInput.className = 'r34-search-input';
        } else {
            searchInput = document.createElement('input');
            searchInput.type = 'text';
            searchInput.name = 'tags';
            searchInput.placeholder = siteConfig.placeholder || 'Enter tags...';
            searchInput.className = 'r34-search-input';
        }

        // --- Searchbar Buttons ---
        const exportBtn = document.createElement('button');
        exportBtn.type = 'button';
        exportBtn.textContent = 'Export';
        exportBtn.className = 'r34-export-btn';
        exportBtn.title = 'Export tags';
        exportBtn.onclick = showExportModal;

        const cheatBtn = document.createElement('button');
        cheatBtn.type = 'button';
        cheatBtn.textContent = '?';
        cheatBtn.className = 'r34-cheat-btn';
        cheatBtn.title = 'Show cheat sheet';
        cheatBtn.onclick = showCheatSheetModal;

        const settingsBtn = document.createElement('button');
        settingsBtn.type = 'button';
        settingsBtn.textContent = '⚙';
        settingsBtn.className = 'r34-settings-btn';
        settingsBtn.title = 'Settings';
        settingsBtn.onclick = showSettingsModal;

        const searchButton = document.createElement('button');
        searchButton.type = 'submit';
        searchButton.textContent = 'Search';
        searchButton.className = 'r34-search-button';

        // --- New Sort/Order/Ratings Row ---
        const sortRow = document.createElement('div');
        sortRow.className = 'r34-sort-row';
        // Sort dropdown
        const sortSelect = document.createElement('select');
        sortSelect.className = 'r34-sort-select';
        const emptyOption = document.createElement('option');
        emptyOption.value = '';
        emptyOption.textContent = 'Sort';
        sortSelect.appendChild(emptyOption);
        // Use site-specific sort options or fallback to defaults
        const sortOptions = siteConfig.sortOptions || [
            { value: 'score', label: 'Score' },
            { value: 'updated', label: 'Updated' },
            { value: 'id', label: 'ID' },
            { value: 'rating', label: 'Rating' },
            { value: 'user', label: 'User' },
            { value: 'height', label: 'Height' },
            { value: 'width', label: 'Width' },
            { value: 'parent', label: 'Parent' },
            { value: 'source', label: 'Source' }
        ];
        sortOptions.forEach(opt => {
            const option = document.createElement('option');
            option.value = opt.value;
            option.textContent = opt.label;
            sortSelect.appendChild(option);
        });
        sortRow.appendChild(sortSelect);
        // Order switch
        const orderSwitch = document.createElement('button');
        orderSwitch.type = 'button';
        orderSwitch.className = 'r34-order-switch';
        orderSwitch.dataset.state = 'desc'; // default to desc
        orderSwitch.textContent = 'Order: Descend \u2193';
        orderSwitch.style.display = 'none'; // hidden by default
        orderSwitch.onclick = function () {
            // Cycle through: desc -> asc -> desc
            const state = orderSwitch.dataset.state;
            if (state === 'desc') {
                orderSwitch.dataset.state = 'asc';
                orderSwitch.textContent = 'Order: Ascend \u2191';
            } else {
                orderSwitch.dataset.state = 'desc';
                orderSwitch.textContent = 'Order: Descend \u2193';
            }
        };
        sortRow.appendChild(orderSwitch);
        // Show/hide order switch based on sort selection
        sortSelect.addEventListener('change', function () {
            if (sortSelect.value) {
                orderSwitch.style.display = '';
            } else {
                orderSwitch.style.display = 'none';
            }
        });
        // Ratings checkboxes
        const ratings = siteConfig.ratings || [
            { value: 'safe', label: 'Safe' },
            { value: 'questionable', label: 'Questionable' },
            { value: 'explicit', label: 'Explicit' }
        ];
        ratings.forEach(r => {
            const label = document.createElement('label');
            label.className = 'r34-rating-label';
            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.value = r.value;
            checkbox.className = 'r34-rating-checkbox';
            label.appendChild(checkbox);
            label.appendChild(document.createTextNode(' ' + r.label));
            sortRow.appendChild(label);
        });

        const tagList = document.createElement('div');
        tagList.className = 'r34-tag-list';

        // --- Include/Exclude Tag Rows ---
        // Include row
        const includeRowWrap = document.createElement('div');
        includeRowWrap.className = 'r34-include-row-wrap';
        const includeRowHeader = document.createElement('div');
        includeRowHeader.className = 'r34-include-row-header';
        const includeRowTitle = document.createElement('span');
        includeRowTitle.textContent = 'Include Tags';
        const includeToggle = document.createElement('button');
        includeToggle.type = 'button';
        includeToggle.className = 'r34-include-toggle';
        includeToggle.textContent = 'See less';
        includeRowHeader.appendChild(includeRowTitle);
        includeRowHeader.appendChild(includeToggle);
        const includeList = document.createElement('div');
        includeList.className = 'r34-include-list';
        includeRowWrap.appendChild(includeRowHeader);
        includeRowWrap.appendChild(includeList);
        let includeExpanded = true;
        includeList.style.display = '';
        includeToggle.onclick = function () {
            includeExpanded = !includeExpanded;
            includeList.style.display = includeExpanded ? '' : 'none';
            includeToggle.textContent = includeExpanded ? 'See less' : 'See more';
        };
        // Exclude row
        const excludeRowWrap = document.createElement('div');
        excludeRowWrap.className = 'r34-exclude-row-wrap';
        const excludeRowHeader = document.createElement('div');
        excludeRowHeader.className = 'r34-exclude-row-header';
        const excludeRowTitle = document.createElement('span');
        excludeRowTitle.textContent = 'Exclude Tags';
        const excludeToggle = document.createElement('button');
        excludeToggle.type = 'button';
        excludeToggle.className = 'r34-exclude-toggle';
        excludeToggle.textContent = 'See less';
        excludeRowHeader.appendChild(excludeRowTitle);
        excludeRowHeader.appendChild(excludeToggle);
        const excludeList = document.createElement('div');
        excludeList.className = 'r34-exclude-list';
        excludeRowWrap.appendChild(excludeRowHeader);
        excludeRowWrap.appendChild(excludeList);
        let excludeExpanded = true;
        excludeList.style.display = '';
        excludeToggle.onclick = function () {
            excludeExpanded = !excludeExpanded;
            excludeList.style.display = excludeExpanded ? '' : 'none';
            excludeToggle.textContent = excludeExpanded ? 'See less' : 'See more';
        };

        // --- Metatag Row ---
        const metatagRowWrap = document.createElement('div');
        metatagRowWrap.className = 'r34-metatag-row-wrap';
        const metatagRowHeader = document.createElement('div');
        metatagRowHeader.className = 'r34-metatag-row-header';
        const metatagRowTitle = document.createElement('span');
        metatagRowTitle.textContent = 'Metatags';
        const metatagToggle = document.createElement('button');
        metatagToggle.type = 'button';
        metatagToggle.className = 'r34-metatag-toggle';
        metatagToggle.textContent = 'See more';
        metatagRowHeader.appendChild(metatagRowTitle);
        metatagRowHeader.appendChild(metatagToggle);
        const metatagList = document.createElement('div');
        metatagList.className = 'r34-metatag-list';
        metatagRowWrap.appendChild(metatagRowHeader);
        metatagRowWrap.appendChild(metatagList);
        let metatagExpanded = false;
        metatagList.style.display = 'none';
        metatagToggle.onclick = function () {
            metatagExpanded = !metatagExpanded;
            metatagList.style.display = metatagExpanded ? '' : 'none';
            metatagToggle.textContent = metatagExpanded ? 'See less' : 'See more';
        };

        // --- All Tags Row ---
        const allTagsRowWrap = document.createElement('div');
        allTagsRowWrap.className = 'r34-all-tags-row-wrap';
        const allTagsRowHeader = document.createElement('div');
        allTagsRowHeader.className = 'r34-all-tags-row-header';
        const allTagsRowTitle = document.createElement('span');
        allTagsRowTitle.textContent = 'All Tags';
        const allTagsToggle = document.createElement('button');
        allTagsToggle.type = 'button';
        allTagsToggle.className = 'r34-all-tags-toggle';
        allTagsToggle.textContent = 'See less';
        allTagsRowHeader.appendChild(allTagsRowTitle);
        allTagsRowHeader.appendChild(allTagsToggle);
        const allTagsList = document.createElement('div');
        allTagsList.className = 'r34-all-tags-list';
        allTagsRowWrap.appendChild(allTagsRowHeader);
        allTagsRowWrap.appendChild(allTagsList);
        let allTagsExpanded = true;
        allTagsList.style.display = '';
        allTagsToggle.onclick = function () {
            allTagsExpanded = !allTagsExpanded;
            allTagsList.style.display = allTagsExpanded ? '' : 'none';
            allTagsToggle.textContent = allTagsExpanded ? 'See less' : 'See more';
        };

        // Track if user is navigating autocomplete with arrow keys
        let autocompleteNavigating = false;

        // --- Event Bindings ---
        function bindInputEvents() {
            searchInput.addEventListener('keydown', function (e) {
                if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
                    autocompleteNavigating = true;
                }
            });
            searchInput.addEventListener('input', function (e) {
                autocompleteNavigating = false;
                let value = searchInput.value;
                // Only split on comma or space if not inside parentheses
                const endsWithSeparator = /[ ,]$/.test(value);

                if (endsWithSeparator) {
                    // Parse the input to check if we're inside a parenthetical group
                    // This handles both grouping parentheses like "( rating:safe ~ rating:questionable )"
                    // and tag name parentheses like "silvia_(artist)"
                    let parenLevel = 0;
                    let inGroupParens = false;
                    let lastChar = '';

                    for (let i = 0; i < value.length - 1; i++) { // -1 to exclude the trailing separator
                        const char = value[i];
                        if (char === '(' && (i === 0 || lastChar === ' ')) {
                            // Opening paren at start or after space - likely a grouping paren
                            parenLevel++;
                            inGroupParens = true;
                        } else if (char === ')' && parenLevel > 0) {
                            parenLevel--;
                            if (parenLevel === 0) {
                                inGroupParens = false;
                            }
                        }
                        if (char !== ' ') lastChar = char;
                    }

                    // Only split if we're not inside grouping parentheses
                    if (parenLevel === 0 && !inGroupParens) {
                        value = value.replace(/[ ,]+$/, '').trim();
                        addTag(value);
                        searchInput.value = '';
                    }
                }
            });
            // Only submit form on Enter if input is empty and there are tags
            searchInput.addEventListener('keydown', function (e) {
                if (e.key === 'Enter' && !e.shiftKey) {
                    if (autocompleteNavigating) {
                        // Let autocomplete handle Enter
                        return;
                    }
                    // If input is empty and there are tags, submit
                    if (searchInput.value.trim() === '' && tags.length > 0) {
                        e.preventDefault();
                        searchInput.value = tags.join(' ');
                        searchForm.submit();
                    } else if (searchInput.value.trim() !== '') {
                        // No suggestion selected, add raw input as tag
                        e.preventDefault();
                        const value = searchInput.value.trim();
                        addTag(value);
                        searchInput.value = '';
                        autocompleteNavigating = false;
                        // Close/hide autocomplete dropdown
                        if (site === 'rule34' && searchInput.awesomplete) {
                            searchInput.awesomplete.close();
                        }
                        if (site === 'e621') {
                            const ul = document.querySelector('ul[role="listbox"]');
                            if (ul) ul.setAttribute('hidden', '');
                        }
                    }
                }
            });
        }

        function bindFormEvents() {
            searchForm.addEventListener('submit', function (e) {
                // Prevent default form submission
                e.preventDefault();
                // --- Inject metatags from UI controls ---
                let metatags = [];
                // Sort + Order (single metatag)
                if (sortSelect.value) {
                    let order = orderSwitch.dataset.state || 'desc';
                    if (site === 'e621' || site === 'danbooru') {
                        // Unified e621/danbooru format: order:score or order:score_asc
                        if (order === 'asc') {
                            metatags.push(`order:${sortSelect.value}_asc`);
                        } else {
                            metatags.push(`order:${sortSelect.value}`);
                        }
                    } else {
                        // rule34 format: sort:score:desc or sort:score:asc
                        metatags.push(`sort:${sortSelect.value}:${order}`);
                    }
                }
                // Ratings (OR logic)
                const checkedRatings = Array.from(sortRow.querySelectorAll('.r34-rating-checkbox:checked')).map(cb => cb.value);
                if (checkedRatings.length === 1) {
                    metatags.push(`rating:${checkedRatings[0]}`);
                } else if (checkedRatings.length > 1) {
                    metatags.push('( ' + checkedRatings.map(r => `rating:${r}`).join(' ~ ') + ' )');
                }
                // Remove any existing metatags of these types from tags
                const metatagPrefixes = (site === 'e621' || site === 'danbooru') ? ['order:', 'rating:', '( rating:'] : ['sort:', 'rating:', '( rating:'];
                tags = tags.filter(tag => !metatagPrefixes.some(prefix => tag.startsWith(prefix)));
                // Add new metatags
                tags = [...tags, ...metatags];
                // Update input value for submission
                if (tags.length > 0) {
                    searchInput.value = tags.join(' ');
                }
                // Actually submit the form with tags
                searchForm.submit();
            });
        }

        // --- Awesomplete integration for rule34 ---
        function bindAwesompleteEvents() {
            if (site === 'rule34') {
                searchInput.addEventListener('awesomplete-selectcomplete', function (e) {
                    const value = searchInput.value.trim();
                    addTag(value);
                    searchInput.value = '';
                });
            }
        }

        // --- Sync metatags with UI changes ---
        function syncMetatagsFromUI() {
            // Remove all order: and rating: metatags (including parenthesized OR metatags and single rating: ones)
            tags = tags.filter(tag => {
                // Use unified order: syntax for both e621 and danbooru
                if (tag.startsWith('order:')) return false;
                // Keep legacy sort: removal for rule34
                if (site === 'rule34' && tag.startsWith('sort:')) return false;
                if (/^rating:(safe|questionable|explicit|general|sensitive)$/.test(tag)) return false;
                if (/^\(\s*rating:(safe|questionable|explicit|general|sensitive)(\s*~\s*rating:(safe|questionable|explicit|general|sensitive))*\s*\)$/.test(tag)) return false;
                return true;
            });
            // Add sort metatag if selected
            if (sortSelect.value) {
                let order = orderSwitch.dataset.state || 'desc';

                if (site === 'e621' || site === 'danbooru') {
                    // Unified e621/danbooru format: order:score or order:score_asc
                    const sortTag = order === 'asc' ? `order:${sortSelect.value}_asc` : `order:${sortSelect.value}`;

                    tags.push(sortTag);
                } else {
                    // rule34 format: sort:score:desc or sort:score:asc
                    const sortTag = `sort:${sortSelect.value}:${order}`;

                    tags.push(sortTag);
                }
            }
            // Add rating metatag(s) with OR logic
            const checkedRatings = Array.from(sortRow.querySelectorAll('.r34-rating-checkbox:checked')).map(cb => cb.value);
            if (checkedRatings.length === 1) {
                tags.push(`rating:${checkedRatings[0]}`);
            } else if (checkedRatings.length > 1) {
                tags.push('( ' + checkedRatings.map(r => `rating:${r}`).join(' ~ ') + ' )');
            }
            tags = Array.from(new Set(tags));
            renderTags(tagList);
        }
        sortSelect.addEventListener('change', syncMetatagsFromUI);
        orderSwitch.addEventListener('click', syncMetatagsFromUI);
        sortRow.querySelectorAll('.r34-rating-checkbox').forEach(cb => {
            cb.addEventListener('change', syncMetatagsFromUI);
        });

        // --- Assemble ---
        searchBarContainer.appendChild(exportBtn);
        searchBarContainer.appendChild(searchInput);
        searchBarContainer.appendChild(cheatBtn);
        searchBarContainer.appendChild(settingsBtn);
        searchBarContainer.appendChild(searchButton);
        searchForm.appendChild(searchBarContainer);
        // Insert the new row below the search bar, above the tag list
        searchForm.appendChild(sortRow);
        centerWrap.appendChild(searchForm);
        centerWrap.appendChild(includeRowWrap);
        centerWrap.appendChild(excludeRowWrap);
        centerWrap.appendChild(metatagRowWrap);
        centerWrap.appendChild(allTagsRowWrap);

        // --- Bind Events ---
        bindInputEvents();
        bindFormEvents();
        bindAwesompleteEvents();

        // Make tagList, metatagList and metatagRowWrap globally accessible for addTag/renderTags
        window.r34_tagList = tagList;
        window.r34_includeList = includeList;
        window.r34_excludeList = excludeList;
        window.r34_includeRowWrap = includeRowWrap;
        window.r34_excludeRowWrap = excludeRowWrap;
        window.r34_metatagList = metatagList;
        window.r34_metatagRowWrap = metatagRowWrap;
        window.r34_allTagsList = allTagsList;
        window.r34_allTagsRowWrap = allTagsRowWrap;
        return { centerWrap, searchForm, searchInput, searchButton, tagList, includeList, excludeList, metatagList, metatagRowWrap, allTagsList, allTagsRowWrap };
    }

    // --- Site-Specific Setup ---
    function setupE621() {
        const originalForm = document.querySelector('form.post-search-form');
        const gallery = document.querySelector('#c-posts');
        if (!originalForm || !gallery) return false;
        const formAction = originalForm.action;
        const formMethod = originalForm.method;
        const { centerWrap, searchForm, searchInput, searchButton, tagList, metatagList, metatagRowWrap } = createSearchSection('e621');
        searchForm.action = formAction;
        searchForm.method = formMethod;
        gallery.parentNode.insertBefore(centerWrap, gallery);
        originalForm.style.display = 'none';
        tags = getTagsFromURL();
        renderTags(tagList);
        searchInput.value = '';
        // Set up theme observer
        const observer = new MutationObserver(updateTagColors);
        observer.observe(document.body, { attributes: true, attributeFilter: ['data-th-main'] });
        return true;
    }

    function setupRule34() {
        const originalForm = document.querySelector('.sidebar .tag-search form');
        const gallery = document.querySelector('#post-list');
        if (!originalForm || !gallery) return false;
        const formAction = originalForm.action;
        const formMethod = originalForm.method;
        const { centerWrap, searchForm, searchInput, searchButton, tagList, metatagList, metatagRowWrap } = createSearchSection('rule34');
        searchForm.action = formAction;
        searchForm.method = formMethod;
        gallery.parentNode.insertBefore(centerWrap, gallery);
        originalForm.style.display = 'none';
        tags = getTagsFromURL();
        renderTags(tagList);
        searchInput.value = '';
        return true;
    }

    function setupDanbooru() {
        // Try multiple possible selectors for Danbooru's search form
        const originalForm = document.querySelector('form[action*="posts"]') ||
            document.querySelector('form.search-form') ||
            document.querySelector('#search-form') ||
            document.querySelector('form:has(input[name="tags"])');

        // Try multiple possible selectors for the posts container
        const gallery = document.querySelector('#posts') ||
            document.querySelector('.posts') ||
            document.querySelector('#post-list') ||
            document.querySelector('.post-list') ||
            document.querySelector('#content');

        if (!originalForm || !gallery) {
            console.log('Danbooru setup failed: originalForm=', originalForm, 'gallery=', gallery);
            return false;
        }

        const formAction = originalForm.action || '/posts';
        const formMethod = originalForm.method || 'GET';
        const { centerWrap, searchForm, searchInput, searchButton, tagList, metatagList, metatagRowWrap } = createSearchSection('danbooru');
        searchForm.action = formAction;
        searchForm.method = formMethod;
        gallery.parentNode.insertBefore(centerWrap, gallery);
        originalForm.style.display = 'none';
        tags = getTagsFromURL();
        renderTags(tagList);
        searchInput.value = '';
        console.log('Danbooru setup completed successfully');
        return true;
    }

    function setupWiki() {
        // For wiki pages, create a simple centered search input
        const content = document.querySelector('#content') || document.querySelector('main') || document.body;
        if (!content) return false;

        // Create simple search form
        const searchWrap = document.createElement('div');
        searchWrap.className = 'r34-wiki-search-wrap';
        searchWrap.style.cssText = `
            display: flex;
            justify-content: center;
            margin: 20px auto;
            max-width: 600px;
            padding: 0 20px;
        `;

        const searchForm = document.createElement('form');
        searchForm.action = '/posts';
        searchForm.method = 'GET';
        searchForm.style.cssText = `
            display: flex;
            gap: 10px;
            width: 100%;
            align-items: center;
        `;

        const searchInput = document.createElement('input');
        searchInput.type = 'text';
        searchInput.name = 'tags';
        searchInput.placeholder = 'Search posts...';
        searchInput.className = 'r34-search-input';
        searchInput.style.cssText = `
            flex: 1;
            border-radius: 18px;
            padding: 8px 16px;
            font-size: 1.1em;
            border: 2px solid #b0d0b0;
            box-shadow: 0 2px 8px 0 rgba(0,0,0,0.06);
            transition: border-color 0.2s;
        `;

        const searchButton = document.createElement('button');
        searchButton.type = 'submit';
        searchButton.textContent = 'Search';
        searchButton.className = 'r34-search-button';
        searchButton.style.cssText = `
            border-radius: 18px;
            padding: 8px 28px;
            font-size: 1.1em;
            border: 2px solid #b0d0b0;
            background: #f8fff8;
            cursor: pointer;
            transition: border-color 0.2s, background 0.2s;
            white-space: nowrap;
        `;

        // Add hover effects
        searchInput.addEventListener('focus', () => {
            searchInput.style.borderColor = '#4a90e2';
        });
        searchInput.addEventListener('blur', () => {
            searchInput.style.borderColor = '#b0d0b0';
        });

        searchButton.addEventListener('mouseenter', () => {
            searchButton.style.borderColor = '#4a90e2';
            searchButton.style.background = '#e0f7fa';
        });
        searchButton.addEventListener('mouseleave', () => {
            searchButton.style.borderColor = '#b0d0b0';
            searchButton.style.background = '#f8fff8';
        });

        searchForm.appendChild(searchInput);
        searchForm.appendChild(searchButton);
        searchWrap.appendChild(searchForm);

        // Insert at the top of content
        content.insertBefore(searchWrap, content.firstChild);

        return true;
    }

    function setupGeneric() {
        const originalForm = findSearchForm();
        if (!originalForm) return false;
        const searchField = originalForm.querySelector('input[name="tags"], textarea[name="tags"]');
        if (!searchField) return false;
        const tagList = document.createElement('div');
        tagList.className = 'r34-tag-list';
        searchField.insertAdjacentElement('afterend', tagList);
        tags = getTagsFromURL();
        renderTags(tagList);
        searchField.value = '';
        searchField.addEventListener('keydown', function (e) {
            if (e.key === 'Enter' && !e.shiftKey) {
                const value = searchField.value.trim();
                if (value) {
                    e.preventDefault();
                    if (!tags.includes(value)) {
                        tags.push(value);
                        renderTags(tagList);
                    }
                    searchField.value = '';
                } else if (tags.length > 0) {
                    e.preventDefault();
                    searchField.value = tags.join(' ');
                    originalForm.submit();
                }
            }
        });
        originalForm.addEventListener('submit', function (e) {
            if (tags.length > 0) {
                searchField.value = tags.join(' ');
            }
        });
        return true;
    }

    function findSearchForm() {
        let form = document.querySelector('.sidebar form input[name="tags"], .sidebar form textarea[name="tags"]');
        if (form) return form.closest('form');
        form = document.querySelector('form input[name="tags"], form textarea[name="tags"]');
        return form ? form.closest('form') : null;
    }

    // --- Style Injection ---
    function injectStyles() {
        const style = document.createElement('style');
        style.textContent = `
        .r34-center-wrap {
            display: flex;
            flex-direction: column;
            align-items: center;
            width: 100%;
            margin-bottom: 20px;
        }
        .r34-search-form {
            width: 90vw;
            max-width: 700px;
            min-width: 220px;
            margin: 0 auto;
        }
        .r34-modern-searchbar {
            display: flex;
            flex-direction: row;
            align-items: center;
            gap: 16px;
            width: 100%;
            margin-bottom: 10px;
            justify-content: center;
        }
                .r34-export-btn, .r34-cheat-btn, .r34-settings-btn {
                    border-radius: 18px;
                    padding: 8px 18px;
                    font-size: 1.1em;
                    border: 2px solid #b0d0b0;
                    background: #f8fff8;
                    cursor: pointer;
                    transition: border-color 0.2s, background 0.2s;
                    white-space: nowrap;
                    flex-shrink: 0;
                }
                .r34-export-btn:hover, .r34-cheat-btn:hover, .r34-settings-btn:hover {
                    border-color: #4a90e2;
                    background: #e0f7fa;
        }
        .r34-search-input {
            flex: 1 1 0%;
            min-width: 0;
            border-radius: 18px;
            padding: 8px 16px;
            font-size: 1.1em;
            border: 2px solid #b0d0b0;
            box-shadow: 0 2px 8px 0 rgba(0,0,0,0.06);
            transition: border-color 0.2s;
            box-sizing: border-box;
            width: 90vw;
            max-width: 500px;
        }
        .r34-search-input:focus {
            border-color: #4a90e2;
            outline: none;
        }
        .r34-search-button {
            border-radius: 18px;
            padding: 8px 28px;
            font-size: 1.1em;
            border: 2px solid #b0d0b0;
            background: #f8fff8;
            cursor: pointer;
            transition: border-color 0.2s, background 0.2s;
            white-space: nowrap;
            flex-shrink: 0;
        }
        .r34-search-button:hover {
            border-color: #4a90e2;
            background: #e0f7fa;
        }
        .r34-tag-list {
            margin: 18px 0 0 0;
            padding: 0;
            display: flex;
            flex-direction: row;
            flex-wrap: wrap;
            gap: 10px;
            max-width: 1000px;
            justify-content: flex-start;
        }
        .r34-tag-item {
            background: #e0ffe0;
            border: 1.5px solid #b0d0b0;
            border-radius: 18px;
            padding: 4px 18px;
            display: flex;
            align-items: center;
            font-size: 1.08em;
            color: #222;
            font-weight: 500;
            transition: background 0.2s, color 0.2s, border-color 0.2s;
        }
        .r34-tag-item .r34-remove-tag {
            margin-left: 12px;
            cursor: pointer;
            color: #c00;
            font-weight: bold;
            font-size: 1.1em;
            }
                .modal {
                    display: none;
                    position: fixed;
                    z-index: 1000;
                    left: 0; top: 0; right: 0; bottom: 0;
                    background: rgba(24, 28, 36, 0.55);
                    backdrop-filter: blur(2.5px);
                    align-items: center;
                    justify-content: center;
                    transition: background 0.2s;
                }
                .modal[style*="display: flex"] {
                    display: flex !important;
                }
                .modal-content {
                    background: #fff;
                    border-radius: 18px;
                    padding: 32px 28px 24px 28px;
                    min-width: 320px;
                    max-width: 800px;
                    width: 50vw;
                    margin: auto;
                    box-shadow: 0 8px 40px 0 rgba(0,0,0,0.18);
                    display: flex;
                    flex-direction: column;
                    gap: 18px;
                    position: relative;
                    font-size: 1.08em;
                    align-items: center;
                    max-height: 80vh;
                    overflow: auto;
                }
                .modal-close {
                    position: absolute;
                    top: 12px;
                    right: 16px;
                    background: none;
                    border: none;
                    font-size: 1.7em;
                    color: #888;
                    cursor: pointer;
                    z-index: 2;
                    padding: 0 8px;
                    line-height: 1;
                    transition: color 0.2s;
                }
                .modal-close:hover {
                    color: #c00;
                }
                .modal-content h3 {
                    margin: 0 0 8px 0;
                    font-size: 1.35em;
                    font-weight: bold;
                    text-align: left;
                    align-self: flex-start;
                    color: #0066cc;
                    text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
                }
                .modal-actions {
                    display: flex;
                    gap: 12px;
                    justify-content: flex-end;
                    margin-top: 8px;
                    flex-wrap: wrap;
                    width: 100%;
                }
                .modal-actions button {
                    border-radius: 16px;
                    padding: 8px 22px;
                    font-size: 1em;
                    border: 2px solid #b0d0b0;
                    background: #f8fff8;
                    cursor: pointer;
                    transition: border-color 0.2s, background 0.2s;
                    font-weight: 500;
                }
                .modal-actions button:hover {
                    border-color: #4a90e2;
                    background: #e0f7fa;
                }
                .modal-pastebin {
                    background: #f6f8fa;
                    border: 1.5px solid #e0e0e0;
                    border-radius: 10px;
                    padding: 12px 14px;
                    font-size: 1em;
                    width: 100%;
                    min-width: 220px;
                    max-width: 100%;
                    box-sizing: border-box;
                    color: #222;
                    margin-bottom: 0;
                    font-family: 'Fira Mono', 'Consolas', 'Menlo', 'Monaco', monospace;
                    overflow: auto;
                    box-shadow: 0 2px 8px 0 rgba(0,0,0,0.04);
                    resize: vertical;
                    height: 260px;
                    min-height: 120px;
                    max-height: 50vh;
                }
                .modal-content pre, .modal-doc {
                    background: #f6f8fa;
                    border: 1.5px solid #e0e0e0;
                    border-radius: 10px;
                    padding: 12px 14px;
                    font-size: 1em;
                    width: 100%;
                    min-width: 220px;
                    max-width: 100%;
                    box-sizing: border-box;
                    color: #222;
                    margin-bottom: 0;
                    font-family: inherit;
                    overflow: auto;
                }
                .modal-content pre {
                    white-space: pre-wrap;
                    word-break: break-word;
                    max-height: 320px;
                    overflow: auto;
                }
                .modal-doc section {
                    margin-bottom: 18px;
                }
                .modal-doc h4 {
                    margin: 0 0 6px 0;
                    font-size: 1.08em;
                    color: #4a90e2;
                    font-weight: bold;
                }
                .dynamic-cheatsheet section {
                    margin-bottom: 25px;
                }
                .dynamic-cheatsheet h4 {
                    color: #0066cc;
                    margin-bottom: 15px;
                    font-size: 1.15em;
                    font-weight: bold;
                    border-bottom: 2px solid #e9ecef;
                    padding-bottom: 5px;
                }
                .dynamic-cheatsheet ul {
                    list-style: none;
                    padding: 0;
                }
                .dynamic-cheatsheet li {
                    margin-bottom: 15px;
                    padding: 12px;
                    background: #f8f9fa;
                    border-radius: 8px;
                    border-left: 4px solid #0066cc;
                }
                .dynamic-cheatsheet h5 {
                    margin: 0 0 8px 0;
                    color: #d63384;
                    font-size: 0.95em;
                    font-weight: bold;
                    font-family: 'Fira Mono', 'Consolas', 'Menlo', 'Monaco', monospace;
                    background: #e9ecef;
                    padding: 4px 8px;
                    border-radius: 4px;
                    display: inline-block;
                }
                .dynamic-cheatsheet p {
                    margin: 0;
                    color: #333;
                    line-height: 1.5;
                    font-size: 0.95em;
                }
                .dynamic-cheatsheet code {
                    background: #e9ecef;
                    padding: 2px 4px;
                    border-radius: 3px;
                    font-family: 'Fira Mono', 'Consolas', 'Menlo', 'Monaco', monospace;
                    color: #d63384;
                    font-size: 0.9em;
                }
                .modal-doc ul {
                    margin: 0 0 0 18px;
                    padding: 0;
                    list-style: disc inside;
                }
                .modal-doc code {
                    background: #e6f7ff;
                    border-radius: 6px;
                    padding: 2px 6px;
                    font-size: 0.98em;
                    color: #00549e;
                }
                .modal-doc b {
                    color: #333;
                }
                .modal-tabs {
                    display: flex;
                    background: #f0f4fa;
                    border-radius: 999px;
                    padding: 4px;
                    gap: 8px;
                    margin-bottom: 16px;
                    width: 100%;
                    justify-content: flex-start;
                }
                .modal-tab {
                    border: none;
                    background: none;
                    border-radius: 999px;
                    padding: 8px 28px;
                    font-size: 1.08em;
                    font-weight: 500;
                    cursor: pointer;
                    transition: background 0.2s, color 0.2s, box-shadow 0.2s;
                    box-shadow: none;
                    outline: none;
                    margin-bottom: 0;
                    color: #00549e;
                }
                .modal-tab.active {
                    background: #4a90e2;
                    color: #fff;
                    box-shadow: 0 2px 8px 0 rgba(74,144,226,0.12);
                }
                .modal-tab:not(.active):hover {
                    background: #e6f7ff;
                    color: #00549e;
                }
                .modal-tab:focus {
                    outline: 2px solid #4a90e2;
                }
                /* Responsive for mobile */
                @media (max-width: 700px) {
                    .modal-content {
                        min-width: 0;
                        max-width: 98vw;
                        width: 98vw;
                        padding: 18px 2vw 12px 2vw;
                        font-size: 1em;
                        max-height: 98vh;
                    }
                    .modal-content h3 {
                        font-size: 1.08em;
                    }
                    .modal-actions {
                        flex-direction: column;
                        align-items: stretch;
                        gap: 8px;
                    }
                    .modal-pastebin, .modal-content pre, .modal-doc {
                        font-size: 0.98em;
                        padding: 8px 6px;
                        width: 98vw;
                        min-width: 0;
                        max-width: 100vw;
                        height: 120px;
                        min-height: 60px;
                        max-height: 30vh;
                    }
                    .modal-tabs {
                        width: 98vw;
                        min-width: 0;
                        max-width: 100vw;
                    }
                }
                /* --- Add width/centering for modal-tabs and modal-pastebin --- */
                .modal-tabs,
                .modal-pastebin {
                    width: 90%;
                    max-width: 760px;
                    min-width: 220px;
                    margin-left: auto;
                    margin-right: auto;
                }
                .modal-form {
                    width: 90%;
                    max-width: 760px;
                    min-width: 220px;
                    margin-left: auto;
                    margin-right: auto;
                    display: flex;
                    flex-direction: column;
                    align-items: stretch;
                    height: auto;
                    max-height: 100%;
                }
                @media (max-width: 700px) {
                    .modal-tabs,
                    .modal-pastebin {
                        width: 98vw;
                        max-width: 100vw;
                        min-width: 0;
                    }
                    .modal-form {
                        width: 98vw;
                        max-width: 100vw;
                        min-width: 0;
                        height: auto;
                        max-height: 100%;
                    }
        }
        /* e621.net coloring */
        body.c-posts.a-index.resp .r34-tag-item {
            background: var(--color-tag-general, #e6f7ff);
            border-color: var(--color-tag-general-alt, #7ecfff);
        }
        /* rule34.xxx coloring */
        body#body .r34-tag-item {
            background: #e6ffe6;
            border-color: #7edc7e;
        }
        @media (max-width: 600px) {
            body#body .r34-search-input {
                width: 100vw;
                max-width: 100vw;
            }
        }
        .r34-sort-row {
            display: flex;
            flex-direction: row;
            align-items: center;
            gap: 18px;
            width: 100%;
            margin: 8px 0 0 0;
            justify-content: flex-start;
        }
        .r34-sort-select {
            border-radius: 14px;
            padding: 7px 18px;
            font-size: 1.08em;
            border: 2px solid #b0d0b0;
            background: #f8fff8;
            cursor: pointer;
            transition: border-color 0.2s, background 0.2s;
            min-width: 90px;
            max-width: 180px;
        }
        .r34-sort-select:focus {
            border-color: #4a90e2;
            outline: none;
        }
        .r34-order-switch {
            border-radius: 14px;
            padding: 7px 18px;
            font-size: 1.08em;
            border: 2px solid #b0d0b0;
            background: #f8fff8;
            cursor: pointer;
            transition: border-color 0.2s, background 0.2s;
            font-weight: 500;
            color: #00549e;
            min-width: 120px;
        }
        .r34-order-switch:focus {
            border-color: #4a90e2;
            outline: none;
        }
        .r34-order-switch[style*="display: none"] {
            display: none !important;
        }
        .r34-rating-label {
            display: flex;
            align-items: center;
            gap: 4px;
            font-size: 1.08em;
            font-weight: 500;
            color: #00549e;
            background: #e6f7ff;
            border-radius: 10px;
            padding: 4px 12px;
            border: 1.5px solid #b0d0b0;
            margin-right: 4px;
            cursor: pointer;
            transition: background 0.2s, border-color 0.2s;
        }
        .r34-rating-checkbox {
            accent-color: #4a90e2;
            width: 1.1em;
            height: 1.1em;
        }
        .r34-rating-label:hover {
            background: #d0eaff;
            border-color: #4a90e2;
        }
        /* Metatag Row */
        .r34-metatag-row-wrap {
            width: 100%;
            margin: 10px 0 0 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 4px;
        }
        .r34-metatag-row-header {
            display: flex;
            flex-direction: row;
            align-items: center;
            gap: 12px;
            font-size: 1.08em;
            font-weight: 600;
            color: #00549e;
            margin-bottom: 2px;
        }
        .r34-metatag-toggle {
            border-radius: 10px;
            padding: 4px 16px;
            font-size: 1em;
            border: 2px solid #b0d0b0;
            background: #f8fff8;
            cursor: pointer;
            transition: border-color 0.2s, background 0.2s;
            font-weight: 500;
            color: #00549e;
        }
        .r34-metatag-toggle:focus {
            border-color: #4a90e2;
            outline: none;
        }
        .r34-metatag-list {
            display: flex;
            flex-direction: row;
            flex-wrap: wrap;
            gap: 10px;
            width: 100%;
            margin-top: 2px;
            justify-content: center;
            margin-left: auto;
            margin-right: auto;
        }
        .r34-metatag-item {
            background: #e6f7ff;
            border: 1.5px solid #7ecfff;
            color: #00549e;
        }
        @media (max-width: 700px) {
            .r34-sort-row {
                flex-direction: column;
                align-items: stretch;
                gap: 10px;
            }
            .r34-metatag-row-header {
                flex-direction: column;
                align-items: flex-start;
                gap: 4px;
            }
            .r34-metatag-list {
                gap: 6px;
            }
        }
        /* Include/Exclude Tag Rows */
        .r34-include-row-wrap, .r34-exclude-row-wrap {
            width: 100%;
            margin: 10px 0 0 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 4px;
        }
        .r34-include-row-header, .r34-exclude-row-header {
            display: flex;
            flex-direction: row;
            align-items: center;
            gap: 12px;
            font-size: 1.08em;
            font-weight: 600;
            color: #00549e;
            margin-bottom: 2px;
        }
        .r34-include-toggle, .r34-exclude-toggle {
            border-radius: 10px;
            padding: 4px 16px;
            font-size: 1em;
            border: 2px solid #b0d0b0;
            background: #f8fff8;
            cursor: pointer;
            transition: border-color 0.2s, background 0.2s;
            font-weight: 500;
            color: #00549e;
        }
        .r34-include-toggle:focus, .r34-exclude-toggle:focus {
            border-color: #4a90e2;
            outline: none;
        }
        .r34-include-list, .r34-exclude-list {
            display: flex;
            flex-direction: row;
            flex-wrap: wrap;
            gap: 10px;
            width: 100%;
            margin-top: 2px;
            justify-content: center;
            margin-left: auto;
            margin-right: auto;
        }
        .r34-include-item, .r34-exclude-item {
            background: #e6f7ff;
            border: 1.5px solid #7ecfff;
            color: #00549e;
        }
        @media (max-width: 700px) {
            .r34-include-row-header, .r34-exclude-row-header {
                flex-direction: column;
                align-items: flex-start;
                gap: 4px;
            }
            .r34-include-list, .r34-exclude-list {
                gap: 6px;
            }
            .r34-all-tags-row-header {
                flex-direction: column;
                align-items: flex-start;
                gap: 4px;
            }
            .r34-all-tags-list {
                gap: 6px;
            }
        }
        /* All Tags Row */
        .r34-all-tags-row-wrap {
            width: 100%;
            margin: 10px 0 0 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 4px;
        }
        .r34-all-tags-row-header {
            display: flex;
            flex-direction: row;
            align-items: center;
            gap: 12px;
            font-size: 1.08em;
            font-weight: 600;
            color: #00549e;
            margin-bottom: 2px;
        }
        .r34-all-tags-toggle {
            border-radius: 10px;
            padding: 4px 16px;
            font-size: 1em;
            border: 2px solid #b0d0b0;
            background: #f8fff8;
            cursor: pointer;
            transition: border-color 0.2s, background 0.2s;
            font-weight: 500;
            color: #00549e;
        }
        .r34-all-tags-toggle:focus {
            border-color: #4a90e2;
            outline: none;
        }
        .r34-all-tags-list {
            display: flex;
            flex-direction: row;
            flex-wrap: wrap;
            gap: 10px;
            width: 100%;
            margin-top: 2px;
            justify-content: center;
            margin-left: auto;
            margin-right: auto;
        }
        /* Settings Form */
        .settings-form {
            display: flex;
            flex-direction: column;
            gap: 16px;
            padding: 8px 0;
        }
        .settings-row {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .settings-label {
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 1.08em;
            font-weight: 500;
            color: #333;
            cursor: pointer;
        }
        .settings-label input[type="checkbox"] {
            accent-color: #4a90e2;
            width: 1.2em;
            height: 1.2em;
        }
    `;
        document.head.appendChild(style);
    }

    // --- e621 Autocomplete hijack: expose jQuery UI Autocomplete instance ---
    function hijackE621Autocomplete() {
        if (location.hostname.endsWith('e621.net')) {
            function exposeE621Autocomplete() {
                var searchInput = document.querySelector('[data-autocomplete="tag-query"], [data-autocomplete="tag-edit"], [data-autocomplete="tag"]');
                if (!searchInput) return;
                var $input = window.jQuery && window.jQuery(searchInput);
                if ($input && $input.autocomplete) {
                    var instance = $input.autocomplete("instance");
                    if (instance) {
                        searchInput.e621Autocomplete = instance;
                    }
                }
            }
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', function () {
                    exposeE621Autocomplete();
                    setTimeout(exposeE621Autocomplete, 1000);
                });
            } else {
                exposeE621Autocomplete();
                setTimeout(exposeE621Autocomplete, 1000);
            }
        }
    }

    // --- Main Entrypoint ---
    let initialized = false;
    function init() {
        if (initialized) {
            console.log('Searchbar Enhancer: Already initialized, skipping...');
            return;
        }

        console.log('Searchbar Enhancer: Initializing...');
        console.log('Site detection: isE621=', isE621, 'isRule34=', isRule34, 'isDanbooru=', isDanbooru, 'isWikiPage=', isWikiPage);
        console.log('Current hostname:', location.hostname);

        injectStyles();
        hijackE621Autocomplete();

        let setupSuccess = false;
        if (isWikiPage) {
            console.log('Setting up Wiki page...');
            setupSuccess = setupWiki();
        } else if (isE621) {
            console.log('Setting up E621...');
            setupSuccess = setupE621();
        } else if (isRule34) {
            console.log('Setting up Rule34...');
            setupSuccess = setupRule34();
        } else if (isDanbooru) {
            console.log('Setting up Danbooru...');
            setupSuccess = setupDanbooru();
        } else {
            console.log('Setting up Generic...');
            setupSuccess = setupGeneric();
        }

        if (setupSuccess !== false) {
            initialized = true;
            console.log('Searchbar Enhancer: Initialization completed');
        }
    }

    // --- Run ---
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // Also try after a short delay in case of dynamic content
    setTimeout(() => {
        if (!initialized) {
            init();
        }
    }, 1000);

})();