Duolingo DuoHacker

Najlepszy hack na Duolingo - Farmuj XP, Klejnoty, Serie i odblokuj Duolingo Max za darmo.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==UserScript==
// @name                Duolingo DuoHacker
// @name:vi             Duolingo DuoHacker
// @name:zh-CN          多邻国 DuoHacker
// @name:zh-TW          多鄰國 DuoHacker
// @name:ja             Duolingo DuoHacker
// @name:ko             듀오링고 DuoHacker
// @name:fr             Duolingo DuoHacker
// @name:de             Duolingo DuoHacker
// @name:es             Duolingo DuoHacker
// @name:pt-BR          Duolingo DuoHacker
// @name:pt-PT          Duolingo DuoHacker
// @name:ru             Дуолинго DuoHacker
// @name:ar             Duolingo DuoHacker دوهاكر
// @name:tr             Duolingo DuoHacker
// @name:id             Duolingo DuoHacker
// @name:th             Duolingo DuoHacker
// @name:pl             Duolingo DuoHacker
// @name:nl             Duolingo DuoHacker
// @name:it             Duolingo DuoHacker
// @name:sv             Duolingo DuoHacker
// @name:da             Duolingo DuoHacker
// @name:fi             Duolingo DuoHacker
// @name:nb             Duolingo DuoHacker
// @name:cs             Duolingo DuoHacker
// @name:hu             Duolingo DuoHacker
// @name:ro             Duolingo DuoHacker
// @name:uk             Дуолінго DuoHacker
// @name:hi             Duolingo DuoHacker
// @name:bn             Duolingo DuoHacker
// @name:fa             Duolingo DuoHacker دوهاکر
// @name:he             Duolingo DuoHacker
// @name:ms             Duolingo DuoHacker
// @name:fil            Duolingo DuoHacker
// @name:el             Duolingo DuoHacker
// @name:hr             Duolingo DuoHacker
// @name:sk             Duolingo DuoHacker
// @name:bg             Дуолинго DuoHacker
// @name:sr             Дуолинго DuoHacker
// @name:lt             Duolingo DuoHacker
// @name:lv             Duolingo DuoHacker
// @name:et             Duolingo DuoHacker
// @name:sl             Duolingo DuoHacker
// @name:ca             Duolingo DuoHacker
// @name:af             Duolingo DuoHacker
// @name:sw             Duolingo DuoHacker
// @name:zu             Duolingo DuoHacker
// @name:mn             Duolingo DuoHacker
// @name:my             Duolingo DuoHacker
// @name:km             Duolingo DuoHacker
// @name:lo             Duolingo DuoHacker
// @name:ur             Duolingo DuoHacker

// @namespace           https://github.com/DuoHacker/DuoHacker
// @version             2026.07.13
// @description         The #1 Duolingo hack - Farm XP, Gems, Streaks and unlock Duolingo Max for free.
// @description:vi      Công cụ hack Duolingo #1 - Farm XP, Gems, Streaks và mở khóa Duolingo Max miễn phí.
// @description:zh-CN   最强 Duolingo 辅助工具 - 自动刷 XP、宝石、连胜,免费解锁 Duolingo Max。
// @description:zh-TW   最強 Duolingo 輔助工具 - 自動刷 XP、寶石、連勝,免費解鎖 Duolingo Max。
// @description:ja      Duolingo最強ハックツール - XP・ジェム・連続記録を自動取得、Duolingo Maxを無料解放。
// @description:ko      최고의 Duolingo 핵 툴 - XP, 젬, 스트릭 자동 획득 및 Duolingo Max 무료 해제.
// @description:fr      Le hack Duolingo #1 - Farmez XP, Gemmes, Séries et débloquez Duolingo Max gratuitement.
// @description:de      Der #1 Duolingo-Hack - XP, Gems, Serien farmen und Duolingo Max kostenlos freischalten.
// @description:es      El hack #1 de Duolingo - Farmea XP, Gemas, Rachas y desbloquea Duolingo Max gratis.
// @description:pt-BR   O hack #1 do Duolingo - Farme XP, Gemas, Sequências e desbloqueie o Duolingo Max de graça.
// @description:pt-PT   O hack #1 do Duolingo - Faça farm de XP, Gemas, Sequências e desbloqueie o Duolingo Max gratuitamente.
// @description:ru      Лучший хак для Duolingo - Фармите XP, Гемы, Серии и разблокируйте Duolingo Max бесплатно.
// @description:ar      أفضل هاك لـ Duolingo - اجمع XP والجواهر والسلاسل وافتح Duolingo Max مجاناً.
// @description:tr      Duolingo için #1 hack - XP, Gem, Seri farm'la ve Duolingo Max'ı ücretsiz aç.
// @description:id      Hack Duolingo #1 - Farm XP, Gems, Streak dan buka kunci Duolingo Max gratis.
// @description:th      แฮก Duolingo อันดับ 1 - ฟาร์ม XP, เพชร, สตรีคและปลดล็อก Duolingo Max ฟรี.
// @description:pl      Najlepszy hack na Duolingo - Farmuj XP, Klejnoty, Serie i odblokuj Duolingo Max za darmo.
// @description:nl      De #1 Duolingo-hack - Farm XP, Edelstenen, Reeksen en ontgrendel Duolingo Max gratis.
// @description:it      Il miglior hack per Duolingo - Fai farm di XP, Gemme, Streak e sblocca Duolingo Max gratis.
// @description:sv      Bästa Duolingo-hacket - Farma XP, Ädelstenar, Streaks och lås upp Duolingo Max gratis.
// @description:da      Den bedste Duolingo-hack - Farm XP, Ædelstene, Striber og lås Duolingo Max op gratis.
// @description:fi      Paras Duolingo-hakki - Farmmaa XP, Jalokivet, Putket ja avaa Duolingo Max ilmaiseksi.
// @description:nb      Den beste Duolingo-hacken - Farm XP, Edelstener, Rekker og lås opp Duolingo Max gratis.
// @description:cs      Nejlepší hack na Duolingo - Farmujte XP, Drahokamy, Série a odemkněte Duolingo Max zdarma.
// @description:hu      A legjobb Duolingo-hack - Farmolj XP-t, Drágaköveket, Sorozatokat és oldd fel a Duolingo Maxot ingyen.
// @description:ro      Cel mai bun hack pentru Duolingo - Farmează XP, Pietre, Serii și deblochează Duolingo Max gratuit.
// @description:uk      Найкращий хак для Duolingo - Фармте XP, Самоцвіти, Серії та розблокуйте Duolingo Max безкоштовно.
// @description:hi      Duolingo का #1 हैक - XP, Gems, Streaks फार्म करें और Duolingo Max मुफ्त में अनलॉक करें।
// @description:bn      সেরা Duolingo হ্যাক - XP, Gems, Streaks ফার্ম করুন এবং বিনামূল্যে Duolingo Max আনলক করুন।
// @description:fa      بهترین هک Duolingo - XP، جواهر و رکورد را فارم کنید و Duolingo Max را رایگان باز کنید.
// @description:he      ההאק הטוב ביותר ל-Duolingo - צבור XP, אבנים יקרות, רצפים ופתח את Duolingo Max בחינם.
// @description:ms      Hack Duolingo terbaik - Farm XP, Permata, Streak dan buka kunci Duolingo Max secara percuma.
// @description:fil     Ang pinakamahusay na Duolingo hack - Mag-farm ng XP, Gems, Streaks at i-unlock ang Duolingo Max nang libre.
// @description:el      Το καλύτερο hack για το Duolingo - Κάντε farm XP, Πετράδια, Σειρές και ξεκλειδώστε το Duolingo Max δωρεάν.
// @description:hr      Najbolji Duolingo hack - Farmajte XP, Dragulje, Nizove i otključajte Duolingo Max besplatno.
// @description:sk      Najlepší hack na Duolingo - Farmujte XP, Drahokamy, Série a odomknite Duolingo Max zadarmo.
// @description:bg      Най-добрият хак за Duolingo - Фармете XP, Скъпоценни камъни, Серии и отключете Duolingo Max безплатно.
// @description:sr      Najbolji Duolingo hak - Farmujte XP, Dragulje, Serije i otključajte Duolingo Max besplatno.
// @description:lt      Geriausias Duolingo įsilaužimas - Farminkite XP, Brangakmenius, Serijas ir atrakinkite Duolingo Max nemokamai.
// @description:lv      Labākais Duolingo hack - Farmējiet XP, Dārgakmeņus, Sērijas un atbloķējiet Duolingo Max bez maksas.
// @description:et      Parim Duolingo häkk - Farmige XP, Kalliskive, Seeriad ja avage Duolingo Max tasuta.
// @description:sl      Najboljši Duolingo hack - Farmajte XP, Dragulji, Serije in odklenite Duolingo Max brezplačno.
// @description:ca      El millor hack per a Duolingo - Fes farm de XP, Gemmes, Ratxes i desbloqueja Duolingo Max gratis.
// @description:af      Die beste Duolingo-hack - Boer XP, Edelstene, Reekse en ontsluit Duolingo Max gratis.
// @description:sw      Hack bora ya Duolingo - Fanya farm ya XP, Vito, Mifululizo na fungua Duolingo Max bila malipo.
// @description:zu      I-hack engcono kakhulu ye-Duolingo - Fama i-XP, Amagugu, Izindawo futhi vula i-Duolingo Max mahhala.
// @description:mn      Duolingo-н хамгийн шилдэг хак - XP, Эрдэнийн чулуу, Цуваа фарм хийж Duolingo Max-ийг үнэгүй нээ.
// @description:my      Duolingo hack အကောင်းဆုံး - XP, Gems, Streaks farm လုပ်ပြီး Duolingo Max ကို အခမဲ့ ဖွင့်ပါ။
// @description:km      ហេគ Duolingo ល្អបំផុត - ដាំ XP, Gems, Streaks និងដោះសោ Duolingo Max ដោយឥតគិតថ្លៃ។
// @description:lo      ແຮັກ Duolingo ອັນດັບໜຶ່ງ - ຟາມ XP, ເພັດ, ສາຍຕໍ່ເນື່ອງ ແລະ ປົດລັອກ Duolingo Max ຟຣີ.
// @description:ur      Duolingo کا بہترین ہیک - XP، Gems، Streaks فارم کریں اور Duolingo Max مفت میں انلاک کریں۔

// @author              DuoHacker

// @match               https://*.duolingo.com/*
// @match               https://*.duolingo.cn/*

// @icon                https://assets.duohacker.io.vn/images/DuoHacker_Logo_NoBG_PNG.png
// @run-at              document-end

// @grant               GM_xmlhttpRequest
// @grant               GM_addStyle
// @connect             duolingo.com
// @connect             ssr-avatars.duolingo.com
// @connect             stories.duolingo.com
// @connect             goals-api.duolingo.com
// @connect             duolingo-leaderboards-prod.duolingo.com
// @connect             raw.githubusercontent.com
// @connect             avatars.githubusercontent.com
// @connect             greasyfork.org
// @connect             assets.duohacker.io.vn
// @connect             d35aaqx5ub95lt.cloudfront.net
// @connect             font.duohacker.io.vn

// @compatible          chrome   Tested on Chrome 120+ with Tampermonkey
// @compatible          firefox  Tested on Firefox 120+ with Tampermonkey / Violentmonkey
// @compatible          edge     Tested on Edge 120+ with Tampermonkey
// @compatible          opera    Supported via Tampermonkey / Violentmonkey
// @compatible          safari   Supported via Userscripts app
// @compatible          brave    Supported via Tampermonkey
// @homepageURL         https://duohacker.io.vn
// @supportURL          https://duohacker.io.vn/discord
// @copyright           2026, DuoHacker (https://github.com/DuoHacker)
// @license             BY-NC-ND 4.0
// ==/UserScript==

(function() {
    'use strict';

    // ── i18n ──────────────────────────────────────────────────────────
    var _I18N_KEY = 'dh2_lang';
    var _lang = localStorage.getItem(_I18N_KEY) || 'vi';
    var _isOutdated = false;
    var _currentConnState = null;

    var _LANGS = {
        en: {
            // ── Language selector ──
            lang_select_label: 'Language',
            lang_en: 'English',
            lang_vi: 'Tiếng Việt',

            // ── Top bar ──
            hide: 'Hide',
            show: 'Show',
            switch_v1: 'Switch to Solver',
            switch_v2: 'Switch to V2',

            // ── Connection status ──
            connecting: 'Connecting',
            connected: 'Connected',
            error: 'Error',
            outdated: 'Outdated',

            // ── Page 1 (main) ──
            donate: 'Donate',
            xp_question: 'How much XP would you like to gain?',
            gems_run_label: 'Click "RUN" to Farm Gems',
            streak_question: 'How many Streak days to restore?',
            extra_features: 'Extra Features',

            // ── Page 2 (extra features) ──
            back: 'Back',
            farm_practice: 'How many practice lesson you would like to solve?',
            farm_practice_sub: '0 = unlimited practice sessions',
            shop_items: 'Shop Items',
            auto_league: 'Auto League #1',
            auto_league_sub: 'Farm XP until rank #1',
            auto_daily_quest: 'Auto Daily Quest',
            auto_daily_sub: 'Complete all daily quests',
            claim_monthly: 'Claim Monthly Quest',
            claim_monthly_sub: 'View & claim monthly quests',
            free_super: 'Free Super Duolingo',
            free_super_sub: 'Activate Super Duolingo for free',

            // ── Page 4 (settings) ──
            loop_delay: 'Loop delay (ms)',
            free_duo_max: 'Free Duolingo Max',
            free_duo_max_sub: 'Client-side only, reload to apply',
            hide_profile: 'Hide Profile',
            auto_solver: 'Auto Solver',
            auto_solver_sub: 'Show Auto Solver buttons during lessons',
            hide_animation: 'Hide Animation',
            hide_animation_sub: 'Hide Duolingo images & animations',
            lesson_shortener: 'Lesson Shortener',
            lesson_shortener_sub: 'Replace lessons with 1 instant question',
            stories_shortener: 'Story Skip Challenges',
            stories_shortener_sub: 'Skip all challenges in stories',
            safe_mode: 'Safe Mode',
            safe_mode_sub: 'Adds more delay between clicks while solving lessons/practices',
            view_credits: 'View Credits',

            // ── Page 3 (shop) ──
            search_placeholder: 'Search items...',
            loading_shop: 'Loading shop...',
            no_items_found: 'No items found.',
            no_items_available: 'No items available.',

            // ── Page 5 (account manager) ──
            account_manager: 'Account Manager',
            no_saved_accounts: 'No saved accounts.',

            // ── Page 6 (monthly quests) ──
            monthly_quests: 'Monthly Quests',
            loading_quests: 'Loading quests...',
            no_monthly_quests: 'No monthly quests found.',
            quest_failed: 'Failed to load quests.',

            // ── Page 7 (credits) ──
            credits: 'Credits',

            // ── Page V1 ──
            xp_farming: 'XP Farming',
            farm_gems: 'Farm Gems',
            streak_farming: 'Streak Farming',
            activate_super_q: 'Would you like to activate Free Super Duolingo?',
            settings: 'Settings',

            // ── Not connected ──
            not_connected: 'Not connected.',

            // ── Buttons (short labels) ──
            btn_get: 'GET',
            btn_run: 'RUN',
            btn_stop: 'STOP',
            btn_save: 'SAVE',
            btn_saved: 'SAVED ✓',
            btn_claim: 'CLAIM ALL',
            btn_activate: 'ACTIVATE',
            btn_done: 'DONE ✓',
            btn_save_current: 'SAVE CURRENT',
            btn_solve_all: 'SOLVE ALL',
            btn_pause: 'PAUSE',
            btn_loading: 'Loading...',
            btn_running: 'Running...',
            btn_got: 'GOT ✓',
            btn_failed: 'FAILED',

            // ── Hide Profile status ──
            profile_private: 'Profile is private',
            profile_public: 'Profile is public',
            status_unavailable: 'Unavailable',
            status_saving: 'Saving\u2026',
            status_failed_retry: 'Failed \u2014 try again',
            status_not_connected: 'Not connected',
            status_loading: 'Loading\u2026',

            // ── Page 11 (leaderboard) ──
            leaderboard: 'Leaderboard',
            leaderboard_sub: 'View your current league',
            your_current_league: 'Your current league',
            lb_loading: 'Loading leaderboard...',
            lb_failed: 'Failed to load leaderboard.',
            choose_reaction: 'Choose Reaction',
            lb_promoted: '⬆ Promoted',
            lb_demoted: '⬇ Demoted',
            lb_safe: '🟡 Safe',
            lb_ended: 'Contest ended',

            // ── Leaderboard separators ──
            lb_rank_up: 'Rank Up',
            lb_rank_down: 'Rank Down',

            // ── Monthly quest status ──
            mq_completed: 'COMPLETED',

            // ── Changelog badge ──
            changelog_current: 'CURRENT',

            // ── Account manager labels ──
            acc_active: 'Active',
            acc_login_btn: 'LOG IN',

            // ── Connection error subtexts ──
            conn_not_logged_in: 'Not logged in',
            conn_invalid_token: 'Invalid token',
            conn_failed_retry: 'Failed — retrying',

            // ── Streak live counter suffixes ──
            streak_day: 'day',
            streak_days: 'days',

            // ── Notification titles ──
            notif_xp_done_title: 'XP Farm Done!',
            notif_xp_stopped_title: 'XP Farm Stopped',
            notif_gem_done_title: 'Gem Farm Done!',
            notif_gem_farm_title: 'Gem Farm',
            notif_streak_done_title: 'Streak Farm Done!',
            notif_streak_farm_title: 'Streak Farm',
            notif_streak_stopped_title: 'Streak Farm Stopped',
            notif_practice_done_title: 'Farm Practice Done!',
            notif_practice_title: 'Farm Practice',
            notif_daily_done_title: 'Daily Quests Done!',
            notif_all_done_title: 'All Done!',
            notif_mq_title: 'Monthly Quests',
            notif_mq_partial_title: 'Partial',
            notif_super_title: 'Super Activated!',
            notif_shop_title: 'Shop',
            notif_error_title: 'Error',
            notif_failed_title: 'Failed',
            notif_stopped_title: 'Stopped',
            notif_busy_title: 'Busy',
            notif_not_connected_title: 'Not connected',
            notif_not_loaded_title: 'Not loaded',
            notif_nothing_to_do_title: 'Nothing to do',
            notif_v1xp_title: 'V1 XP',
            notif_acc_saved_title: 'Account Saved',
            notif_acc_removed_title: 'Removed',
            notif_acc_already_title: 'Already saved',
            notif_acc_not_found_title: 'Not found',

            // ── Notification bodies ──
            notif_xp_done_body: (xp, loops) => `Farmed ${xp} XP in ${loops} loops.`,
            notif_xp_done_inf_body: (xp) => `+${xp} XP gained.`,
            notif_xp_stopped_body: (xp) => `+${xp} XP gained.`,
            notif_gem_done_body: (gems) => `+${gems} gems gained.`,
            notif_gem_no_rewards: 'No rewards available. Retrying…',
            notif_streak_done_body: (days) => `Restored ${days} streak days.`,
            notif_streak_no_days: 'No days could be saved. Please try again.',
            notif_streak_done_inf_body: (days, unit) => `+${days} ${unit} gained.`,
            notif_streak_stopped_body: (days) => `Processed ${days} days.`,
            notif_practice_done_body: (n) => `Completed ${n} practice(s).`,
            notif_practice_next_body: (n, total) => `Done ${n}${total > 0 ? ' / ' + total : ''} — loading next...`,
            notif_practice_nav: 'Navigating to practice...',
            notif_practice_busy: 'Practice farm already running.',
            notif_daily_done_body: (n) => `Completed ${n} metric(s).`,
            notif_all_done_body: 'All daily quests completed.',
            notif_daily_error: 'Could not load quest data.',
            notif_daily_fail: 'Quest completion failed.',
            notif_mq_all_body: (n) => `All ${n} quest(s) claimed!`,
            notif_mq_partial_body: (ok, total) => `${ok}/${total} quests claimed.`,
            notif_mq_fail: 'No quests could be claimed.',
            notif_mq_not_loaded: 'Open Monthly Quests first.',
            notif_mq_nothing: 'No unclaimed quests found.',
            notif_super_body: 'Free Super Duolingo activated!',
            notif_super_fail: 'Activation failed. You may already have Super.',
            notif_shop_got: (name) => `Got ${name}!`,
            notif_shop_fail: 'Failed to get item.',
            notif_v1xp_errors: 'Too many errors, stopping.',
            notif_v1xp_done_body: (xp) => `Farmed ${xp} XP.`,
            notif_v1gem_done_body: (gems) => `+${gems} gems gained.`,
            notif_v1streak_done_body: (days) => `Farmed ${days} streak days.`,
            notif_stopped_body: 'Farm stopped.',
            notif_busy_v1: 'Stop current V1 farm first.',
            notif_not_connected_body: 'Please wait.',
            notif_not_connected_conn: 'Please wait for connection.',
            notif_acc_saved_body: (name) => `Saved account: ${name}`,
            notif_acc_removed_body: 'Account removed from list.',
            notif_acc_already_body: 'This account is already in the list.',
            notif_acc_not_found_body: 'Account not found.',
        },
        vi: {
            // ── Language selector ──
            lang_select_label: 'Ngôn ngữ',
            lang_en: 'English',
            lang_vi: 'Tiếng Việt',

            // ── Top bar ──
            hide: 'Ẩn',
            show: 'Hiện',
            switch_v1: 'Đổi thành Solver',
            switch_v2: 'Đổi thành V2',

            // ── Connection status ──
            connecting: 'Đang kết nối',
            connected: 'Đã kết nối',
            error: 'Lỗi',
            outdated: 'Phiên bản cũ',

            // ── Page 1 (main) ──
            donate: 'Quyên góp',
            xp_question: 'Số XP bạn muốn farm',
            gems_run_label: 'Nhấn vào "CHẠY" để farm Gem',
            streak_question: 'Số Streak bạn muốn khôi phục',
            extra_features: 'Tính năng khác',

            // ── Page 2 (extra features) ──
            back: 'Quay lại',
            farm_practice: 'Số bài Practice bạn muốn giải?',
            farm_practice_sub: '0 = vô hạn luồng cày',
            shop_items: 'Shop vật phẩm',
            auto_league: 'Tự động cày bảng xếp hạng',
            auto_league_sub: 'Cày XP đến khi đạt #1',
            auto_daily_quest: 'Hoàn thành nhiệm vụ hằng ngày',
            auto_daily_sub: 'Hoàn thành tất cả nhiệm vụ hằng ngày',
            claim_monthly: 'Hack huy hiệu tháng',
            claim_monthly_sub: 'Xem & nhận huy hiệu tháng',
            free_super: 'Duolingo Super miễn phí',
            free_super_sub: 'Kích hoạt Duolingo Super khoảng 3 ngày miễn phí',

            // ── Page 4 (settings) ──
            loop_delay: 'Độ trễ vòng',
            free_duo_max: 'Duolingo Max miễn phí',
            free_duo_max_sub: 'Chỉ hoạt động ở Web, làm mới trang để áp dụng',
            hide_profile: 'Ẩn profile',
            auto_solver: 'Tự động giải',
            auto_solver_sub: 'Tự động giải bài trong lesson',
            hide_animation: 'Ẩn hiệu ứng',
            hide_animation_sub: 'Ẩn hình ảnh và hiệu ứng của Duolingo',
            lesson_shortener: 'Rút gọn bài học',
            lesson_shortener_sub: 'Thay thế các bài học bằng 1 câu hỏi',
            stories_shortener: 'Skip toàn bộ câu hỏi Stories',
            stories_shortener_sub: 'Bỏ qua tất cả câu hỏi trong stories',
            safe_mode: 'Chế độ an toàn',
            safe_mode_sub: 'Tăng độ trễ giữa các lượt click khi giải lesson/farm practice',
            view_credits: 'Xem credit',

            // ── Page 3 (shop) ──
            search_placeholder: 'Tìm kiếm vật phẩm...',
            loading_shop: 'Đang tải shop...',
            no_items_found: 'Không tìm thấy vật phẩm nào.',
            no_items_available: 'Không có vật phẩm nào.',

            // ── Page 5 (account manager) ──
            account_manager: 'Quản lý tài khoản',
            no_saved_accounts: 'Chưa có tài khoản nào được lưu.',

            // ── Page 6 (monthly quests) ──
            monthly_quests: 'Nhiệm vụ hằng tháng',
            loading_quests: 'Đang tải nhiệm vụ...',
            no_monthly_quests: 'Không tìm thấy nhiệm vụ tháng này.',
            quest_failed: 'Tải nhiệm vụ thất bại.',

            // ── Page 7 (credits) ──
            credits: 'Credits',

            // ── Page V1 ──
            xp_farming: 'Cày XP',
            farm_gems: 'Cày Gem',
            streak_farming: 'Cày Streak',
            activate_super_q: 'Bạn có muốn kích hoạt Super Duolingo miễn phí không?',
            settings: 'Cài đặt',

            // ── Not connected ──
            not_connected: 'Chưa kết nối.',

            // ── Buttons (short labels) ──
            btn_get: 'LẤY',
            btn_run: 'CHẠY',
            btn_stop: 'DỪNG',
            btn_save: 'LƯU',
            btn_saved: 'ĐÃ LƯU ✓',
            btn_claim: 'NHẬN TẤT CẢ',
            btn_activate: 'KÍCH HOẠT',
            btn_done: 'XONG ✓',
            btn_save_current: 'LƯU HIỆN TẠI',
            btn_solve_all: 'GIẢI HẾT',
            btn_pause: 'TẠM DỪNG',
            btn_loading: 'Đang tải...',
            btn_running: 'Đang chạy...',
            btn_got: 'ĐÃ LẤY ✓',
            btn_failed: 'THẤT BẠI',

            // ── Hide Profile status ──
            profile_private: 'Hồ sơ đã ẩn',
            profile_public: 'Hồ sơ công khai',
            status_unavailable: 'Không khả dụng',
            status_saving: 'Đang lưu…',
            status_failed_retry: 'Lỗi — thử lại',
            status_not_connected: 'Chưa kết nối',
            status_loading: 'Đang tải…',

            // ── Page 11 (leaderboard) ──
            leaderboard: 'Bảng xếp hạng',
            leaderboard_sub: 'Xem giải đấu hiện tại',
            your_current_league: 'Giải đấu hiện tại',
            lb_loading: 'Đang tải bảng xếp hạng...',
            lb_failed: 'Không tải được bảng xếp hạng.',
            choose_reaction: 'Chọn Reaction',
            lb_promoted: '⬆ Thăng hạng',
            lb_demoted: '⬇ Xuống hạng',
            lb_safe: '🟡 An toàn',
            lb_ended: 'Giải đấu đã kết thúc',

            // ── Leaderboard separators ──
            lb_rank_up: 'Thăng hạng',
            lb_rank_down: 'Xuống hạng',

            // ── Monthly quest status ──
            mq_completed: 'HOÀN THÀNH',

            // ── Changelog badge ──
            changelog_current: 'HIỆN TẠI',

            // ── Account manager labels ──
            acc_active: 'Đang dùng',
            acc_login_btn: 'ĐĂNG NHẬP',

            // ── Connection error subtexts ──
            conn_not_logged_in: 'Chưa đăng nhập',
            conn_invalid_token: 'Token không hợp lệ',
            conn_failed_retry: 'Lỗi — đang thử lại',

            // ── Streak live counter suffixes ──
            streak_day: 'ngày',
            streak_days: 'ngày',

            // ── Notification titles ──
            notif_xp_done_title: 'Farm XP xong!',
            notif_xp_stopped_title: 'Đã dừng farm XP',
            notif_gem_done_title: 'Farm Gem xong!',
            notif_gem_farm_title: 'Farm Gem',
            notif_streak_done_title: 'Farm Streak xong!',
            notif_streak_farm_title: 'Farm Streak',
            notif_streak_stopped_title: 'Đã dừng farm Streak',
            notif_practice_done_title: 'Farm Practice xong!',
            notif_practice_title: 'Farm Practice',
            notif_daily_done_title: 'Nhiệm vụ ngày xong!',
            notif_all_done_title: 'Xong hết!',
            notif_mq_title: 'Nhiệm vụ tháng',
            notif_mq_partial_title: 'Một phần',
            notif_super_title: 'Kích hoạt Super!',
            notif_shop_title: 'Shop',
            notif_error_title: 'Lỗi',
            notif_failed_title: 'Thất bại',
            notif_stopped_title: 'Đã dừng',
            notif_busy_title: 'Đang bận',
            notif_not_connected_title: 'Chưa kết nối',
            notif_not_loaded_title: 'Chưa tải',
            notif_nothing_to_do_title: 'Không có gì',
            notif_v1xp_title: 'V1 XP',
            notif_acc_saved_title: 'Đã lưu tài khoản',
            notif_acc_removed_title: 'Đã xóa',
            notif_acc_already_title: 'Đã có rồi',
            notif_acc_not_found_title: 'Không tìm thấy',

            // ── Notification bodies ──
            notif_xp_done_body: (xp, loops) => `Đã farm ${xp} XP trong ${loops} vòng.`,
            notif_xp_done_inf_body: (xp) => `+${xp} XP đã nhận.`,
            notif_xp_stopped_body: (xp) => `+${xp} XP đã nhận.`,
            notif_gem_done_body: (gems) => `+${gems} gem đã nhận.`,
            notif_gem_no_rewards: 'Không có phần thưởng. Đang thử lại…',
            notif_streak_done_body: (days) => `Đã khôi phục ${days} ngày streak.`,
            notif_streak_no_days: 'Không thể lưu ngày nào. Vui lòng thử lại.',
            notif_streak_done_inf_body: (days, unit) => `+${days} ${unit} đã nhận.`,
            notif_streak_stopped_body: (days) => `Đã xử lý ${days} ngày.`,
            notif_practice_done_body: (n) => `Đã hoàn thành ${n} bài practice.`,
            notif_practice_next_body: (n, total) => `Xong ${n}${total > 0 ? ' / ' + total : ''} — đang tải tiếp...`,
            notif_practice_nav: 'Đang điều hướng đến practice...',
            notif_practice_busy: 'Farm practice đang chạy rồi.',
            notif_daily_done_body: (n) => `Đã hoàn thành ${n} chỉ tiêu.`,
            notif_all_done_body: 'Đã hoàn thành tất cả nhiệm vụ ngày.',
            notif_daily_error: 'Không thể tải dữ liệu nhiệm vụ.',
            notif_daily_fail: 'Hoàn thành nhiệm vụ thất bại.',
            notif_mq_all_body: (n) => `Đã nhận ${n} nhiệm vụ!`,
            notif_mq_partial_body: (ok, total) => `${ok}/${total} nhiệm vụ đã nhận.`,
            notif_mq_fail: 'Không thể nhận nhiệm vụ nào.',
            notif_mq_not_loaded: 'Mở trang Nhiệm vụ tháng trước.',
            notif_mq_nothing: 'Không có nhiệm vụ nào chưa nhận.',
            notif_super_body: 'Đã kích hoạt Super Duolingo miễn phí!',
            notif_super_fail: 'Kích hoạt thất bại. Có thể bạn đã có Super rồi.',
            notif_shop_got: (name) => `Đã nhận ${name}!`,
            notif_shop_fail: 'Không lấy được vật phẩm.',
            notif_v1xp_errors: 'Quá nhiều lỗi, đang dừng.',
            notif_v1xp_done_body: (xp) => `Đã farm ${xp} XP.`,
            notif_v1gem_done_body: (gems) => `+${gems} gem đã nhận.`,
            notif_v1streak_done_body: (days) => `Đã farm ${days} ngày streak.`,
            notif_stopped_body: 'Đã dừng farm.',
            notif_busy_v1: 'Dừng farm V1 hiện tại trước.',
            notif_not_connected_body: 'Vui lòng chờ.',
            notif_not_connected_conn: 'Vui lòng chờ kết nối.',
            notif_acc_saved_body: (name) => `Đã lưu tài khoản: ${name}`,
            notif_acc_removed_body: 'Đã xóa tài khoản khỏi danh sách.',
            notif_acc_already_body: 'Tài khoản này đã được lưu rồi.',
            notif_acc_not_found_body: 'Không tìm thấy tài khoản.',
        }
    };

    function _t(key, ...args) {
        const d = _LANGS[_lang] || _LANGS.en;
        const v = d[key];
        const en = _LANGS.en[key];
        // fallback to EN if VI value empty
        const val = (v !== undefined && v !== '') ? v : (en !== undefined ? en : key);
        // Support function values for parameterised strings
        return typeof val === 'function' ? val(...args) : val;
    }

    function _setLang(l) {
        if (l !== 'vi' && l !== 'en') return;
        _lang = l;
        localStorage.setItem(_I18N_KEY, l);
        _applyLang();
    }

    function _applyLang() {
        // Static text nodes mapped by element id → translation key
        const _ID_MAP = {
            'DH_Hide_Txt': () => document.getElementById('DH_Hide_Ico')?.textContent === '􀋮' ? 'show' : 'hide',
            'DH_Conn_Txt': () => _currentConnState === 'connected' && _isOutdated ? 'outdated' : (_currentConnState || 'connecting'),
            // Page 1
            'DH_Donate_Btn_Lbl': 'donate',
            'DH_XP_Question': 'xp_question',
            'DH_Gem_Run_Label': 'gems_run_label',
            'DH_Streak_Question': 'streak_question',
            'DH_ExtraFeatures_Lbl': 'extra_features',
            // Page 2
            'DH_Back_Txt': 'back',
            'DH_Practice_Question': 'farm_practice',
            'DH_Practice_Sub': 'farm_practice_sub',
            'DH_Shop_Lbl': 'shop_items',
            'DH_League_Title': 'auto_league',
            'DH_League_Sub': 'auto_league_sub',
            'DH_Quest_Title': 'auto_daily_quest',
            'DH_Quest_Sub': 'auto_daily_sub',
            'DH_Monthly_Title': 'claim_monthly',
            'DH_Monthly_Sub': 'claim_monthly_sub',
            // Page 4
            'DH_Settings_Back_Txt': 'back',
            'DH_LoopDelay_Lbl': 'loop_delay',
            'DH_FreeDuoMax_Lbl': 'free_duo_max',
            'DH_FreeDuoMax_Sub': 'free_duo_max_sub',
            'DH_HideProfile_Lbl': 'hide_profile',
            'DH_AutoSolver_Lbl': 'auto_solver',
            'DH_AutoSolver_Sub': 'auto_solver_sub',
            'DH_HideAnim_Lbl': 'hide_animation',
            'DH_HideAnim_Sub': 'hide_animation_sub',
            'DH_LessonShortener_Lbl': 'lesson_shortener',
            'DH_LessonShortener_Sub': 'lesson_shortener_sub',
            'DH_StoriesShortener_Lbl': 'stories_shortener',
            'DH_StoriesShortener_Sub': 'stories_shortener_sub',
            'DH_SafeMode_Lbl': 'safe_mode',
            'DH_SafeMode_Sub': 'safe_mode_sub',
            'DH_Credits_Btn': 'view_credits',
            // Page 9 (license) / Page 10 (changelog)
            'DH_License_Back_Txt': 'back',
            'DH_Changelog_Back_Txt': 'back',
            // Page 3 shop back (now has id)
            'DH_Shop_Back_Txt': 'back',
            // Account / Monthly Quest back + static labels
            'DH_AccMgr_Back_Txt': 'back',
            'DH_AccMgr_Title': 'account_manager',
            'DH_MQ_Back_Txt': 'back',
            'DH_MQ_Title': 'monthly_quests',
            // Page 7
            'DH_Credits_Back_Txt': 'back',
            'DH_Credits_Title': 'credits',
            // Page V1
            'DH_V1_XP_Title': 'xp_farming',
            'DH_V1_Gem_Title': 'farm_gems',
            'DH_V1_Streak_Title': 'streak_farming',
            'DH_V1_Settings_Lbl': 'extra_features',
            'DH_V1_Settings_Back_Txt': 'back',
            // Switch buttons
            'DH_SwitchV1_Lbl': 'switch_v1',
            'DH_SwitchV2_Lbl': 'switch_v2',
            // Main action buttons
            'DH_XP_Lbl': 'btn_get',
            'DH_Gem_Lbl': 'btn_run',
            'DH_Streak_Lbl': 'btn_run',
            'DH_Practice_Lbl': 'btn_run',
            'DH_League_Lbl': 'btn_run',
            'DH_Quest_Lbl': 'btn_run',
            'DH_MonthlyQuest_Claim_Lbl': 'btn_get',
            'DH_Delay_Lbl': 'btn_save',

            // Account / Monthly Quest
            'DH_AccSave_Lbl': 'btn_save_current',
            'DH_MQ_ClaimAll_Lbl': 'btn_claim',

            // V1 buttons
            'DH_V1_XP_Lbl': 'btn_run',
            'DH_V1_Gem_Lbl': 'btn_run',
            'DH_V1_Streak_Lbl': 'btn_run',
            // Static initial state text
            'DH_AccNoSaved_Txt': 'no_saved_accounts',
            'DH_ShopLoading_Txt': 'loading_shop',
            // Page 11 (leaderboard)
            'DH_LB_Back_Txt': 'back',
            // Page 12 (reaction picker)
            'DH_React_Back_Txt': 'back',
            'DH_React_Title': 'choose_reaction',
            'DH_LB_Title': 'leaderboard',
            'DH_React_Title': 'choose_reaction',
            'DH_LB_Loading_Txt': 'lb_loading',
            'DH_LB_Nav_Lbl': 'leaderboard',
        };

        for (const [id, key] of Object.entries(_ID_MAP)) {
            const el = document.getElementById(id);
            if (!el) continue;
            if (typeof key === 'function') {
                el.textContent = _t(key());
            } else if (key) {
                el.textContent = _t(key);
            }
        }

        // Re-apply connection status text so it updates on lang switch
        // try/catch because _currentConnState is declared later (let TDZ on first _applyLang call)


        // Placeholder attributes
        const ph = document.getElementById('DH_Shop_Search');
        if (ph) ph.placeholder = _t('search_placeholder');

        // Lang selector button label
        const lb = document.getElementById('DH_LangSelector_Lbl');
        if (lb) {
            lb.innerHTML = _lang === 'vi' ?
                `<img src="https://d35aaqx5ub95lt.cloudfront.net/vendor/2b077d42185bc45d4896ed55f15c4fea.svg" style="width:20px;height:14px;border-radius:2px;vertical-align:middle;flex-shrink:0;object-fit:cover;" aria-hidden="true"> VI` :
                `<img src="https://d35aaqx5ub95lt.cloudfront.net/vendor/bbe17e16aa4a106032d8e3521eaed13e.svg" style="width:20px;height:14px;border-radius:2px;vertical-align:middle;flex-shrink:0;object-fit:cover;" aria-hidden="true"> EN`
        }
        // Dropdown option labels
        const opEn = document.getElementById('DH_LangOpt_en');
        const opVi = document.getElementById('DH_LangOpt_vi');
        if (opEn) opEn.innerHTML = '<img src="https://d35aaqx5ub95lt.cloudfront.net/vendor/bbe17e16aa4a106032d8e3521eaed13e.svg" style="width:20px;height:14px;border-radius:2px;vertical-align:middle;flex-shrink:0;object-fit:cover;" aria-hidden="true"> ' + _t('lang_en');
        if (opVi) opVi.innerHTML = '<img src="https://d35aaqx5ub95lt.cloudfront.net/vendor/2b077d42185bc45d4896ed55f15c4fea.svg" style="width:20px;height:14px;border-radius:2px;vertical-align:middle;flex-shrink:0;object-fit:cover;" aria-hidden="true"> ' + _t('lang_vi');
    }
    // ── End i18n ──────────────────────────────────────────────────────

    // ── Unified fetch hook (Lesson Shortener + Stories Shortener + Free Max) ──
    //
    // ROOT CAUSE OF PREVIOUS CONFLICT:
    //   _installFakeSuper() injected a <script> into page context that replaced
    //   window.fetch AFTER _lsInstallHook() already wrapped unsafeWindow.fetch.
    //   Result: the page-context fetch (used by React/Duolingo) was the FakeSuper
    //   hook, which called the *original* fetch — completely bypassing the LS/SS
    //   hook that lived only in the sandbox layer.
    //
    // FIX: One single <script> injection handles ALL three features together.
    //   The sandbox LS hook still runs for the /2023-05-23/sessions POST (which
    //   goes through GM_xmlhttpRequest anyway), but the page-context hook covers
    //   the GET intercepts that React actually uses.
    //
    const _LS_KEY = 'dh2_lessonShortener';
    const _SS_KEY = 'dh2_storiesShortener';

    // ── Page-context injection: FakeSuper + Stories Shortener GET intercept ──
    // Runs immediately at script load, before React captures fetch references.
    (function _installPageContextHook() {
        const lsKey = _LS_KEY; // closure — will be stringified below
        const ssKey = _SS_KEY;
        const superKey = 'dh2_super';

        const script = document.createElement('script');
        script.textContent = `(function() {
            var LS_KEY    = ${JSON.stringify(_LS_KEY)};
            var SS_KEY    = ${JSON.stringify(_SS_KEY)};
            var SUPER_KEY = 'dh2_super';

            var USER_RX = /https?:\\/\\/(?:[a-zA-Z0-9-]+\\.)?duolingo\\.[a-zA-Z]{2,6}(?:\\.[a-zA-Z]{2})?\\/\\d{4}-\\d{2}-\\d{2}\\/users\\/.+/;
            var STORY_RX = /https?:\\/\\/stories\\.duolingo\\.com\\/api2\\/stories\\//;
            var SESSION_PATH = '/2023-05-23/sessions';

            var SUPER_ITEMS = {
                gold_subscription: {
                    itemName: 'gold_subscription',
                    subscriptionInfo: {
                        vendor: 'STRIPE',
                        renewing: true,
                        isFamilyPlan: true,
                        expectedExpiration: 9999999999000
                    }
                }
            };

            var STORY_CHALLENGES = new Set(['MULTIPLE_CHOICE','ARRANGE','MATCH','SELECT_PHRASE','POINT_TO_PHRASE','CHALLENGE_PROMPT']);

            function modSuper(j) {
                try {
                    var d = JSON.parse(j);
                    d.hasPlus = true;
                    if (!d.trackingProperties || typeof d.trackingProperties !== 'object') d.trackingProperties = {};
                    d.trackingProperties.has_item_gold_subscription = true;
                    d.shopItems = Object.assign({}, d.shopItems, SUPER_ITEMS);
                    return JSON.stringify(d);
                } catch(e) { return j; }
            }

            function modStory(j) {
                try {
                    var d = JSON.parse(j);
                    if (Array.isArray(d.elements)) {
                        // [PATCHED] chỉ giữ HEADER, bỏ hết LINE + challenge
                        d.elements = d.elements.filter(function(el) {
                            return el.type === 'HEADER';
                        });
                    }
                    return JSON.stringify(d);
                } catch(e) { return j; }
            }

            function makeResponse(text, orig) {
                var hdrs = {};
                try { orig.headers.forEach(function(v,k){ hdrs[k]=v; }); } catch(e) {}
                hdrs['content-type'] = 'application/json';
                return new Response(text, {
                    status: orig.status,
                    statusText: orig.statusText,
                    headers: hdrs
                });
            }

            // --- fetch hook ---
            var origFetch = window.fetch;
            window.__dhOrigFetch = origFetch;
            window.fetch = function dhFetch(resource, options) {
                var url    = resource instanceof Request ? resource.url : String(resource || '');
                var method = resource instanceof Request ? resource.method : ((options && options.method) || 'GET');
                method = method.toUpperCase();

                var wantsSuper  = localStorage.getItem(SUPER_KEY) === 'true';
                var wantsSS     = localStorage.getItem(SS_KEY) === 'true';
                var wantsLS     = localStorage.getItem(LS_KEY) === 'true';

                // Super: intercept GET /users/... responses
                if (method === 'GET' && wantsSuper && USER_RX.test(url) && url.indexOf('/shop-items') === -1) {
                    return origFetch.apply(this, arguments).then(function(r) {
                        return r.clone().text().then(function(text) {
                            return makeResponse(modSuper(text), r);
                        });
                    });
                }

                // Stories Shortener: intercept GET stories API
                if (method === 'GET' && wantsSS && STORY_RX.test(url)) {
                    return origFetch.apply(this, arguments).then(function(r) {
                        return r.clone().text().then(function(text) {
                            return makeResponse(modStory(text), r);
                        });
                    });
                }

                // Lesson Shortener: intercept POST /sessions (page-context path)
                // Note: the sandbox hook below also handles this for direct GM calls.
                // This covers any React-initiated session creation.
                if (method === 'POST' && wantsLS && url.indexOf(SESSION_PATH) !== -1) {
                    return origFetch.apply(this, arguments).then(function(r) {
                        return r.clone().json().then(function(data) {
                            var n = (data.type && data.type.indexOf('LEGENDARY') === 0) ? 2 : 1;
                            var ll = (window._dhUser && window._dhUser.learningLanguage) || data.learningLanguage || 'en';
                            var fl = (window._dhUser && window._dhUser.fromLanguage)    || data.fromLanguage    || 'en';
                            var lsChallenge = {
                                character: {
                                    url: 'https://d2pur3iezf4d1j.cloudfront.net/images/61e19bb4a1ff1d94e58d58b33db58c36',
                                    image: { pdf: 'https://d2pur3iezf4d1j.cloudfront.net/images/61e19bb4a1ff1d94e58d58b33db58c36', svg: 'https://d2pur3iezf4d1j.cloudfront.net/images/52a5a774c4de18f4a4e8c91d91788347' },
                                    gender: 'MALE',
                                    correctAnimation:  'https://simg-ssl.duolingo.com/lottie/Vikram_CORRECT_Cropped_SpiritFingers.json',
                                    incorrectAnimation:'https://simg-ssl.duolingo.com/lottie/Dan_INCORRECT_Cropped.json',
                                    idleAnimation:     'https://simg-ssl.duolingo.com/lottie/Vikram_IDLE_Cropped.json',
                                    name: 'VIKRAM'
                                },
                                prompt: 'Thank you for using our script! 🥰',
                                choices: ['https://duohacker.io.vn'],
                                correctIndex: 0,
                                options: [
                                    { text: 'https://duohacker.io.vn' }
                                ],
                                type: 'assist',
                                id: 'duohacker',
                                newWords: [],
                                progressUpdates: [],
                                canBeSkipped: false,
                                metadata: {
                                    learning_language: ll,
                                    ui_language: fl,
                                    from_language: fl,
                                    type: 'assist',
                                    specific_type: 'assist',
                                    other_options: []
                                }
                            };
                            data.challenges = Array.from({ length: n }, function() { return lsChallenge; });
                            data.adaptiveChallenges = [];
                            data.adaptiveInterleavedChallenges = { challenges: [], speakOrListenReplacementIndices: [] };
                            return new Response(JSON.stringify(data), {
                                status: r.status,
                                statusText: r.statusText,
                                headers: { 'Content-Type': 'application/json' }
                            });
                        }).catch(function() { return r; });
                    });
                }

                return origFetch.apply(this, arguments);
            };
            window.fetch._isDH = true;

            // --- XHR hook (fallback) ---
            var origOpen = XMLHttpRequest.prototype.open;
            var origSend = XMLHttpRequest.prototype.send;
            XMLHttpRequest.prototype.open = function(method, url) {
                var m = (method || '').toUpperCase();
                this._dh_isUserGet   = m === 'GET'  && USER_RX.test(url)  && url.indexOf('/shop-items') === -1;
                this._dh_isStoryGet  = m === 'GET'  && STORY_RX.test(url);
                origOpen.apply(this, arguments);
            };
            XMLHttpRequest.prototype.send = function() {
                var xhr = this;
                if (xhr._dh_isUserGet || xhr._dh_isStoryGet) {
                    var origChange = xhr.onreadystatechange;
                    xhr.onreadystatechange = function() {
                        if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
                            try {
                                var raw = xhr.responseText;
                                if (xhr._dh_isUserGet && localStorage.getItem(SUPER_KEY) === 'true') raw = modSuper(raw);
                                if (xhr._dh_isStoryGet && localStorage.getItem(SS_KEY) === 'true')   raw = modStory(raw);
                                Object.defineProperty(xhr, 'responseText', { writable: true, configurable: true, value: raw });
                                Object.defineProperty(xhr, 'response',     { writable: true, configurable: true, value: raw });
                            } catch(e) {}
                        }
                        if (origChange) origChange.apply(this, arguments);
                    };
                }
                origSend.apply(this, arguments);
            };
        })();`;
        document.documentElement.appendChild(script);
        script.remove();
    })();

    // ── Sandbox-layer hook: handles Lesson Shortener POST /sessions ────────
    // (GM_xmlhttpRequest / direct fetch calls from userscript sandbox)
    const _lsOriginalFetch = unsafeWindow.fetch;
    let _lsRealFetch = _lsOriginalFetch;

    function _lsInstallHook() {
        const cur = unsafeWindow.fetch;
        // Don't re-wrap our own hook; also skip if page-context already injected
        if (!cur || cur._isDHLS) return;
        _lsRealFetch = cur;
        unsafeWindow.__dhRealFetch = cur;
        const hooked = async function(resource, options) {
            const url = resource instanceof Request ? resource.url : String(resource);
            const parsedUrl = new URL(url, location.origin);
            const method =
                options?.method ||
                (resource instanceof Request ? resource.method : 'GET');

            const isCreateSession =
                parsedUrl.pathname === '/2023-05-23/sessions' &&
                method.toUpperCase() === 'POST';

            if (isCreateSession && localStorage.getItem(_LS_KEY) === 'true') {
                try {
                    const res = await _lsRealFetch.call(this, resource, options);
                    const data = await res.clone().json();
                    const n = data.type?.startsWith('LEGENDARY') ? 2 : 1;
                    const ll = unsafeWindow._dhUser?.learningLanguage || data.learningLanguage || 'en';
                    const fl = unsafeWindow._dhUser?.fromLanguage || data.fromLanguage || 'en';
                    const lsChallenge = {
                        character: {
                            url: 'https://d2pur3iezf4d1j.cloudfront.net/images/61e19bb4a1ff1d94e58d58b33db58c36',
                            image: { pdf: 'https://d2pur3iezf4d1j.cloudfront.net/images/61e19bb4a1ff1d94e58d58b33db58c36', svg: 'https://d2pur3iezf4d1j.cloudfront.net/images/52a5a774c4de18f4a4e8c91d91788347' },
                            gender: 'MALE',
                            correctAnimation: 'https://simg-ssl.duolingo.com/lottie/Vikram_CORRECT_Cropped_SpiritFingers.json',
                            incorrectAnimation: 'https://simg-ssl.duolingo.com/lottie/Dan_INCORRECT_Cropped.json',
                            idleAnimation: 'https://simg-ssl.duolingo.com/lottie/Vikram_IDLE_Cropped.json',
                            name: 'VIKRAM'
                        },
                        prompt: 'Thank you for using our script! 🥰',
                        choices: ['https://duohacker.io.vn'],
                        correctIndex: 0,
                        options: [
                            { text: 'https://duohacker.io.vn' }
                        ],
                        type: 'assist',
                        id: 'duohacker',
                        newWords: [],
                        progressUpdates: [],
                        canBeSkipped: false,
                        metadata: {
                            learning_language: ll,
                            ui_language: fl,
                            from_language: fl,
                            type: 'assist',
                            specific_type: 'assist',
                            other_options: []
                        }
                    };
                    data.challenges = Array.from({
                        length: n
                    }, () => lsChallenge);
                    data.adaptiveChallenges = [];
                    data.adaptiveInterleavedChallenges = {
                        challenges: [],
                        speakOrListenReplacementIndices: []
                    };
                    return new Response(JSON.stringify(data), {
                        status: res.status,
                        statusText: res.statusText,
                        headers: {
                            'Content-Type': 'application/json'
                        }
                    });
                } catch (e) {}
            }
            // Stories Shortener — sandbox layer (fallback, page-context hook is primary)
            const isStory =
                parsedUrl.hostname === 'stories.duolingo.com' &&
                parsedUrl.pathname.startsWith('/api2/stories/') &&
                method.toUpperCase() === 'GET';

            if (isStory && localStorage.getItem(_SS_KEY) === 'true') {
                try {
                    const _storyFetch = unsafeWindow.__dhRealFetch || _lsRealFetch;
                    const res = await _storyFetch.call(this, resource, options);
                    const data = await res.clone().json();
                    if (Array.isArray(data.elements)) {
                        // [PATCHED] chỉ giữ HEADER, bỏ hết LINE + challenge
                        data.elements = data.elements.filter(el => el.type === 'HEADER');
                    }
                    return new Response(JSON.stringify(data), {
                        status: res.status,
                        statusText: res.statusText,
                        headers: {
                            'Content-Type': 'application/json'
                        }
                    });
                } catch (e) {}
            }

            return (unsafeWindow.__dhRealFetch || _lsRealFetch).call(this, resource, options);
        };
        hooked._isDHLS = true;
        unsafeWindow.fetch = hooked;
    }
    // Install hook immediately (fetch exists at document-end)
    _lsInstallHook();
    // Periodic re-check: Duolingo may replace fetch during Legendary sessions
    setInterval(() => {
        if (unsafeWindow.fetch && !unsafeWindow.fetch._isDHLS) _lsInstallHook();
    }, 2000);
    let _lsHref = location.href;
    new MutationObserver(() => {
        if (location.href === _lsHref) return;
        _lsHref = location.href;
        if (unsafeWindow.fetch && !unsafeWindow.fetch._isDHLS) _lsInstallHook();
    }).observe(document.documentElement, {
        subtree: true,
        childList: true
    });
    // ──────────────────────────────────────────────────────────────────

    GM_addStyle(`

@font-face {
    font-family: "SF Pro Rounded";
    src: url("https://font.duohacker.io.vn/SF-Pro-Rounded-Regular.otf") format("opentype");
    font-weight: 400; font-display: swap;
}
@font-face {
    font-family: "SF Pro Rounded";
    src: url("https://font.duohacker.io.vn/SF-Pro-Rounded-Semibold.otf") format("opentype");
    font-weight: 600; font-display: swap;
}
@font-face {
    font-family: "SF Pro Rounded";
    src: url("https://font.duohacker.io.vn/SF-Pro-Rounded-Bold.otf") format("opentype");
    font-weight: 700; font-display: swap;
}
@font-face {
    font-family: "SF Pro Rounded";
    src: url("https://font.duohacker.io.vn/SF-Pro-Rounded-Heavy.otf") format("opentype");
    font-weight: 800; font-display: swap;
}
@font-face {
    font-family: "SF Pro Rounded";
    src: url("https://font.duohacker.io.vn/SF-Pro-Rounded-Black.otf") format("opentype");
    font-weight: 900; font-display: swap;
}

:root {
    --DH-blue:   0, 122, 255;
    --DH-green:  52, 199, 89;
    --DH-red:    255, 59, 48;
    --DH-orange: 255, 149, 0;
    /* Corner system — giống PRO, nâng lên khi browser support superellipse */
    --DH-corner-s:  superellipse(1.32);
    --DH-corner-r-s:  6px;
    --DH-corner-r-m:  8px;
    --DH-corner-r-ml: 12px;
    --DH-corner-r-l:  16px;
    --DH-corner-r-xl: 20px;
}
@media (prefers-color-scheme: dark) {
    :root {
        --DH-blue:   10, 132, 255;
        --DH-green:  48, 209, 88;
        --DH-red:    255, 69, 58;
    }
}

#DH_Root * { box-sizing: border-box; }
#DH_Root p, #DH_Root span, #DH_Root button, #DH_Root input, #DH_Root label, #DH_Root div {
    font-family: 'SF Pro Rounded', 'din-round', -apple-system, sans-serif !important;
}
#DH_Root p, #DH_Root span { margin: 0; padding: 0; }
#DH_Root svg { flex-shrink: 0; }

.DH_Main {
    display: inline-flex; flex-direction: column;
    justify-content: flex-end; align-items: flex-end;
    gap: 8px; position: fixed; right: 16px; bottom: 16px;
    z-index: 2147483647;
}
@media (max-width: 699px) {
    .DH_Main { margin-bottom: 80px; }
}

.DH_Main_Box {
    display: flex; width: 312px; padding: 16px;
    box-sizing: border-box;
    flex-direction: column; justify-content: center; align-items: center; gap: 8px;
    overflow: hidden; border-radius: 20px;
    outline: 2px solid rgb(var(--color-eel, 117,117,117), 0.10); outline-offset: -2px;
    background: rgb(var(--color-snow), 0.90);
    backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
    transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.DH_HStack_Auto { display:flex; align-items:center; justify-content:space-between; align-self:stretch; }
.DH_HStack_8    { display:flex; align-items:center; gap:8px; align-self:stretch; }
.DH_HStack_4    { display:flex; align-items:center; gap:4px; align-self:stretch; }
.DH_VStack_8    { display:flex; flex-direction:column; justify-content:center; align-items:center; gap:8px; align-self:stretch; }
.DH_VStack_4    { display:flex; flex-direction:column; justify-content:center; align-items:center; gap:4px; align-self:stretch; }
.DH_NoSel       { user-select:none; -webkit-user-select:none; }
.DH_Divider     { align-self:stretch; height:1px; background:rgb(var(--color-eel,117,117,117), 0.10); flex-shrink:0; }

.DH_T1 { font-size:15px; font-weight:700; line-height:normal; color:rgb(var(--color-wolf,60,60,67), 0.80); margin:0; }
.DH_T2 { font-size:13px; font-weight:600; line-height:normal; color:rgb(var(--color-wolf,60,60,67), 0.50); margin:0; }

.DH_Btn {
    display:flex; height:40px; padding:10px 12px 10px 10px; box-sizing:border-box;
    align-items:center; gap:6px; flex:1 0 0;
    border-radius:8px; border:none; cursor:pointer;
    user-select:none; -webkit-user-select:none;
    transition: filter 0.4s cubic-bezier(0.16,1,0.32,1), transform 0.4s cubic-bezier(0.16,1,0.32,1);
}
.DH_Btn:hover  { filter:brightness(0.88); transform:scale(1.04); }
.DH_Btn:active { filter:brightness(0.82); transform:scale(0.96); }

.DH_Btn_Blue_Ghost {
    outline:2px solid rgba(var(--DH-blue),0.20); outline-offset:-2px;
    background:linear-gradient(0deg,rgba(var(--DH-blue),0.10),rgba(var(--DH-blue),0.10)),rgb(var(--color-snow),0.80);
    backdrop-filter:blur(16px);
}

.DH_Btn_Eel {
    outline:2px solid rgb(var(--color-eel,117,117,117),0.20); outline-offset:-2px;
    background:rgb(var(--color-eel,117,117,117),0.10);
}
.DH_Btn_Icon { flex:none!important; width:40px; padding:10px!important; justify-content:center; }

.DH_Input_Wrap {
    display:flex; height:48px; padding:16px; box-sizing:border-box;
    align-items:center; flex:1 0 0; gap:6px;
    border-radius:8px;
    outline:2px solid rgba(var(--DH-blue),0.20); outline-offset:-2px;
    background:rgba(var(--DH-blue),0.10);
    position:relative; overflow:hidden;
}
.DH_Input {
    border:none!important; outline:none!important; background:none!important;
    text-align:right; font-size:16px!important; font-weight:600!important;
    color:rgb(var(--DH-blue))!important; font-family:inherit!important; width:100%;
    -moz-appearance:textfield;
}
.DH_Input::placeholder { color:rgba(var(--DH-blue),0.50)!important; }
.DH_Input::-webkit-outer-spin-button,
.DH_Input::-webkit-inner-spin-button { -webkit-appearance:none; margin:0; }

.DH_Input_Btn {
    display:flex; height:48px; padding:12px 14px; box-sizing:border-box;
    justify-content:center; align-items:center;
    border-radius:8px; border:none; cursor:pointer;
    user-select:none; -webkit-user-select:none;
    outline:2px solid rgba(0,0,0,0.20); outline-offset:-2px;
    background:rgb(var(--DH-blue));
    white-space:nowrap; flex-shrink:0;
    transition:
        width 0.8s cubic-bezier(0.77,0,0.18,1),
        background 0.8s cubic-bezier(0.16,1,0.32,1),
        outline 0.8s cubic-bezier(0.16,1,0.32,1),
        filter 0.4s cubic-bezier(0.16,1,0.32,1),
        transform 0.4s cubic-bezier(0.16,1,0.32,1);
}
.DH_Input_Btn:hover  { filter:brightness(0.88); transform:scale(1.04); }
.DH_Input_Btn:active { filter:brightness(0.82); transform:scale(0.96); }
.DH_Input_Btn:disabled { opacity:0.38; pointer-events:none; }
.DH_Mode_Btn {
    display:flex; height:48px; width:48px; padding:0; box-sizing:border-box;
    justify-content:center; align-items:center; flex-shrink:0;
    border-radius:8px; border:none; cursor:pointer;
    user-select:none; -webkit-user-select:none;
    outline:2px solid rgba(0,0,0,0.20); outline-offset:-2px;
    background:rgb(var(--DH-blue));
    transition:filter 0.4s cubic-bezier(0.16,1,0.32,1),transform 0.4s cubic-bezier(0.16,1,0.32,1);
}
.DH_Mode_Btn:hover  { filter:brightness(0.88); transform:scale(1.04); }
.DH_Mode_Btn:active { filter:brightness(0.82); transform:scale(0.96); }
.DH_Mode_Icon {
    display:flex; align-items:center; justify-content:center;
    color:#FFF;
    font-size:18px; font-weight:700; line-height:normal;
    transition:opacity 0.3s, transform 0.3s;
}
.DH_Btn_Label {
    font-size:15px; font-weight:800; letter-spacing:0.2px;
    transition:opacity 0.4s, filter 0.4s, color 0.4s;
}

.DH_Sm_Btn {
    display:flex; height:38px; padding:0 16px;
    justify-content:center; align-items:center;
    border-radius:8px; border:none; cursor:pointer;
    user-select:none; flex-shrink:0;
    outline:2px solid rgba(0,0,0,0.20); outline-offset:-2px;
    background:rgb(var(--DH-blue));
    white-space:nowrap;
    transition:
        width 0.8s cubic-bezier(0.77,0,0.18,1),
        background 0.8s cubic-bezier(0.16,1,0.32,1),
        outline 0.8s cubic-bezier(0.16,1,0.32,1),
        filter 0.4s cubic-bezier(0.16,1,0.32,1),
        transform 0.4s cubic-bezier(0.16,1,0.32,1);
}
.DH_Sm_Btn:hover  { filter:brightness(0.88); transform:scale(1.04); }
.DH_Sm_Btn:active { filter:brightness(0.82); transform:scale(0.96); }
.DH_Sm_Btn:disabled { opacity:0.38; pointer-events:none; }
.DH_Sm_Btn_Label { font-size:13px; font-weight:800; transition:opacity 0.4s, filter 0.4s, color 0.4s; }

.DH_Prog_Wrap {
    align-self:stretch; height:0; border-radius:3px;
    background:rgba(var(--DH-blue),0.10); overflow:hidden;
    transition:height 0.4s cubic-bezier(0.16,1,0.32,1);
}
.DH_Prog_Wrap.on { height:4px; }
.DH_Prog_Fill {
    height:100%; border-radius:3px; background:rgb(var(--DH-blue));
    width:0%; transition:width 0.5s cubic-bezier(0.16,1,0.32,1);
    box-shadow:0 0 6px rgba(var(--DH-blue),0.35);
}

.DH_Avatar {
    width:32px; height:32px; border-radius:50%;
    background:rgba(var(--DH-blue),0.10);
    overflow:hidden; flex-shrink:0;
    display:flex; align-items:center; justify-content:center; font-size:16px;
}

.DH_Stat_Ico { width:15px; height:15px; display:block; flex-shrink:0; }
.DH_Stat_Val { font-size:13px!important; font-weight:700!important; color:rgb(var(--color-wolf,60,60,67),0.60)!important; }

.DH_Toggle { position:relative; width:44px; height:26px; flex-shrink:0; cursor:pointer; }
.DH_Toggle input { opacity:0; width:0; height:0; }
.DH_Toggle_Slider {
    position:absolute; cursor:pointer; inset:0;
    background:rgb(var(--color-eel,117,117,117),0.22); border-radius:26px; transition:background 0.3s;
}
.DH_Toggle_Slider:before {
    content:''; position:absolute; width:20px; height:20px; left:3px; bottom:3px;
    background:#fff; border-radius:50%; transition:transform 0.3s; box-shadow:0 2px 4px rgba(0,0,0,0.2);
}
.DH_Toggle input:checked + .DH_Toggle_Slider { background:rgb(var(--DH-blue)); }
.DH_Toggle input:checked + .DH_Toggle_Slider:before { transform:translateX(18px); }

.DH_Shimmer {
    position:absolute; pointer-events:none; top:0; left:0;
    transform:translateX(-150px);
    animation:DH_Shimmer 5s ease-in-out infinite 1.5s;
}
@keyframes DH_Shimmer {
    0%  { transform:translateX(-150px); }
    18% { transform:translateX(340px); }
    100%{ transform:translateX(340px); }
}
@keyframes DH_Spin { from{transform:rotate(0deg)} to{transform:rotate(360deg)} }
.DH_Spin_Ico { animation:DH_Spin 1.5s linear infinite; display:inline-block; }

.DH_Page { display:none; }
.DH_Page.active { display:flex; flex-direction:column; gap:8px; align-self:stretch; align-items:center; }

.DH_Notif_Main {
    display: flex;
    justify-content: center;
    align-items: center;

    transition: 0.8s cubic-bezier(0.16, 1, 0.32, 1);
    width: 300px;
    position: fixed;
    left: calc(50% - (300px / 2));
    z-index: 2147483647;
    bottom: 16px;
    border-radius: var(--DH-corner-r-l);
    corner-shape: var(--DH-corner-s);
    pointer-events: none;
}
.DH_Notif_Box {
    display: flex;
    width: 300px;
    padding: 16px;
    box-sizing: border-box;
    flex-direction: column;
    justify-content: flex-start;
    align-items: center;
    gap: 4px;

    border-radius: var(--DH-corner-r-l);
    corner-shape: var(--DH-corner-s);
    outline: 2px solid rgb(var(--color-eel), 0.10);
    outline-offset: -2px;
    background: rgb(var(--color-snow), 0.90);
    backdrop-filter: blur(16px);

    transition: 0.8s cubic-bezier(0.16, 1, 0.32, 1);
    filter: blur(16px);
    opacity: 0;
    pointer-events: auto;
    position: fixed;
}

.DH_Shop_Grid { display:grid; grid-template-columns:repeat(2,1fr); gap:8px; align-self:stretch; }
.DH_Shop_Card {
    display:flex; flex-direction:column; align-items:center;
    gap:6px; padding:12px 8px; border-radius:12px;
    outline:1.5px solid rgb(var(--color-eel,117,117,117),0.13); outline-offset:-1px;
    background:rgb(var(--color-eel,117,117,117),0.05);
    transition:outline-color 0.2s, background 0.2s; text-align:center;
}
.DH_Shop_Card:hover { outline-color:rgba(var(--DH-blue),0.30); background:rgba(var(--DH-blue),0.04); }
.DH_Shop_Ico { width:36px; height:36px; object-fit:contain; }
.DH_Shop_Name { font-size:11px; font-weight:700; color:rgb(var(--color-wolf,60,60,67),0.75); line-height:1.3; flex:1; }
.DH_Shop_Btn {
    width:100%; height:28px; border-radius:7px; border:none; cursor:pointer;
    font-size:11px; font-weight:800; color:#fff;
    background:rgb(var(--DH-blue));
    outline:2px solid rgba(0,0,0,0.20); outline-offset:-2px;
    transition:filter 0.4s cubic-bezier(0.16,1,0.32,1), transform 0.4s cubic-bezier(0.16,1,0.32,1), background 0.4s;
}
.DH_Shop_Btn:hover  { filter:brightness(0.88); transform:scale(1.03); }
.DH_Shop_Btn:active { transform:scale(0.97); }
.DH_Shop_Btn.loading { background:rgb(var(--color-eel,117,117,117),0.12); color:rgb(var(--color-eel,117,117,117),0.50); outline-color:rgb(var(--color-eel,117,117,117),0.18); pointer-events:none; }
.DH_Shop_Btn.got     { background:rgba(var(--DH-green),0.12); color:rgb(var(--DH-green)); outline-color:rgba(var(--DH-green),0.25); pointer-events:none; }
.DH_Shop_Btn.fail    { background:rgba(var(--DH-red),0.10); color:rgb(var(--DH-red)); outline-color:rgba(var(--DH-red),0.22); pointer-events:none; }
.DH_Cat_Header {
    align-self:stretch; font-size:11px; font-weight:800; text-transform:uppercase;
    letter-spacing:0.6px; color:rgb(var(--color-wolf,60,60,67),0.40);
    padding:4px 0 2px; text-align:center;
    border-bottom:1px solid rgb(var(--color-eel,117,117,117),0.10); margin-bottom:2px;
}

.DH_Scroll_Inner {
    align-self:stretch; overflow-y:auto;
    max-height:220px; display:flex; flex-direction:column; gap:6px; padding-right:2px;
}
.DH_Scroll_Inner::-webkit-scrollbar { width:3px; }
.DH_Scroll_Inner::-webkit-scrollbar-thumb { background:rgba(var(--DH-blue),0.2); border-radius:3px; }
.DH_Scroll_Inner { scrollbar-width:thin; scrollbar-color:rgba(var(--DH-blue),0.2) transparent; }
.DH_Credit_Card { display:flex; flex-direction:column; gap:6px; padding:10px 12px; border-radius:12px; background:rgba(var(--DH-blue),0.06); outline:1.5px solid rgba(var(--DH-blue),0.12); outline-offset:-1.5px; align-self:stretch; }
.DH_Credit_Card_Header { display:flex; align-items:center; gap:8px; }
.DH_Credit_Thumb { width:28px; height:28px; border-radius:8px; object-fit:cover; flex-shrink:0; background:rgba(var(--DH-blue),0.1); }
.DH_Credit_Script { font-size:13px; font-weight:800; color:rgb(var(--color-wolf,60,60,67)); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.DH_Credit_Author { font-size:11px; color:rgba(var(--color-wolf,60,60,67),0.55); }
.DH_Credit_Task { font-size:11px; color:rgba(var(--color-wolf,60,60,67),0.7); line-height:1.4; }
.DH_Credit_Link { font-size:11px; font-weight:700; color:rgb(var(--DH-blue)); text-decoration:none; }
.DH_Credit_Link:hover { opacity:0.75; }

.DH_Search {
    align-self:stretch; height:40px; padding:0 14px;
    border-radius:8px; border:none;
    outline:2px solid rgb(var(--color-eel,117,117,117),0.15); outline-offset:-2px;
    background:rgb(var(--color-eel,117,117,117),0.06);
    font-size:14px; font-weight:600;
    color:rgb(var(--color-wolf,60,60,67),0.80); transition:outline-color 0.2s;
}
.DH_Search:focus { outline-color:rgba(var(--DH-blue),0.35); }
.DH_Search::placeholder { color:rgb(var(--color-wolf,60,60,67),0.35); }

.DH_Btn_Ico {
    width:28px; height:28px; border-radius:7px; flex-shrink:0;
    background:rgba(var(--DH-blue),0.12);
    display:flex; align-items:center; justify-content:center;
}

.DH_Acc_Card {
    display:flex; align-items:center; gap:10px; align-self:stretch;
    padding:10px 12px; border-radius:12px;
    outline:1.5px solid rgb(var(--color-eel,117,117,117),0.13); outline-offset:-1px;
    background:rgb(var(--color-eel,117,117,117),0.05);
    transition:outline-color 0.2s, background 0.2s;
    position:relative; overflow:hidden;
}
.DH_Acc_Card:hover { outline-color:rgba(var(--DH-blue),0.30); background:rgba(var(--DH-blue),0.04); }
.DH_Acc_Avatar {
    width:36px; height:36px; border-radius:50%; flex-shrink:0;
    background:rgba(var(--DH-blue),0.10); overflow:hidden;
    display:flex; align-items:center; justify-content:center; font-size:16px;
}
.DH_Acc_Info { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.DH_Acc_Name { font-size:13px!important; font-weight:700!important; color:rgb(var(--color-wolf,60,60,67),0.85)!important; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.DH_Acc_Sub  { font-size:11px!important; font-weight:600!important; color:rgb(var(--color-wolf,60,60,67),0.45)!important; }
.DH_Acc_Sub.active { color:rgb(var(--DH-green))!important; display:flex; align-items:center; gap:4px; }
.DH_Acc_Action_Row { display:flex; gap:5px; flex-shrink:0; }
.DH_Acc_Btn {
    height:26px; padding:0 8px; border-radius:7px; border:none; cursor:pointer;
    font-size:10px; font-weight:800; color:#fff;
    background:rgb(var(--DH-blue));
    outline:2px solid rgba(0,0,0,0.15); outline-offset:-2px;
    transition:filter 0.3s,transform 0.3s;
}
.DH_Acc_Btn:hover  { filter:brightness(0.88); transform:scale(1.06); }
.DH_Acc_Btn:active { transform:scale(0.95); }
.DH_Acc_Btn.del { background:rgba(var(--DH-red),0.10); color:rgb(var(--DH-red)); outline-color:rgba(var(--DH-red),0.20); }
.DH_Acc_Btn.del:hover { background:rgb(var(--DH-red)); color:#fff; }

.DH_Quest_Item {
    display:flex; align-items:center; gap:10px; align-self:stretch;
    padding:10px 12px; border-radius:12px;
    outline:1.5px solid rgb(var(--color-eel,117,117,117),0.13); outline-offset:-1px;
    background:rgb(var(--color-eel,117,117,117),0.05);
    transition:outline-color 0.2s, background 0.2s;
}
.DH_Quest_Item.done { outline-color:rgba(var(--DH-green),0.25); background:rgba(var(--DH-green),0.04); }
.DH_Quest_Icon { width:40px; height:40px; object-fit:contain; flex-shrink:0; }
.DH_Quest_Info { flex:1; min-width:0; display:flex; flex-direction:column; gap:3px; }
.DH_Quest_Title { font-size:12px!important; font-weight:700!important; color:rgb(var(--color-wolf,60,60,67),0.85)!important; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.DH_Quest_Meta  { font-size:10px!important; font-weight:600!important; color:rgb(var(--color-wolf,60,60,67),0.45)!important; }
.DH_Quest_Bar_Bg { height:4px; border-radius:2px; background:rgba(var(--DH-blue),0.10); overflow:hidden; align-self:stretch; }
.DH_Quest_Bar_Fill { height:100%; background:rgb(var(--DH-blue)); border-radius:2px; transition:width 0.5s; }
.DH_Quest_Item.done .DH_Quest_Bar_Fill { background:rgb(var(--DH-green)); }
.DH_Quest_Get_Btn {
    height:26px; padding:0 8px; flex-shrink:0; border-radius:7px; border:none; cursor:pointer;
    font-size:10px; font-weight:800; white-space:nowrap;
    background:rgb(var(--DH-blue)); color:#fff;
    outline:2px solid rgba(0,0,0,0.15); outline-offset:-2px;
    transition:filter 0.3s,transform 0.3s;
}
.DH_Quest_Get_Btn:hover  { filter:brightness(0.88); transform:scale(1.06); }
.DH_Quest_Get_Btn:active { transform:scale(0.95); }
.DH_Quest_Get_Btn.done { background:rgba(var(--DH-green),0.10); color:rgb(var(--DH-green)); outline-color:rgba(var(--DH-green),0.20); pointer-events:none; }


/* ── PAGE 12: Reaction Picker ── */
.DH_LB_Reaction {
    position:absolute; top:-4px; right:-4px;
    width:19px; height:19px; object-fit:contain;
    border-radius:50%;
    background:rgba(var(--color-swan,255,255,255),1);
    box-shadow:0 1px 4px rgba(0,0,0,0.18);
    pointer-events:none;
}
.DH_LB_Reaction.me {
    cursor:pointer; pointer-events:auto;
    transition:transform 0.15s;
}
.DH_LB_Reaction.me:hover { transform:scale(1.22); }
.DH_React_Btn {
    width:100%; height:28px; border-radius:7px; border:none; cursor:pointer;
    font-size:11px; font-weight:800; color:#fff;
    background:rgb(var(--DH-blue));
    outline:2px solid rgba(0,0,0,0.20); outline-offset:-2px;
    transition:filter 0.4s cubic-bezier(0.16,1,0.32,1), transform 0.4s cubic-bezier(0.16,1,0.32,1), background 0.4s;
}
.DH_React_Btn:hover  { filter:brightness(0.88); transform:scale(1.03); }
.DH_React_Btn:active { transform:scale(0.97); }
.DH_React_Btn.active  { background:rgba(var(--DH-green),0.12); color:rgb(var(--DH-green)); outline-color:rgba(var(--DH-green),0.25); pointer-events:none; }
.DH_React_Btn.loading { background:rgb(var(--color-eel,117,117,117),0.12); color:rgb(var(--color-eel,117,117,117),0.50); outline-color:rgb(var(--color-eel,117,117,117),0.18); pointer-events:none; }
.DH_React_Btn.success { background:rgba(var(--DH-green),0.12); color:rgb(var(--DH-green)); outline-color:rgba(var(--DH-green),0.25); pointer-events:none; }
/* ── PAGE 11: Leaderboard ── */
.DH_LB_Row {
    display:flex; align-items:center; gap:10px;
    padding:9px 10px; border-radius:14px;
    transition:background 0.15s;
}
.DH_LB_Row.me {
    outline:2px solid rgba(var(--DH-blue),0.50); outline-offset:-2px;
    background:rgba(var(--DH-blue),0.07);
}
.DH_LB_RankWrap {
    width:31px; flex-shrink:0;
    display:flex; align-items:center; justify-content:center;
}
.DH_LB_RankNum {
    font-size:13px; font-weight:800;
    color:rgba(var(--color-wolf,60,60,67),0.55);
}
.DH_LB_RankMedal { width:29px; height:29px; object-fit:contain; display:block; }
.DH_LB_AvatarWrap {
    position:relative; flex-shrink:0;
    width:44px; height:44px;
}
.DH_LB_AvatarImg {
    width:44px; height:44px; border-radius:50%;
    object-fit:cover; display:block;
    border:2px solid rgba(var(--color-eel,117,117,117),0.18);
    background:rgba(var(--DH-blue),0.08);
}
.DH_LB_AvatarFallback {
    width:44px; height:44px; border-radius:50%;
    background:rgba(var(--DH-blue),0.10);
    display:flex; align-items:center; justify-content:center;
    font-size:17px;
    border:2px solid rgba(var(--color-eel,117,117,117),0.18);
}
.DH_LB_AvatarWrap.prom .DH_LB_AvatarImg,
.DH_LB_AvatarWrap.prom .DH_LB_AvatarFallback { border-color:rgba(var(--DH-green),0.55); }
.DH_LB_AvatarWrap.dem  .DH_LB_AvatarImg,
.DH_LB_AvatarWrap.dem  .DH_LB_AvatarFallback { border-color:rgba(var(--DH-red),0.40); }
.DH_LB_ZoneIcon {
    position:absolute; bottom:-3px; right:-3px;
    width:17px; height:17px; object-fit:contain;
}
.DH_LB_Info {
    flex:1; min-width:0;
    display:flex; flex-direction:column; gap:1px;
}
.DH_LB_Name {
    font-size:12px; font-weight:700;
    color:rgb(var(--color-wolf,60,60,67));
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.DH_LB_Row.me .DH_LB_Name { color:rgb(var(--DH-blue)); }
.DH_LB_Status {
    font-size:10px; font-weight:600;
    color:rgba(var(--color-wolf,60,60,67),0.40);
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.DH_LB_Status.active { color:rgba(28,176,246,0.80); }
.DH_LB_ScoreWrap {
    flex-shrink:0; text-align:right;
}
.DH_LB_Score {
    font-size:12px; font-weight:800;
    color:rgba(var(--color-wolf,60,60,67),0.70);
    white-space:nowrap;
}
.DH_LB_Row.me .DH_LB_Score { color:rgb(var(--DH-blue)); }
.DH_LB_ScoreUnit {
    font-size:9px; font-weight:600;
    color:rgba(var(--color-wolf,60,60,67),0.40);
}
.DH_LB_Row.me .DH_LB_ScoreUnit { color:rgba(var(--DH-blue),0.70); }
.DH_LB_Sep {
    display:flex; align-items:center; gap:8px;
    margin:4px 2px; height:14px;
}
.DH_LB_SepLine { flex:1; height:1px; border-radius:1px; }
.DH_LB_SepLine.prom { background:rgba(var(--DH-green),0.30); }
.DH_LB_SepLine.dem  { background:rgba(var(--DH-red),0.25); }
.DH_LB_SepIcon { width:14px; height:14px; object-fit:contain; flex-shrink:0; }
.DH_LB_SepTxt {
    font-size:9px; font-weight:700; flex-shrink:0;
}
.DH_LB_SepTxt.prom { color:rgb(var(--DH-green)); }
.DH_LB_SepTxt.dem  { color:rgb(var(--DH-red)); }
.DH_LB_Header {
    display:flex; align-items:center; gap:10px; align-self:stretch;
    padding:10px 12px; border-radius:14px;
    background:rgba(var(--color-eel,117,117,117),0.05);
    outline:1.5px solid rgba(var(--color-eel,117,117,117),0.10); outline-offset:-1px;
}
.DH_LB_LeagueImg { width:36px; height:36px; object-fit:contain; flex-shrink:0; }
.DH_LB_LeagueName { font-size:15px; font-weight:800; color:rgb(var(--color-wolf,60,60,67)); }

#DH_AccSettings_Btn        { transform-origin: right center; }
#DH_AccSettings_Btn:hover  { filter:brightness(0.85); transform:scale(1.1); }
#DH_AccSettings_Btn:active { transform:scale(0.92); }

/* PAGE 9: License */
#DH_Page_9 { flex:1; min-height:0; }
.DH_License_Scroll {
    flex:1;
    min-height:0;
    overflow-y:auto;
    overflow-x:hidden;
    padding:14px 16px;
    border-radius:12px;
    outline:2px solid rgba(var(--color-eel,117,117,117),0.10);
    outline-offset:-2px;
    background:rgba(var(--color-eel,117,117,117),0.04);
    align-self:stretch;
    max-height:320px;
}
.DH_License_Scroll::-webkit-scrollbar { width:3px; }
.DH_License_Scroll::-webkit-scrollbar-thumb { background:rgba(var(--color-eel,117,117,117),0.20); border-radius:2px; }
#DH_License_Text {
    font-size:11.5px;
    font-weight:600;
    line-height:1.65;
    color:rgb(var(--color-wolf,60,60,67),0.75);
    white-space:pre-wrap;
    margin:0;
    word-break:break-word;
}
@media (max-width:699px) {
    .DH_License_Scroll { max-height:calc(100svh - 240px); }
    .DH_Main { margin-bottom:80px; }
}
.DH_LangSelector {
    position: fixed;
    top: 16px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 2147483647;
}

.DH_LangSelector_Btn {
    height: 38px;
    padding: 0 14px;
    display: flex;
    align-items: center;
    gap: 7px;
    border: none;
    border-radius: 10px;
    cursor: pointer;

    background: rgb(var(--color-snow), 0.92);
    outline: 2px solid rgba(var(--DH-blue), 0.20);
    outline-offset: -2px;
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);

    color: rgb(var(--DH-blue));
    font-size: 13px;
    font-weight: 800;
    transition: filter 0.2s, transform 0.2s;
}

.DH_LangSelector_Btn:hover {
    filter: brightness(0.92);
    transform: scale(1.03);
}

.DH_LangSelector_Arrow {
    transition: transform 0.2s;
}

.DH_LangSelector.open .DH_LangSelector_Arrow {
    transform: rotate(180deg);
}

.DH_LangMenu {
    position: absolute;
    top: calc(100% + 7px);
    left: 50%;
    transform: translateX(-50%) translateY(-5px);

    min-width: 150px;
    padding: 6px;
    display: flex;
    flex-direction: column;
    gap: 3px;

    border-radius: 12px;
    background: rgb(var(--color-snow), 0.96);
    outline: 2px solid rgb(var(--color-eel, 117, 117, 117), 0.12);
    outline-offset: -2px;
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.12);

    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 0.2s, transform 0.2s, visibility 0.2s;
}

.DH_LangSelector.open .DH_LangMenu {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateX(-50%) translateY(0);
}

.DH_LangOption {
    width: 100%;
    height: 36px;
    padding: 0 11px;
    border: none;
    border-radius: 8px;
    cursor: pointer;

    display: flex !important;
    flex-direction: row !important;
    align-items: center !important;
    justify-content: flex-start !important;
    gap: 8px !important;

    text-align: left;
    white-space: nowrap;
    line-height: 1;

    background: transparent;
    color: rgb(var(--color-wolf, 60, 60, 67), 0.82);
    font-size: 13px;
    font-weight: 700;
}

.DH_LangOption > svg {
    display: block !important;
    width: 20px;
    height: 14px;
    flex: 0 0 20px;
}

.DH_LangOption > span {
    display: inline-block !important;
    line-height: 14px;
}

.DH_LangOption:hover {
    background: rgba(var(--DH-blue), 0.10);
    color: rgb(var(--DH-blue));
}

@supports (corner-shape: superellipse(1)) {
    .DH_Main_Box,
    .DH_Notif_Box,
    .DH_Btn,
    .DH_Input_Wrap,
    .DH_Input_Btn,
    .DH_Mode_Btn,
    .DH_Sm_Btn,
    .DH_Shop_Card,
    .DH_Shop_Btn,
    .DH_Credit_Card,
    .DH_Credit_Thumb,
    .DH_Search,
    .DH_Btn_Ico,
    .DH_Acc_Card,
    .DH_Acc_Btn,
    .DH_Quest_Item,
    .DH_Quest_Get_Btn,
    .DH_React_Btn,
    .DH_LB_Row,
    .DH_LB_Header,
    .DH_License_Scroll,
    .DH_LangSelector_Btn,
    .DH_LangMenu,
    .DH_LangOption {
        corner-shape: var(--DH-corner-s);
    }
}

`);

    if (CSS.supports('corner-shape', 'superellipse(1.32)')) {
        const _dhCornerEl = document.createElement('style');
        _dhCornerEl.textContent = `:root {
    --DH-corner-r-s:  6px;
    --DH-corner-r-m:  10px;
    --DH-corner-r-ml: 16px;
    --DH-corner-r-l:  20px;
    --DH-corner-r-xl: 26px;
}`;
        document.head.appendChild(_dhCornerEl);
    }

    // Mount UI only after DOM is ready
    function _dhMountUI() {
        if (!document.body) {
            setTimeout(_dhMountUI, 10);
            return;
        }
        const _wrap = document.createElement('div');
        _wrap.id = 'DH_Root';
        _wrap.innerHTML = `
    <div class="DH_LangSelector" id="DH_LangSelector">
        <button
            type="button"
            class="DH_LangSelector_Btn DH_NoSel"
            id="DH_LangSelector_Btn"
        >
            <span
                id="DH_LangSelector_Lbl"
                style="display:inline-flex;align-items:center;gap:5px;"
            >
                <svg
                    width="20"
                    height="14"
                    viewBox="0 0 60 40"
                    style="border-radius:2px;flex-shrink:0;"
                    aria-hidden="true"
                    focusable="false"
                >
                    <rect
                        width="60"
                        height="40"
                        fill="#DA251D"
                    ></rect>

                    <polygon
                        points="30,9 32.59,16.44 40.46,16.60 34.18,21.36 36.47,28.90 30,24.40 23.53,28.90 25.82,21.36 19.54,16.60 27.41,16.44"
                        fill="#FFCD00"
                    ></polygon>
                </svg>

                <span>VI</span>
            </span>

            <svg
                class="DH_LangSelector_Arrow"
                width="10"
                height="6"
                viewBox="0 0 10 6"
                fill="none"
                aria-hidden="true"
            >
                <path
                    d="M1 1L5 5L9 1"
                    stroke="currentColor"
                    stroke-width="1.8"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                ></path>
            </svg>
        </button>

        <div class="DH_LangMenu" id="DH_LangMenu">
            <button
                type="button"
                class="DH_LangOption DH_NoSel"
                id="DH_LangOpt_vi"
                data-lang="vi"
            >
                Tiếng Việt
            </button>

            <button
                type="button"
                class="DH_LangOption DH_NoSel"
                id="DH_LangOpt_en"
                data-lang="en"
            >
                English
            </button>
        </div>
    </div>
<div class="DH_Notif_Main" id="DH_Notif_Main"></div>

<div class="DH_Main" id="DH_Main">

    <div class="DH_HStack_8" style="align-self:flex-end;">
<div class="DH_Btn DH_Btn_Blue_Ghost DH_NoSel" id="DH_SwitchV1_Btn"
     style="flex:none; display:none; order:-1;">
    <p class="DH_T1 DH_NoSel" id="DH_SwitchV1_Ico" style="color:rgb(var(--DH-blue));font-size:18px;line-height:1;">􀱏</p>
    <p class="DH_T1 DH_NoSel" id="DH_SwitchV1_Lbl" style="color:rgb(var(--DH-blue));font-size:13px;">Switch to V1</p>
</div>

<div class="DH_Btn DH_Btn_Blue_Ghost DH_NoSel" id="DH_SwitchV2_Btn"
     style="flex:none; display:none; order:-1;">
    <p class="DH_T1 DH_NoSel" id="DH_SwitchV2_Ico" style="color:rgb(var(--DH-blue));font-size:18px;line-height:1;">􀂑</p>
    <p class="DH_T1 DH_NoSel" id="DH_SwitchV2_Lbl" style="color:rgb(var(--DH-blue));font-size:13px;">Switch to V2</p>
</div>
        <div class="DH_Btn DH_NoSel" id="DH_Hide_Btn"
             style="flex:none; outline:2px solid rgba(0,0,0,0.20); outline-offset:-2px; background:rgb(var(--DH-blue)); backdrop-filter:blur(16px);">
            <p class="DH_T1 DH_NoSel" id="DH_Hide_Ico" style="color:#fff;font-size:18px;line-height:1;">􀋰</p>
            <p class="DH_T1 DH_NoSel" id="DH_Hide_Txt" style="color:#fff;">Hide</p>
        </div>
    </div>

    <div class="DH_Main_Box" id="DH_Main_Box">

        <div class="DH_Page active" id="DH_Page_1">

            <!-- Row 1: Connection btn + Settings icon -->
            <div class="DH_HStack_8">
                <div class="DH_Btn DH_Btn_Eel DH_NoSel" id="DH_Conn_Btn" style="padding:10px 0 10px 10px; transition:background 0.8s cubic-bezier(0.16,1,0.32,1), outline 0.8s cubic-bezier(0.16,1,0.32,1), filter 0.4s cubic-bezier(0.16,1,0.32,1), transform 0.4s cubic-bezier(0.16,1,0.32,1);">
                    <span id="DH_Conn_Ico" style="display:flex;align-items:center;justify-content:center;flex-shrink:0;"><span class="DH_Spin_Ico" style="font-size:18px;line-height:1;">􀓞</span></span>
                    <p class="DH_T1 DH_NoSel" id="DH_Conn_Txt" style="color:rgb(var(--color-eel,117,117,117),0.70);">Connecting</p>
                </div>
                <div class="DH_Btn DH_Btn_Icon DH_NoSel" id="DH_TopSettings_Btn" style="outline:2px solid rgba(var(--DH-blue),0.20);outline-offset:-2px;background:linear-gradient(0deg,rgba(var(--DH-blue),0.10),rgba(var(--DH-blue),0.10)),rgb(var(--color-snow),0.80);backdrop-filter:blur(16px);" title="Settings">
                    <svg width="18" height="18" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
                        <path d="M18.1,11c-3.9,0-7,3.1-7,7s3.1,7,7,7c3.9,0,7-3.1,7-7S22,11,18.1,11z M18.1,23c-2.8,0-5-2.2-5-5s2.2-5,5-5c2.8,0,5,2.2,5,5S20.9,23,18.1,23z" fill="rgb(var(--DH-blue))" stroke="rgb(var(--DH-blue))" stroke-width="1.2" stroke-linejoin="round" paint-order="stroke fill"/>
                        <path d="M32.8,14.7L30,13.8l-0.6-1.5l1.4-2.6c0.3-0.6,0.2-1.4-0.3-1.9l-2.4-2.4c-0.5-0.5-1.3-0.6-1.9-0.3l-2.6,1.4l-1.5-0.6l-0.9-2.8C21,2.5,20.4,2,19.7,2h-3.4c-0.7,0-1.3,0.5-1.4,1.2L14,6c-0.6,0.1-1.1,0.3-1.6,0.6L9.8,5.2C9.2,4.9,8.4,5,7.9,5.5L5.5,7.9C5,8.4,4.9,9.2,5.2,9.8l1.3,2.5c-0.2,0.5-0.4,1.1-0.6,1.6l-2.8,0.9C2.5,15,2,15.6,2,16.3v3.4c0,0.7,0.5,1.3,1.2,1.5L6,22.1l0.6,1.5l-1.4,2.6c-0.3,0.6-0.2,1.4,0.3,1.9l2.4,2.4c0.5,0.5,1.3,0.6,1.9,0.3l2.6-1.4l1.5,0.6l0.9,2.9c0.2,0.6,0.8,1.1,1.5,1.1h3.4c0.7,0,1.3-0.5,1.5-1.1l0.9-2.9l1.5-0.6l2.6,1.4c0.6,0.3,1.4,0.2,1.9-0.3l2.4-2.4c0.5-0.5,0.6-1.3,0.3-1.9l-1.4-2.6l0.6-1.5l2.9-0.9c0.6-0.2,1.1-0.8,1.1-1.5v-3.4C34,15.6,33.5,14.9,32.8,14.7z M32,19.4l-3.6,1.1L28.3,21c-0.3,0.7-0.6,1.4-0.9,2.1l-0.3,0.5l1.8,3.3l-2,2l-3.3-1.8l-0.5,0.3c-0.7,0.4-1.4,0.7-2.1,0.9l-0.5,0.1L19.4,32h-2.8l-1.1-3.6L15,28.3c-0.7-0.3-1.4-0.6-2.1-0.9l-0.5-0.3l-3.3,1.8l-2-2l1.8-3.3l-0.3-0.5c-0.4-0.7-0.7-1.4-0.9-2.1l-0.1-0.5L4,19.4v-2.8l3.4-1l0.2-0.5c0.2-0.8,0.5-1.5,0.9-2.2l0.3-0.5L7.1,9.1l2-2l3.2,1.8l0.5-0.3c0.7-0.4,1.4-0.7,2.2-0.9l0.5-0.2L16.6,4h2.8l1.1,3.5L21,7.7c0.7,0.2,1.4,0.5,2.1,0.9l0.5,0.3l3.3-1.8l2,2l-1.8,3.3l0.3,0.5c0.4,0.7,0.7,1.4,0.9,2.1l0.1,0.5l3.6,1.1V19.4z" fill="rgb(var(--DH-blue))" stroke="rgb(var(--DH-blue))" stroke-width="1.2" stroke-linejoin="round" paint-order="stroke fill"/>
                    </svg>
                </div>
            </div>
            <!-- Row 2: Donate + YouTube + Discord + GitHub -->
            <div class="DH_HStack_8">
                <div class="DH_Btn DH_NoSel" id="DH_Donate_Btn" style="padding:10px 0 10px 10px;outline:2px solid rgba(0,0,0,0.20);outline-offset:-2px;background:url(https://duohacker.io.vn/wallpaper.png) lightgray 50% / cover no-repeat;">
                    <svg width="19" height="17" viewBox="0 0 24 22" fill="#FFF" xmlns="http://www.w3.org/2000/svg">
                        <path d="M12 21.593c-.425-.394-8.993-7.755-8.993-12.419C3.007 4.984 6.077 2 9.535 2c1.997 0 3.714.862 4.465 2.099C14.75 2.862 16.467 2 18.465 2 21.922 2 25 4.984 25 9.174c0 4.664-8.571 12.025-8.993 12.419L12 21.593z" transform="translate(-1 -1)"/>
                    </svg>
                    <p class="DH_T1 DH_NoSel" id="DH_Donate_Btn_Lbl" style="color:#FFF;font-size:15px;font-weight:700;">Donate</p>
                </div>
                <div class="DH_Btn DH_Btn_Icon DH_NoSel" id="DH_YouTube_Btn" style="background:#FF0000;outline:2px solid rgba(0,0,0,.18);outline-offset:-2px;">
                    <svg width="18" height="13" viewBox="0 0 22 16" fill="#FFF"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.2043 1.0885C20.1084 1.33051 20.8189 2.041 21.0609 2.9451C21.4982 4.58216 21.5 7.99976 21.5 7.99976C21.5 7.99976 21.5 11.4174 21.0609 13.0544C20.8189 13.9585 20.1084 14.669 19.2043 14.911C17.5673 15.3501 11 15.3501 11 15.3501C11 15.3501 4.43274 15.3501 2.79568 14.911C1.89159 14.669 1.1811 13.9585 0.939084 13.0544C0.5 11.4174 0.5 7.99976 0.5 7.99976C0.5 7.99976 0.5 4.58216 0.939084 2.9451C1.1811 2.041 1.89159 1.33051 2.79568 1.0885C4.43274 0.649414 11 0.649414 11 0.649414C11 0.649414 17.5673 0.649414 19.2043 1.0885ZM14.3541 8.00005L8.89834 11.1497V4.85038L14.3541 8.00005Z"/></svg>
                </div>
                <div class="DH_Btn DH_Btn_Icon DH_NoSel" id="DH_Discord_Btn" style="background:rgb(88,101,242);outline:2px solid rgba(0,0,0,.18);outline-offset:-2px;">
                    <svg width="18" height="14" viewBox="0 0 22 16" fill="#FFF"><path d="M18.289 1.34C16.9296.714 15.4761.259 13.9565 0c-.1866.332-.4046.779-.5549 1.134-1.6154-.239-3.2159-.239-4.8016 0C8.4497.779 8.2267.332 8.0384 0 6.5172.259 5.062.716 3.7027 1.343.9608 5.421.2175 9.398.5892 13.318c1.8185 1.337 3.5809 2.149 5.3136 2.68.4278-.579.8093-1.195 1.138-1.845-.6259-.234-1.2255-.523-1.7921-.858.1503-.11.2973-.225.4393-.307 3.4554 1.591 7.2098 1.591 10.624 0 .1437.118.2907.233.4393.342-.6262.337-1.2274.626-1.8534.86.3287.648.7086 1.265 1.138 1.845 1.7343-.531 3.4983-1.343 5.3168-2.681.4361-4.545-.7449-8.484-3.121-11.978ZM7.5115 10.908c-1.0373 0-1.8879-.954-1.8879-2.114 0-1.161.8325-2.115 1.8879-2.115 1.0555 0 1.9061.954 1.8879 2.115.0016 1.16-.8325 2.114-1.8879 2.114Zm6.9769 0c-1.0373 0-1.8879-.954-1.8879-2.114 0-1.161.8324-2.115 1.8879-2.115 1.0554 0 1.9061.954 1.8879 2.115 0 1.16-.8325 2.114-1.8879 2.114Z"/></svg>
                </div>
                <div class="DH_Btn DH_Btn_Icon DH_NoSel" id="DH_GitHub_Btn" style="background:#24292e;outline:2px solid rgba(255,255,255,.18);outline-offset:-2px;">
                    <svg width="18" height="18" viewBox="0 0 22 22" fill="#FFF"><path fill-rule="evenodd" clip-rule="evenodd" d="M11.009.5C5.198.5.5 5.313.5 11.266c0 4.759 3.01 8.788 7.186 10.214.522.107.713-.232.713-.517 0-.25-.017-1.105-.017-1.997-2.923.642-3.532-1.283-3.532-1.283-.47-1.248-1.166-1.568-1.166-1.568-.957-.659.07-.659.07-.659 1.062.071 1.619 1.105 1.619 1.105.94 1.64 2.453 1.176 3.062.891.087-.695.366-1.176.661-1.444-2.332-.25-4.785-1.176-4.785-5.312 0-1.176.418-2.139 1.08-2.887-.106-.267-.461-1.373.105-2.852 0 0 .888-.285 2.899 1.09a9.847 9.847 0 0 1 2.636-.356c.888 0 1.793.125 2.628.356 2.01-1.375 2.898-1.09 2.898-1.09.566 1.479.21 2.585.105 2.852.662.748 1.08 1.711 1.08 2.887 0 4.136-2.453 5.045-4.803 5.312.383.338.714.98.714 2.004 0 1.444-.018 2.606-.018 2.963 0 .285.192.624.714.48C18.49 20.054 21.5 16.025 21.5 11.266 21.517 5.313 16.802.5 11.009.5Z"/></svg>
                </div>
            </div>

            <div class="DH_HStack_8" id="DH_User_Row" style="display:none;gap:10px;">
                <div class="DH_Avatar" id="DH_Avatar">👤</div>
                <div class="DH_VStack_4" style="flex:1 0 0;min-width:0;align-items:flex-start;">
                    <p class="DH_T1 DH_NoSel" id="DH_UName" style="font-size:14px;align-self:stretch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"></p>
                    <div class="DH_HStack_4" style="gap:10px;">
                        <div class="DH_HStack_4" style="gap:3px;">
                            <img class="DH_Stat_Ico" src="https://d35aaqx5ub95lt.cloudfront.net/images/profile/01ce3a817dd01842581c3d18debcbc46.svg">
                            <span class="DH_Stat_Val DH_NoSel" id="DH_UXP">0</span>
                        </div>
                        <div class="DH_HStack_4" style="gap:3px;">
                            <img class="DH_Stat_Ico" src="https://d35aaqx5ub95lt.cloudfront.net/images/gems/45c14e05be9c1af1d7d0b54c6eed7eee.svg">
                            <span class="DH_Stat_Val DH_NoSel" id="DH_UGems">0</span>
                        </div>
                        <div class="DH_HStack_4" style="gap:3px;">
                            <img class="DH_Stat_Ico" src="https://d35aaqx5ub95lt.cloudfront.net/images/icons/398e4298a3b39ce566050e5c041949ef.svg">
                            <span class="DH_Stat_Val DH_NoSel" id="DH_UStreak">0</span>
                        </div>
                    </div>
                </div>
                <svg id="DH_AccSettings_Btn" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" title="Account Manager"
     style="cursor:pointer;flex-shrink:0;transition:filter 0.3s,transform 0.3s;transform-origin:center;">
    <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z" fill="rgb(var(--DH-blue))"/>
</svg>
            </div>

            <div class="DH_Divider"></div>

                   <div class="DH_VStack_8">
                <p class="DH_T1 DH_NoSel" id="DH_XP_Question" style="align-self:stretch;">How much XP would you like to gain?</p>
                <div class="DH_HStack_8">
                    <button class="DH_Mode_Btn DH_NoSel" id="DH_XP_Mode_Btn" title="Switch to Time Mode"><span class="DH_Mode_Icon" id="DH_XP_Mode_Icon">#</span></button>
                    <div class="DH_Input_Wrap" id="DH_XP_Input_Wrap">
                        <input type="number" class="DH_Input" id="DH_XP_Input" placeholder="0" min="30" max="500000">
                    </div>
                    <button class="DH_Input_Btn DH_NoSel" id="DH_XP_Btn" disabled>
                        <span class="DH_Btn_Label" id="DH_XP_Lbl" style="color:#fff;">GET</span>
                    </button>
                </div>
                <div class="DH_Prog_Wrap" id="DH_XP_Prog"><div class="DH_Prog_Fill" id="DH_XP_Fill"></div></div>
            </div>

            <!-- Gems Farm -->
            <div class="DH_VStack_8" id="DH_Gem_Section">
                <p class="DH_T1 DH_NoSel" id="DH_Gem_Run_Label" style="align-self:stretch;">Click "RUN" to Farm Gems</p>
                <div class="DH_HStack_8">
                    <button class="DH_Mode_Btn DH_NoSel" id="DH_Gem_Mode_Btn" title="Switch to Infinite Mode"><span class="DH_Mode_Icon" id="DH_Gem_Mode_Icon">#</span></button>
                    <div class="DH_Input_Wrap" id="DH_Gem_Input_Wrap" style="position:relative;overflow:hidden;">
                        <svg class="DH_Shimmer" width="120" height="48" viewBox="0 0 120 48" fill="none">
                            <path opacity="0.4" d="M72 0H96L72 48H48L72 0Z" fill="rgb(var(--DH-blue))"/>
                            <path opacity="0.4" d="M24 0H60L36 48H0L24 0Z" fill="rgb(var(--DH-blue))"/>
                            <path opacity="0.4" d="M108 0H120L96 48H84L108 0Z" fill="rgb(var(--DH-blue))"/>
                        </svg>
                        <input type="number" class="DH_Input" id="DH_Gem_Input" placeholder="0" readonly style="pointer-events:none;">
                    </div>
                    <button class="DH_Input_Btn DH_NoSel" id="DH_Gem_Btn" disabled>
                        <span class="DH_Btn_Label" id="DH_Gem_Lbl" style="color:#fff;">RUN</span>
                    </button>
                </div>
            </div>

            <div class="DH_VStack_8">
                <p class="DH_T1 DH_NoSel" id="DH_Streak_Question" style="align-self:stretch;">How many Streak days to restore?</p>
                <div class="DH_HStack_8">
                    <button class="DH_Mode_Btn DH_NoSel" id="DH_Streak_Mode_Btn" title="Switch to Time Mode"><span class="DH_Mode_Icon" id="DH_Streak_Mode_Icon">#</span></button>
                    <div class="DH_Input_Wrap" id="DH_Streak_Input_Wrap">
                        <input type="number" class="DH_Input" id="DH_Streak_Input" placeholder="0" min="1" max="3650">
                    </div>
                    <button class="DH_Input_Btn DH_NoSel" id="DH_Streak_Btn" disabled>
                        <span class="DH_Btn_Label" id="DH_Streak_Lbl" style="color:#fff;">RUN</span>
                    </button>
                </div>
                <div class="DH_Prog_Wrap" id="DH_Streak_Prog"><div class="DH_Prog_Fill" id="DH_Streak_Fill"></div></div>
            </div>

            <div class="DH_Divider"></div>


            <div class="DH_Btn DH_Btn_Blue_Ghost DH_NoSel" id="DH_Settings_Btn" style="align-self:stretch; justify-content:space-between; padding:10px 12px;">
        <p class="DH_T1 DH_NoSel" id="DH_ExtraFeatures_Lbl" style="color:rgb(var(--DH-blue));">Extra Features</p>
    <svg width="8" height="13" viewBox="0 0 8 13" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="M1 1l6 5.5L1 12" stroke="rgb(var(--DH-blue))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
    </svg>
</div>


            <div class="DH_HStack_Auto">
                <p class="DH_T2 DH_NoSel" style="color:rgba(var(--DH-blue),0.45);">duohacker.io.vn</p>
                <p class="DH_T2 DH_NoSel" id="DH_Version_Txt" style="color:rgb(var(--DH-blue));font-weight:700;cursor:pointer;">v2026.07.13</p>
            </div>
        </div>


        <div class="DH_Page" id="DH_Page_2">
            <div class="DH_HStack_4 DH_NoSel" id="DH_Back_Btn" style="align-self:flex-start;cursor:pointer;opacity:0.55;">
                <svg width="8" height="14" viewBox="0 0 9 16" fill="none"><path d="M8 1L2 8l6 7" stroke="rgb(var(--color-wolf,60,60,67))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <p class="DH_T1" id="DH_Back_Txt">Back</p>
            </div>

            <!-- Shop Items nav -->
            <div class="DH_Btn DH_Btn_Blue_Ghost DH_NoSel"
     id="DH_Shop_Btn"
     style="align-self:stretch;justify-content:space-between;padding:10px 12px;">

    <div style="display:flex;align-items:center;">
        <p class="DH_T1 DH_NoSel"
           id="DH_Shop_Lbl"
           style="color:rgb(var(--DH-blue));">
            Shop Items
        </p>
    </div>

    <svg width="8" height="13" viewBox="0 0 8 13" fill="none">
        <path d="M1 1l6 5.5L1 12"
              stroke="rgb(var(--DH-blue))"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"/>
    </svg>
</div>

            <!-- Leaderboard nav -->
            <div class="DH_Btn DH_Btn_Blue_Ghost DH_NoSel"
     id="DH_LB_Nav_Btn"
     style="align-self:stretch;justify-content:space-between;padding:10px 12px;">
    <p class="DH_T1 DH_NoSel" id="DH_LB_Nav_Lbl" style="color:rgb(var(--DH-blue));">Leaderboard</p>
    <svg width="8" height="13" viewBox="0 0 8 13" fill="none">
        <path d="M1 1l6 5.5L1 12" stroke="rgb(var(--DH-blue))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
    </svg>
</div>

            <!-- Auto League -->
            <div class="DH_HStack_Auto" style="align-self:stretch;">
                <div style="display:flex;flex-direction:column;gap:2px;flex:1;min-width:0;">
                    <p class="DH_T1 DH_NoSel" id="DH_League_Title">Auto League #1</p>
                    <p class="DH_T2 DH_NoSel" id="DH_League_Sub" style="font-size:11px;">Farm XP until rank #1</p>
                </div>
                <button class="DH_Sm_Btn DH_NoSel" id="DH_League_Btn" disabled>
                    <span class="DH_Sm_Btn_Label" id="DH_League_Lbl" style="color:#fff;">RUN</span>
                </button>
            </div>
            <div class="DH_Prog_Wrap" id="DH_League_Prog" style="align-self:stretch;"><div class="DH_Prog_Fill" id="DH_League_Fill"></div></div>

            <!-- Auto Daily Quest - SIMPLIFIED -->
            <div class="DH_HStack_Auto" style="align-self:stretch;">
                <div style="display:flex;flex-direction:column;gap:2px;flex:1;min-width:0;">
                    <p class="DH_T1 DH_NoSel" id="DH_Quest_Title">Auto Daily Quest</p>
                    <p class="DH_T2 DH_NoSel" id="DH_Quest_Sub" style="font-size:11px;">Complete all daily quests</p>
                </div>
                <button class="DH_Sm_Btn DH_NoSel" id="DH_Quest_Btn" disabled>
                    <span class="DH_Sm_Btn_Label" id="DH_Quest_Lbl" style="color:#fff;">RUN</span>
                </button>
            </div>

            <!-- Monthly Quest -->
            <div class="DH_HStack_Auto" style="align-self:stretch;cursor:pointer;" id="DH_MonthlyQuest_Nav_Btn">
                <div style="display:flex;flex-direction:column;gap:2px;flex:1;min-width:0;">
                    <p class="DH_T1 DH_NoSel" id="DH_Monthly_Title">Claim Monthly Quest</p>
                    <p class="DH_T2 DH_NoSel" id="DH_Monthly_Sub" style="font-size:11px;">View &amp; claim monthly quests</p>
                </div>
                <button class="DH_Sm_Btn DH_NoSel" id="DH_MonthlyQuest_Claim_Btn" disabled>
                    <span class="DH_Sm_Btn_Label" id="DH_MonthlyQuest_Claim_Lbl" style="color:#fff;">GET</span>
                </button>
            </div>

            <div class="DH_Divider"></div>
        </div>
        <div class="DH_Page" id="DH_Page_4">
            <div class="DH_HStack_4 DH_NoSel" id="DH_Settings_Back_Btn" style="align-self:flex-start;cursor:pointer;opacity:0.55;">
                <svg width="8" height="14" viewBox="0 0 9 16" fill="none"><path d="M8 1L2 8l6 7" stroke="rgb(var(--color-wolf,60,60,67))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <p class="DH_T1" id="DH_Settings_Back_Txt">Back</p>
            </div>
            <div style="width:100%;gap:8px;display:flex;flex-direction:column;align-items:flex-start;overflow-y:auto;max-height:520px;padding-right:2px;" class="DH_Scroll_Inner">

            <!-- Loop Delay -->
            <div class="DH_VStack_8" style="align-self:stretch;">
                <p class="DH_T1 DH_NoSel" id="DH_LoopDelay_Lbl" style="align-self:stretch;">Loop delay (ms)</p>
                <div class="DH_HStack_8">
                    <div class="DH_Input_Wrap">
                        <p class="DH_T1 DH_NoSel" style="color:rgba(var(--DH-blue),0.50);font-size:13px;flex-shrink:0;">ms</p>
                        <input type="number" class="DH_Input" id="DH_Delay_Input" placeholder="500" min="0" max="10000">
                    </div>
                    <button class="DH_Input_Btn DH_NoSel" id="DH_Delay_Btn">
                        <span class="DH_Btn_Label" id="DH_Delay_Lbl" style="color:#fff;">SAVE</span>
                    </button>
                </div>
            </div>

            <div class="DH_Divider"></div>

            <!-- Free Duolingo Max toggle -->
            <div class="DH_HStack_Auto" style="align-self:stretch;padding:4px 0;">
                <div style="display:flex;flex-direction:column;gap:2px;">
                    <p class="DH_T1 DH_NoSel" id="DH_FreeDuoMax_Lbl">Free Duolingo Max</p>
                    <p class="DH_T2 DH_NoSel" id="DH_FreeDuoMax_Sub" style="font-size:11px;">Client-side only, reload to apply</p>
                </div>
                <label class="DH_Toggle">
                    <input type="checkbox" id="DH_Super_Toggle">
                    <span class="DH_Toggle_Slider"></span>
                </label>
            </div>

            <!-- Hide Profile toggle -->
            <div class="DH_HStack_Auto" style="align-self:stretch;padding:4px 0;">
                <div style="display:flex;flex-direction:column;gap:2px;">
                    <p class="DH_T1 DH_NoSel" id="DH_HideProfile_Lbl">Hide Profile</p>
                    <p class="DH_T2 DH_NoSel" id="DH_HideProfile_Status" style="font-size:11px;">Loading…</p>
                </div>
                <label class="DH_Toggle">
                    <input type="checkbox" id="DH_HideProfile_Toggle" disabled>
                    <span class="DH_Toggle_Slider"></span>
                </label>
            </div>

            <!-- Inject Solver toggle -->
            <div class="DH_HStack_Auto" style="align-self:stretch;padding:4px 0;">
                <div style="display:flex;flex-direction:column;gap:2px;">
                    <p class="DH_T1 DH_NoSel" id="DH_AutoSolver_Lbl">Auto Solver</p>
                    <p class="DH_T2 DH_NoSel" id="DH_AutoSolver_Sub" style="font-size:11px;">Show Auto Solver buttons during lessons</p>
                </div>
                <label class="DH_Toggle">
                    <input type="checkbox" id="DH_Solver_Toggle">
                    <span class="DH_Toggle_Slider"></span>
                </label>
            </div>

            <!-- Hide Animation toggle -->
            <div class="DH_HStack_Auto" style="align-self:stretch;padding:4px 0;">
                <div style="display:flex;flex-direction:column;gap:2px;">
                    <p class="DH_T1 DH_NoSel" id="DH_HideAnim_Lbl">Hide Animation</p>
                    <p class="DH_T2 DH_NoSel" id="DH_HideAnim_Sub" style="font-size:11px;">Hide Duolingo images &amp; animations</p>
                </div>
                <label class="DH_Toggle">
                    <input type="checkbox" id="DH_HideAnim_Toggle">
                    <span class="DH_Toggle_Slider"></span>
                </label>
            </div>

            <!-- Lesson Shortener toggle -->
            <div class="DH_HStack_Auto" style="align-self:stretch;padding:4px 0;">
                <div style="display:flex;flex-direction:column;gap:2px;">
                    <p class="DH_T1 DH_NoSel" id="DH_LessonShortener_Lbl">Lesson Shortener</p>
                    <p class="DH_T2 DH_NoSel" id="DH_LessonShortener_Sub" style="font-size:11px;">Replace lessons with 1 instant question</p>
                </div>
                <label class="DH_Toggle">
                    <input type="checkbox" id="DH_LS_Toggle">
                    <span class="DH_Toggle_Slider"></span>
                </label>
            </div>

            <!-- Stories Shortener toggle -->
            <div class="DH_HStack_Auto" style="align-self:stretch;padding:4px 0;">
                <div style="display:flex;flex-direction:column;gap:2px;">
                    <p class="DH_T1 DH_NoSel" id="DH_StoriesShortener_Lbl">Stories Shortener</p>
                    <p class="DH_T2 DH_NoSel" id="DH_StoriesShortener_Sub" style="font-size:11px;">Skip all challenges in stories</p>
                </div>
                <label class="DH_Toggle">
                    <input type="checkbox" id="DH_SS_Toggle">
                    <span class="DH_Toggle_Slider"></span>
                </label>
            </div>

            <!-- Safe Mode toggle -->
            <div class="DH_HStack_Auto" style="align-self:stretch;padding:4px 0;">
                <div style="display:flex;flex-direction:column;gap:2px;">
                    <p class="DH_T1 DH_NoSel" id="DH_SafeMode_Lbl">Safe Mode</p>
                    <p class="DH_T2 DH_NoSel" id="DH_SafeMode_Sub" style="font-size:11px;">Adds more delay between clicks while solving lessons/practices</p>
                </div>
                <label class="DH_Toggle">
                    <input type="checkbox" id="DH_SafeMode_Toggle">
                    <span class="DH_Toggle_Slider"></span>
                </label>
            </div>



            <div class="DH_Divider"></div>
            <div class="DH_HStack_Auto" style="align-self:stretch;padding:2px 0;">
                <p class="DH_T2 DH_NoSel" id="DH_License_Open_Btn" style="color:rgba(var(--DH-blue),0.45);cursor:pointer;text-decoration:underline;text-underline-offset:2px;">DuoHacker V2</p>
                <p class="DH_T2 DH_NoSel" id="DH_Credits_Btn" style="color:rgb(var(--DH-blue));cursor:pointer;font-weight:700;text-decoration:underline;text-underline-offset:2px;">View Credits</p>
            </div>
            </div>
        </div>

        <!-- PAGE 9: License -->
        <div class="DH_Page" id="DH_Page_9" style="flex:1;min-height:0;">
            <div class="DH_HStack_4 DH_NoSel" id="DH_License_Back_Btn" style="align-self:flex-start;cursor:pointer;opacity:0.55;">
                <svg width="8" height="14" viewBox="0 0 9 16" fill="none"><path d="M8 1L2 8l6 7" stroke="rgb(var(--color-wolf,60,60,67))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <p class="DH_T1" id="DH_License_Back_Txt">Back</p>
            </div>
            <div class="DH_HStack_Auto DH_NoSel" style="align-self:stretch;">
                <p class="DH_T1" style="font-size:15px;">BY-NC-ND 4.0 License</p>
                <p class="DH_T2" style="font-size:11px;color:rgba(var(--DH-blue),0.50);">DuoHacker</p>
            </div>
            <div class="DH_License_Scroll">
                <p id="DH_License_Text" class="DH_NoSel">Loading…</p>
            </div>
        </div>

        <!-- PAGE 3: Shop -->
        <div class="DH_Page" id="DH_Page_3">
            <div class="DH_HStack_4 DH_NoSel" id="DH_Shop_Back_Btn" style="align-self:flex-start;cursor:pointer;opacity:0.55;">
                <svg width="8" height="14" viewBox="0 0 9 16" fill="none"><path d="M8 1L2 8l6 7" stroke="rgb(var(--color-wolf,60,60,67))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <p class="DH_T1" id="DH_Shop_Back_Txt">Back</p>
            </div>

            <input type="text" class="DH_Search" id="DH_Shop_Search" placeholder="Search items...">

            <!-- Shop scrollable container -->
            <div class="DH_Scroll_Inner" id="DH_Shop_Container" style="max-height:300px;">
                <p class="DH_T2 DH_NoSel" id="DH_ShopLoading_Txt" style="text-align:center;padding:8px 0;">Loading shop...</p>
            </div>
        </div>

        <!-- PAGE 5: Account Manager -->
        <div class="DH_Page" id="DH_Page_5">
            <div class="DH_HStack_4 DH_NoSel" id="DH_AccMgr_Back_Btn" style="align-self:flex-start;cursor:pointer;opacity:0.55;">
                <svg width="8" height="14" viewBox="0 0 9 16" fill="none"><path d="M8 1L2 8l6 7" stroke="rgb(var(--color-wolf,60,60,67))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <p class="DH_T1" id="DH_AccMgr_Back_Txt">Back</p>
            </div>

            <div class="DH_HStack_Auto" style="align-self:stretch;">
                <p class="DH_T1 DH_NoSel" id="DH_AccMgr_Title" style="font-size:14px;font-weight:800;">Account Manager</p>
                <button class="DH_Sm_Btn DH_NoSel" id="DH_AccSave_Btn" disabled style="height:30px;padding:0 12px;">
                    <span class="DH_Sm_Btn_Label" id="DH_AccSave_Lbl" style="color:#fff;font-size:11px;">SAVE CURRENT</span>
                </button>
            </div>

            <div class="DH_Divider"></div>

            <div id="DH_AccList_Wrap" class="DH_Scroll_Inner" style="max-height:260px;width:100%;gap:6px;">
                <p class="DH_T2 DH_NoSel" id="DH_AccNoSaved_Txt" style="text-align:center;padding:8px 0;">No saved accounts.</p>
            </div>
        </div>

        <!-- PAGE 7: Credits -->
        <div class="DH_Page" id="DH_Page_7">
            <div class="DH_HStack_4 DH_NoSel" id="DH_Credits_Back_Btn" style="align-self:flex-start;cursor:pointer;opacity:0.55;">
                <svg width="8" height="14" viewBox="0 0 9 16" fill="none"><path d="M8 1L2 8l6 7" stroke="rgb(var(--color-wolf,60,60,67))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <p class="DH_T1" id="DH_Credits_Back_Txt">Back</p>
            </div>
            <p class="DH_T1 DH_NoSel" id="DH_Credits_Title" style="font-size:14px;font-weight:800;align-self:stretch;">Credits</p>
            <div class="DH_Divider"></div>
            <div id="DH_Credits_Container" class="DH_Scroll_Inner" style="max-height:280px;width:100%;gap:10px;">
            </div>
        </div>

        <!-- PAGE 6: Monthly Quests -->
        <div class="DH_Page" id="DH_Page_6">
            <div class="DH_HStack_4 DH_NoSel" id="DH_MQ_Back_Btn" style="align-self:flex-start;cursor:pointer;opacity:0.55;">
                <svg width="8" height="14" viewBox="0 0 9 16" fill="none"><path d="M8 1L2 8l6 7" stroke="rgb(var(--color-wolf,60,60,67))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <p class="DH_T1" id="DH_MQ_Back_Txt">Back</p>
            </div>

            <div class="DH_HStack_Auto" style="align-self:stretch;">
                <p class="DH_T1 DH_NoSel" id="DH_MQ_Title" style="font-size:14px;font-weight:800;">Monthly Quests</p>
                <button class="DH_Sm_Btn DH_NoSel" id="DH_MQ_ClaimAll_Btn" disabled style="height:30px;padding:0 12px;">
                    <span class="DH_Sm_Btn_Label" id="DH_MQ_ClaimAll_Lbl" style="color:#fff;font-size:11px;">CLAIM</span>
                </button>
            </div>

            <div class="DH_Divider"></div>
            <div class="DH_Prog_Wrap" id="DH_MQ_Prog" style="align-self:stretch;"><div class="DH_Prog_Fill" id="DH_MQ_Fill"></div></div>

            <div id="DH_MQ_Container" class="DH_Scroll_Inner" style="max-height:300px;width:100%;">
                <p class="DH_T2 DH_NoSel" style="text-align:center;padding:8px 0;">Loading quests...</p>
            </div>
        </div>

        <!-- PAGE V1: V1 Mode — simple farm with live counters, no extra features -->
        <div class="DH_Page" id="DH_Page_V1">

            <!-- Row 1: Connection btn + Settings icon -->
            <div class="DH_HStack_8">
                <div class="DH_Btn DH_Btn_Eel DH_NoSel" id="DH_V1_Conn_Btn" style="padding:10px 0 10px 10px; transition:background 0.8s cubic-bezier(0.16,1,0.32,1), outline 0.8s cubic-bezier(0.16,1,0.32,1), filter 0.4s cubic-bezier(0.16,1,0.32,1), transform 0.4s cubic-bezier(0.16,1,0.32,1);">
                    <span id="DH_V1_Conn_Ico" style="display:flex;align-items:center;justify-content:center;flex-shrink:0;"><span class="DH_Spin_Ico" style="font-size:18px;line-height:1;">􀓞</span></span>
                    <p class="DH_T1 DH_NoSel" id="DH_V1_Conn_Txt" style="color:rgb(var(--color-eel,117,117,117),0.70);">Connecting</p>
                </div>
                <div class="DH_Btn DH_Btn_Icon DH_NoSel" id="DH_V1_TopSettings_Btn" style="outline:2px solid rgba(var(--DH-blue),0.20);outline-offset:-2px;background:linear-gradient(0deg,rgba(var(--DH-blue),0.10),rgba(var(--DH-blue),0.10)),rgb(var(--color-snow),0.80);backdrop-filter:blur(16px);" title="Settings">
                    <svg width="18" height="18" viewBox="0 0 36 36" fill="none"><path d="M18.1,11c-3.9,0-7,3.1-7,7s3.1,7,7,7c3.9,0,7-3.1,7-7S22,11,18.1,11z M18.1,23c-2.8,0-5-2.2-5-5s2.2-5,5-5c2.8,0,5,2.2,5,5S20.9,23,18.1,23z" fill="rgb(var(--DH-blue))" stroke="rgb(var(--DH-blue))" stroke-width="1.2" stroke-linejoin="round" paint-order="stroke fill"/><path d="M32.8,14.7L30,13.8l-0.6-1.5l1.4-2.6c0.3-0.6,0.2-1.4-0.3-1.9l-2.4-2.4c-0.5-0.5-1.3-0.6-1.9-0.3l-2.6,1.4l-1.5-0.6l-0.9-2.8C21,2.5,20.4,2,19.7,2h-3.4c-0.7,0-1.3,0.5-1.4,1.2L14,6c-0.6,0.1-1.1,0.3-1.6,0.6L9.8,5.2C9.2,4.9,8.4,5,7.9,5.5L5.5,7.9C5,8.4,4.9,9.2,5.2,9.8l1.3,2.5c-0.2,0.5-0.4,1.1-0.6,1.6l-2.8,0.9C2.5,15,2,15.6,2,16.3v3.4c0,0.7,0.5,1.3,1.2,1.5L6,22.1l0.6,1.5l-1.4,2.6c-0.3,0.6-0.2,1.4,0.3,1.9l2.4,2.4c0.5,0.5,1.3,0.6,1.9,0.3l2.6-1.4l1.5,0.6l0.9,2.9c0.2,0.6,0.8,1.1,1.5,1.1h3.4c0.7,0,1.3-0.5,1.5-1.1l0.9-2.9l1.5-0.6l2.6,1.4c0.6,0.3,1.4,0.2,1.9-0.3l2.4-2.4c0.5-0.5,0.6-1.3,0.3-1.9l-1.4-2.6l0.6-1.5l2.9-0.9c0.6-0.2,1.1-0.8,1.1-1.5v-3.4C34,15.6,33.5,14.9,32.8,14.7z M32,19.4l-3.6,1.1L28.3,21c-0.3,0.7-0.6,1.4-0.9,2.1l-0.3,0.5l1.8,3.3l-2,2l-3.3-1.8l-0.5,0.3c-0.7,0.4-1.4,0.7-2.1,0.9l-0.5,0.1L19.4,32h-2.8l-1.1-3.6L15,28.3c-0.7-0.3-1.4-0.6-2.1-0.9l-0.5-0.3l-3.3,1.8l-2-2l1.8-3.3l-0.3-0.5c-0.4-0.7-0.7-1.4-0.9-2.1l-0.1-0.5L4,19.4v-2.8l3.4-1l0.2-0.5c0.2-0.8,0.5-1.5,0.9-2.2l0.3-0.5L7.1,9.1l2-2l3.2,1.8l0.5-0.3c0.7-0.4,1.4-0.7,2.2-0.9l0.5-0.2L16.6,4h2.8l1.1,3.5L21,7.7c0.7,0.2,1.4,0.5,2.1,0.9l0.5,0.3l3.3-1.8l2,2l-1.8,3.3l0.3,0.5c0.4,0.7,0.7,1.4,0.9,2.1l0.1,0.5l3.6,1.1V19.4z" fill="rgb(var(--DH-blue))" stroke="rgb(var(--DH-blue))" stroke-width="1.2" stroke-linejoin="round" paint-order="stroke fill"/></svg>
                </div>
            </div>
            <!-- Row 2: Donate + YouTube + Discord + GitHub -->
            <div class="DH_HStack_8">
                <div class="DH_Btn DH_NoSel" id="DH_V1_Donate_Btn" style="padding:10px 0 10px 10px;outline:2px solid rgba(0,0,0,0.20);outline-offset:-2px;background:url(https://duohacker.io.vn/wallpaper.png) lightgray 50% / cover no-repeat;">
                    <svg width="19" height="17" viewBox="0 0 24 22" fill="#FFF"><path d="M12 21.593c-.425-.394-8.993-7.755-8.993-12.419C3.007 4.984 6.077 2 9.535 2c1.997 0 3.714.862 4.465 2.099C14.75 2.862 16.467 2 18.465 2 21.922 2 25 4.984 25 9.174c0 4.664-8.571 12.025-8.993 12.419L12 21.593z" transform="translate(-1 -1)"/></svg>
                    <p class="DH_T1 DH_NoSel" style="color:#FFF;font-size:15px;font-weight:700;">Donate</p>
                </div>
                <div class="DH_Btn DH_Btn_Icon DH_NoSel" id="DH_V1_YouTube_Btn" style="background:#FF0000;outline:2px solid rgba(0,0,0,.18);outline-offset:-2px;">
                    <svg width="18" height="13" viewBox="0 0 22 16" fill="#FFF"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.2043 1.0885C20.1084 1.33051 20.8189 2.041 21.0609 2.9451C21.4982 4.58216 21.5 7.99976 21.5 7.99976C21.5 7.99976 21.5 11.4174 21.0609 13.0544C20.8189 13.9585 20.1084 14.669 19.2043 14.911C17.5673 15.3501 11 15.3501 11 15.3501C11 15.3501 4.43274 15.3501 2.79568 14.911C1.89159 14.669 1.1811 13.9585 0.939084 13.0544C0.5 11.4174 0.5 7.99976 0.5 7.99976C0.5 7.99976 0.5 4.58216 0.939084 2.9451C1.1811 2.041 1.89159 1.33051 2.79568 1.0885C4.43274 0.649414 11 0.649414 11 0.649414C11 0.649414 17.5673 0.649414 19.2043 1.0885ZM14.3541 8.00005L8.89834 11.1497V4.85038L14.3541 8.00005Z"/></svg>
                </div>
                <div class="DH_Btn DH_Btn_Icon DH_NoSel" id="DH_V1_Discord_Btn" style="background:rgb(88,101,242);outline:2px solid rgba(0,0,0,.18);outline-offset:-2px;">
                    <svg width="18" height="14" viewBox="0 0 22 16" fill="#FFF"><path d="M18.289 1.34C16.9296.714 15.4761.259 13.9565 0c-.1866.332-.4046.779-.5549 1.134-1.6154-.239-3.2159-.239-4.8016 0C8.4497.779 8.2267.332 8.0384 0 6.5172.259 5.062.716 3.7027 1.343.9608 5.421.2175 9.398.5892 13.318c1.8185 1.337 3.5809 2.149 5.3136 2.68.4278-.579.8093-1.195 1.138-1.845-.6259-.234-1.2255-.523-1.7921-.858.1503-.11.2973-.225.4393-.307 3.4554 1.591 7.2098 1.591 10.624 0 .1437.118.2907.233.4393.342-.6262.337-1.2274.626-1.8534.86.3287.648.7086 1.265 1.138 1.845 1.7343-.531 3.4983-1.343 5.3168-2.681.4361-4.545-.7449-8.484-3.121-11.978ZM7.5115 10.908c-1.0373 0-1.8879-.954-1.8879-2.114 0-1.161.8325-2.115 1.8879-2.115 1.0555 0 1.9061.954 1.8879 2.115.0016 1.16-.8325 2.114-1.8879 2.114Zm6.9769 0c-1.0373 0-1.8879-.954-1.8879-2.114 0-1.161.8324-2.115 1.8879-2.115 1.0554 0 1.9061.954 1.8879 2.115 0 1.16-.8325 2.114-1.8879 2.114Z"/></svg>
                </div>
                <div class="DH_Btn DH_Btn_Icon DH_NoSel" id="DH_V1_GitHub_Btn" style="background:#24292e;outline:2px solid rgba(255,255,255,.18);outline-offset:-2px;">
                    <svg width="18" height="18" viewBox="0 0 22 22" fill="#FFF"><path fill-rule="evenodd" clip-rule="evenodd" d="M11.009.5C5.198.5.5 5.313.5 11.266c0 4.759 3.01 8.788 7.186 10.214.522.107.713-.232.713-.517 0-.25-.017-1.105-.017-1.997-2.923.642-3.532-1.283-3.532-1.283-.47-1.248-1.166-1.568-1.166-1.568-.957-.659.07-.659.07-.659 1.062.071 1.619 1.105 1.619 1.105.94 1.64 2.453 1.176 3.062.891.087-.695.366-1.176.661-1.444-2.332-.25-4.785-1.176-4.785-5.312 0-1.176.418-2.139 1.08-2.887-.106-.267-.461-1.373.105-2.852 0 0 .888-.285 2.899 1.09a9.847 9.847 0 0 1 2.636-.356c.888 0 1.793.125 2.628.356 2.01-1.375 2.898-1.09 2.898-1.09.566 1.479.21 2.585.105 2.852.662.748 1.08 1.711 1.08 2.887 0 4.136-2.453 5.045-4.803 5.312.383.338.714.98.714 2.004 0 1.444-.018 2.606-.018 2.963 0 .285.192.624.714.48C18.49 20.054 21.5 16.025 21.5 11.266 21.517 5.313 16.802.5 11.009.5Z"/></svg>
                </div>
            </div>

            <div class="DH_Divider"></div>

            <!-- User row (reused from V2 data) -->
            <div class="DH_HStack_8" id="DH_V1_User_Row" style="display:none;gap:10px;">
                <div class="DH_Avatar" id="DH_V1_Avatar">👤</div>
                <div class="DH_VStack_4" style="flex:1 0 0;min-width:0;align-items:flex-start;">
                    <p class="DH_T1 DH_NoSel" id="DH_V1_UName" style="font-size:14px;align-self:stretch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"></p>
                    <div class="DH_HStack_4" style="gap:10px;">
                        <div class="DH_HStack_4" style="gap:3px;">
                            <img class="DH_Stat_Ico" src="https://d35aaqx5ub95lt.cloudfront.net/images/profile/01ce3a817dd01842581c3d18debcbc46.svg">
                            <span class="DH_Stat_Val DH_NoSel" id="DH_V1_UXP">0</span>
                        </div>
                        <div class="DH_HStack_4" style="gap:3px;">
                            <img class="DH_Stat_Ico" src="https://d35aaqx5ub95lt.cloudfront.net/images/gems/45c14e05be9c1af1d7d0b54c6eed7eee.svg">
                            <span class="DH_Stat_Val DH_NoSel" id="DH_V1_UGems">0</span>
                        </div>
                        <div class="DH_HStack_4" style="gap:3px;">
                            <img class="DH_Stat_Ico" src="https://d35aaqx5ub95lt.cloudfront.net/images/icons/398e4298a3b39ce566050e5c041949ef.svg">
                            <span class="DH_Stat_Val DH_NoSel" id="DH_V1_UStreak">0</span>
                        </div>
                    </div>
                </div>
            </div>

            <div class="DH_Divider" id="DH_V1_User_Divider" style="display:none;"></div>

            <!-- Farm Practice -->
            <div class="DH_VStack_8" style="align-self:stretch;">
                <p class="DH_T1 DH_NoSel" id="DH_Practice_Question" style="align-self:stretch;font-weight:700;">How many practice lesson you would like to solve?</p>
                <div class="DH_HStack_8">
                    <div class="DH_Input_Wrap" id="DH_Practice_Input_Wrap">
                        <input type="number" class="DH_Input" id="DH_Practice_Input" placeholder="0" min="0" max="9999">
                    </div>
                    <button class="DH_Input_Btn DH_NoSel" id="DH_Practice_Btn" disabled>
                        <span class="DH_Btn_Label" id="DH_Practice_Lbl" style="color:#fff;">RUN</span>
                    </button>
                </div>

            </div>

            <div class="DH_Divider"></div>

            <!-- Extra Features nav (solver page) -->
            <div class="DH_Btn DH_Btn_Blue_Ghost DH_NoSel" id="DH_V1_Settings_Btn" style="align-self:stretch; justify-content:space-between; padding:10px 12px;">
                <p class="DH_T1 DH_NoSel" id="DH_V1_Settings_Lbl" style="color:rgb(var(--DH-blue));">Extra Features</p>
                <svg width="8" height="13" viewBox="0 0 8 13" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M1 1l6 5.5L1 12" stroke="rgb(var(--DH-blue))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                </svg>
            </div>

            <div class="DH_HStack_Auto">
                <p class="DH_T2 DH_NoSel" style="color:rgba(var(--DH-blue),0.45);">duohacker.io.vn</p>
                <p class="DH_T2 DH_NoSel" style="color:rgba(var(--DH-blue),0.45);">Solver</p>
            </div>
        </div>

        <!-- PAGE 10: Changelog -->
        <div class="DH_Page" id="DH_Page_10">
            <div class="DH_HStack_4 DH_NoSel" id="DH_Changelog_Back_Btn" style="align-self:flex-start;cursor:pointer;opacity:0.55;">
                <svg width="8" height="14" viewBox="0 0 9 16" fill="none"><path d="M8 1L2 8l6 7" stroke="rgb(var(--color-wolf,60,60,67))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <p class="DH_T1" id="DH_Changelog_Back_Txt">Back</p>
            </div>
            <div style="width:100%;display:flex;flex-direction:column;gap:12px;overflow-y:auto;max-height:360px;padding-right:2px;" class="DH_Scroll_Inner" id="DH_Changelog_List">
                <!-- populated by JS -->
            </div>
        </div>

        <!-- PAGE 11: Leaderboard -->
        <div class="DH_Page" id="DH_Page_11">
            <div class="DH_HStack_4 DH_NoSel" id="DH_LB_Back_Btn" style="align-self:flex-start;cursor:pointer;opacity:0.55;">
                <svg width="8" height="14" viewBox="0 0 9 16" fill="none"><path d="M8 1L2 8l6 7" stroke="rgb(var(--color-wolf,60,60,67))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <p class="DH_T1" id="DH_LB_Back_Txt">Back</p>
            </div>

            <!-- League header card -->
            <div class="DH_LB_Header">
                <img id="DH_LB_LeagueImg" class="DH_LB_LeagueImg" src="" alt="" onerror="this.style.display='none'">
                <p class="DH_LB_LeagueName DH_NoSel" id="DH_LB_LeagueName">—</p>
            </div>

            <!-- Rankings list -->
            <div class="DH_Scroll_Inner" id="DH_LB_List" style="max-height:408px;display:flex;flex-direction:column;gap:3px;align-self:stretch;">
                <p class="DH_T2 DH_NoSel" id="DH_LB_Loading_Txt" style="text-align:center;padding:16px 0;">Loading leaderboard...</p>
            </div>
        </div>

        <!-- PAGE 12: Reaction Picker -->
        <div class="DH_Page" id="DH_Page_12">
            <div class="DH_HStack_4 DH_NoSel" id="DH_React_Back_Btn" style="align-self:flex-start;cursor:pointer;opacity:0.55;">
                <svg width="8" height="14" viewBox="0 0 9 16" fill="none"><path d="M8 1L2 8l6 7" stroke="rgb(var(--color-wolf,60,60,67))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <p class="DH_T1" id="DH_React_Back_Txt">Back</p>
            </div>
            <p class="DH_T1 DH_NoSel" id="DH_React_Title" style="font-weight:700;">Choose Reaction</p>
            <div class="DH_Scroll_Inner" id="DH_React_Container" style="max-height:300px;"></div>
        </div>
    </div>
</div>
`;
        document.body.appendChild(_wrap);
        const _langSelector = document.getElementById('DH_LangSelector');
        const _langSelectorBtn = document.getElementById('DH_LangSelector_Btn');

        _langSelectorBtn.addEventListener('click', event => {
            event.stopPropagation();
            _langSelector.classList.toggle('open');
        });

        document.querySelectorAll('.DH_LangOption').forEach(option => {
            option.addEventListener('click', event => {
                event.stopPropagation();

                const lang = option.dataset.lang;
                if (lang !== 'vi' && lang !== 'en') return;

                _setLang(lang);
                _langSelector.classList.remove('open');
            });
        });

        document.addEventListener('click', event => {
            if (!_langSelector.contains(event.target)) {
                _langSelector.classList.remove('open');
            }
        });


        _applyLang();


        let _jwt = null,
            _sub = null,
            _lbCache = null,
            _lbPrefetching = false,
            _lbCohortId = null,
            _hdrs = null,
            _user = null,
            _privacy = null;
        let _v1Mode = false;
        let _v1Running = false,
            _v1Task = null;
        const _v1Earned = {
            xp: 0,
            gems: 0,
            streak: 0
        };

        function _v1UpdateDisplay() {
            requestAnimationFrame(() => {
                const xi = document.getElementById('DH_V1_XP_Input');
                const gi = document.getElementById('DH_V1_Gem_Input');
                const si = document.getElementById('DH_V1_Streak_Input');
                if (xi) xi.value = _v1Earned.xp > 0 ? String(_v1Earned.xp) : '';
                if (gi) gi.value = _v1Earned.gems > 0 ? String(_v1Earned.gems) : '';
                if (si) si.value = _v1Earned.streak > 0 ? String(_v1Earned.streak) : '';
            });
        }

        function _v1SyncUser() {
            if (!_user) return;
            const row = document.getElementById('DH_V1_User_Row');
            const div = document.getElementById('DH_V1_User_Divider');
            if (row) row.style.display = 'flex';
            if (div) div.style.display = '';
            document.getElementById('DH_V1_UName').textContent = _user.username || '';
            document.getElementById('DH_V1_UXP').textContent = (_user.totalXp || 0).toLocaleString();
            document.getElementById('DH_V1_UGems').textContent = (_user.gems || 0).toLocaleString();
            document.getElementById('DH_V1_UStreak').textContent = (_user.streak || 0).toLocaleString();
            const av = document.getElementById('DH_V1_Avatar');
            // If blob avatar already loaded from leaderboard, mirror it instead of overwriting with lower-quality picture
            if (_panelAvatarLoaded) {
                const v2Img = document.getElementById('DH_Avatar') && document.getElementById('DH_Avatar').querySelector('img');
                if (v2Img && av) {
                    av.innerHTML = '';
                    const img = document.createElement('img');
                    img.src = v2Img.src;
                    img.style.cssText = 'width:100%;height:100%;object-fit:cover;border-radius:50%;display:block;';
                    img.draggable = false;
                    av.appendChild(img);
                }
            } else if (_user.picture) {
                let hq = _user.picture.replace(/\/(medium|large|small)$/, '/xlarge');
                if (!hq.endsWith('/xlarge') && hq.includes('duolingo.com/ssr-avatars')) hq += '/xlarge';
                const img = document.createElement('img');
                img.src = hq;
                img.style.cssText = 'width:100%;height:100%;object-fit:cover;border-radius:50%;display:block;';
                img.onerror = function() { av.innerHTML = '👤'; };
                av.innerHTML = '';
                av.appendChild(img);
            }
        }
        let _remoteVersion = '';
        let _running = false,
            _task = null,
            _hidden = false;
        let _delay = parseInt(localStorage.getItem('dh2_delay') || '500', 10);
        let _shopItems = [];
        const _sleep = ms => new Promise(r => setTimeout(r, ms));
        let _SAFE_MODE = localStorage.getItem('duohacker_safe_mode') === 'true';
        // When Safe Mode is on, click/solve cadence is slowed down + randomized
        // to look more human and reduce risk of desync (e.g. RESULT_CODE_HUNG).
        const _safeWait = (baseMs) => _SAFE_MODE ? Math.round(baseMs * 2.5 + Math.random() * 500) : baseMs;
        const GOALS_API = 'https://goals-api.duolingo.com';

        // ── Leaderboard ──────────────────────────────────────────────────────
        const _LB_ID = '7d9f5dd1-8423-491a-91f2-2532052038ce';
        const _LB_IMG = 'https://d35aaqx5ub95lt.cloudfront.net/images/leagues/';
        const _LEAGUE_TIERS = [
            { name: 'Bronze',   emoji: '🥉', img: _LB_IMG + '192181672ada150becd83a74a4266ae9.svg' },
            { name: 'Silver',   emoji: '🥈', img: _LB_IMG + '8148b17e32d8706a82c02688f559e9ef.svg'  },
            { name: 'Gold',     emoji: '🏅', img: _LB_IMG + '0e249b5f869b806da7406b815f4d60c6.svg' },
            { name: 'Sapphire', emoji: '💎', img: _LB_IMG + '3ced84eb1f0274ec0f02b24ae6e3d29b.svg' },
            { name: 'Ruby',     emoji: '❤️‍🔥', img: _LB_IMG + '74d6ab6e5b6f92e7d16a4a6664d1fafd.svg' },
            { name: 'Emerald',  emoji: '💚', img: _LB_IMG + 'f480e032c5222395e73dac88ce3592bb.svg' },
            { name: 'Amethyst', emoji: '💜', img: _LB_IMG + '7f895707cd44583692d20481dcd9e0d0.svg' },
            { name: 'Pearl',    emoji: '🤍', img: _LB_IMG + 'f902954eeaa88fd2cb12f9168b4f68cb.svg'  },
            { name: 'Obsidian', emoji: '🖤', img: _LB_IMG + '57f0c6b260d33493a0cddc4ab38d6833.svg' },
            { name: 'Diamond',  emoji: '💠', img: _LB_IMG + 'afe5c7067cd5fb7de936d3928ea7add6.svg'  },
        ];

        async function _fetchLeaderboard() {
            if (!_jwt || !_sub) return null;
            try {
                const r = await _gm('GET',
                    `https://duolingo-leaderboards-prod.duolingo.com/leaderboards/${_LB_ID}/users/${_sub}?client_unlocked=true&get_reactions=true&_=${Date.now()}`,
                    null, _hdrs);
                if (r.status !== 200) return null;
                const data = JSON.parse(r.responseText);
                const raw = Array.isArray(data) ? data[0] : data;
                // Validate structure like DuoXPy — banned/unjoined accounts
                // return status 200 but with missing active/cohort/rankings.
                if (!raw?.active?.cohort?.rankings?.length) return null;
                if (!raw?.active?.contest?.ruleset) return null;
                // user_id / is_demoted / is_promoted live at TOP LEVEL of the
                // response, NOT inside `active`. Merge both so _renderLeaderboard
                // can destructure cleanly.
                const merged = Object.assign({}, raw.active);
                if (raw.user_id    != null) merged.user_id    = raw.user_id;
                if (raw.is_demoted != null) merged.is_demoted = raw.is_demoted;
                if (raw.is_promoted!= null) merged.is_promoted= raw.is_promoted;
                if (raw.score      != null) merged.score      = raw.score;
                if (raw.tier       != null) merged.tier       = raw.tier;
                // Store cohort_id for reaction PATCH
                if (merged.cohort?.cohort_id) _lbCohortId = merged.cohort.cohort_id;
                return merged;
            } catch { return null; }
        }

        const _LB_MEDAL = [
            'https://d35aaqx5ub95lt.cloudfront.net/images/leagues/9e4f18c0bc42c7508d5fa5b18346af11.svg',
            'https://d35aaqx5ub95lt.cloudfront.net/images/leagues/cc7b8f8582e9cfb88408ab851ec2e9bd.svg',
            'https://d35aaqx5ub95lt.cloudfront.net/images/leagues/eef523c872b71178ef5acb2442d453a2.svg',
        ];
        const _LB_REACT_BASE = 'https://d35aaqx5ub95lt.cloudfront.net/images/leagues/';
        const _LB_VENDOR_BASE = 'https://d35aaqx5ub95lt.cloudfront.net/vendor/';
        const _REACTION_ICONS = {
            'TROPHY':         _LB_REACT_BASE + '22df4cb957e6cf2d7198b6e5449a342e.svg',
            'TROPHY,winner':  _LB_REACT_BASE + '1795aa8b3b10d243e5d138a79bde360a.svg',
            'POPPER':         _LB_REACT_BASE + '2ceb401cae52712705b66a77df83ce40.svg',
            'SUNGLASSES':     _LB_REACT_BASE + '2439bac00452e99ba7bf6a7ed0b04196.svg',
            'ONE_HUNDRED':    _LB_REACT_BASE + '5642e1e72813a88e8973b551a2004c7f.svg',
            'CAT':            _LB_REACT_BASE + '535fc27de224cc7d311dbb5de4f33be6.svg',
            'POOP':           _LB_REACT_BASE + 'beb0df263d0f696bc7095d56b448ca78.svg',
            'POPCORN':        _LB_REACT_BASE + '573de2bc90b2499eeb2b3738cff90133.svg',
            'EYES':           _LB_REACT_BASE + 'a8e5c18e80054228b2c61168846ff643.svg',
            'ANGRY':          _LB_REACT_BASE + 'f12703218fc80de76a63e650726f742e.svg',
            'DUMPSTER_FIRE':  _LB_REACT_BASE + '9fadb349c2ece257386a0e576359c867.svg',
            'FLEX':           _LB_REACT_BASE + '6b8a8db5ac7f847e7e87efe97c8b451a.svg',
            'FLAG,chess':     _LB_VENDOR_BASE + 'c8bad7c09aaf7bc29ddddc50808adb54.svg',
            'FLAG,math':      _LB_VENDOR_BASE + '395c8a6ee9783610b578b02fda405e85.svg',
            'FLAG,music':     _LB_VENDOR_BASE + '7fee27d21187165ccb88aef0234b6101.svg',
            'NONE':           _LB_REACT_BASE + 'a35f1db4398fd29e66f1abc33a0d11a2.svg',
        };
        // Reaction icon names for picker display
        const _REACTION_NAMES = {
            'TROPHY': 'Trophy', 'TROPHY,winner': 'Diamond Trophy',
            'POPPER': 'Popper', 'SUNGLASSES': 'Sunglasses',
            'ONE_HUNDRED': '100', 'CAT': 'Cat', 'POOP': 'Poop', 'POPCORN': 'Popcorn',
            'EYES': 'Eyes', 'ANGRY': 'Angry', 'DUMPSTER_FIRE': 'Fire', 'FLEX': 'Flex',
            'FLAG,chess': 'Chess', 'FLAG,math': 'Math', 'FLAG,music': 'Music',
            'NONE': 'None',
        };

        // Get best avatar URL: prefer custom picture, fallback to lb avatar_url, then default
        function _bestAvatarUrl(picture, userId) {
            const isDefault = !picture || picture.includes('/avatar/default');
            if (!isDefault) {
                let hq = picture.replace(/\/(medium|large|small)$/, '/xlarge');
                if (!hq.endsWith('/xlarge') && hq.includes('duolingo.com/ssr-avatars')) hq += '/xlarge';
                return hq;
            }
            // Try leaderboard cache for league-assigned avatar
            const rankings = _lbCache?.cohort?.rankings || [];
            const lbUser = rankings.find(u => String(u.user_id) === String(userId));
            if (lbUser?.avatar_url && !lbUser.avatar_url.includes('/avatar/default')) {
                return lbUser.avatar_url + '/large';
            }
            return '';
        }
        function _reactionIconUrl(r) {
            if (!r || r === 'NONE') return _REACTION_ICONS['NONE'];
            return _REACTION_ICONS[r] || _REACTION_ICONS['NONE'];
        }
                const _LB_PROM_ICON = 'https://d35aaqx5ub95lt.cloudfront.net/images/leagues/577cf633b59ce72791f725d0cb973061.svg';
        const _LB_DEM_ICON  = 'https://d35aaqx5ub95lt.cloudfront.net/images/leagues/248453c5e2d9de19fba7a2f4fef7f016.svg';


        async function _renderReactionPicker() {
            const container = document.getElementById('DH_React_Container');
            if (!container) return;
            container.innerHTML = '';
            const myRank = (_lbCache?.cohort?.rankings || []).find(u => String(u.user_id) === String(_sub));
            const curReact = myRank?.reaction || 'NONE';

            const GROUPS = [
                {
                    label: 'League Emojis',
                    keys: ['TROPHY','TROPHY,winner','POPPER','SUNGLASSES','ONE_HUNDRED',
                           'CAT','POOP','POPCORN','EYES','ANGRY','DUMPSTER_FIRE','FLEX','NONE']
                },
                {
                    label: 'Course Emojis',
                    keys: ['FLAG,chess','FLAG,math','FLAG,music']
                }
            ];

            GROUPS.forEach(group => {
                const header = document.createElement('div');
                header.className = 'DH_Cat_Header DH_NoSel';
                header.textContent = group.label;
                container.appendChild(header);

                const grid = document.createElement('div');
                grid.className = 'DH_Shop_Grid';

                group.keys.forEach(key => {
                    const isActive = key === curReact;
                    const card = document.createElement('div');
                    card.className = 'DH_Shop_Card';

                    const ico = document.createElement('img');
                    ico.className = 'DH_Shop_Ico';
                    ico.src = _REACTION_ICONS[key];
                    ico.alt = key;

                    const lbl = document.createElement('div');
                    lbl.className = 'DH_Shop_Name DH_NoSel';
                    lbl.textContent = _REACTION_NAMES[key] || key;

                    const btn = document.createElement('button');
                    btn.className = 'DH_React_Btn' + (isActive ? ' active' : '');
                    btn.textContent = isActive ? 'ACTIVE' : 'EQUIP';

                    btn.addEventListener('click', async () => {
                        if (btn.classList.contains('loading') || btn.classList.contains('active')) return;
                        btn.className = 'DH_React_Btn loading';
                        btn.textContent = '...';
                        try {
                            const cohortId = _lbCohortId || _lbCache?.cohort?.cohort_id;
                            if (!cohortId) throw new Error('no cohort');
                            const url = `https://duolingo-leaderboards-prod.duolingo.com/reactions/${cohortId}/users/${_sub}`;
                            const patchHdrs = Object.assign({}, _hdrs, {'Content-Type':'application/json'});
                            const r = await _gm('PATCH', url, { reaction: key }, patchHdrs);
                            if (r.status === 200) {
                                if (_lbCache?.cohort?.rankings) {
                                    const me = _lbCache.cohort.rankings.find(u => String(u.user_id) === String(_sub));
                                    if (me) me.reaction = key;
                                }
                                btn.className = 'DH_React_Btn success';
                                btn.textContent = 'ACTIVE';
                                setTimeout(() => _goBack(), 700);
                            } else {
                                btn.className = 'DH_React_Btn';
                                btn.textContent = 'EQUIP';
                            }
                        } catch(e) {
                            btn.className = 'DH_React_Btn';
                            btn.textContent = 'EQUIP';
                        }
                    });

                    card.appendChild(ico);
                    card.appendChild(lbl);
                    card.appendChild(btn);
                    grid.appendChild(card);
                });

                container.appendChild(grid);
            });
        }

        // Preload reaction SVG icons so they're cached before picker opens
        (function _preloadReactionIcons() {
            Object.values(_REACTION_ICONS).forEach(url => {
                const img = new Image();
                img.src = url;
            });
        })();
        // Update #DH_Avatar and #DH_V1_Avatar from leaderboard data.
        // Called right after pre-fetch so the avatar appears without opening the LB tab.
        let _panelAvatarLoaded = false;
        function _updatePanelAvatar(lb) {
            if (_panelAvatarLoaded) return;
            const rankings = lb?.cohort?.rankings || [];
            const me = rankings.find(u => String(u.user_id) === String(_sub));
            if (!me || !me.avatar_url) return;
            GM_xmlhttpRequest({
                method: 'GET', url: me.avatar_url + '/large',
                responseType: 'blob', timeout: 8000,
                onload: function(r) {
                    if (r.status !== 200 || !r.response) return;
                    var blobUrl = URL.createObjectURL(r.response);
                    _panelAvatarLoaded = true;
                    ['DH_Avatar', 'DH_V1_Avatar'].forEach(function(elId) {
                        var el = document.getElementById(elId);
                        if (!el) return;
                        el.innerHTML = '';
                        var img = document.createElement('img');
                        img.src = blobUrl;
                        img.style.cssText = 'width:100%;height:100%;object-fit:cover;border-radius:50%;display:block;';
                        img.draggable = false;
                        el.appendChild(img);
                    });
                },
                onerror: function() {}, ontimeout: function() {}
            });
        }

        function _renderLeaderboard(lb) {
            const list    = document.getElementById('DH_LB_List');
            const nameEl  = document.getElementById('DH_LB_LeagueName');
            const imgEl   = document.getElementById('DH_LB_LeagueImg');
            if (!list) return;

            if (!lb) {
                list.innerHTML = `<p class="DH_T2 DH_NoSel" style="text-align:center;padding:16px 0;">${_t('lb_failed')}</p>`;
                return;
            }

            try {
            const { cohort, contest, user_id: myUserId, is_demoted: myDemoted, is_promoted: myPromoted } = lb;
            const { rankings, tier } = cohort;
            const { num_promoted, num_demoted } = contest.ruleset;

            const tierInfo = _LEAGUE_TIERS[tier] || { name: `League ${tier}`, emoji: '🏆', img: null };
            const nProm    = (num_promoted || [])[tier] || 0;
            const nDem     = (num_demoted  || [])[tier] || 0;
            const total    = rankings.length;
            // _sub is the authenticated user's Duolingo ID always prefer it
            const myId     = String(_sub ?? myUserId ?? '');

            // League header
            if (nameEl) nameEl.textContent = tierInfo.name + ' League';
            if (imgEl) {
                if (tierInfo.img) { imgEl.src = tierInfo.img; imgEl.style.display = ''; }
                else imgEl.style.display = 'none';
            }

            // Update panel avatar (no-op if already done)
            _updatePanelAvatar(lb);

            // Rank rows
            const _avatarQueue = [];
            list.innerHTML = '';
            rankings.forEach((u, i) => {
                const rank  = i + 1;
                const isMe  = String(u.user_id) === myId;
                // Only show promoted zone; demoted zone removed (too buggy)
                const inP   = isMe ? !!myPromoted : (rank <= nProm);
                const inD   = false;

                const row = document.createElement('div');
                row.className = 'DH_LB_Row DH_NoSel' + (isMe ? ' me' : '');

                // ── Rank wrap (medal img or number) ──
                const rw = document.createElement('div');
                rw.className = 'DH_LB_RankWrap';
                if (rank <= 3 && _LB_MEDAL[rank - 1]) {
                    const mi = document.createElement('img');
                    mi.className = 'DH_LB_RankMedal';
                    mi.src = _LB_MEDAL[rank - 1];
                    mi.alt = String(rank);
                    rw.appendChild(mi);
                } else {
                    const rn = document.createElement('span');
                    rn.className = 'DH_LB_RankNum';
                    rn.textContent = String(rank);
                    rw.appendChild(rn);
                }
                row.appendChild(rw);

                // ── Avatar wrap ──
                const aw = document.createElement('div');
                aw.className = 'DH_LB_AvatarWrap' + (inP ? ' prom' : inD ? ' dem' : '');
                // Reaction icon overlay (top-right of avatar)
                // Show for isMe always (clickable to change), for others only if they have a reaction
                const hasReact = u.reaction && u.reaction !== 'NONE' && !!_REACTION_ICONS[u.reaction];
                if (isMe || hasReact) {
                    const ri = document.createElement('img');
                    ri.className = 'DH_LB_Reaction' + (isMe ? ' me' : '');
                    ri.src = _reactionIconUrl(hasReact ? u.reaction : 'NONE');
                    ri.alt = '';
                    if (isMe) ri.addEventListener('click', () => _goPage(12));
                    aw.appendChild(ri);
                }
                const avUrl = u.avatar_url || '';
                // Show initial letter as placeholder while avatar loads via GM blob
                const _makeInitialFb = (name) => {
                    const fb = document.createElement('div');
                    fb.className = 'DH_LB_AvatarFallback';
                    fb.textContent = (name || '?')[0].toUpperCase();
                    return fb;
                };
                aw.appendChild(_makeInitialFb(u.display_name));
                // Queue for batch load (12 at a time)
                if (avUrl) _avatarQueue.push({ url: avUrl + '/large', awEl: aw, inP, inD,
                    reaction: (isMe || hasReact) ? (hasReact ? u.reaction : 'NONE') : null,
                    isMe: isMe });
                // Zone icon overlay (↑↓)
                if (inP || inD) _appendZoneIcon(aw, inP);
                row.appendChild(aw);

                // ── Name (wrapped in flex:1 container so score stays right) ──
                const ni = document.createElement('div');
                ni.className = 'DH_LB_Info';
                const nm = document.createElement('p');
                nm.className = 'DH_LB_Name';
                nm.textContent = u.display_name;
                ni.appendChild(nm);
                row.appendChild(ni);

                // ── Score ──
                const sw = document.createElement('div');
                sw.className = 'DH_LB_ScoreWrap';

                const sc = document.createElement('p');
                sc.className = 'DH_LB_Score';
                sc.textContent = u.score.toLocaleString();
                sw.appendChild(sc);

                const su = document.createElement('p');
                su.className = 'DH_LB_ScoreUnit';
                su.textContent = 'XP';
                sw.appendChild(su);

                row.appendChild(sw);
                list.appendChild(row);

                // ── Zone separators ──
                if (rank === nProm && rank < total) {
                    list.appendChild(_makeSep('prom'));
                }
                // Rank Down separator removed
            });

            // Batch-load avatars 12 at a time
            (function _batchAvatars(queue, offset) {
                var CHUNK = 12;
                if (offset >= queue.length) return;
                var slice = queue.slice(offset, offset + CHUNK);
                var done = 0;
                function onDone() { if (++done === slice.length) _batchAvatars(queue, offset + CHUNK); }
                slice.forEach(function(item) {
                    GM_xmlhttpRequest({
                        method: 'GET', url: item.url, responseType: 'blob', timeout: 8000,
                        onload: function(r) {
                            if (r.status === 200 && r.response) {
                                var bUrl = URL.createObjectURL(r.response);
                                item.awEl.innerHTML = '';
                                var ai = document.createElement('img');
                                ai.className = 'DH_LB_AvatarImg'; ai.src = bUrl; ai.alt = '';
                                item.awEl.appendChild(ai);
                                if (item.inP || item.inD) _appendZoneIcon(item.awEl, item.inP);
                                // Re-add reaction icon (was cleared by innerHTML='')
                                if (item.reaction !== null && item.reaction !== undefined) {
                                    var ri2 = document.createElement('img');
                                    ri2.className = 'DH_LB_Reaction' + (item.isMe ? ' me' : '');
                                    ri2.src = _reactionIconUrl(item.reaction);
                                    ri2.alt = '';
                                    if (item.isMe) ri2.addEventListener('click', function() { _goPage(12); });
                                    item.awEl.appendChild(ri2);
                                }
                            }
                            onDone();
                        },
                        onerror: onDone, ontimeout: onDone
                    });
                });
            })(_avatarQueue, 0);
            } catch(e) {
                // Any crash (e.g. missing cohort/contest) → show failed instead of stuck spinner
                if (list) list.innerHTML = `<p class="DH_T2 DH_NoSel" style="text-align:center;padding:16px 0;">${_t('lb_failed')}</p>`;
            }
        }

        function _appendZoneIcon(wrap, isPromo) {
            const zi = document.createElement('img');
            zi.className = 'DH_LB_ZoneIcon';
            zi.src = isPromo ? _LB_PROM_ICON : _LB_DEM_ICON;
            zi.alt = '';
            wrap.appendChild(zi);
        }

        function _makeSep(type) {
            const sep = document.createElement('div');
            sep.className = 'DH_LB_Sep';
            const l1 = document.createElement('div');
            l1.className = 'DH_LB_SepLine ' + type;
            const ic = document.createElement('img');
            ic.className = 'DH_LB_SepIcon';
            ic.src = type === 'prom' ? _LB_PROM_ICON : _LB_DEM_ICON;
            ic.alt = '';
            const tx = document.createElement('span');
            tx.className = 'DH_LB_SepTxt ' + type;
            tx.textContent = type === 'prom' ? _t('lb_rank_up') : _t('lb_rank_down');
            const l2 = document.createElement('div');
            l2.className = 'DH_LB_SepLine ' + type;
            sep.appendChild(l1);
            sep.appendChild(ic);
            sep.appendChild(tx);
            sep.appendChild(l2);
            return sep;
        }

        let _pageHistory = [1];

        let _questState = null;


        let _solverUI = null;
        let _isAutoMode = false;
        let _solvingIntervalId = null;
        let _isInLesson = false;
        const _SOLVE_SPEED = 0.1;
        let _INJECT_SOLVER_ENABLED = localStorage.getItem('duohacker_inject_solver') === 'true';

        const _autoSolver = {
            findReact: (dom, traverseUp = 1) => {
                const key = Object.keys(dom).find(key => {
                    return key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$");
                });
                const domFiber = dom[key];
                if (!domFiber) return null;
                const GetCompFiber = fiber => {
                    let parentFiber = fiber.return;
                    while (typeof parentFiber.type == "string") {
                        parentFiber = parentFiber.return;
                    }
                    return parentFiber;
                };
                let compFiber = GetCompFiber(domFiber);
                for (let i = 0; i < traverseUp; i++) {
                    compFiber = GetCompFiber(compFiber);
                }
                return compFiber.stateNode;
            },
            // Lấy hàm continueStory() qua React fiber — bền hơn việc dò selector DOM
            // (cách Autolingo content_scripts dùng để tự lướt qua các màn story không có challenge)
            findStoryContinue: () => {
                try {
                    const node = document.getElementsByClassName('_2neC7')[0];
                    if (!node) return null;
                    const key = Object.keys(node).find(k => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'));
                    if (!key) return null;
                    const fn = node[key]?.return?.memoizedProps?.continueStory;
                    return typeof fn === 'function' ? fn : null;
                } catch {
                    return null;
                }
            },
            findStoryChallenge: (dom) => {
                // Walk fiber tree upward looking for story challenge props (type in uppercase like MULTIPLE_CHOICE)
                const key = Object.keys(dom || {}).find(k => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'));
                if (!key) return null;
                let fiber = dom[key];
                let depth = 0;
                while (fiber && depth < 60) {
                    const props = fiber.memoizedProps || fiber.pendingProps;
                    if (props) {
                        // Story challenge props have type in UPPER_CASE
                        if (props.currentElement?.type && props.currentElement.type === props.currentElement.type.toUpperCase()) return props.currentElement;
                        if (props.element?.type && props.element.type === props.element.type.toUpperCase()) return props.element;
                        if (props.challenge?.type && props.challenge.type === props.challenge.type.toUpperCase()) return props.challenge;
                        if (props.currentChallenge?.type) return props.currentChallenge;
                    }
                    fiber = fiber.return;
                    depth++;
                }
                return null;
            },
            determineChallengeType: () => {
                try {
                    const t = window.sol?.type;
                    if (!t) return false;

                    if (t === 'speak' || t === 'listenSpeak' ||
                        document.querySelector('[data-test="challenge challenge-listenSpeak"]') ||
                        document.querySelectorAll('[data-test*="challenge-speak"]').length > 0) return 'Challenge Speak';

                    if (t === 'listenMatch') return 'Listen Match';

                    const isStory = location.hostname.includes('stories.duolingo.com') ||
                        location.pathname.includes('/stories') ||
                        !!document.querySelector('.FmlUF') ||
                        !!document.querySelector('[data-test="stories-choice"]') ||
                        !!document.querySelector('[data-test="story-start"]') ||
                        !!document.querySelector('[data-test="stories-player-continue"]') ||
                        !!document.querySelector('[data-test="stories-player-done"]');

                    const tl = t?.toLowerCase();
                    if (isStory) {
                        if (tl === 'arrange') return 'Story Arrange';
                        if (tl === 'multiple-choice' || tl === 'multiple_choice' || tl === 'select-phrases') return 'Story Multiple Choice';
                        if (tl === 'point-to-phrase' || tl === 'point_to_phrase') return 'Story Point to Phrase';
                        if (tl === 'match') return 'Story Pairs';
                        if (tl === 'gap-fill' || tl === 'gap_fill') return 'Story Gap Fill';
                    }
                    // Fallback: story-exclusive types
                    if (tl === 'arrange') return 'Story Arrange';
                    if (tl === 'multiple-choice' || tl === 'multiple_choice') return 'Story Multiple Choice';
                    if (tl === 'point-to-phrase' || tl === 'point_to_phrase') return 'Story Point to Phrase';
                    if (tl === 'gap-fill' || tl === 'gap_fill') return 'Story Gap Fill';

                    if (t === 'typeCloze') return 'Type Cloze';
                    if (t === 'typeClozeTable') return 'Type Cloze Table';
                    if (t === 'tapClozeTable') return 'Tap Cloze Table';
                    if (t === 'typeCompleteTable') return 'Type Complete Table';
                    if (t === 'tapCompleteTable') return 'Tap Complete Table';
                    if (t === 'patternTapComplete') return 'Pattern Tap Complete';
                    if (t === 'syllableTap') return 'Syllable Tap';
                    if (t === 'syllableListenTap') return 'Syllable Listen Tap';

                    if (t === 'listenTap') return 'Listen Tap';

                    if (t === 'listen') return 'Listen Type';

                    if (t === 'translate') return 'Translate';

                    if (t === 'completeReverseTranslation') return 'Complete Reverse';

                    if (document.querySelectorAll('[data-test*="challenge-partialReverseTranslate"]').length > 0) return 'Partial Reverse';

                    if (document.querySelectorAll('[data-test="challenge challenge-characterWrite"]').length > 0) {
                        if (document.querySelector('g._25Ktp')) return 'Character Write Drag';
                        if (document.querySelectorAll('path._1e5Zt').length > 0) return 'Character Write Draw';
                        return 'Character Write Freehand';
                    }

                    if (t === 'judge') return 'Judge';

                    if (t === 'dialogue' || t === 'characterIntro' || t === 'selectTranscription') return 'Dialogue';

                    if (t === 'characterMatch' || t === 'match') {
                        if (document.querySelectorAll('[data-test$="challenge-tap-token"]').length > 0) return 'Pairs';
                    }

                    if (t === 'select' || t === 'characterSelect' || t === 'form' ||
                        t === 'readComprehension' || t === 'listenComprehension' ||
                        t === 'selectPronunciation') {
                        return 'Select Card';
                    }

                    if (document.querySelectorAll('[data-test*="challenge-name"]').length > 0 &&
                        document.querySelectorAll('[data-test="challenge-choice"]').length > 0) return 'Challenge Name';

                    if (document.querySelectorAll('[data-test="challenge-choice"]').length > 0) {
                        if (document.querySelectorAll('[data-test="challenge-text-input"]').length > 0) return 'Challenge Choice with Text Input';
                        return 'Challenge Choice';
                    }

                    if (t === 'orderTapComplete') return 'Order Tap Complete';

                    if (document.querySelectorAll('[data-test$="challenge-tap-token"]').length > 0) {
                        if (window.sol?.pairs !== undefined) return 'Pairs';
                        if (window.sol?.correctTokens !== undefined) return 'Tokens Run';
                        if (window.sol?.correctIndices !== undefined) return 'Indices Run';
                    }
                    if (document.querySelectorAll('[data-test="challenge-tap-token-text"]').length > 0) return 'Fill in the Gap';

                    if (document.querySelectorAll('[data-test="challenge-text-input"]').length > 0) return 'Challenge Text Input';
                    if (document.querySelectorAll('textarea[data-test="challenge-translate-input"]').length > 0) return 'Challenge Translate Input';

                    if (document.querySelectorAll('[data-test="daily-quest-progress-slide"]').length > 0) return 'Daily Quest Progress';
                    if (document.querySelectorAll('[data-test="streak-slide"]').length > 0) return 'Streak Slide';
                    if (document.querySelectorAll('[data-test="leaderboard-slide"]').length > 0) return 'Leaderboard Slide';

                    return false;
                } catch (error) {
                    return false;
                }
            },
            setInputValue: (element, value) => {
                const isTextarea = element.tagName === 'TEXTAREA';
                const prototype = isTextarea ? window.HTMLTextAreaElement : window.HTMLInputElement;
                const setter = Object.getOwnPropertyDescriptor(prototype.prototype, "value").set;
                setter.call(element, value);
                element.dispatchEvent(new Event('input', {
                    bubbles: true
                }));
            },
            // Word-by-word "humanized" typing for text-input challenges.
            // Falls back to instant fill when Safe Mode is off (no behavior change).
            typeHumanized: async (element, value) => {
                if (!_SAFE_MODE || !value) {
                    _autoSolver.setInputValue(element, value);
                    return;
                }
                const isTextarea = element.tagName === 'TEXTAREA';
                const prototype = isTextarea ? window.HTMLTextAreaElement : window.HTMLInputElement;
                const setter = Object.getOwnPropertyDescriptor(prototype.prototype, "value").set;
                const words = String(value).split(' ');
                let typed = '';
                for (let i = 0; i < words.length; i++) {
                    typed += (i > 0 ? ' ' : '') + words[i];
                    setter.call(element, typed);
                    element.dispatchEvent(new Event('input', {
                        bubbles: true
                    }));
                    await _autoSolver.delay(110 + Math.random() * 180);
                }
            },
            // Same idea but for contenteditable spans (Partial Reverse Translate).
            typeHumanizedCE: async (element, value) => {
                const setter = Object.getOwnPropertyDescriptor(Node.prototype, "textContent").set;
                if (!_SAFE_MODE || !value) {
                    setter.call(element, value);
                    element.dispatchEvent(new Event('input', { bubbles: true }));
                    return;
                }
                const words = String(value).split(' ');
                let typed = '';
                for (let i = 0; i < words.length; i++) {
                    typed += (i > 0 ? ' ' : '') + words[i];
                    setter.call(element, typed);
                    element.dispatchEvent(new Event('input', { bubbles: true }));
                    await _autoSolver.delay(110 + Math.random() * 180);
                }
            },
            delay: ms => new Promise(resolve => setTimeout(resolve, ms)),
            handleChallengeName: async () => {
                const articles = window.sol.articles;
                const correctSolution = window.sol.correctSolutions[0];
                const matchingArticle = articles.find(article => correctSolution.startsWith(article));
                if (matchingArticle !== undefined) {
                    const matchingIndex = articles.indexOf(matchingArticle);
                    const remainingValue = correctSolution.substring(matchingArticle.length).trim();
                    const selectedElement = document.querySelector(`[data-test="challenge-choice"]:nth-child(${matchingIndex + 1})`);
                    if (selectedElement) {
                        selectedElement.click();
                        await _autoSolver.delay(_safeWait(50));
                    }
                    const input = document.querySelector('[data-test="challenge-text-input"]');
                    if (input) await _autoSolver.typeHumanized(input, remainingValue);
                }
            },
            handlePairs: async () => {
                const buttons = document.querySelectorAll('[data-test*="challenge-tap-token"]:not(span)');
                const texts = document.querySelectorAll('[data-test="challenge-tap-token-text"]');
                if (texts.length !== buttons.length || buttons.length === 0) return;
                for (const pair of window.sol.pairs || []) {
                    for (let i = 0; i < buttons.length; i++) {
                        const button = buttons[i];
                        if (button.disabled) continue;
                        const text = texts[i].innerText.toLowerCase().trim();
                        const matches = text === pair.transliteration?.toLowerCase().trim() ||
                            text === pair.character?.toLowerCase().trim() ||
                            text === pair.learningToken?.toLowerCase().trim() ||
                            text === pair.fromToken?.toLowerCase().trim();
                        if (matches) {
                            button.click();
                            await _autoSolver.delay(_safeWait(50));
                        }
                    }
                }
            },
            handleTokensRun: async () => {
                const allTokens = document.querySelectorAll('[data-test$="challenge-tap-token"]');
                const clickedTokens = [];
                const tokensToClick = [];
                for (const correctToken of window.sol.correctTokens) {
                    const matchingElements = Array.from(allTokens).filter(el => el.textContent.trim() === correctToken.trim());
                    if (matchingElements.length > 0) {
                        const matchIndex = clickedTokens.filter(token => token.textContent.trim() === correctToken.trim()).length;
                        const elementToClick = matchingElements[matchIndex] || matchingElements[0];
                        if (!elementToClick.disabled) {
                            tokensToClick.push(elementToClick);
                            clickedTokens.push(elementToClick);
                        }
                    }
                }
                if (!_SAFE_MODE) {
                    tokensToClick.forEach(token => token.click());
                } else {
                    for (const token of tokensToClick) {
                        token.click();
                        await _autoSolver.delay(_safeWait(50));
                    }
                }
            },
            handleIndicesRun: async () => {
                if (!window.sol.correctIndices) return;
                const wordBank = document.querySelector('div[data-test="word-bank"]') || document.querySelector('.eSgkc');
                if (!wordBank) return;
                const bankButtons = Array.from(wordBank.querySelectorAll('button[data-test*="challenge-tap-token"]:not(span)'));
                for (const index of window.sol.correctIndices) {
                    if (index >= 0 && index < bankButtons.length) {
                        const button = bankButtons[index];
                        if (!button.disabled && button.getAttribute('aria-disabled') !== 'true') {
                            button.click();
                            await _autoSolver.delay(_safeWait(50));
                        }
                    }
                }
            },
            handleTapCompleteTable: async () => {
                const solutionRows = window.sol.displayTableTokens.slice(1);
                const tableRowElements = document.querySelectorAll('tbody tr');
                const wordBank = document.querySelector('div[data-test="word-bank"]');
                const wordBankButtons = wordBank ? wordBank.querySelectorAll('button[data-test*="-challenge-tap-token"]') : [];
                const usedWordBankIndexes = new Set();
                for (let rowIndex = 0; rowIndex < solutionRows.length; rowIndex++) {
                    const solutionRow = solutionRows[rowIndex];
                    const answerCellData = solutionRow[1];
                    const correctToken = answerCellData.find(token => token.isBlank);
                    if (correctToken) {
                        const correctAnswerText = correctToken.text;
                        const currentRowElement = tableRowElements[rowIndex];
                        let clicked = false;
                        const buttons = currentRowElement.querySelectorAll('button[data-test*="-challenge-tap-token"]');
                        for (const button of buttons) {
                            const buttonTextElm = button.querySelector('[data-test="challenge-tap-token-text"]');
                            if (buttonTextElm && buttonTextElm.innerText.trim() === correctAnswerText && !button.disabled) {
                                button.click();
                                await _autoSolver.delay(_safeWait(50));
                                clicked = true;
                                break;
                            }
                        }
                        if (!clicked && wordBankButtons.length > 0) {
                            for (let i = 0; i < wordBankButtons.length; i++) {
                                if (usedWordBankIndexes.has(i)) continue;
                                const button = wordBankButtons[i];
                                const buttonTextElm = button.querySelector('[data-test="challenge-tap-token-text"]');
                                if (buttonTextElm && buttonTextElm.innerText.trim() === correctAnswerText && !button.disabled) {
                                    button.click();
                                    await _autoSolver.delay(_safeWait(50));
                                    usedWordBankIndexes.add(i);
                                    break;
                                }
                            }
                        }
                    }
                }
            },
            handleChallenge: async (type) => {
                try {
                    switch (type) {
                        case 'Order Tap Complete': {
                            const blanks = window.sol.displayTokens?.filter(t => t.isBlank);
                            if (!blanks || blanks.length === 0) break;
                            const choicesData = window.sol.choices || [];
                            const usedChoiceIdx = new Set();
                            for (const blank of blanks) {
                                // Find the choices index for this blank (skip already-used ones)
                                let choiceIdx = -1;
                                for (let i = 0; i < choicesData.length; i++) {
                                    if (usedChoiceIdx.has(i)) continue;
                                    const choiceText = typeof choicesData[i] === 'object' ? choicesData[i].text : choicesData[i];
                                    if (choiceText?.trim() === blank.text?.trim()) {
                                        choiceIdx = i;
                                        break;
                                    }
                                }
                                if (choiceIdx === -1) continue;
                                usedChoiceIdx.add(choiceIdx);
                                // Click the DOM button at that index
                                const buttons = document.querySelectorAll('[data-test="word-bank"] [data-test*="challenge-tap-token"]:not(span)');
                                if (buttons[choiceIdx]) {
                                    buttons[choiceIdx].click();
                                    await _autoSolver.delay(_safeWait(50));
                                }
                            }
                            break;
                        }
                        case 'Challenge Speak':
                        case 'Listen Match':
                        case 'Listen Speak':
                            document.querySelector('button[data-test="player-skip"]')?.click();
                            break;

                        case 'Select Card': {
                            const idx = window.sol.correctIndex ?? 0;

                            const cards = document.querySelectorAll('[data-test="challenge-choice-card"]');
                            if (cards.length > 0) {
                                cards[idx]?.click();
                            } else {
                                document.querySelectorAll('[data-test="challenge-choice"]')[idx]?.click();
                            }
                            break;
                        }

                        case 'Judge': {
                            const ci = window.sol.correctIndices?.[0] ?? 0;
                            document.querySelectorAll('[data-test="challenge-judge-text"]')[ci]?.click();
                            break;
                        }

                        case 'Dialogue': {
                            const idx = window.sol.correctIndex ?? 0;

                            const judgeItems = document.querySelectorAll('[data-test="challenge-judge-text"]');
                            if (judgeItems.length > 0) {
                                judgeItems[idx]?.click();
                            } else {
                                document.querySelectorAll('[data-test="challenge-choice"]')[idx]?.click();
                            }
                            break;
                        }

                        case 'Translate': {
                            const {
                                correctTokens,
                                correctSolutions
                            } = window.sol;
                            if (correctTokens && correctTokens.length > 0) {
                                const tokens = document.querySelectorAll('[data-test$="challenge-tap-token"]');
                                const usedIndexes = [];
                                for (const word of correctTokens) {
                                    for (let i = 0; i < tokens.length; i++) {
                                        if (usedIndexes.includes(i)) continue;
                                        if (tokens[i].innerText.trim() === word.trim() && !tokens[i].disabled) {
                                            tokens[i].click();
                                            usedIndexes.push(i);
                                            await _autoSolver.delay(_safeWait(40));
                                            break;
                                        }
                                    }
                                }
                            } else if (correctSolutions) {
                                const ta = document.querySelector('textarea[data-test="challenge-translate-input"]');
                                if (ta) await _autoSolver.typeHumanized(ta, correctSolutions[0]);
                            }
                            break;
                        }

                        case 'Listen Tap': {
                            const tokens = document.querySelectorAll('[data-test$="challenge-tap-token"]');
                            const usedIdx = [];
                            for (const word of (window.sol.correctTokens || [])) {
                                for (let i = 0; i < tokens.length; i++) {
                                    if (usedIdx.includes(i)) continue;
                                    if (tokens[i].innerText.trim() === word.trim() && !tokens[i].disabled) {
                                        tokens[i].click();
                                        usedIdx.push(i);
                                        await _autoSolver.delay(_safeWait(40));
                                        break;
                                    }
                                }
                            }
                            break;
                        }

                        case 'Listen Type': {
                            const answer = window.sol.prompt || window.sol.correctSolutions?.[0] || '';
                            const ta = document.querySelector('textarea[data-test="challenge-translate-input"]') ||
                                document.querySelector('[data-test="challenge-text-input"]');
                            if (ta) await _autoSolver.typeHumanized(ta, answer);
                            break;
                        }

                        case 'Complete Reverse': {
                            const blankTokens = window.sol.displayTokens?.filter(t => t.isBlank);
                            const inputFields = document.querySelectorAll('[data-test="challenge-text-input"]');
                            if (blankTokens && blankTokens.length > 1 && inputFields.length > 1) {
                                // Multi-blank support
                                for (let index = 0; index < inputFields.length; index++) {
                                    if (blankTokens[index]) {
                                        await _autoSolver.typeHumanized(inputFields[index], blankTokens[index].text);
                                    }
                                }
                            } else {
                                // Single blank (fallback)
                                const answer = blankTokens?.[0]?.text || window.sol.correctSolutions?.[0] || '';
                                const input = document.querySelector('[data-test="challenge-text-input"]');
                                if (input) await _autoSolver.typeHumanized(input, answer);
                            }
                            break;
                        }

                        case 'Challenge Choice':
                            document.querySelectorAll("[data-test='challenge-choice']")[window.sol.correctIndex]?.click();
                            break;
                        case 'Challenge Choice with Text Input': {
                            const choiceInput = document.querySelector('[data-test="challenge-text-input"]');
                            if (choiceInput) {
                                const answer = window.sol.correctSolutions ? window.sol.correctSolutions[0].split(/(?<=^\S+)\s/)[1] : (window.sol.displayTokens ? window.sol.displayTokens.find(t => t.isBlank)?.text : window.sol.prompt);
                                await _autoSolver.typeHumanized(choiceInput, answer);
                            }
                            break;
                        }
                        case 'Challenge Text Input': {
                            const input = document.querySelector('[data-test="challenge-text-input"]');
                            if (input) {
                                const answer = window.sol.correctSolutions?.[0] || (window.sol.displayTokens ? window.sol.displayTokens.find(t => t.isBlank)?.text : window.sol.prompt);
                                await _autoSolver.typeHumanized(input, answer);
                            }
                            break;
                        }
                        case 'Challenge Translate Input': {
                            const textarea = document.querySelector('textarea[data-test="challenge-translate-input"]');
                            if (textarea) await _autoSolver.typeHumanized(textarea, window.sol.correctSolutions?.[0] || window.sol.prompt);
                            break;
                        }
                        case 'Partial Reverse': {
                            const partialElm = document.querySelector('[data-test*="challenge-partialReverseTranslate"]')?.querySelector("span[contenteditable]");
                            if (partialElm) {
                                const text = window.sol?.displayTokens?.filter(t => t.isBlank)?.map(t => t.text)?.join('')?.trim();
                                await _autoSolver.typeHumanizedCE(partialElm, text);
                            }
                            break;
                        }
                        case 'Type Cloze': {
                            const clozeInput = document.querySelector('input[type="text"].b4jqk');
                            if (clozeInput) {
                                const targetToken = window.sol.displayTokens.find(t => t.damageStart !== undefined);
                                if (targetToken) {
                                    const correctEnding = targetToken.text.slice(targetToken.damageStart);
                                    await _autoSolver.typeHumanized(clozeInput, correctEnding);
                                }
                            }
                            break;
                        }
                        case 'Type Cloze Table': {
                            const tableRows = document.querySelectorAll('tbody tr');
                            const tcRows = window.sol.displayTableTokens.slice(1);
                            for (let i = 0; i < tcRows.length; i++) {
                                const answerCell = tcRows[i][1]?.find(t => typeof t.damageStart === "number");
                                if (answerCell && tableRows[i]) {
                                    const input = tableRows[i].querySelector('input[type="text"].b4jqk');
                                    if (input) {
                                        const correctEnding = answerCell.text.slice(answerCell.damageStart);
                                        await _autoSolver.typeHumanized(input, correctEnding);
                                    }
                                }
                            }
                            break;
                        }
                        case 'Tap Cloze Table': {
                            const tapTableRows = document.querySelectorAll('tbody tr');
                            window.sol.displayTableTokens.slice(1).forEach((rowTokens, i) => {
                                const answerCell = rowTokens[1]?.find(t => typeof t.damageStart === "number");
                                if (!answerCell || !tapTableRows[i]) return;
                                const wordBank = document.querySelector('[data-test="word-bank"]');
                                const wordButtons = wordBank ? Array.from(wordBank.querySelectorAll('button[data-test*="challenge-tap-token"]:not([aria-disabled="true"])')) : [];
                                const correctWord = answerCell.text;
                                const correctEnding = correctWord.slice(answerCell.damageStart);
                                let endingMatched = "";
                                for (let btn of wordButtons) {
                                    if (!correctEnding.startsWith(endingMatched + btn.innerText)) continue;
                                    btn.click();
                                    endingMatched += btn.innerText;
                                    if (endingMatched === correctEnding) break;
                                }
                            });
                            break;
                        }
                        case 'Type Complete Table': {
                            const completeTableRows = document.querySelectorAll('tbody tr');
                            const tctRows = window.sol.displayTableTokens.slice(1);
                            for (let i = 0; i < tctRows.length; i++) {
                                const answerCell = tctRows[i][1]?.find(t => t.isBlank);
                                if (!answerCell || !completeTableRows[i]) continue;
                                const input = completeTableRows[i].querySelector('input[type="text"].b4jqk');
                                if (input) await _autoSolver.typeHumanized(input, answerCell.text);
                            }
                            break;
                        }
                        case 'Pattern Tap Complete': {
                            const patternWordBank = document.querySelector('[data-test="word-bank"]');
                            if (!patternWordBank) return;
                            const correctIndex = window.sol.correctIndex ?? 0;
                            const correctText = window.sol.choices[correctIndex];
                            const patternButtons = Array.from(patternWordBank.querySelectorAll('button[data-test*="challenge-tap-token"]:not([aria-disabled="true"])'));
                            const targetButton = patternButtons.find(btn => btn.innerText.trim() === correctText);
                            if (targetButton) targetButton.click();
                            break;
                        }
                        case 'Story Arrange': {
                            const arrangeChoices = document.querySelectorAll('[data-test*="challenge-tap-token"]:not(span)');
                            for (let i = 0; i < window.sol.phraseOrder.length; i++) {
                                arrangeChoices[window.sol.phraseOrder[i]].click();
                                await _autoSolver.delay(_safeWait(50));
                            }
                            break;
                        }
                        case 'Story Multiple Choice': {
                            const storyChoices = document.querySelectorAll('[data-test="stories-choice"]');
                            storyChoices[window.sol.correctAnswerIndex]?.click();
                            break;
                        }
                        case 'Story Point to Phrase': {
                            const phraseChoices = document.querySelectorAll('[data-test="challenge-tap-token-text"]');
                            const parts = window.sol.transcriptParts || window.sol.parts || [];
                            let phraseCorrectIndex = -1;
                            for (let i = 0; i < parts.length; i++) {
                                if (parts[i].selectable === true) {
                                    phraseCorrectIndex += 1;
                                    if (window.sol.correctAnswerIndex === i) {
                                        phraseChoices[phraseCorrectIndex]?.parentElement.click();
                                        break;
                                    }
                                }
                            }
                            break;
                        }
                        case 'Story Gap Fill': {
                            // correctIndices = [idx1, idx2, ...] — index into choices[], click in order
                            const correctIndices = window.sol.correctIndices || [];
                            const choicesList = window.sol.choices || [];
                            for (const idx of correctIndices) {
                                const targetText = choicesList[idx];
                                if (targetText == null) continue;
                                // Re-query each time to skip already-used (disabled) buttons
                                const gapBtns = document.querySelectorAll('[data-test="stories-choice"], [data-test*="challenge-tap-token"]:not(span):not([disabled]):not([aria-disabled="true"])');
                                for (const btn of gapBtns) {
                                    if (btn.innerText.trim() === targetText.trim() && !btn.disabled && btn.getAttribute('aria-disabled') !== 'true') {
                                        btn.click();
                                        await _autoSolver.delay(_safeWait(50));
                                        break;
                                    }
                                }
                            }
                            break;
                        }
                        case 'Story Pairs': {
                            const storyButtons = document.querySelectorAll('[data-test*="challenge-tap-token"]:not(span)');
                            const storyTexts = document.querySelectorAll('[data-test="challenge-tap-token-text"]');
                            const textToElementMap = new Map();
                            for (let i = 0; i < storyButtons.length; i++) {
                                const text = storyTexts[i].innerText.toLowerCase().trim();
                                textToElementMap.set(text, storyButtons[i]);
                            }
                            for (const key in window.sol.dictionary) {
                                if (window.sol.dictionary.hasOwnProperty(key)) {
                                    const value = window.sol.dictionary[key];
                                    const keyPart = key.split(":")[1].toLowerCase().trim();
                                    const normalizedValue = value.toLowerCase().trim();
                                    const element1 = textToElementMap.get(keyPart);
                                    const element2 = textToElementMap.get(normalizedValue);
                                    if (element1 && !element1.disabled) {
                                        element1.click();
                                        await _autoSolver.delay(_safeWait(50));
                                    }
                                    if (element2 && !element2.disabled) {
                                        element2.click();
                                        await _autoSolver.delay(_safeWait(50));
                                    }
                                }
                            }
                            break;
                        }
                        case 'Challenge Name':
                            await _autoSolver.handleChallengeName();
                            break;
                        case 'Pairs':
                            await _autoSolver.handlePairs();
                            break;
                        case 'Tokens Run':
                            await _autoSolver.handleTokensRun();
                            break;
                        case 'Indices Run':
                        case 'Fill in the Gap':
                            await _autoSolver.handleIndicesRun();
                            break;
                        case 'Tap Complete Table':
                            await _autoSolver.handleTapCompleteTable();
                            break;

                        case 'Syllable Tap':
                        case 'Syllable Listen Tap': {
                            const correctIndices = window.sol.correctIndices;
                            const choicesData = window.sol.choices;
                            const domButtons = Array.from(document.querySelectorAll('[data-test="word-bank"] [data-test$="challenge-tap-token"]'));
                            if (!_SAFE_MODE) {
                                correctIndices.forEach(index => {
                                    const correctText = choicesData[index].text;
                                    const matchingButton = domButtons.find(btn => btn.innerText.trim() === correctText);
                                    if (matchingButton) matchingButton.click();
                                });
                            } else {
                                for (const index of correctIndices) {
                                    const correctText = choicesData[index].text;
                                    const matchingButton = domButtons.find(btn => btn.innerText.trim() === correctText);
                                    if (matchingButton) {
                                        matchingButton.click();
                                        await _autoSolver.delay(_safeWait(50));
                                    }
                                }
                            }
                            break;
                        }

                        case 'Character Write Drag': {
                            const sleep = ms => new Promise(r => setTimeout(r, ms));
                            const createEvent = (type, x, y, buttons) => new MouseEvent(type, {
                                bubbles: true,
                                clientX: x,
                                clientY: y,
                                buttons,
                                button: 0
                            });
                            const normalize = str => str ? str.replace(/\s/g, '') : '';
                            const strokes = window.sol.strokes;
                            for (let i = 0; i < strokes.length; i++) {
                                const targetPathData = normalize(strokes[i].path);
                                let path, handle;
                                while (!path || !handle) {
                                    const candidates = document.querySelectorAll('path._1e5Zt');
                                    path = Array.from(candidates).find(p => normalize(p.getAttribute('d')) === targetPathData);
                                    handle = document.querySelector('g._25Ktp');
                                    if (!path || !handle) await sleep(10);
                                }
                                const matrix = path.getScreenCTM();
                                const len = path.getTotalLength();
                                const start = path.getPointAtLength(0).matrixTransform(matrix);
                                const end = path.getPointAtLength(len).matrixTransform(matrix);
                                handle.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
                                const steps = 10;
                                for (let s = 1; s <= steps; s++) {
                                    const p = path.getPointAtLength((s / steps) * len).matrixTransform(matrix);
                                    const move = createEvent('mousemove', p.x, p.y, 1);
                                    handle.dispatchEvent(move);
                                    document.dispatchEvent(move);
                                }
                                const finalMove = createEvent('mousemove', end.x, end.y, 1);
                                handle.dispatchEvent(finalMove);
                                document.dispatchEvent(finalMove);
                                await sleep(5);
                                handle.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
                                document.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
                            }
                            break;
                        }

                        case 'Character Write Draw': {
                            const sleep = ms => new Promise(r => setTimeout(r, ms));
                            const createEvent = (type, x, y, buttons) => new MouseEvent(type, {
                                bubbles: true,
                                clientX: x,
                                clientY: y,
                                buttons,
                                button: 0
                            });
                            const normalize = str => str ? str.replace(/\s/g, '') : '';
                            const strokes = window.sol.strokes;
                            for (let i = 0; i < strokes.length; i++) {
                                const targetPathData = normalize(strokes[i].path);
                                let path, cursor;
                                while (!path || !cursor) {
                                    const candidates = document.querySelectorAll('path._1e5Zt');
                                    path = Array.from(candidates).find(p => normalize(p.getAttribute('d')) === targetPathData);
                                    cursor = document.querySelector('g._1h31R:not(._25Ktp)');
                                    if (!path || !cursor) await sleep(10);
                                }
                                const matrix = path.getScreenCTM();
                                const len = path.getTotalLength();
                                const start = path.getPointAtLength(0).matrixTransform(matrix);
                                const end = path.getPointAtLength(len).matrixTransform(matrix);
                                cursor.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
                                document.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
                                const steps = 10;
                                for (let s = 1; s <= steps; s++) {
                                    const p = path.getPointAtLength((s / steps) * len).matrixTransform(matrix);
                                    const move = createEvent('mousemove', p.x, p.y, 1);
                                    cursor.dispatchEvent(move);
                                    document.dispatchEvent(move);
                                }
                                const finalMove = createEvent('mousemove', end.x, end.y, 1);
                                cursor.dispatchEvent(finalMove);
                                document.dispatchEvent(finalMove);
                                await sleep(5);
                                cursor.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
                                document.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
                            }
                            break;
                        }

                        case 'Character Write Freehand': {
                            const sleep = ms => new Promise(r => setTimeout(r, ms));
                            const createEvent = (type, x, y, buttons) => new MouseEvent(type, {
                                bubbles: true,
                                clientX: x,
                                clientY: y,
                                buttons,
                                button: 0
                            });
                            const normalize = str => str ? str.replace(/\s/g, '') : '';
                            const freehandStrokes = window.sol.strokes.filter(s => s.strokeDrawMode === 'FREEHAND');
                            for (let i = 0; i < freehandStrokes.length; i++) {
                                const targetPathData = normalize(freehandStrokes[i].path);
                                let path, svg;
                                while (!path || !svg) {
                                    const candidates = document.querySelectorAll('path._22UPm');
                                    path = Array.from(candidates).find(p => normalize(p.getAttribute('d')) === targetPathData);
                                    svg = document.querySelector('svg.o1rqi');
                                    if (!path || !svg) await sleep(10);
                                }
                                const matrix = path.getScreenCTM();
                                const len = path.getTotalLength();
                                const start = path.getPointAtLength(0).matrixTransform(matrix);
                                const end = path.getPointAtLength(len).matrixTransform(matrix);
                                svg.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
                                document.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
                                const steps = 10;
                                for (let s = 1; s <= steps; s++) {
                                    const p = path.getPointAtLength((s / steps) * len).matrixTransform(matrix);
                                    const move = createEvent('mousemove', p.x, p.y, 1);
                                    svg.dispatchEvent(move);
                                    document.dispatchEvent(move);
                                }
                                const finalMove = createEvent('mousemove', end.x, end.y, 1);
                                svg.dispatchEvent(finalMove);
                                document.dispatchEvent(finalMove);
                                await sleep(5);
                                svg.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
                                document.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
                            }
                            break;
                        }

                        case 'Daily Quest Progress':
                        case 'Streak Slide':
                        case 'Leaderboard Slide':
                            document.querySelector('[data-test="player-next"]')?.click();
                            break;
                    }
                } catch (error) {

                }
            },
            clickNext: () => {
                const inStory = location.hostname.includes('stories.duolingo.com') ||
                    location.pathname.includes('/stories') ||
                    !!document.querySelector('.FmlUF') ||
                    !!document.querySelector('[data-test="stories-choice"]') ||
                    !!document.querySelector('[data-test="story-start"]') ||
                    !!document.querySelector('[data-test="stories-player-continue"]') ||
                    !!document.querySelector('[data-test="stories-player-done"]');

                if (inStory) {
                    const continueStory = _autoSolver.findStoryContinue();
                    if (continueStory) {
                        continueStory();
                        return;
                    }
                }

                const nextBtn = document.querySelector('[data-test="player-next"]') ||
                    document.querySelector('[data-test="stories-player-continue"]') ||
                    document.querySelector('[data-test="stories-player-done"]');
                if (!nextBtn) return;
                const isDisabled = nextBtn.getAttribute('aria-disabled') === 'true' || nextBtn.disabled;
                if (!isDisabled) nextBtn.click();
            },
            solve: async () => {
                if (_autoSolver._isBusy) return;
                _autoSolver._isBusy = true;
                const sleep = ms => new Promise(r => setTimeout(r, ms));
                const skipSelectors = [
                    '[data-test="practice-hub-ad-no-thanks-button"]',
                    '[data-test="plus-no-thanks"]',
                    '[data-test="story-start"]',
                    '.vpDIE', // "Đọc câu này sau" / "Tôi không thể nói lúc này"
                    '._1N-oo._36Vd3._16r-S._1ZBYz._23KDq._1S2uf.HakPM',
                    '._8AMBh._2vfJy._3Qy5R._28UWu._3h0lA._1S2uf._1E9sc', // Cant speak modal button variant 1
                    '._1Qh5D._36g4N._2YF0P._28UWu._3h0lA._1S2uf._1E9sc', // Cant speak modal button variant 2
                    '._3bBpU._1x5JY._1M9iF._36g4N._2YF0P.T7I0c._2EnxW.MYehf',
                    '._2V6ug._1ursp._7jW2t._28UWu._3h0lA._1S2uf._1E9sc', // No Thanks Legendary
                    '._1rcV8._1VYyp._1ursp._7jW2t._1gKir', // Language Score
                    '._2V6ug._1ursp._7jW2t._3zgLG', // Create Profile Later
                ];
                skipSelectors.forEach(sel => document.querySelector(sel)?.click());
                try {
                    let mainElement = document.querySelector('._3yE3H');
                    if (!mainElement) mainElement = document.querySelector('.RMEuZ._1GVfY') || document.querySelector('[data-test="challenge"]') || document.querySelector('[class*="challenge"]');
                    // Stories elements
                    if (!mainElement) mainElement = document.querySelector('[data-test="stories-choice"]')?.closest('[class]') || document.querySelector('[data-test="challenge-tap-token-text"]')?.closest('[class]') || document.querySelector('.FmlUF');
                    if (!mainElement) {
                        _autoSolver.clickNext();
                        _autoSolver._isBusy = false;
                        return;
                    }
                    const reactInstance = _autoSolver.findReact(mainElement);
                    window.sol = reactInstance?.props?.currentChallenge;
                    if (!window.sol) {
                        _autoSolver.clickNext();
                        _autoSolver._isBusy = false;
                        return;
                    }
                    const challengeType = _autoSolver.determineChallengeType();
                    if (challengeType && challengeType !== 'Challenge Speak' && challengeType !== 'Listen Match' && challengeType !== 'Listen Speak') {
                        await Promise.race([
                            _autoSolver.handleChallenge(challengeType),
                            new Promise(r => setTimeout(r, _SAFE_MODE ? 9000 : 2000))
                        ]);
                        if (_SAFE_MODE) await sleep(80);
                    } else if (challengeType === 'Challenge Speak' || challengeType === 'Listen Match' || challengeType === 'Listen Speak') {
                        // Dismiss "Đọc câu này sau" / "Tôi không thể nói lúc này" modal trước
                        const speakModalSelectors = ['.vpDIE', '._8AMBh._2vfJy._3Qy5R._28UWu._3h0lA._1S2uf._1E9sc', '._1Qh5D._36g4N._2YF0P._28UWu._3h0lA._1S2uf._1E9sc'];
                        for (const sel of speakModalSelectors) {
                            const btn = document.querySelector(sel);
                            if (btn) {
                                btn.click();
                                await sleep(300);
                                break;
                            }
                        }
                        // Chờ player-skip xuất hiện rồi click
                        let skipBtn = document.querySelector('button[data-test="player-skip"]');
                        if (!skipBtn) {
                            await sleep(500);
                            skipBtn = document.querySelector('button[data-test="player-skip"]');
                        }
                        skipBtn?.click();
                        await sleep(150);
                    }
                    _autoSolver.clickNext();
                    if (_SAFE_MODE) await sleep(120);
                } catch (error) {
                    _autoSolver.clickNext();
                }
                _autoSolver._isBusy = false;
            },
            _isBusy: false,
            _solveLoopRunning: false,
            toggleAutoMode: () => {
                _isAutoMode = !_isAutoMode;
                _autoSolver.updateUI();
                if (_isAutoMode && !_autoSolver._solveLoopRunning) {
                    _autoSolver._solveLoopRunning = true;
                    const initialUrl = window.location.href;
                    (async function loop() {
                        while (_isAutoMode) {
                            if (window.location.href !== initialUrl) {
                                _isAutoMode = false;
                                _autoSolver.updateUI();
                                break;
                            }
                            const t0 = Date.now();
                            await _autoSolver.solve();
                            await new Promise(r => setTimeout(r, _safeWait(100)));
                            if (!_isAutoMode) break;
                            const elapsed = Date.now() - t0;
                            const wait = Math.max(0, _safeWait(400) - elapsed);
                            if (wait > 0) await new Promise(r => setTimeout(r, wait));
                        }
                        _autoSolver._solveLoopRunning = false;
                    })();
                } else if (!_isAutoMode) {
                    _autoSolver._solveLoopRunning = false;
                }
            },
            createUI: () => {
                if (_solverUI) return;
                _solverUI = document.createElement('div');
                _solverUI.id = 'nightware-solver-ui';
                _solverUI.style.cssText = `position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 2147483647; display: flex; gap: 12px; animation: slideUp 0.3s ease-out; pointer-events: auto;`;
                _solverUI.innerHTML = `
            <button class="nw-solver-btn" id="nw-solve-single" style="padding: 12px 24px; background: #89e219; border: none; border-bottom: 4px solid #58cc02; border-radius: 12px; color: white; font-weight: 700; font-size: 14px; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 4px 12px rgba(0,0,0,0.15); pointer-events: auto;">SOLVE</button>
            <button class="nw-solver-btn" id="nw-solve-all" style="padding: 12px 24px; background: #ffc800; border: none; border-bottom: 4px solid #ff9600; border-radius: 12px; color: white; font-weight: 700; font-size: 14px; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 4px 12px rgba(0,0,0,0.15); pointer-events: auto;">SOLVE ALL</button>
        `;
                const style = document.createElement('style');
                style.textContent = `@keyframes slideUp { from { opacity: 0; transform: translateX(-50%) translateY(20px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } } .nw-solver-btn:hover { filter: brightness(1.1); transform: translateY(-2px); } .nw-solver-btn:active { border-bottom: 0px; transform: translateY(2px); }`;
                document.head.appendChild(style);
                document.body.appendChild(_solverUI);
                document.getElementById('nw-solve-single').addEventListener('click', () => _autoSolver.solve());
                document.getElementById('nw-solve-all').addEventListener('click', () => _autoSolver.toggleAutoMode());
                document.addEventListener('keydown', (e) => {
                    if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
                        if (e.shiftKey) _autoSolver.toggleAutoMode();
                        else _autoSolver.solve();
                    }
                });
            },
            removeUI: () => {
                if (_solverUI) {
                    _solverUI.remove();
                    _solverUI = null;
                }
                if (_solvingIntervalId) {
                    clearInterval(_solvingIntervalId);
                    _solvingIntervalId = null;
                }
                _isAutoMode = false;
            },
            updateUI: () => {
                const btn = document.getElementById('nw-solve-all');
                if (btn) {
                    btn.textContent = _isAutoMode ? 'PAUSE' : 'SOLVE ALL';
                    btn.style.background = _isAutoMode ? '#ff4b4b' : '#1cb0f6';
                    btn.style.borderBottomColor = _isAutoMode ? '#cc0000' : '#2b70c9';
                }
            },
            checkAndToggle: () => {
                const currentIsInLesson = window.location.pathname.includes('/lesson') || window.location.pathname.includes('/practice');
                if (currentIsInLesson !== _isInLesson) {
                    _isInLesson = currentIsInLesson;
                    if (_isInLesson && _INJECT_SOLVER_ENABLED) {
                        setTimeout(() => _autoSolver.createUI(), 500);
                    } else {
                        _autoSolver.removeUI();
                    }
                }
            }
        }
        setInterval(() => _autoSolver.checkAndToggle(), 1000);

        let _lessonSolving = false;
        let _currentLessonCount = 0;
        let _lessonsToSolve = 0;

        function _getJwt() {
            const m = document.cookie.match(/(^| )jwt_token=([^;]+)/);
            return m ? m[2] : null;
        }

        function _decodeJwt(t) {
            try {
                const b = t.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
                const pad = b.padEnd(b.length + (4 - b.length % 4) % 4, '=');
                return JSON.parse(decodeURIComponent(atob(pad).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')));
            } catch {
                return null;
            }
        }

        function _buildHdrs(jwt) {
            return {
                'Content-Type': 'application/json',
                'Authorization': 'Bearer ' + jwt,
                'User-Agent': navigator.userAgent
            };
        }

        function _goalHdrs(jwt) {
            return {
                'Content-Type': 'application/json',
                'x-requested-with': 'XMLHttpRequest',
                'accept': 'application/json; charset=UTF-8',
                'Authorization': 'Bearer ' + jwt
            };
        }

        function _gm(method, url, data, hdrs) {
            return new Promise((res, rej) => GM_xmlhttpRequest({
                method,
                url,
                headers: hdrs || _hdrs,
                data: data ? JSON.stringify(data) : null,
                onload: r => res(r),
                onerror: () => rej(new Error('Network')),
                timeout: 15000,
                ontimeout: () => rej(new Error('Timeout'))
            }));
        }

        function _setBtnState(btnId, cfg, labelText) {
            const btn = document.getElementById(btnId);
            if (!btn) return;
            const lbl = btn.querySelector('.DH_Btn_Label') || btn.querySelector('.DH_Sm_Btn_Label');
            if (!lbl) return;

            const prevTxt = lbl.textContent;
            lbl.textContent = labelText;
            const newW = btn.offsetWidth;
            lbl.textContent = prevTxt;

            btn.style.width = btn.offsetWidth + 'px';
            requestAnimationFrame(() => {
                lbl.style.opacity = '0';
                lbl.style.filter = 'blur(4px)';
                btn.style.width = newW + 'px';
                btn.style.background = cfg.bg;
                btn.style.outline = `solid 2px ${cfg.outline}`;
                btn.style.outlineOffset = '-2px';
            });
            setTimeout(() => {
                lbl.style.transition = '0s';
                lbl.style.color = cfg.tc;
                void lbl.offsetWidth;
                lbl.style.transition = '0.4s';
                lbl.textContent = labelText;
                requestAnimationFrame(() => {
                    lbl.style.opacity = '1';
                    lbl.style.filter = 'blur(0)';
                });
                setTimeout(() => {
                    btn.style.width = '';
                }, 400);
            }, 400);
        }

        const _C_BLUE = {
            bg: 'rgb(var(--DH-blue))',
            outline: 'rgba(0,0,0,0.18)',
            tc: '#fff'
        };
        const _C_GREEN = {
            bg: 'rgba(var(--DH-green),0.10)',
            outline: 'rgba(var(--DH-green),0.22)',
            tc: 'rgb(var(--DH-green))'
        };
        const _C_RED = {
            bg: 'rgba(var(--DH-red),0.10)',
            outline: 'rgba(var(--DH-red),0.22)',
            tc: 'rgb(var(--DH-red))'
        };
        const _C_GRAY = {
            bg: 'rgb(var(--color-eel,117,117,117),0.10)',
            outline: 'rgb(var(--color-eel,117,117,117),0.20)',
            tc: 'rgb(var(--color-eel,117,117,117),0.60)'
        };

        function _resetBtn(btnId, label) {
            const btn = document.getElementById(btnId);
            if (!btn) return;
            btn.disabled = false;
            _setBtnState(btnId, _C_BLUE, label);
            const prog = document.getElementById(btnId.replace('_Btn', '_Prog'));
            if (prog) setTimeout(() => prog.classList.remove('on'), 2000);
        }

        function _setBtnProgress(btnId, pct) {
            const fill = document.getElementById(btnId.replace('_Btn', '_Fill'));
            if (fill) fill.style.width = pct + '%';
        }

        function _setBtnRunning(btnId) {
            const btn = document.getElementById(btnId);
            if (!btn) return;
            btn.disabled = false;
            _setBtnState(btnId, _C_RED, _t('btn_stop'));
            const prog = document.getElementById(btnId.replace('_Btn', '_Prog'));
            if (prog) prog.classList.add('on');
        }

        function _setBtnDone(btnId, label) {
            const btn = document.getElementById(btnId);
            if (!btn) return;
            _setBtnState(btnId, _C_GREEN, label || _t('btn_done'));
            const fill = document.getElementById(btnId.replace('_Btn', '_Fill'));
            if (fill) fill.style.width = '100%';
        }

        const _GF_SCRIPT_URL = 'https://greasyfork.org/en/scripts/561041-duolingo-duohacker';
        const _CURRENT_VER = '2026.07.13';

        /* ── Changelog Popup ─��� */
        const _CHANGELOG = [{
            version: '2026.07.13',
            changes: [
                'Added "Farming Modes"',
                'Added "Solver" page',
                'Fixed "Leaderboard" feature',
                'Improved UI',
                'Removed "V1" page',
            ]
        }, ];

        function _setConn(state, label) {
            _currentConnState = state;
            if (state === 'connected' && _isOutdated) {
                state = 'outdated';
                label = `v${_remoteVersion} available — click to update`;
            }

            const btn = document.getElementById('DH_Conn_Btn');
            const ico = document.getElementById('DH_Conn_Ico');
            const txt = document.getElementById('DH_Conn_Txt');
            if (!btn || !ico || !txt) return;
            ico.classList.remove('DH_Spin_Ico');

            if (state === 'outdated') {
                const newBtn = btn.cloneNode(true);
                btn.parentNode.replaceChild(newBtn, btn);
                const nb = document.getElementById('DH_Conn_Btn');
                const ni = document.getElementById('DH_Conn_Ico');
                const nt = document.getElementById('DH_Conn_Txt');


                nb.style.background = `linear-gradient(0deg,rgba(var(--DH-orange),0.10),rgba(var(--DH-orange),0.10)),rgb(var(--color-snow),0.90)`;
                nb.style.outline = `2px solid rgba(var(--DH-orange),0.30)`;
                nb.style.outlineOffset = '-2px';
                nb.style.cursor = 'pointer';
                nb.style.display = 'flex';
                nb.style.alignItems = 'center';


                nt.textContent = _t('outdated');
                nt.style.color = `rgb(var(--DH-orange))`;


                ni.textContent = '';
                ni.style.display = 'flex';
                ni.style.alignItems = 'center';
                ni.style.justifyContent = 'center';

                ni.style.color = `rgb(var(--DH-orange))`;
                ni.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="flex-shrink:0;display:block;"><path fill="currentColor" d="M21.171 15.398l-5.912-9.854c-.776-1.293-1.963-2.033-3.259-2.033s-2.483.74-3.259 2.031l-5.912 9.856c-.786 1.309-.872 2.705-.235 3.83.636 1.126 1.878 1.772 3.406 1.772h12c1.528 0 2.77-.646 3.406-1.771.637-1.125.551-2.521-.235-3.831zm-9.171 2.151c-.854 0-1.55-.695-1.55-1.549 0-.855.695-1.551 1.55-1.551s1.55.696 1.55 1.551c0 .854-.696 1.549-1.55 1.549zm1.633-7.424c-.011.031-1.401 3.468-1.401 3.468-.038.094-.13.156-.231.156s-.193-.062-.231-.156l-1.391-3.438c-.09-.233-.129-.443-.129-.655 0-.965.785-1.75 1.75-1.75s1.75.785 1.75 1.75c0 .212-.039.422-.117.625z"/></svg>`;

                nb.title = label;
                nb.onclick = () => window.open(_GF_SCRIPT_URL, '_blank');
                document.getElementById('DH_User_Row').style.display = 'flex';
                // Mirror outdated state to Solver page conn button
                const _v1b = document.getElementById('DH_V1_Conn_Btn');
                const _v1i = document.getElementById('DH_V1_Conn_Ico');
                const _v1t = document.getElementById('DH_V1_Conn_Txt');
                if (_v1b) {
                    _v1b.style.background = `linear-gradient(0deg,rgba(var(--DH-orange),0.10),rgba(var(--DH-orange),0.10)),rgb(var(--color-snow),0.90)`;
                    _v1b.style.outline = `2px solid rgba(var(--DH-orange),0.30)`;
                    _v1b.style.outlineOffset = '-2px';
                }
                if (_v1i) { _v1i.innerHTML = ni.innerHTML; _v1i.style.color = `rgb(var(--DH-orange))`; _v1i.style.display = 'flex'; _v1i.style.alignItems = 'center'; _v1i.style.justifyContent = 'center'; }
                if (_v1t) { _v1t.textContent = _t('outdated'); _v1t.style.color = `rgb(var(--DH-orange))`; }
                return;
            }

            const _SVG_CONNECTING = `<span class="DH_Spin_Ico" style="font-size:18px;line-height:1;">􀓞</span>`;
            const _SVG_CONNECTED = `<span style="font-size:18px;line-height:1;">􀤆</span>`;
            const _SVG_ERROR = `<span style="font-size:18px;line-height:1;">􀇿</span>`;
            const S = {
                connecting: {
                    bg: `rgb(var(--color-eel,117,117,117),0.10)`,
                    outline: `rgb(var(--color-eel,117,117,117),0.20)`,
                    tc: `rgb(var(--color-eel,117,117,117),0.70)`,
                    t: _t('connecting'),
                    svg: _SVG_CONNECTING
                },
                connected: {
                    bg: `linear-gradient(0deg,rgba(var(--DH-green),0.10),rgba(var(--DH-green),0.10)),rgb(var(--color-snow),0.90)`,
                    outline: `rgba(var(--DH-green),0.22)`,
                    tc: `rgb(var(--DH-green))`,
                    t: _t('connected'),
                    svg: _SVG_CONNECTED
                },
                error: {
                    bg: `rgba(var(--DH-red),0.08)`,
                    outline: `rgba(var(--DH-red),0.20)`,
                    tc: `rgb(var(--DH-red))`,
                    t: label || _t('error'),
                    svg: _SVG_ERROR
                },
            } [state];
            btn.style.background = S.bg;
            btn.style.outline = `2px solid ${S.outline}`;
            btn.style.outlineOffset = '-2px';
            txt.textContent = S.t;
            txt.style.color = S.tc;
            ico.innerHTML = S.svg;
            ico.style.color = S.tc;
            ico.style.display = 'flex';
            ico.style.alignItems = 'center';
            ico.style.justifyContent = 'center';
            // Mirror to Solver page conn button
            const _v1b = document.getElementById('DH_V1_Conn_Btn');
            const _v1i = document.getElementById('DH_V1_Conn_Ico');
            const _v1t = document.getElementById('DH_V1_Conn_Txt');
            if (_v1b) { _v1b.style.background = S.bg; _v1b.style.outline = `2px solid ${S.outline}`; _v1b.style.outlineOffset = '-2px'; }
            if (_v1i) { _v1i.innerHTML = S.svg; _v1i.style.color = S.tc; _v1i.style.display = 'flex'; _v1i.style.alignItems = 'center'; _v1i.style.justifyContent = 'center'; }
            if (_v1t) { _v1t.textContent = S.t; _v1t.style.color = S.tc; }
            if (state === 'connected') {
                document.getElementById('DH_User_Row').style.display = 'flex';
                // Pre-fetch leaderboard in background → instant open + early avatar update
                if (!_lbCache && !_lbPrefetching) {
                    _lbPrefetching = true;
                    _fetchLeaderboard().then(lb => {
                        _lbCache = lb; _lbPrefetching = false;
                        if (lb) _updatePanelAvatar(lb);
                    });
                }
            }
        }

        async function _checkVersionOnLoad() {
            try {
                const r = await new Promise((res, rej) => GM_xmlhttpRequest({
                    method: 'GET',
                    url: `https://greasyfork.org/scripts/561041.json`,
                    headers: {
                        'Accept': 'application/json'
                    },
                    onload: r => res(r),
                    onerror: () => rej(),
                    timeout: 5000
                }));
                if (r.status !== 200) return;
                const data = JSON.parse(r.responseText);
                const remoteVer = (data.version || '').trim();
                const cmp = (v1, v2) => {
                    const a = v1.split('.').map(Number);
                    const b = v2.split('.').map(Number);
                    for (let i = 0; i < Math.max(a.length, b.length); i++) {
                        if ((a[i] || 0) > (b[i] || 0)) return 1;
                        if ((a[i] || 0) < (b[i] || 0)) return -1;
                    }
                    return 0;
                };
                if (cmp(remoteVer, _CURRENT_VER) !== 0) {
                    _isOutdated = true;
                    _remoteVersion = remoteVer;
                    const txt = document.getElementById('DH_Conn_Txt');
                    if (_currentConnState === 'connected') {
                        _setConn('outdated', `v${remoteVer} available`);
                    }
                }
            } catch (e) {}
        }
        setTimeout(_checkVersionOnLoad, 2000);

        let _animBusy = false;

        function _doHide(val) {
            if (_animBusy) return;
            _animBusy = true;
            _hidden = val;
            const main = document.getElementById('DH_Main');
            const box = document.getElementById('DH_Main_Box');
            const h = box.offsetHeight;
            const hideIco = document.getElementById('DH_Hide_Ico');
            const hideTxt = document.getElementById('DH_Hide_Txt');
            const hideBtn = document.getElementById('DH_Hide_Btn');
            const switchV1Btn = document.getElementById('DH_SwitchV1_Btn');
            const switchV2Btn = document.getElementById('DH_SwitchV2_Btn');
            const EASE = '0.8s cubic-bezier(0.16,1,0.32,1)';
            main.style.transition = EASE;
            box.style.transition = `opacity ${EASE}, filter ${EASE}, transform ${EASE}`;
            if (val) {
                if (switchV1Btn) {
                    switchV1Btn.style.transition = `opacity 0.4s cubic-bezier(0.16,1,0.32,1), filter 0.4s cubic-bezier(0.16,1,0.32,1)`;
                    switchV1Btn.style.opacity = '0';
                    switchV1Btn.style.filter = 'blur(8px)';
                    setTimeout(() => { switchV1Btn.style.display = 'none'; switchV1Btn.style.opacity = ''; switchV1Btn.style.filter = ''; switchV1Btn.style.transition = ''; }, 400);
                }
                if (switchV2Btn) {
                    switchV2Btn.style.transition = `opacity 0.4s cubic-bezier(0.16,1,0.32,1), filter 0.4s cubic-bezier(0.16,1,0.32,1)`;
                    switchV2Btn.style.opacity = '0';
                    switchV2Btn.style.filter = 'blur(8px)';
                    setTimeout(() => { switchV2Btn.style.display = 'none'; switchV2Btn.style.opacity = ''; switchV2Btn.style.filter = ''; switchV2Btn.style.transition = ''; }, 400);
                }
                hideBtn.style.background = `linear-gradient(0deg,rgba(var(--DH-blue),0.10),rgba(var(--DH-blue),0.10)),rgb(var(--color-snow),0.80)`;
                hideBtn.style.outline = `2px solid rgba(var(--DH-blue),0.20)`;
                if (hideIco) { hideIco.textContent = '􀋮'; hideIco.style.color = 'rgb(var(--DH-blue))'; }
                hideTxt.textContent = _t('show');
                hideTxt.style.color = 'rgb(var(--DH-blue))';
                main.style.bottom = `-${h-8}px`;
                box.style.filter = 'blur(8px)';
                box.style.opacity = '0';
                box.style.transform = 'scale(0.96)';
            } else {
                if (switchV1Btn && !_v1Mode) {
                    switchV1Btn.style.display = '';
                    switchV1Btn.style.opacity = '0';
                    switchV1Btn.style.filter = 'blur(8px)';
                    switchV1Btn.style.transition = `opacity 0.5s cubic-bezier(0.16,1,0.32,1), filter 0.5s cubic-bezier(0.16,1,0.32,1)`;
                    void switchV1Btn.offsetWidth;
                    switchV1Btn.style.opacity = '';
                    switchV1Btn.style.filter = '';
                    setTimeout(() => { switchV1Btn.style.transition = ''; }, 500);
                }
                if (switchV2Btn && _v1Mode) {
                    switchV2Btn.style.display = '';
                    switchV2Btn.style.opacity = '0';
                    switchV2Btn.style.filter = 'blur(8px)';
                    switchV2Btn.style.transition = `opacity 0.5s cubic-bezier(0.16,1,0.32,1), filter 0.5s cubic-bezier(0.16,1,0.32,1)`;
                    void switchV2Btn.offsetWidth;
                    switchV2Btn.style.opacity = '';
                    switchV2Btn.style.filter = '';
                    setTimeout(() => { switchV2Btn.style.transition = ''; }, 500);
                }
                hideBtn.style.background = `rgb(var(--DH-blue))`;
                hideBtn.style.outline = `2px solid rgba(0,0,0,0.20)`;
                if (hideIco) { hideIco.textContent = '􀋰'; hideIco.style.color = '#fff'; }
                hideTxt.textContent = _t('hide');
                hideTxt.style.color = '#fff';
                main.style.bottom = '16px';
                box.style.filter = '';
                box.style.opacity = '';
                box.style.transform = 'scale(1)';
            }
            setTimeout(() => {
                main.style.transition = '';
                box.style.transition = '';
                box.style.transform = '';
                _animBusy = false;
            }, 800);
        }

        let _curPage = 1,
            _pageBusy = false;

        function _goPage(to) {
            if (_pageBusy || _curPage === to) return;
            _pageBusy = true;
            const box = document.getElementById('DH_Main_Box');
            const fromEl = document.getElementById(`DH_Page_${_curPage}`);
            const toEl = document.getElementById(`DH_Page_${to}`);
            if (!fromEl || !toEl) {
                _pageBusy = false;
                return;
            }
            const oldH = box.offsetHeight;
            const oldW = box.offsetWidth;
            fromEl.style.display = 'none';
            toEl.classList.add('active');
            toEl.style.opacity = '0';
            // Pre-render content BEFORE measuring height so animation uses correct size
            if (to === 11) {
                box.style.width = '357px';
                if (_lbCache) _renderLeaderboard(_lbCache);
                else {
                    const lbList = document.getElementById('DH_LB_List');
                    if (lbList) lbList.innerHTML = `<p class="DH_T2 DH_NoSel" style="text-align:center;padding:16px 0;">${_t('lb_loading')}</p>`;
                }
            } else if (to === 12) {
                box.style.width = '357px';
                _renderReactionPicker();
            } else if (to === 4) {
                box.style.width = '340px';
            } else {
                box.style.width = '';
            }
            const newH = box.offsetHeight;
            const newW = box.offsetWidth;
            toEl.classList.remove('active');
            toEl.style.opacity = '';
            fromEl.style.display = '';
            box.style.width = oldW + 'px';
            box.style.height = oldH + 'px';
            box.style.transition = 'height 0.6s cubic-bezier(0.34, 1.56, 0.64, 1), width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1)';
            fromEl.style.transition = 'opacity 0.35s cubic-bezier(0.4, 0, 0.2, 1), filter 0.35s cubic-bezier(0.4, 0, 0.2, 1), transform 0.35s cubic-bezier(0.4, 0, 0.2, 1)';
            fromEl.style.opacity = '0';
            fromEl.style.filter = 'blur(6px)';
            fromEl.style.transform = 'scale(0.96)';
            requestAnimationFrame(() => {
                box.style.height = newH + 'px';
                box.style.width = newW + 'px';
            });
            setTimeout(() => {
                fromEl.classList.remove('active');
                fromEl.style.cssText = '';
                toEl.classList.add('active');
                toEl.style.opacity = '0';
                toEl.style.filter = 'blur(6px)';
                toEl.style.transform = 'scale(1.04)';
                void toEl.offsetWidth;
                toEl.style.transition = 'opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1), filter 0.4s cubic-bezier(0.4, 0, 0.2, 1), transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)';
                toEl.style.opacity = '1';
                toEl.style.filter = 'blur(0)';
                toEl.style.transform = 'scale(1)';
                _pageHistory.push(to);
                _curPage = to;
                if (to === 2) {
                    const lgB = document.getElementById('DH_League_Btn');
                    const qB = document.getElementById('DH_Quest_Btn');
                    if (_user && lgB) lgB.disabled = false;
                    if (_user && qB) qB.disabled = false;
                }
                if (to === 11) {
                    _fetchLeaderboard().then(lb => { _lbCache = lb; _renderLeaderboard(lb); });
                } else if (to === 4) {
                    const delI = document.getElementById('DH_Delay_Input');
                    if (delI) delI.value = _delay;
                }
                setTimeout(() => {
                    box.style.height = '';
                    box.style.width = '';
                    box.style.transition = '';
                    toEl.style.cssText = '';
                    _pageBusy = false;
                }, 400);
            }, 380);
        }

        function _goBack() {
            if (_pageHistory.length > 1) _pageHistory.pop();
            const prev = _pageHistory[_pageHistory.length - 1] || 1;

            _pageHistory.pop();
            _goPage(prev);
        }

        let _notifCount = 0;
        let _currentNotif = [];
        let _notifsHovered = false;

        const _notifMain = document.querySelector('.DH_Notif_Main');
        _notifMain.addEventListener('mouseenter', () => { _notifsHovered = true; });
        _notifMain.addEventListener('mouseleave', () => { _notifsHovered = false; });

        const _notifHTML = `<div class="DH_Notif_Box">
    <div class="DH_HStack_4" style="align-items: center;">
        <p class="DH_T1 DH_NoSel DH_Notif_Icon_1"></p>
        <p class="DH_T1 DH_NoSel DH_Notif_Text_1" style="flex: 1 0 0;"></p>
        <p class="DH_T1 DH_NoSel DH_Notif_Close" style="align-self: stretch; cursor: pointer; transition: filter 0.4s cubic-bezier(0.16, 1, 0.32, 1), transform 0.4s cubic-bezier(0.16, 1, 0.32, 1);">􀆄</p>
    </div>
    <p class="DH_T2 DH_NoSel DH_Notif_Text_2" style="color: rgb(var(--color-wolf), 0.4); align-self: stretch; overflow-wrap: break-word;"></p>
</div>`;

        function _notif(icon, title, body, dur = 5) {
            _notifCount++;
            let notifID = _notifCount;
            _currentNotif.push(notifID);

            let element = new DOMParser().parseFromString(_notifHTML, 'text/html').body.firstChild;
            element.id = 'DH_Notif_Box_' + notifID;
            _notifMain.appendChild(element);

            let iconEl = element.querySelector('.DH_Notif_Icon_1');
            if (icon === '') {
                iconEl.style.display = 'none';
            } else if (icon === 'checkmark') {
                iconEl.style.color = 'rgb(var(--DLP-green))';
                iconEl.textContent = '􀁣';
            } else if (icon === 'warning') {
                iconEl.style.color = 'rgb(var(--DLP-orange))';
                iconEl.textContent = '􀁟';
            } else if (icon === 'error') {
                iconEl.style.color = 'rgb(var(--DLP-pink))';
                iconEl.textContent = '􀇿';
            } else if (typeof icon === 'object') {
                iconEl.style.color = icon.color;
                iconEl.textContent = icon.icon;
            } else {
                iconEl.textContent = icon;
            }

            element.querySelector('.DH_Notif_Text_1').innerHTML = title;
            let bodyEl = element.querySelector('.DH_Notif_Text_2');
            if (body && body !== '') {
                bodyEl.innerHTML = body;
            } else {
                bodyEl.style.display = 'none';
            }

            let notification = document.querySelector('#DH_Notif_Box_' + notifID);
            let notificationHeight = notification.offsetHeight;
            notification.style.bottom = '-' + notificationHeight + 'px';

            setTimeout(() => {
                requestAnimationFrame(() => {
                    notification.style.bottom = '16px';
                    notification.style.filter = 'blur(0px)';
                    notification.style.opacity = '1';
                });
            }, 50);

            let isBusyDisappearing = false;

            let timerData = null;
            if (dur !== 0) {
                timerData = {
                    remaining: dur * 1000,
                    lastTimestamp: Date.now(),
                    timeoutHandle: null,
                    paused: false,
                };
                timerData.timeoutHandle = setTimeout(internalDisappear, timerData.remaining);
            }

            let repeatInterval = setInterval(() => {
                if (document.body.offsetWidth <= 963) {
                    requestAnimationFrame(() => {
                        _notifMain.style.width = '300px';
                        _notifMain.style.position = 'fixed';
                        _notifMain.style.left = '16px';
                    });
                } else {
                    requestAnimationFrame(() => {
                        _notifMain.style.width = '';
                        _notifMain.style.position = '';
                        _notifMain.style.left = '';
                    });
                }

                if (isBusyDisappearing) return;

                if (timerData) {
                    if (_notifsHovered && !timerData.paused) {
                        clearTimeout(timerData.timeoutHandle);
                        let elapsed = Date.now() - timerData.lastTimestamp;
                        timerData.remaining -= elapsed;
                        timerData.paused = true;
                    }
                    if (!_notifsHovered && timerData.paused) {
                        timerData.paused = false;
                        timerData.lastTimestamp = Date.now();
                        timerData.timeoutHandle = setTimeout(internalDisappear, timerData.remaining);
                    }
                }

                if (_notifsHovered) {
                    let allIDs = _currentNotif.slice();
                    let bottoms = {};
                    let currentBottom = 16;
                    for (let i = allIDs.length - 1; i >= 0; i--) {
                        let notifEl = document.querySelector('#DH_Notif_Box_' + allIDs[i]);
                        if (!notifEl) continue;
                        notifEl.style.width = '';
                        notifEl.style.height = '';
                        notifEl.style.transform = '';
                        bottoms[allIDs[i]] = currentBottom;
                        currentBottom += notifEl.offsetHeight + 8;
                    }
                    notification.style.bottom = bottoms[notifID] + 'px';

                    let totalHeight = 0;
                    for (let i = 0; i < allIDs.length; i++) {
                        let notifEl = document.querySelector('#DH_Notif_Box_' + allIDs[i]);
                        if (notifEl) totalHeight += notifEl.offsetHeight;
                    }
                    if (allIDs.length > 1) totalHeight += (allIDs.length - 1) * 8;
                    _notifMain.style.height = totalHeight + 'px';
                } else {
                    _notifMain.style.height = '';
                    notification.style.bottom = '16px';
                    if (_currentNotif[_currentNotif.length - 1] !== notifID) {
                        notification.style.height = notificationHeight + 'px';
                        requestAnimationFrame(() => {
                            let latestNotif = document.querySelector(
                                '#DH_Notif_Box_' + String(_currentNotif[_currentNotif.length - 1])
                            );
                            if (latestNotif) {
                                notification.style.height = latestNotif.offsetHeight + 'px';
                            }
                            notification.style.width = '284px';
                            notification.style.transform = 'translateY(-8px)';
                        });
                    } else {
                        requestAnimationFrame(() => {
                            notification.style.height = notificationHeight + 'px';
                            notification.style.width = '';
                            notification.style.transform = '';
                        });
                    }
                }
            }, 20);

            function internalDisappear() {
                if (timerData && timerData.timeoutHandle) clearTimeout(timerData.timeoutHandle);
                if (isBusyDisappearing) return;
                isBusyDisappearing = true;
                _currentNotif.splice(_currentNotif.indexOf(notifID), 1);

                requestAnimationFrame(() => {
                    notification.style.bottom = '-' + notificationHeight + 'px';
                    notification.style.filter = 'blur(16px)';
                    notification.style.opacity = '0';
                });
                clearInterval(repeatInterval);
                setTimeout(() => {
                    notification.remove();
                    if (_currentNotif.length === 0) {
                        _notifMain.style.height = '';
                    }
                }, 800);
            }

            notification.querySelector('.DH_Notif_Close').addEventListener('click', internalDisappear);

            return { close: internalDisappear };
        }

        async function _connect() {
            _panelAvatarLoaded = false;
            _setConn('connecting');
            _jwt = _getJwt();
            if (!_jwt) {
                _setConn('error', _t('conn_not_logged_in'));
                return;
            }
            const dec = _decodeJwt(_jwt);
            if (!dec) {
                _setConn('error', _t('conn_invalid_token'));
                return;
            }
            _sub = dec.sub;
            _hdrs = _buildHdrs(_jwt);
            try {
                const r = await _gm('GET', `https://www.duolingo.com/2017-06-30/users/${_sub}?fields=id,username,fromLanguage,learningLanguage,streak,totalXp,gems,picture,streakData,timezone`);
                if (r.status !== 200) throw new Error(r.status);
                _user = JSON.parse(r.responseText);
                unsafeWindow._dhUser = _user;
                _setConn('connected');
                _renderUser(_user);
                _getPrivacy().then(v => {
                    _privacy = v;
                    _applyHideProfileToggle();
                });
                _v1FetchSkillId();
                ['DH_XP_Btn', 'DH_Gem_Btn', 'DH_Streak_Btn', 'DH_League_Btn', 'DH_Quest_Btn', 'DH_Practice_Btn'].forEach(id => {
                    const b = document.getElementById(id);
                    if (b) b.disabled = false;
                });

                const saveBtn = document.getElementById('DH_AccSave_Btn');
                if (saveBtn) saveBtn.disabled = false;
                const mqClaimNav = document.getElementById('DH_MonthlyQuest_Claim_Btn');
                if (mqClaimNav) mqClaimNav.disabled = false;

                // Preload tất cả dữ liệu ngay sau khi kết nối thành công
                setTimeout(() => {
                    _loadShop(); // Load shop items
                    _renderAccounts(); // Load account manager
                    _loadMonthlyQuests(); // Load monthly quests
                    _loadLicense(); // Load license
                    // Prefetch leaderboard so page 11 opens instantly
                    _fetchLeaderboard().then(lb => { _lbCache = lb; }).catch(() => {});
                }, 500);
            } catch (e) {
                _setConn('error', _t('conn_failed_retry'));
                setTimeout(_connect, 8000);
            }
        }

        function _renderUser(u) {
            if (!u) return;
            document.getElementById('DH_UName').textContent = u.username || '';
            _v1SyncUser();
            document.getElementById('DH_UXP').textContent = (u.totalXp || 0).toLocaleString();
            document.getElementById('DH_UGems').textContent = (u.gems || 0).toLocaleString();
            document.getElementById('DH_UStreak').textContent = (u.streak || 0).toLocaleString();
            const av = document.getElementById('DH_Avatar');
            const bestUrl = _bestAvatarUrl(u.picture, _sub);
            if (bestUrl) {
                const avImg = document.createElement('img');
                avImg.src = bestUrl;
                avImg.style.cssText = 'width:100%;height:100%;object-fit:cover;border-radius:50%;display:block;';
                avImg.draggable = false;
                avImg.onerror = function() {
                    // picture failed — try league fallback before emoji
                    const lbUrl = _bestAvatarUrl('', _sub);
                    if (lbUrl) {
                        const fb = document.createElement('img');
                        fb.src = lbUrl;
                        fb.style.cssText = avImg.style.cssText;
                        fb.draggable = false;
                        fb.onerror = function() { av.innerHTML = '👤'; };
                        av.innerHTML = '';
                        av.appendChild(fb);
                    } else {
                        av.innerHTML = '👤';
                    }
                };
                av.innerHTML = '';
                av.appendChild(avImg);
            } else if (av.innerHTML === '' || av.innerHTML === '👤') {
                av.innerHTML = '👤';
            }
        }

        async function _farmXP(txp) {
            const MIN = 30,
                MAX = 499;
            let loops = Math.floor(txp / MAX),
                rem = txp % MAX;
            if (rem > 0 && rem < MIN && loops > 0) {
                loops--;
                rem += MAX;
            }
            const total = loops + (rem >= MIN ? 1 : 0);
            let cur = 0,
                earned = 0;
            _setBtnRunning('DH_XP_Btn');
            const _isInfXP = total >= 99999;
            function _xpLive() {
                const inp = document.getElementById('DH_XP_Input');
                const wrap = document.getElementById('DH_XP_Input_Wrap');
                if (inp) {
                    inp.type = 'text';
                    inp.readOnly = true;
                    inp.style.pointerEvents = 'none';
                    inp.value = earned > 0 ? '+' + earned.toLocaleString() + ' XP' : '...';
                }
                if (wrap) wrap.style.opacity = '1';
            }
            function _xpLiveReset() {
                const inp = document.getElementById('DH_XP_Input');
                const wrap = document.getElementById('DH_XP_Input_Wrap');
                if (inp) { inp.type = 'number'; inp.readOnly = false; inp.style.pointerEvents = ''; inp.value = ''; }
                if (wrap) wrap.style.opacity = '';
            }
            for (let i = 0; i < loops; i++) {
                if (!_running) break;
                const ok = await _storyXP(469);
                if (ok) {
                    earned += MAX;
                    cur++;
                    if (_user) { _user.totalXp = (_user.totalXp || 0) + MAX; _renderUser(_user); }
                }
                if (_isInfXP) { _xpLive(); }
                else { _setBtnProgress('DH_XP_Btn', Math.floor((cur / total) * 100)); }
                await _sleep(_delay);
            }
            if (rem >= MIN && _running) {
                const ok = await _storyXP(Math.min(rem - MIN, 469));
                if (ok) {
                    earned += rem;
                    cur++;
                    if (_user) { _user.totalXp = (_user.totalXp || 0) + rem; _renderUser(_user); }
                }
                if (_isInfXP) { _xpLive(); }
                else { _setBtnProgress('DH_XP_Btn', 100); }
            }
            if (_running) {
                _xpLiveReset();
                _setBtnDone('DH_XP_Btn', _t('btn_done'));
                _notif('✅', _t('notif_xp_done_title'), _t('notif_xp_done_body', earned.toLocaleString(), cur));
                setTimeout(_connect, 1500);
                setTimeout(() => _resetBtn('DH_XP_Btn', _t('btn_get')), 3000);
            } else {
                _xpLiveReset();
                if (_isInfXP) {
                    if (earned > 0) _notif('✅', _t('notif_xp_done_title'), _t('notif_xp_done_inf_body', earned.toLocaleString()));
                } else {
                    if (earned > 0) _notif('⏹️', _t('notif_xp_stopped_title'), _t('notif_xp_stopped_body', earned.toLocaleString()));
                }
                _resetBtn('DH_XP_Btn', _t('btn_get'));
            }
        }

        async function _storyXP(hh) {
            try {
                const now = Math.floor(Date.now() / 1000),
                    dur = Math.floor(Math.random() * 121 + 300);
                const r = await _gm('POST', 'https://stories.duolingo.com/api2/stories/fr-en-le-passeport/complete', {
                    awardXp: true,
                    completedBonusChallenge: true,
                    fromLanguage: 'fr',
                    learningLanguage: 'en',
                    hasXpBoost: false,
                    illustrationFormat: 'svg',
                    isFeaturedStoryInPracticeHub: true,
                    isLegendaryMode: true,
                    isV2Redo: false,
                    isV2Story: false,
                    masterVersion: true,
                    maxScore: 0,
                    score: 0,
                    happyHourBonusXp: hh,
                    startTime: now,
                    endTime: now + dur
                });
                return r.status === 200;
            } catch {
                return false;
            }
        }

        // ── Slug probe — used only by V1 Mode infinite farm (fallback detection) ──
        let _workingSlug = null,
            _workingSlugFrom = null,
            _workingSlugLearn = null;
        let _probingSlugPromise = null;
        const _SLUG_CANDIDATES = () => [
            ['vi-en-le-passeport', 'vi', 'en'],
            ['fr-en-le-passeport', 'fr', 'en'],
            ['en-fr-le-passeport', 'en', 'fr'],
            ['es-en-le-passeport', 'es', 'en'],
            ['de-en-le-passeport', 'de', 'en'],
            ['pt-en-le-passeport', 'pt', 'en'],
            ['it-en-le-passeport', 'it', 'en'],
        ];
        async function _probeSlug() {
            if (_workingSlug) return _workingSlug;
            if (_probingSlugPromise) return _probingSlugPromise;
            _probingSlugPromise = (async () => {
                const now = Math.floor(Date.now() / 1000);
                const tryCandidate = ([slug, from, learn]) => _gm('POST', `https://stories.duolingo.com/api2/stories/${slug}/complete`, {
                    awardXp: false,
                    completedBonusChallenge: false,
                    fromLanguage: from,
                    learningLanguage: learn,
                    hasXpBoost: false,
                    illustrationFormat: 'svg',
                    isFeaturedStoryInPracticeHub: true,
                    isLegendaryMode: true,
                    isV2Redo: false,
                    isV2Story: false,
                    masterVersion: true,
                    maxScore: 0,
                    score: 0,
                    happyHourBonusXp: 0,
                    startTime: now,
                    endTime: now + 300
                }).then(r => {
                    if (r.status === 200 || r.status === 429) return [slug, from, learn];
                    return null;
                }).catch(() => null);

                const winner = await new Promise(resolve => {
                    let settled = false;
                    let pending = _SLUG_CANDIDATES().length;
                    _SLUG_CANDIDATES().forEach(c => {
                        tryCandidate(c).then(result => {
                            pending--;
                            if (result && !settled) {
                                settled = true;
                                resolve(result);
                            } else if (pending === 0 && !settled) resolve(null);
                        });
                    });
                });

                if (winner) {
                    _workingSlug = winner[0];
                    _workingSlugFrom = winner[1];
                    _workingSlugLearn = winner[2];
                }
                _probingSlugPromise = null;
                return winner ? winner[0] : null;
            })();
            return _probingSlugPromise;
        }

        // ── Gem Farm Helpers (ported from GemHelper) ──────────────────────────────
        async function _fetchGemRewards() {
            try {
                const response = await fetch(`https://www.duolingo.com/2023-05-23/users/${_sub}?fields=rewardBundles`, {
                    headers: {
                        'authorization': `Bearer ${_jwt}`,
                        'content-type': 'application/json'
                    }
                });

                if (!response.ok) return [];

                const data = await response.json();
                const bundles = data.rewardBundles || [];

                const gemRewards = [];
                for (const bundle of bundles) {
                    for (const reward of bundle.rewards || []) {
                        if (!reward.consumed && (reward.id.includes('GEMS') || reward.currency === 'GEMS')) {
                            gemRewards.push({
                                id: reward.id,
                                amount: reward.amount || 0
                            });
                        }
                    }
                }

                return gemRewards;
            } catch (e) {
                return [];
            }
        }

        async function _exploitGemReward(rewardId) {
            const body = {
                consumed: true,
                fromLanguage: _user.fromLanguage,
                learningLanguage: _user.learningLanguage,
                pathLevelSpecifics: {
                    anchorSkillId: 'f22fd38157eea63965dc39eeac3c40c1',
                    indexSinceAnchorSkill: 0,
                    treeId: '14b1a2672c1bb3b250ebaa31b86c343e',
                    nodeState: 'active'
                }
            };
            try {
                // Use native fetch like Gem Helper instead of _gm wrapper
                const response = await fetch(`https://www.duolingo.com/2017-06-30/users/${_sub}/rewards/${rewardId}`, {
                    method: 'PATCH',
                    headers: {
                        'authorization': `Bearer ${_jwt}`,
                        'content-type': 'application/json',
                        'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
                        'x-amzn-trace-id': `User=${_sub}`,
                        'x-requested-with': 'XMLHttpRequest',
                        'referer': 'https://www.duolingo.com/learn',
                        'origin': 'https://www.duolingo.com'
                    },
                    body: JSON.stringify(body)
                });

                return response.ok;
            } catch (e) {
                return false;
            }
        }

        async function _getGemCount() {
            try {
                const response = await fetch(`https://www.duolingo.com/2023-05-23/users/${_sub}?fields=gemsConfig`, {
                    headers: {
                        'authorization': `Bearer ${_jwt}`,
                        'content-type': 'application/json'
                    }
                });

                if (!response.ok) return null;

                const data = await response.json();
                return data.gemsConfig?.gems ?? null;
            } catch (e) {
                return null;
            }
        }

        const THREADS = 8;

        async function _farmGems() {
            _setBtnState('DH_Gem_Btn', _C_RED, _t('btn_stop'));
            const btn = document.getElementById('DH_Gem_Btn');
            if (btn) btn.disabled = false;

            const gemInput = document.getElementById('DH_Gem_Input');
            if (gemInput) gemInput.value = '';

            let totalGained = 0;

            outer:
                while (_running && _task === 'gem') {
                    const rewards = await _fetchGemRewards();

                    if (rewards.length === 0) {
                        await _sleep(Math.max(200, _delay));
                        continue;
                    }

                    const gemsBefore = await _getGemCount() ?? (_user?.gems ?? 0);

                    // Fire all rewards in parallel batches — no inter-batch delay
                    for (let i = 0; i < rewards.length; i += THREADS) {
                        if (!_running || _task !== 'gem') break outer;
                        const batch = rewards.slice(i, i + THREADS);
                        await Promise.all(batch.map(r => _exploitGemReward(r.id)));
                    }

                    // Single gem count check after full pass — avoids extra API calls
                    const now = await _getGemCount();
                    if (now !== null) {
                        const gained = Math.max(0, now - gemsBefore);
                        totalGained += gained;
                        if (gemInput) gemInput.value = String(totalGained);
                        if (_user) {
                            _user.gems = now;
                            _renderUser(_user);
                        }
                    }

                    // Short cooldown before next pass
                    await _sleep(Math.max(200, _delay / 2));
                }

            // Always reset button after farm ends (stopped or done)
            _setBtnState('DH_Gem_Btn', _C_BLUE, _t('btn_run'));
            if (btn) btn.disabled = !_user;
            if (totalGained > 0) {
                _notif('✅', _t('notif_gem_done_title'), _t('notif_gem_done_body', totalGained.toLocaleString()));
                setTimeout(_connect, 1500);
            }
        }



        // ── Timezone helpers ───────────────────────────────────────────────────────
        function _accountTimezone() {
            return (_user && _user.timezone) ? _user.timezone : 'America/New_York';
        }

        function _tzParts(timeZone, date) {
            const dtf = new Intl.DateTimeFormat('en-US', {
                timeZone, hourCycle: 'h23',
                year: 'numeric', month: '2-digit', day: '2-digit',
                hour: '2-digit', minute: '2-digit', second: '2-digit'
            });
            const out = {};
            dtf.formatToParts(date).forEach(p => { if (p.type !== 'literal') out[p.type] = p.value; });
            return out;
        }

        function _wallClockToSeconds(timeZone, year, month, day, hour, minute, second) {
            const guess = Date.UTC(year, month - 1, day, hour, minute, second);
            const parts = _tzParts(timeZone, new Date(guess));
            const localAsUtc = Date.UTC(+parts.year, +parts.month - 1, +parts.day,
                                        +parts.hour, +parts.minute, +parts.second);
            return Math.floor((guess - (localAsUtc - guess)) / 1000);
        }

        function _accountToday(timeZone) {
            const p = _tzParts(timeZone, new Date());
            return { year: +p.year, month: +p.month, day: +p.day };
        }
        // ────────────────────────────────────────────────────────────────────────


        async function _farmStreak(days) {
            const CH = ["assist","characterIntro","characterMatch","characterPuzzle","characterSelect","characterTrace","characterWrite","completeReverseTranslation","definition","dialogue","extendedMatch","extendedListenMatch","form","freeResponse","gapFill","judge","listen","listenComplete","listenMatch","match","name","listenComprehension","listenIsolation","listenSpeak","listenTap","orderTapComplete","partialListen","partialReverseTranslate","patternTapComplete","radioBinary","radioImageSelect","radioListenMatch","radioListenRecognize","radioSelect","readComprehension","reverseAssist","sameDifferent","select","selectPronunciation","selectTranscription","svgPuzzle","syllableTap","syllableListenTap","speak","tapCloze","tapClozeTable","tapComplete","tapCompleteTable","tapDescribe","translate","transliterate","transliterationAssist","typeCloze","typeClozeTable","typeComplete","typeCompleteTable","writeComprehension"];

            // Timezone-aware base date
            const tz = _accountTimezone();
            let base;
            const dateStr = _user?.streakData?.currentStreak?.startDate;
            if (dateStr && /^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
                const [y, m, d] = dateStr.split('-').map(Number);
                base = { year: y, month: m, day: d };
            } else {
                base = _accountToday(tz);
            }

            let doneLoops = 0, savedDays = 0;
            const _isInfSt = days >= 9999;
            function _stLive() {
                const inp = document.getElementById('DH_Streak_Input');
                const wrap = document.getElementById('DH_Streak_Input_Wrap');
                if (inp) {
                    inp.type = 'text';
                    inp.readOnly = true;
                    inp.style.pointerEvents = 'none';
                    inp.value = savedDays > 0 ? '+' + savedDays + ' ' + _t(savedDays === 1 ? 'streak_day' : 'streak_days') : '...';
                }
                if (wrap) wrap.style.opacity = '1';
            }
            function _stLiveReset() {
                const inp = document.getElementById('DH_Streak_Input');
                const wrap = document.getElementById('DH_Streak_Input_Wrap');
                if (inp) { inp.type = 'number'; inp.readOnly = false; inp.style.pointerEvents = ''; inp.value = ''; }
                if (wrap) wrap.style.opacity = '';
            }
            _setBtnRunning('DH_Streak_Btn');

            while (_running && doneLoops < days) {
                if (_isInfSt) { _stLive(); }
                else { _setBtnProgress('DH_Streak_Btn', Math.floor((doneLoops / days) * 100)); }

                // endTime = 12:00:00 of (base - 1 - doneLoops) theo timezone tài khoản
                const endSecs = _wallClockToSeconds(
                    tz, base.year, base.month, base.day - 1 - doneLoops, 12, 0, 0
                );

                let sess = null;
                await new Promise(r => GM_xmlhttpRequest({
                    method: 'POST',
                    url: 'https://www.duolingo.com/2017-06-30/sessions',
                    headers: _hdrs,
                    data: JSON.stringify({
                        challengeTypes: CH,
                        fromLanguage: _user.fromLanguage,
                        isFinalLevel: false, isV2: true, juicy: true,
                        learningLanguage: _user.learningLanguage,
                        smartTipsVersion: 2, type: 'GLOBAL_PRACTICE'
                    }),
                    onload: res => { if (res.status === 200) { try { sess = JSON.parse(res.responseText); } catch {} } r(); },
                    onerror: () => r(), timeout: 15000, ontimeout: () => r()
                }));

                if (!_running) break;

                if (sess?.id) {
                    let ok = false;
                    await new Promise(r => GM_xmlhttpRequest({
                        method: 'PUT',
                        url: `https://www.duolingo.com/2017-06-30/sessions/${sess.id}`,
                        headers: _hdrs,
                        data: JSON.stringify({
                            ...sess,
                            heartsLeft: 5,
                            startTime: endSecs - 1, endTime: endSecs,
                            enableBonusPoints: false, failed: false,
                            maxInLessonStreak: 9, shouldLearnThings: true
                        }),
                        onload: res => { ok = res.status === 200; r(); },
                        onerror: () => r(), timeout: 15000, ontimeout: () => r()
                    }));

                    if (ok) {
                        savedDays++;
                        if (_user) {
                            _user.streak = (_user.streak || 0) + 1;
                            _renderUser(_user);
                        }
                        if (_isInfSt) { _stLive(); }
                    }
                }

                doneLoops++;
                await _sleep(_delay);
            }

            if (_running) {
                _stLiveReset();
                _setBtnProgress('DH_Streak_Btn', 100);
                _setBtnDone('DH_Streak_Btn', _t('btn_done'));
                if (savedDays > 0) {
                    _notif('🔥', _t('notif_streak_done_title'), _t('notif_streak_done_body', savedDays));
                } else {
                    _notif('⚠️', _t('notif_streak_farm_title'), _t('notif_streak_no_days'));
                }
                setTimeout(_connect, 1500);
                setTimeout(() => _resetBtn('DH_Streak_Btn', _t('btn_run')), 3000);
            } else {
                _stLiveReset();
                if (_isInfSt) {
                    if (savedDays > 0) _notif('🔥', _t('notif_streak_done_title'), _t('notif_streak_done_inf_body', savedDays, _t(savedDays === 1 ? 'streak_day' : 'streak_days')));
                } else {
                    if (savedDays > 0) _notif('⏹', _t('notif_streak_stopped_title'), _t('notif_streak_stopped_body', savedDays));
                }
                _resetBtn('DH_Streak_Btn', _t('btn_run'));
            }
            _running = false;
        }

        async function _farmPractice(count) {

            _lessonsToSolve = count;
            _currentLessonCount = 0;
            if (_lessonSolving) {
                _notif('⚠️', _t('notif_busy_title'), _t('notif_practice_busy'));
                return;
            }
            _lessonSolving = true;
            const btn = document.getElementById('DH_Practice_Btn');
            _setBtnState('DH_Practice_Btn', _C_RED, _t('btn_stop'));
            _notif('📚', _t('notif_practice_title'), _t('notif_practice_nav'), 3);

            if (!window.location.pathname.startsWith('/practice')) {
                window.location.assign('/practice');
                return;
            }
            await _solveCurrentLesson();
        }

        async function _solveCurrentLesson() {

            let waited = 0;
            while (!document.querySelector('[data-test="challenge"]') && !document.querySelector('._3yE3H') && waited < 10000 && _lessonSolving) {
                await _sleep(500);
                waited += 500;
            }
            if (!_lessonSolving) return;

            const deadline = Date.now() + 180000;
            while (_lessonSolving && Date.now() < deadline) {
                const done = document.querySelector('[data-test="session-over"]') ||
                    document.querySelector('[data-test="session-complete-slide"]') ||
                    document.querySelector('[data-test="session-complete"]');
                if (done) {
                    _currentLessonCount++;
                    try {
                        const s = JSON.parse(sessionStorage.getItem('dh2_practice') || '{}');
                        s.done = _currentLessonCount;
                        sessionStorage.setItem('dh2_practice', JSON.stringify(s));
                    } catch {}
                    await _sleep(_safeWait(500));
                    break;
                }
                await _autoSolver.solve();
                await _sleep(_safeWait(400));
            }

            if (!_lessonSolving) return;

            if (_lessonsToSolve > 0 && _currentLessonCount >= _lessonsToSolve) {
                _notif('✅', _t('notif_practice_done_title'), _t('notif_practice_done_body', _currentLessonCount));
                _stopPractice();
                return;
            }

            _notif('📚', _t('notif_practice_title'), _t('notif_practice_next_body', _currentLessonCount, _lessonsToSolve), 2);
            await _sleep(800);
            if (_lessonSolving) window.location.assign('/practice');
        }

        function _stopPractice() {
            _lessonSolving = false;
            _setBtnState('DH_Practice_Btn', _C_BLUE, _t('btn_run'));
            const btn = document.getElementById('DH_Practice_Btn');
            if (btn) btn.disabled = !_user;
        }

        function _resumePracticeIfNeeded() {
            const saved = sessionStorage.getItem('dh2_practice');
            if (!saved) return;
            try {
                const {
                    active,
                    count,
                    done
                } = JSON.parse(saved);
                if (!active) return;
                _lessonsToSolve = count;
                _currentLessonCount = done;
                sessionStorage.setItem('dh2_practice', JSON.stringify({
                    active: true,
                    count,
                    done
                }));
                if (window.location.pathname.startsWith('/practice')) {
                    _lessonSolving = true;
                    if (_user) {
                        _setBtnState('DH_Practice_Btn', _C_RED, _t('btn_stop'));
                        _solveCurrentLesson();
                    } else {

                        const orig = _setConn.bind(null);
                        const check = setInterval(() => {
                            if (_user) {
                                clearInterval(check);
                                _setBtnState('DH_Practice_Btn', _C_RED, _t('btn_stop'));
                                _solveCurrentLesson();
                            }
                        }, 500);
                    }
                }
            } catch {}
        }

        async function _farmLeague() {
            const LB = 'https://duolingo-leaderboards-prod.duolingo.com/leaderboards/7d9f5dd1-8423-491a-91f2-2532052038ce';
            const prog = document.getElementById('DH_League_Prog');
            const fill = document.getElementById('DH_League_Fill');
            if (prog) prog.classList.add('on');
            _setBtnState('DH_League_Btn', _C_RED, _t('btn_stop'));
            while (_running) {
                try {
                    const r = await _gm('GET', `${LB}/users/${_sub}?client_unlocked=true&get_reactions=true&_=${Date.now()}`);
                    if (r.status !== 200) {
                        await _sleep(3000);
                        continue;
                    }
                    const data = JSON.parse(r.responseText);
                    const ranks = data?.active?.cohort?.rankings || [];
                    const me = ranks.find(u => u.user_id == _sub);
                    if (!me) {
                        _notif('⚠️', 'Not in league!', 'Join a league first.');
                        break;
                    }
                    const rank = ranks.indexOf(me) + 1;
                    const top1 = ranks[0];
                    const gap = top1.score - me.score;
                    if (rank === 1 && gap <= 0) {
                        _notif('🏆', 'League #1!', 'You reached Rank #1!');
                        break;
                    }
                    if (fill) fill.style.width = Math.min(95, Math.floor((me.score / Math.max(top1.score, 1)) * 100)) + '%';
                    if (gap + 100 > 0) {
                        const ok = await _storyXP(469);
                        if (!ok) await _sleep(3000);
                    }
                    await _sleep(_delay);
                } catch {
                    await _sleep(5000);
                }
            }
            _resetLeague();
        }

        function _getQuestTimestamp(goalId) {
            const m = goalId.match(/^(\d{4})_(\d{2})_monthly/);
            if (m) {
                return new Date(Date.UTC(parseInt(m[1]), parseInt(m[2]) - 1, 15, 12, 0, 0)).toISOString();
            }
            return new Date().toISOString();
        }
        async function _getGoals() {
            return new Promise(r => GM_xmlhttpRequest({
                method: 'GET',
                url: `${GOALS_API}/schema?ui_language=en&_=${Date.now()}`,
                headers: _goalHdrs(_jwt),
                onload: res => r(res.status === 200 ? JSON.parse(res.responseText) : null),
                onerror: () => r(null)
            }));
        }
        async function _getProgress() {
            const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
            return new Promise(r => GM_xmlhttpRequest({
                method: 'GET',
                url: `${GOALS_API}/users/${_sub}/progress?timezone=${encodeURIComponent(tz)}&ui_language=en`,
                headers: _goalHdrs(_jwt),
                onload: res => r(res.status === 200 ? JSON.parse(res.responseText) : null),
                onerror: () => r(null)
            }));
        }
        async function _bruteForceGoals(metrics) {
            const updates = metrics.map(m => ({
                metric: m,
                quantity: 2000
            }));
            updates.push({
                metric: 'QUESTS',
                quantity: 1
            });
            return new Promise(r => GM_xmlhttpRequest({
                method: 'POST',
                url: `${GOALS_API}/users/${_sub}/progress/batch`,
                headers: _goalHdrs(_jwt),
                data: JSON.stringify({
                    metric_updates: updates,
                    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
                    timestamp: new Date().toISOString()
                }),
                onload: res => r(res.status === 200),
                onerror: () => r(false)
            }));
        }
        async function _updateGoal(metric, amount, goalId) {
            return new Promise(r => GM_xmlhttpRequest({
                method: 'POST',
                url: `${GOALS_API}/users/${_sub}/progress/batch`,
                headers: _goalHdrs(_jwt),
                data: JSON.stringify({
                    metric_updates: [{
                        metric,
                        quantity: amount
                    }],
                    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
                    timestamp: _getQuestTimestamp(goalId)
                }),
                onload: res => r(res.status === 200),
                onerror: () => r(false)
            }));
        }

        async function _farmDailyQuest() {
            _setBtnState('DH_Quest_Btn', _C_GRAY, _t('btn_loading'));
            const [schema, progress] = await Promise.all([_getGoals(), _getProgress()]);
            if (!schema || !progress) {
                _notif('❌', _t('notif_error_title'), _t('notif_daily_error'));
                _resetBtn('DH_Quest_Btn', _t('btn_run'));
                return;
            }
            const earned = new Set(progress.badges?.earned || []);
            const daily = (schema.goals || []).filter(g => g.category && g.category.includes('DAILY'));
            const metrics = new Set(daily.filter(g => !earned.has(g.badgeId) && !earned.has(g.goalId) && g.metric).map(g => g.metric));
            if (!metrics.size) {
                _notif('✅', _t('notif_all_done_title'), _t('notif_all_done_body'));
                _setBtnDone('DH_Quest_Btn', _t('btn_done'));
                setTimeout(() => _resetBtn('DH_Quest_Btn', _t('btn_run')), 3000);
                return;
            }
            _setBtnState('DH_Quest_Btn', _C_RED, _t('btn_running'));
            const ok = await _bruteForceGoals(Array.from(metrics));
            if (ok) {
                _notif('✅', _t('notif_daily_done_title'), _t('notif_daily_done_body', metrics.size));
                _setBtnDone('DH_Quest_Btn', _t('btn_done'));
                setTimeout(() => _resetBtn('DH_Quest_Btn', _t('btn_run')), 3000);
            } else {
                _notif('❌', _t('notif_error_title'), _t('notif_daily_fail'));
                _resetBtn('DH_Quest_Btn', _t('btn_run'));
            }
        }

        function _formatItem(id) {
            return id.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
        }

        function _categorizeItem(item) {
            const id = item.id || '';
            if (id.includes('streak_freeze')) return {
                cat: 'Streak Freezes',
                icon: 'https://d35aaqx5ub95lt.cloudfront.net/images/icons/216ddc11afcbb98f44e53d565ccf479e.svg'
            };
            if (id.includes('xp_boost')) return {
                cat: 'XP Boosts',
                icon: 'https://d35aaqx5ub95lt.cloudfront.net/images/icons/68c1fd0f467456a4c607ecc0ac040533.svg'
            };
            if (id.includes('health') || id.includes('heart')) return {
                cat: 'Hearts',
                icon: 'https://d35aaqx5ub95lt.cloudfront.net/images/hearts/547ffcf0e6256af421ad1a32c26b8f1a.svg'
            };
            if (id.includes('gem')) return {
                cat: 'Gems',
                icon: 'https://d35aaqx5ub95lt.cloudfront.net/images/gems/45c14e05be9c1af1d7d0b54c6eed7eee.svg'
            };
            if (item.type === 'outfit') return {
                cat: 'Outfits',
                icon: 'https://d35aaqx5ub95lt.cloudfront.net/vendor/0cecd302cf0bcd0f73d51768feff75fe.svg'
            };
            if (id.includes('free_taste')) return {
                cat: 'Free Taste',
                icon: 'https://d35aaqx5ub95lt.cloudfront.net/images/super/11db6cd6f69cb2e3c5046b915be8e669.svg'
            };
            return {
                cat: 'Misc',
                icon: 'https://d35aaqx5ub95lt.cloudfront.net/images/leagues/9fadb349c2ece257386a0e576359c867.svg'
            };
        }

        async function _getShopItems() {
            return new Promise(r => GM_xmlhttpRequest({
                method: 'GET',
                url: 'https://www.duolingo.com/2023-05-23/shop-items',
                headers: _hdrs,
                onload: res => {
                    try {
                        if (res.status === 200) r(JSON.parse(res.responseText).shopItems || []);
                        else r([]);
                    } catch {
                        r([]);
                    }
                },
                onerror: () => r([])
            }));
        }

        async function _buyShopItem(itemId) {
            const payload = {
                itemName: itemId,
                isFree: true,
                consumed: true,
                fromLanguage: _user.fromLanguage,
                learningLanguage: _user.learningLanguage
            };
            return new Promise(r => GM_xmlhttpRequest({
                method: 'POST',
                url: `https://www.duolingo.com/2017-06-30/users/${_sub}/shop-items`,
                headers: _hdrs,
                data: JSON.stringify(payload),
                onload: res => r(res.status === 200),
                onerror: () => r(false)
            }));
        }

        let _allShopItems = [];

        function _renderShop(items, filter = '') {
            const container = document.getElementById('DH_Shop_Container');
            container.innerHTML = '';

            // ── Pinned: Super 3 Days ──
            const _sSec = document.createElement('div');
            _sSec.className = 'DH_Cat_Header DH_NoSel';
            _sSec.textContent = 'Super';
            container.appendChild(_sSec);
            const _sGrid = document.createElement('div');
            _sGrid.className = 'DH_Shop_Grid';
            const _sCard = document.createElement('div');
            _sCard.className = 'DH_Shop_Card';
            _sCard.innerHTML = `<img src="https://d35aaqx5ub95lt.cloudfront.net/images/legendary/158dbe277bf83116d04692b969a27aa3.svg" class="DH_Shop_Ico"><div class="DH_Shop_Name DH_NoSel">Super 3 Days</div><button class="DH_Shop_Btn">GET</button>`;
            const _sBtn = _sCard.querySelector('.DH_Shop_Btn');
            _sBtn.onclick = async () => {
                _sBtn.className = 'DH_Shop_Btn loading';
                _sBtn.textContent = '...';
                try {
                    const payload = {
                        itemName: 'immersive_subscription',
                        isFree: true,
                        consumed: true,
                        fromLanguage: _user.fromLanguage,
                        learningLanguage: _user.learningLanguage,
                        productId: 'com.duolingo.immersive_free_trial_subscription'
                    };
                    const r = await _gm('POST', `https://www.duolingo.com/2017-06-30/users/${_sub}/shop-items`, payload);
                    if (r.status === 200 || r.status === 201) {
                        _sBtn.className = 'DH_Shop_Btn got';
                        _sBtn.textContent = _t('btn_got');
                        _notif('✅', _t('notif_super_title'), _t('notif_super_body'), 7);
                        setTimeout(() => { _sBtn.className = 'DH_Shop_Btn'; _sBtn.textContent = _t('btn_get'); }, 3000);
                    } else {
                        _sBtn.className = 'DH_Shop_Btn fail';
                        _sBtn.textContent = _t('btn_failed');
                        _notif('❌', _t('notif_failed_title'), _t('notif_super_fail'), 5);
                        setTimeout(() => { _sBtn.className = 'DH_Shop_Btn'; _sBtn.textContent = _t('btn_get'); }, 2000);
                    }
                } catch {
                    _sBtn.className = 'DH_Shop_Btn fail';
                    _sBtn.textContent = _t('btn_failed');
                    setTimeout(() => { _sBtn.className = 'DH_Shop_Btn'; _sBtn.textContent = _t('btn_get'); }, 2000);
                }
            };
            _sGrid.appendChild(_sCard);
            container.appendChild(_sGrid);

            const valid = items.filter(i => i.currencyType === 'XGM' && !i.id.includes('gift'));
            const f = filter.trim().toLowerCase();
            const filtered = f ? valid.filter(i => (i.name || _formatItem(i.id)).toLowerCase().includes(f)) : valid;
            if (!filtered.length) {
                const p = document.createElement('p');
                p.className = 'DH_T2 DH_NoSel';
                p.style.cssText = 'text-align:center;padding:8px 0;';
                p.textContent = f ?
                    _t('no_items_found') :
                    _t('no_items_available');
                container.appendChild(p);
                return;
            }
            const ORDER = ['Streak Freezes', 'XP Boosts', 'Hearts', 'Gems', 'Outfits', 'Free Taste', 'Misc'];
            const grouped = {};
            filtered.forEach(i => {
                const {
                    cat,
                    icon
                } = _categorizeItem(i);
                if (!grouped[cat]) grouped[cat] = [];
                let name = i.name || _formatItem(i.id);
                if (i.id.includes('xp_boost') && i.id.match(/\d+$/)) name += ' Mins';
                grouped[cat].push({
                    ...i,
                    displayName: name,
                    icon,
                    cat
                });
            });
            ORDER.forEach(cat => {
                if (!grouped[cat]) return;
                const header = document.createElement('div');
                header.className = 'DH_Cat_Header DH_NoSel';
                header.textContent = cat;
                container.appendChild(header);
                const grid = document.createElement('div');
                grid.className = 'DH_Shop_Grid';
                grouped[cat].forEach(item => {
                    const card = document.createElement('div');
                    card.className = 'DH_Shop_Card';
                    card.innerHTML = `
                <img src="${item.icon}" class="DH_Shop_Ico">
                <div class="DH_Shop_Name DH_NoSel">${item.displayName}</div>
                <button class="DH_Shop_Btn" data-id="${item.id}">GET</button>`;
                    const ico = card.querySelector('.DH_Shop_Ico');
                    if (ico) ico.onerror = function() {
                        this.style.display = 'none';
                        const fb = document.createElement('div');
                        fb.style.cssText = 'width:36px;height:36px;display:flex;align-items:center;justify-content:center;font-size:22px;';
                        fb.textContent = '🎁';
                        this.parentNode.insertBefore(fb, this);
                    };
                    const btn = card.querySelector('.DH_Shop_Btn');
                    btn.onclick = async () => {
                        btn.className = 'DH_Shop_Btn loading';
                        btn.textContent = '...';
                        setTimeout(() => {
                            if (btn.textContent === '...') btn.textContent = '50%';
                        }, 300);
                        const ok = await _buyShopItem(item.id);
                        btn.textContent = '100%';
                        setTimeout(() => {
                            if (ok) {
                                btn.className = 'DH_Shop_Btn got';
                                btn.textContent = _t('btn_got');
                                _notif('🎁', 'Shop', 'Got ' + item.displayName + '!');
                                setTimeout(() => {
                                    btn.className = 'DH_Shop_Btn';
                                    btn.textContent = _t('btn_get');
                                }, 3000);
                            } else {
                                btn.className = 'DH_Shop_Btn fail';
                                btn.textContent = _t('btn_failed');
                                setTimeout(() => {
                                    btn.className = 'DH_Shop_Btn';
                                    btn.textContent = _t('btn_get');
                                }, 2000);
                                _notif('❌', 'Shop', 'Failed to get item.');
                            }
                        }, 300);
                    };
                    grid.appendChild(card);
                });
                container.appendChild(grid);
            });
        }

        async function _loadShop() {
            const container = document.getElementById('DH_Shop_Container');
            const cached = localStorage.getItem('dh2_shop');
            if (cached) {
                try {
                    const items = JSON.parse(cached);
                    if (items && items.length) {
                        _allShopItems = items;
                        _renderShop(items);
                        return;
                    }
                } catch {
                    localStorage.removeItem('dh2_shop');
                }
            }
            container.innerHTML = `
    <p class="DH_T2 DH_NoSel"
       style="text-align:center;padding:8px 0;">
        ${_t('loading_shop')}
    </p>
`;
            const items = await _getShopItems();
            if (items.length) localStorage.setItem('dh2_shop', JSON.stringify(items));
            _allShopItems = items;
            _renderShop(items);
        }

        // _installFakeSuper() has been merged into the unified page-context hook
        // at the top of the script (together with Lesson Shortener & Stories Shortener).
        // No separate call needed here.

        function _resetLeague() {
            const btn = document.getElementById('DH_League_Btn');
            if (!btn) return;
            btn.disabled = false;
            _setBtnState('DH_League_Btn', _C_BLUE, _t('btn_run'));
            const fill = document.getElementById('DH_League_Fill');
            if (fill) fill.style.width = '0%';
            const prog = document.getElementById('DH_League_Prog');
            if (prog) setTimeout(() => prog.classList.remove('on'), 2000);
        }




        function _v1UpdateDisplayNow() {
            requestAnimationFrame(() => {
                const xi = document.getElementById('DH_V1_XP_Input');
                const gi = document.getElementById('DH_V1_Gem_Input');
                const si = document.getElementById('DH_V1_Streak_Input');


                if (xi) {
                    xi.value = _v1Earned.xp > 0 ? String(_v1Earned.xp) : '';
                }
                if (gi) {
                    gi.value = _v1Earned.gems > 0 ? String(_v1Earned.gems) : '';
                }
                if (si) {
                    si.value = _v1Earned.streak > 0 ? String(_v1Earned.streak) : '';
                }
            });
        }

        function _v1SetBtnState(btnId, cfg, label) {
            const btn = document.getElementById(btnId);
            if (!btn) return;
            const lbl = btn.querySelector('.DH_Btn_Label');
            if (!lbl) return;
            btn.style.background = cfg.bg;
            btn.style.outline = `2px solid ${cfg.outline}`;
            btn.style.outlineOffset = '-2px';
            lbl.style.color = cfg.tc;
            lbl.textContent = label;
        }

        function _v1SetProg(id, pct) {
            const prog = document.getElementById(id + '_Prog');
            const fill = document.getElementById(id + '_Fill');
            if (prog && !prog.classList.contains('on')) prog.classList.add('on');
            if (fill) fill.style.width = Math.min(100, Math.max(1, pct)) + '%';
        }

        function _v1ClearProg(id) {
            const prog = document.getElementById(id + '_Prog');
            const fill = document.getElementById(id + '_Fill');
            setTimeout(() => {
                if (prog) prog.classList.remove('on');
            }, 2000);
            if (fill) fill.style.width = '0%';
        }


        let _v1SkillId = null;
        async function _v1FetchSkillId() {
            if (_v1SkillId) return _v1SkillId;
            try {
                const r = await _gm('GET',
                    `https://www.duolingo.com/2017-06-30/users/${_sub}?fields=currentCourse{pathSectioned{units{levels{pathLevelMetadata{skillId},pathLevelClientData{skillId}}}}}`
                );
                if (r.status !== 200) return null;
                const d = JSON.parse(r.responseText);
                const sections = d.currentCourse?.pathSectioned || [];
                for (const sec of sections)
                    for (const unit of (sec.units || []))
                        for (const lvl of (unit.levels || [])) {
                            const sid = lvl.pathLevelMetadata?.skillId || lvl.pathLevelClientData?.skillId;
                            if (sid) {
                                _v1SkillId = sid;
                                return sid;
                            }
                        }
            } catch {}
            return null;
        }


        async function _v1XP110Once() {
            const sid = await _v1FetchSkillId();
            if (!sid) return false;
            try {
                const sr = await _gm('POST', 'https://www.duolingo.com/2017-06-30/sessions', {
                    challengeTypes: [],
                    fromLanguage: _user.fromLanguage,
                    learningLanguage: _user.learningLanguage,
                    type: 'UNIT_TEST',
                    skillIds: [sid]
                });
                if (!sr || sr.status !== 200) return false;
                const sess = JSON.parse(sr.responseText);
                const now = Math.floor(Date.now() / 1000);
                const ur = await _gm('PUT', `https://www.duolingo.com/2017-06-30/sessions/${sess.id}`, {
                    id: sess.id,
                    metadata: sess.metadata,
                    type: 'UNIT_TEST',
                    fromLanguage: _user.fromLanguage,
                    learningLanguage: _user.learningLanguage,
                    challenges: [],
                    adaptiveChallenges: [],
                    sessionExperimentRecord: [],
                    experiments_with_treatment_contexts: [],
                    adaptiveInterleavedChallenges: [],
                    sessionStartExperiments: [],
                    trackingProperties: [],
                    ttsAnnotations: [],
                    heartsLeft: 0,
                    startTime: now,
                    enableBonusPoints: true,
                    endTime: now + 60,
                    failed: false,
                    maxInLessonStreak: 9,
                    shouldLearnThings: true,
                    hasBoost: true,
                    happyHourBonusXp: 10,
                    pathLevelSpecifics: {
                        unitIndex: 0
                    }
                });
                if (ur && ur.status === 200) {
                    const d = JSON.parse(ur.responseText);
                    return d?.awardedXp || d?.xpGain || 110;
                }
            } catch {}
            return false;
        }


        async function _v1FarmXP() {
            _v1Earned.xp = 0;
            _v1UpdateDisplayNow();
            _v1SetBtnState('DH_V1_XP_Btn', _C_RED, _t('btn_stop'));
            _v1SetProg('DH_V1_XP', 1);

            // Always start with story API (499 XP), silently fallback to global API (110 XP) on failure
            let use499 = true;

            let cons429 = 0;
            const MAX_429 = 2;
            let fallbackErrors = 0;
            const MAX_FALLBACK = 5;
            let loopPct = 0;
            let fallbackLoops = 0; // count loops in fallback mode, re-probe every 10

            // Slug is fixed (fr-en-le-passeport), no need to probe before starting
            _workingSlug = 'fr-en-le-passeport';
            _workingSlugFrom = 'fr';
            _workingSlugLearn = 'en';
            _v1FetchSkillId();

            while (_v1Running && _v1Task === 'xp') {
                if (use499) {
                    let status = 0;
                    try {
                        const now = Math.floor(Date.now() / 1000);
                        const dur = Math.floor(Math.random() * 121 + 300);
                        const r = await _gm('POST', `https://stories.duolingo.com/api2/stories/${_workingSlug}/complete`, {
                            awardXp: true,
                            completedBonusChallenge: true,
                            fromLanguage: _workingSlugFrom,
                            learningLanguage: _workingSlugLearn,
                            hasXpBoost: false,
                            illustrationFormat: 'svg',
                            isFeaturedStoryInPracticeHub: true,
                            isLegendaryMode: true,
                            isV2Redo: false,
                            isV2Story: false,
                            masterVersion: true,
                            maxScore: 0,
                            score: 0,
                            happyHourBonusXp: 469,
                            startTime: now,
                            endTime: now + dur
                        });
                        status = r.status;
                    } catch {}

                    if (status === 200) {
                        cons429 = 0;
                        fallbackErrors = 0;
                        _v1Earned.xp += 499;
                        _v1UpdateDisplayNow();
                        if (_user) { _user.totalXp = (_user.totalXp || 0) + 499; _v1SyncUser(); }
                        loopPct = (loopPct + 2) % 99 + 1;
                        _v1SetProg('DH_V1_XP', loopPct);
                    } else if (status === 429) {
                        cons429++;
                        if (cons429 >= MAX_429) {
                            use499 = false;
                            fallbackLoops = 0;
                        }
                        await _sleep(_delay * 2);
                        continue;
                    } else {
                        use499 = false;
                        fallbackLoops = 0;
                        continue;
                    }
                } else {
                    // Global API — DuoFarmer-style UNIT_TEST session (110 XP)
                    // Every 10 fallback loops, try story API again
                    if (fallbackLoops > 0 && fallbackLoops % 10 === 0) {
                        use499 = true;
                        cons429 = 0;
                    }
                    fallbackLoops++;
                    const earned = await _v1XP110Once();
                    if (earned) {
                        fallbackErrors = 0;
                        _v1Earned.xp += earned;
                        _v1UpdateDisplayNow();
                        loopPct = (loopPct + 1) % 99 + 1;
                        _v1SetProg('DH_V1_XP', loopPct);
                    } else {
                        fallbackErrors++;
                        if (fallbackErrors >= MAX_FALLBACK) {
                            _notif('❌', 'V1 XP', 'Too many errors, stopping.');
                            break;
                        }
                        await _sleep(_delay * 3);
                        continue;
                    }
                }
                await _sleep(_delay);
            }

            _v1ClearProg('DH_V1_XP');
            _v1SetBtnState('DH_V1_XP_Btn', _C_BLUE, _t('btn_run'));
            const _v1xb = document.getElementById('DH_V1_XP_Btn'); if (_v1xb) _v1xb.disabled = !_user;
            _v1Running = false;
            _v1Task = null;
            if (_v1Earned.xp > 0) {
                _notif('✅', 'XP Farm Done!', `Farmed ${_v1Earned.xp.toLocaleString()} XP.`);
                setTimeout(_connect, 1500);
            }
        }

        async function _v1FarmGems() {
            _v1Earned.gems = 0;
            _v1UpdateDisplayNow();
            _v1SetBtnState('DH_V1_Gem_Btn', _C_RED, _t('btn_stop'));
            _v1SetProg('DH_V1_Gem', 1);

            let loopPct = 0;

            outer:
                while (_v1Running && _v1Task === 'gems') {
                    const rewards = await _fetchGemRewards();

                    if (rewards.length === 0) {
                        _notif('⚠️', 'Gem Farm', 'No rewards available. Retrying…', 3);
                        await _sleep(_delay * 2);
                        continue;
                    }

                    const gemsBefore = await _getGemCount() ?? (_user?.gems ?? 0);

                    // Fire all rewards in parallel batches — no inter-batch delay
                    for (let i = 0; i < rewards.length; i += THREADS) {
                        if (!_v1Running || _v1Task !== 'gems') break outer;
                        const batch = rewards.slice(i, i + THREADS);
                        await Promise.all(batch.map(r => _exploitGemReward(r.id)));
                    }

                    // Single gem count check after full pass
                    const now = await _getGemCount();
                    if (now !== null) {
                        const cycle = Math.max(0, now - gemsBefore);
                        _v1Earned.gems += cycle;
                        _v1UpdateDisplayNow();
                        loopPct = (loopPct + 1) % 99 + 1;
                        _v1SetProg('DH_V1_Gem', loopPct);
                        if (_user) {
                            _user.gems = now;
                            _v1SyncUser();
                        }
                    }

                    // Short cooldown before next pass
                    await _sleep(Math.max(200, _delay / 2));
                }

            _v1ClearProg('DH_V1_Gem');
            _v1SetBtnState('DH_V1_Gem_Btn', _C_BLUE, _t('btn_run'));
            const _v1gb = document.getElementById('DH_V1_Gem_Btn'); if (_v1gb) _v1gb.disabled = !_user;
            _v1Running = false;
            _v1Task = null;
            if (_v1Earned.gems > 0) {
                _notif('✅', 'Gem Farm Done!', `+${_v1Earned.gems.toLocaleString()} gems gained.`);
                setTimeout(_connect, 1500);
            }
        }



        async function _v1FarmStreak() {
            const CH = ["assist","characterIntro","characterMatch","characterPuzzle","characterSelect","characterTrace","characterWrite","completeReverseTranslation","definition","dialogue","extendedMatch","extendedListenMatch","form","freeResponse","gapFill","judge","listen","listenComplete","listenMatch","match","name","listenComprehension","listenIsolation","listenSpeak","listenTap","orderTapComplete","partialListen","partialReverseTranslate","patternTapComplete","radioBinary","radioImageSelect","radioListenMatch","radioListenRecognize","radioSelect","readComprehension","reverseAssist","sameDifferent","select","selectPronunciation","selectTranscription","svgPuzzle","syllableTap","syllableListenTap","speak","tapCloze","tapClozeTable","tapComplete","tapCompleteTable","tapDescribe","translate","transliterate","transliterationAssist","typeCloze","typeClozeTable","typeComplete","typeCompleteTable","writeComprehension"];
            _v1Earned.streak = 0;
            _v1UpdateDisplayNow();
            _v1SetBtnState('DH_V1_Streak_Btn', _C_RED, _t('btn_stop'));
            _v1SetProg('DH_V1_Streak', 1);

            // Timezone-aware base date
            const tz = _accountTimezone();
            let base;
            const dateStr = _user?.streakData?.currentStreak?.startDate;
            if (dateStr && /^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
                const [y, m, d] = dateStr.split('-').map(Number);
                base = { year: y, month: m, day: d };
            } else {
                base = _accountToday(tz);
            }

            let dayIdx = 0;

            while (_v1Running && _v1Task === 'streak') {
                const endSecs = _wallClockToSeconds(
                    tz, base.year, base.month, base.day - 1 - dayIdx, 12, 0, 0
                );

                let sess = null;
                await new Promise(r => GM_xmlhttpRequest({
                    method: 'POST',
                    url: 'https://www.duolingo.com/2017-06-30/sessions',
                    headers: _hdrs,
                    data: JSON.stringify({
                        challengeTypes: CH,
                        fromLanguage: _user.fromLanguage,
                        isFinalLevel: false, isV2: true, juicy: true,
                        learningLanguage: _user.learningLanguage,
                        smartTipsVersion: 2, type: 'GLOBAL_PRACTICE'
                    }),
                    onload: res => { if (res.status === 200) { try { sess = JSON.parse(res.responseText); } catch {} } r(); },
                    onerror: () => r(), timeout: 15000, ontimeout: () => r()
                }));

                if (sess?.id) {
                    let ok = false;
                    await new Promise(r => GM_xmlhttpRequest({
                        method: 'PUT',
                        url: `https://www.duolingo.com/2017-06-30/sessions/${sess.id}`,
                        headers: _hdrs,
                        data: JSON.stringify({
                            ...sess,
                            heartsLeft: 5,
                            startTime: endSecs - 1, endTime: endSecs,
                            enableBonusPoints: false, failed: false,
                            maxInLessonStreak: 9, shouldLearnThings: true
                        }),
                        onload: res => { ok = res.status === 200; r(); },
                        onerror: () => r(), timeout: 15000, ontimeout: () => r()
                    }));

                    if (ok) {
                        _v1Earned.streak++;
                        if (_user) {
                            _user.streak = (_user.streak || 0) + 1;
                            _v1SyncUser();
                        }
                        _v1UpdateDisplayNow();
                    }
                }

                const loopPct = (dayIdx % 9) * 11 + 1;
                _v1SetProg('DH_V1_Streak', loopPct);
                dayIdx++;
            }

            _v1ClearProg('DH_V1_Streak');
            _v1SetBtnState('DH_V1_Streak_Btn', _C_BLUE, _t('btn_run'));
            const _v1sb = document.getElementById('DH_V1_Streak_Btn'); if (_v1sb) _v1sb.disabled = !_user;
            _v1Running = false;
            _v1Task = null;
            if (_v1Earned.streak > 0) {
                _notif('🔥', 'Streak Farm Done!', `Farmed ${_v1Earned.streak} streak days.`);
                setTimeout(_connect, 1500);
            } else {
                _notif('⚠️', 'Streak Farm', 'No days could be saved. Please try again.');
            }
        }

                function _v1RunToggle(task) {
            if (_v1Running && _v1Task === task) {
                _v1Running = false;
                _v1Task = null;
                return;
            }
            if (_v1Running) {
                _notif('⚠️', 'Busy', 'Stop current V1 farm first.');
                return;
            }
            if (!_user) {
                _notif('⚠️', 'Not connected', 'Please wait.');
                return;
            }
            _v1Running = true;
            _v1Task = task;
            if (task === 'xp') _v1FarmXP();
            if (task === 'gems') _v1FarmGems();
            if (task === 'streak') _v1FarmStreak();
        }


        async function _run(type, val) {
            if (_running) {
                _running = false;
                _notif('⏹️', 'Stopped', 'Farm stopped.');
                return;
            }
            if (!_user) {
                _notif('⚠️', 'Not connected', 'Please wait.');
                return;
            }
            _running = true;
            _task = type;
            try {
                if (type === 'xp') await _farmXP(val);
                if (type === 'gem') await _farmGems();
                if (type === 'streak') await _farmStreak(val);
                if (type === 'league') await _farmLeague();
            } catch (e) {
                _notif('❌', 'Error', e.message);
            }
            // If stopped mid-farm, reset XP/Streak buttons (Gem and League reset themselves)
            if (!_running) {
                if (type === 'xp') _resetBtn('DH_XP_Btn', _t('btn_get'));
                if (type === 'streak') _resetBtn('DH_Streak_Btn', _t('btn_run'));
            }
            _running = false;
            _task = null;
        }

        function _accGetAll() {
            try {
                return JSON.parse(localStorage.getItem('dh2_accounts') || '[]');
            } catch {
                return [];
            }
        }

        function _accSetAll(arr) {
            localStorage.setItem('dh2_accounts', JSON.stringify(arr));
        }

        function _accSaveCurrent() {
            if (!_user || !_jwt || !_sub) {
                _notif('⚠️', 'Not connected', 'Please wait for connection.');
                return;
            }
            const all = _accGetAll();
            if (all.find(a => a.id == _sub)) {
                _notif('ℹ️', 'Already saved', 'This account is already in the list.');
                return;
            }
            const pic = _bestAvatarUrl(_user.picture, _sub);
            all.push({
                id: _sub,
                username: _user.username || 'User',
                pic,
                token: _jwt
            });
            _accSetAll(all);
            _notif('✅', 'Account Saved', `Saved account: ${_user.username}`);
            _renderAccounts();
        }

        function _accRemove(id) {
            const all = _accGetAll().filter(a => a.id != id);
            _accSetAll(all);
            _renderAccounts();
            _notif('🗑️', 'Removed', 'Account removed from list.');
        }

        function _accLogin(id) {
            const acc = _accGetAll().find(a => a.id == id);
            if (!acc) {
                _notif('⚠️', 'Not found', 'Account not found.');
                return;
            }
            document.cookie = `jwt_token=${acc.token}; domain=.duolingo.com; path=/; max-age=31536000`;
            window.location.reload();
        }

        function _renderAccounts() {
            const wrap = document.getElementById('DH_AccList_Wrap');
            if (!wrap) return;
            const all = _accGetAll();
            const saveBtn = document.getElementById('DH_AccSave_Btn');
            if (saveBtn) saveBtn.disabled = !_user;

            if (all.length === 0) {
                wrap.innerHTML = `
    <p class="DH_T2 DH_NoSel"
       style="text-align:center;padding:8px 0;">
        ${_t('no_saved_accounts')}
    </p>
`;
                return;
            }
            wrap.innerHTML = '';
            all.forEach(acc => {
                const card = document.createElement('div');
                card.className = 'DH_Acc_Card';
                const isCurrentUser = _sub && acc.id == _sub;
                const picHtml = acc.pic ?
                    `<img src="${acc.pic}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;" onerror="this.src='https://raw.githubusercontent.com/DuoHacker/DuoHacker/refs/heads/main/images/avatar-default.jpg'">` :
                    `<img src="https://raw.githubusercontent.com/DuoHacker/DuoHacker/refs/heads/main/images/avatar-default.jpg" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`;
                card.innerHTML = `
            <div class="DH_Acc_Avatar">${picHtml}</div>
            <div class="DH_Acc_Info">
                <p class="DH_Acc_Name DH_NoSel">${acc.username}</p>
                ${isCurrentUser
                    ? `<p class="DH_Acc_Sub active DH_NoSel"><svg width="8" height="8" viewBox="0 0 8 8"><circle cx="4" cy="4" r="4" fill="rgb(var(--DH-green))"><animate attributeName="opacity" values="1;0.4;1" dur="2s" repeatCount="indefinite"/></circle></svg>${_t('acc_active')}</p>`
                    : `<p class="DH_Acc_Sub DH_NoSel">ID: ${acc.id}</p>`
                }
            </div>
            <div class="DH_Acc_Action_Row">
                ${!isCurrentUser?`<button class="DH_Acc_Btn login" data-id="${acc.id}">${_t('acc_login_btn')}</button>`:''}
                <button class="DH_Acc_Btn del" data-id="${acc.id}">✕</button>
            </div>
        `;
                card.querySelector('.del').addEventListener('click', e => {
                    e.stopPropagation();
                    _accRemove(acc.id);
                });
                const loginBtn = card.querySelector('.login');
                if (loginBtn) loginBtn.addEventListener('click', e => {
                    e.stopPropagation();
                    _accLogin(acc.id);
                });
                wrap.appendChild(card);
            });
        }

        async function _accRefreshAll() {
            const all = _accGetAll();
            if (!all.length) return;

            let changed = false;
            await Promise.all(all.map(async (acc) => {
                try {
                    const r = await _gm(
                        'GET',
                        `https://www.duolingo.com/2017-06-30/users/${acc.id}?fields=id,username,picture`,
                        null,
                        { 'Authorization': 'Bearer ' + acc.token, 'Content-Type': 'application/json' }
                    );
                    if (r.status !== 200) return;
                    const fresh = JSON.parse(r.responseText);

                    const pic = _bestAvatarUrl(fresh.picture, fresh.id || acc.id);

                    if (fresh.username && fresh.username !== acc.username) {
                        acc.username = fresh.username;
                        changed = true;
                    }
                    if (pic !== undefined && pic !== acc.pic) {
                        acc.pic = pic;
                        changed = true;
                    }
                } catch { /* token hết hạn hoac lỗi mạng — bỏ qua */ }
            }));

            if (changed) {
                _accSetAll(all);
                _renderAccounts();
            }
        }

        function _goalHdrsLocal(jwt) {
            return {
                'Content-Type': 'application/json',
                'accept': 'application/json; charset=UTF-8',
                'x-requested-with': 'XMLHttpRequest',
                'Authorization': 'Bearer ' + jwt,
                'cookie': 'jwt_token=' + jwt,
                'origin': 'https://www.duolingo.com',
                'x-amzn-trace-id': 'User=' + (_sub || ''),
            };
        }

        function _mqGm(method, url, data, hdrs) {
            return new Promise((res, rej) => GM_xmlhttpRequest({
                method,
                url,
                headers: hdrs,
                data: data ? JSON.stringify(data) : null,
                onload: r => res(r),
                onerror: () => rej(new Error('Network')),
                timeout: 15000,
                ontimeout: () => rej(new Error('Timeout'))
            }));
        }

        function _mqGetTimestamp(goalId) {
            const m = goalId.match(/^(\d{4})_(\d{2})_monthly/);
            if (m) {
                const d = new Date(Date.UTC(parseInt(m[1]), parseInt(m[2]) - 1, 15, 12, 0, 0));
                return d.toISOString();
            }
            return new Date().toISOString();
        }

        async function _loadMonthlyQuests() {
            const cont = document.getElementById('DH_MQ_Container');
            if (!cont) return;
            const claimBtn = document.getElementById('DH_MQ_ClaimAll_Btn');
            if (!_jwt || !_sub) {
                cont.innerHTML = `<p class="DH_T2 DH_NoSel" style="text-align:center;padding:8px 0;">${_t('not_connected')}</p>`;
                return;
            }
            cont.innerHTML = `<p class="DH_T2 DH_NoSel" style="text-align:center;padding:8px 0;">⟳ ${_t('loading_quests')}</p>`;
            const gh = _goalHdrsLocal(_jwt);
            const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
            try {
                const [schemaR, progR] = await Promise.all([
                    _mqGm('GET', `${GOALS_API}/schema?ui_language=en&_=${Date.now()}`, null, gh),
                    _mqGm('GET', `${GOALS_API}/users/${_sub}/progress?timezone=${tz}&ui_language=en`, null, gh)
                ]);
                if (schemaR.status !== 200 || progR.status !== 200) throw new Error('API error');
                const schema = JSON.parse(schemaR.responseText);
                const prog = JSON.parse(progR.responseText);
                const progress = prog.goals?.progress || {};
                const earned = new Set(prog.badges?.earned || []);
                _questState = {
                    schema,
                    progress,
                    earned
                };
                _renderMonthlyQuests(schema, progress, earned, gh);
                if (claimBtn) {
                    claimBtn.disabled = false;
                }
            } catch (e) {
                console.error('[DH] Monthly quest load error:', e, e?.message);
                cont.innerHTML = `<p class="DH_T2 DH_NoSel" style="text-align:center;color:rgb(var(--DH-red));padding:8px 0;">${_t('quest_failed')}: ${e?.message||''}</p>`;
            }
        }


        // Build a map of goalId -> schema goal for quick lookup
        function _buildSchemaMap(schema) {
            const map = new Map();
            (schema.goals || []).forEach(g => {
                const m = g.goalId.match(/^(\d{4}_\d{2})_monthly/);
                if (!m) return;
                const key = m[1];
                const ex = map.get(key);
                if (!ex || (g.category?.includes('CHALLENGE') && !ex.category?.includes('CHALLENGE'))) {
                    map.set(key, g);
                }
            });
            return map;
        }

        function _renderMonthlyQuests(schema, progress, earned, gh) {
            const cont = document.getElementById('DH_MQ_Container');
            if (!cont) return;
            cont.innerHTML = '';
            const now = new Date();
            const curYM = `${now.getFullYear()}_${String(now.getMonth()+1).padStart(2,'0')}`;

            // Schema map: YYYY_MM -> best goal entry
            const schemaMap = _buildSchemaMap(schema);

            // All metrics from schema (for brute-force old quest claim)
            const allMetrics = [];
            const metricSeen = {};
            (schema.goals || []).forEach(g => {
                if (g.metric && !metricSeen[g.metric] && g.metric !== 'QUESTS') {
                    metricSeen[g.metric] = true;
                    allMetrics.push(g.metric);
                }
            });

            // Collect all monthly YYYY_MM keys from progress (sorted newest first)
            const monthKeys = [];
            Object.keys(progress).forEach(k => {
                const m = k.match(/^(\d{4}_\d{2})_monthly/);
                if (m) monthKeys.push(m[1]);
            });
            // Also add keys from schema not in progress
            schemaMap.forEach((_, key) => { if (!monthKeys.includes(key)) monthKeys.push(key); });
            // Dedupe and sort descending
            const uniqMonths = [...new Set(monthKeys)].sort().reverse();

            if (uniqMonths.length === 0) {
                cont.innerHTML = `<p class="DH_T2 DH_NoSel" style="text-align:center;padding:8px 0;">${_t('no_monthly_quests')}</p>`;
                return;
            }

            uniqMonths.forEach(ym => {
                const isCurrentMonth = ym === curYM;
                const schemaGoal = schemaMap.get(ym);
                const progressKey = Object.keys(progress).find(k => k.startsWith(ym + '_monthly'));
                const rawProg = progressKey ? progress[progressKey] : undefined;
                let cur = typeof rawProg === 'number' ? rawProg : (rawProg?.progress || 0);

                const isEarned = schemaGoal
                    ? (earned.has(schemaGoal.badgeId) || earned.has(schemaGoal.goalId))
                    : (cur > 0 && cur >= (schemaGoal?.threshold || 1));
                const tgt = schemaGoal?.threshold || 1;
                let pct = isEarned ? 100 : Math.min(100, (cur / tgt) * 100);
                if (isEarned) cur = tgt;
                const remaining = Math.max(0, tgt - cur);

                const [yr, mo] = ym.split('_');
                const monthLabel = new Date(parseInt(yr), parseInt(mo)-1, 1)
                    .toLocaleString('default', { month: 'long', year: 'numeric' });
                const title = schemaGoal?.title?.uiString || `Monthly Quest – ${monthLabel}`;
                const metric = schemaGoal?.metric || 'XP';

                let icon = 'https://d35aaqx5ub95lt.cloudfront.net/images/achievement/aca5f82d97f5e67c1acb1ea05a0e6d1a.svg';
                if (schemaGoal) {
                    const badge = schema.badges?.find(x => x.badgeId === schemaGoal.badgeId);
                    if (badge?.icon?.enabled?.lightMode) {
                        icon = badge.icon.enabled.lightMode.svg || badge.icon.enabled.lightMode.url || icon;
                    }
                }

                const item = document.createElement('div');
                item.className = `DH_Quest_Item${isEarned ? ' done' : ''}`;
                item.innerHTML = `
            <img src="${icon}" class="DH_Quest_Icon" onerror="this.src='https://d35aaqx5ub95lt.cloudfront.net/images/achievement/aca5f82d97f5e67c1acb1ea05a0e6d1a.svg'">
            <div class="DH_Quest_Info">
                <p class="DH_Quest_Title DH_NoSel">${title}</p>
                <p class="DH_Quest_Meta DH_NoSel">${isEarned ? _t('mq_completed') : cur + ' / ' + tgt + ' · ' + metric}</p>
                <div class="DH_Quest_Bar_Bg"><div class="DH_Quest_Bar_Fill" style="width:${pct}%"></div></div>
            </div>
            ${!isEarned && !isCurrentMonth ? `<button class="DH_Quest_Get_Btn" data-ym="${ym}" data-metric="${metric}" data-amount="${remaining}" data-id="${progressKey || ym + '_monthly_challenge'}">GET</button>` : ''}
            ${!isEarned && isCurrentMonth && remaining > 0 ? `<button class="DH_Quest_Get_Btn" data-ym="${ym}" data-metric="${metric}" data-amount="${remaining}" data-id="${progressKey || ym + '_monthly_challenge'}">GET +${remaining}</button>` : ''}
            ${isEarned ? `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="flex-shrink:0;"><circle cx="12" cy="12" r="10" fill="rgba(var(--DH-green),0.15)" stroke="rgb(var(--DH-green))" stroke-width="1.5"/><path d="M7.5 12.5l3 3 6-6" stroke="rgb(var(--DH-green))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>` : ''}
        `;

                const btn = item.querySelector('.DH_Quest_Get_Btn');
                if (btn) btn.addEventListener('click', async () => {
                    btn.disabled = true;
                    btn.textContent = '…';
                    try {
                        const [qYr, qMo] = btn.dataset.ym.split('_');
                        const ts = new Date(Date.UTC(parseInt(qYr), parseInt(qMo)-1, 15, 12, 0, 0)).toISOString();
                        // Send only the specific metric for this month
                        const updates = [{ metric: btn.dataset.metric, quantity: parseInt(btn.dataset.amount) }];
                        const payload = {
                            metric_updates: updates,
                            timestamp: ts,
                            timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
                        };
                        // Call twice: 1st updates progress, 2nd triggers badge award (Duolingo quirk)
                        const r1 = await _mqGm('POST', `${GOALS_API}/users/${_sub}/progress/batch`, payload, gh);
                        if (r1.status !== 200) {
                            btn.textContent = 'ERR ' + r1.status;
                            btn.disabled = false;
                            return;
                        }
                        await new Promise(res => setTimeout(res, 800));
                        const r2 = await _mqGm('POST', `${GOALS_API}/users/${_sub}/progress/batch`, payload, gh);
                        if (r2.status === 200) {
                            btn.textContent = '✓';
                            btn.classList.add('done');
                            _notif('✅', 'Quest Done!', 'Progress injected successfully.');
                            setTimeout(() => _loadMonthlyQuests(), 900);
                        } else {
                            btn.textContent = 'ERR ' + r2.status;
                            btn.disabled = false;
                        }
                    } catch(e) {
                        btn.textContent = 'ERR';
                        btn.disabled = false;
                    }
                });
                cont.appendChild(item);
            });
        }

        async function _claimAllMonthly() {
            if (!_questState || !_questState.schema) {
                _notif('⚠️', 'Not loaded', 'Open Monthly Quests first.');
                return;
            }
            const cont = document.getElementById('DH_MQ_Container');
            const buttons = cont ? [...cont.querySelectorAll('.DH_Quest_Get_Btn')] : [];
            if (buttons.length === 0) {
                _notif('ℹ️', 'Nothing to do', 'No unclaimed quests found.');
                return;
            }

            const claimBtn = document.getElementById('DH_MQ_ClaimAll_Btn');
            const lbl = claimBtn?.querySelector('.DH_Sm_Btn_Label');
            if (claimBtn) claimBtn.disabled = true;
            if (lbl) lbl.textContent = '0%';

            const progEl = document.getElementById('DH_MQ_Prog');
            const fillEl = document.getElementById('DH_MQ_Fill');
            if (progEl) progEl.classList.add('on');
            if (fillEl) fillEl.style.width = '0%';

            buttons.forEach(b => { b.disabled = true; b.textContent = '…'; });

            const gh = _goalHdrsLocal(_jwt);
            const total = buttons.length;
            let done = 0, ok = 0;

            async function _claimOne(btn) {
                const [qYr, qMo] = btn.dataset.ym.split('_');
                const ts = new Date(Date.UTC(parseInt(qYr), parseInt(qMo) - 1, 15, 12, 0, 0)).toISOString();
                const payload = {
                    metric_updates: [{ metric: btn.dataset.metric, quantity: parseInt(btn.dataset.amount) }],
                    timestamp: ts,
                    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
                };
                try {
                    const r1 = await _mqGm('POST', `${GOALS_API}/users/${_sub}/progress/batch`, payload, gh);
                    if (r1.status !== 200) return false;
                    await new Promise(res => setTimeout(res, 800));
                    const r2 = await _mqGm('POST', `${GOALS_API}/users/${_sub}/progress/batch`, payload, gh);
                    return r2.status === 200;
                } catch { return false; }
            }

            const THREADS = 3;
            for (let i = 0; i < buttons.length; i += THREADS) {
                const chunk = buttons.slice(i, i + THREADS);
                const results = await Promise.all(chunk.map(b => _claimOne(b)));
                results.forEach((success, j) => {
                    const b = chunk[j];
                    b.textContent = success ? '✓' : 'ERR';
                    if (success) { b.classList.add('done'); ok++; }
                    done++;
                });
                const pct = Math.round((done / total) * 100);
                if (fillEl) fillEl.style.width = pct + '%';
                if (lbl) lbl.textContent = pct + '%';
            }

            if (progEl) progEl.classList.remove('on');
            if (claimBtn) claimBtn.disabled = false;
            if (lbl) lbl.textContent = _t('btn_claim');

            if (ok === total) {
                _notif('✅', 'Monthly Quests', `All ${total} quest(s) claimed!`);
            } else if (ok > 0) {
                _notif('⚠️', 'Partial', `${ok}/${total} quests claimed.`);
            } else {
                _notif('❌', 'Failed', 'No quests could be claimed.');
            }
            setTimeout(() => _loadMonthlyQuests(), 900);
        }

        document.getElementById('DH_Hide_Btn').addEventListener('click', () => _doHide(!_hidden));


        document.getElementById('DH_SwitchV1_Btn').addEventListener('click', () => {
            if (_pageBusy) return;
            _v1Mode = true;
            document.getElementById('DH_SwitchV1_Btn').style.display = 'none';
            document.getElementById('DH_SwitchV2_Btn').style.display = '';
            _v1SyncUser();
            _applyLang();
            _goPage('V1');
        });

        document.getElementById('DH_SwitchV2_Btn').addEventListener('click', () => {
            if (_pageBusy) return;
            _v1Mode = false;
            if (_v1Running) {
                _v1Running = false;
                _v1Task = null;
            }
            document.getElementById('DH_SwitchV2_Btn').style.display = 'none';
            document.getElementById('DH_SwitchV1_Btn').style.display = '';
            _goPage(1);
        });


        // V1 XP/Gem/Streak buttons removed from Solver page

        async function _activateFreeSuper_REMOVED(btnId, lblId, progId, fillId) { return; // removed
            const jwt = _getSuperJWT();
            if (!jwt) {
                _notif('⚠️', 'Not logged in', 'Could not find your Duolingo token. Please log in first.', 5);
                return;
            }

            const btn = document.getElementById(btnId);
            const lbl = document.getElementById(lblId);
            const prog = document.getElementById(progId);
            const fill = document.getElementById(fillId);

            btn.disabled = true;
            lbl.textContent = '...';
            prog.classList.add('on');
            fill.style.width = '30%';

            const _resetBtn = () => {
                lbl.textContent = _t('btn_activate');
                btn.disabled = false;
            };
            const _endProg = () => {
                fill.style.width = '100%';
                setTimeout(() => {
                    prog.classList.remove('on');
                    fill.style.width = '0%';
                }, 800);
            };
            const _failProg = () => {
                fill.style.width = '0%';
                prog.classList.remove('on');
            };

            if (!_user || !_sub || !_hdrs) {
                _failProg();
                _resetBtn();
                _notif('⚠️', 'Not connected', 'Please wait for connection and try again.', 5);
                return;
            }

            fill.style.width = '60%';
            try {
                const payload = {
                    itemName: 'immersive_subscription',
                    isFree: true,
                    consumed: true,
                    fromLanguage: _user.fromLanguage,
                    learningLanguage: _user.learningLanguage,
                    productId: 'com.duolingo.immersive_free_trial_subscription'
                };
                const r = await _gm('POST', `https://www.duolingo.com/2017-06-30/users/${_sub}/shop-items`, payload);
                if (r.status === 200 || r.status === 201) {
                    _endProg();
                    _resetBtn();
                    _notif('✅', 'Super Activated!', 'Free Super Duolingo activated!', 7);
                } else {
                    _failProg();
                    _resetBtn();
                    _notif('❌', 'Failed', 'Activation failed. You may already have Super.', 6);
                }
            } catch (_) {
                _failProg();
                _resetBtn();
                _notif('❌', 'Network error', 'Could not reach Duolingo API.', 5);
            }
        }

        document.getElementById('DH_V1_Settings_Btn').addEventListener('click', () => _goPage(2));
        document.getElementById('DH_Discord_Btn').addEventListener('click', () => window.open('https://duohacker.io.vn/discord', '_blank'));
        document.getElementById('DH_GitHub_Btn').addEventListener('click', () => window.open('https://duohacker.io.vn/github', '_blank'));
        document.getElementById('DH_YouTube_Btn').addEventListener('click', () => window.open('https://duohacker.io.vn/youtube', '_blank'));
        document.getElementById('DH_TopSettings_Btn').addEventListener('click', () => {
            _goPage(4);
            _initHideProfileToggle();
        });
        document.getElementById('DH_Donate_Btn').addEventListener('click', () => {
            window.open('https://duohacker.io.vn/donation#choose', '_blank');
        });
        // Solver page social/donate/settings buttons
        document.getElementById('DH_V1_Discord_Btn').addEventListener('click', () => window.open('https://duohacker.io.vn/discord', '_blank'));
        document.getElementById('DH_V1_GitHub_Btn').addEventListener('click', () => window.open('https://duohacker.io.vn/github', '_blank'));
        document.getElementById('DH_V1_YouTube_Btn').addEventListener('click', () => window.open('https://duohacker.io.vn/youtube', '_blank'));
        document.getElementById('DH_V1_Donate_Btn').addEventListener('click', () => window.open('https://duohacker.io.vn/donation#choose', '_blank'));
        document.getElementById('DH_V1_TopSettings_Btn').addEventListener('click', () => { _goPage(4); _initHideProfileToggle(); });

        async function _getPrivacy() {
            if (!_sub || !_hdrs) return null;
            try {
                const r = await _gm('GET', `https://www.duolingo.com/2023-05-23/users/${_sub}/privacy-settings?fields=privacySettings`);
                if (r.status !== 200) return null;
                const data = JSON.parse(r.responseText);
                const social = data.privacySettings?.find(x => x.id === 'disable_social');
                return social ? social.enabled : null;
            } catch (e) {
                return null;
            }
        }
        async function _setPrivacy(hide) {
            if (!_sub || !_hdrs) return false;
            try {
                const r = await _gm('PATCH', `https://www.duolingo.com/2023-05-23/users/${_sub}/privacy-settings?fields=privacySettings`, {
                    DISABLE_SOCIAL: hide
                });
                return r.status === 200 || r.status === 204;
            } catch (e) {
                return false;
            }
        }

        function _applyHideProfileToggle() {
            const tog = document.getElementById('DH_HideProfile_Toggle');
            const lbl = document.getElementById('DH_HideProfile_Status');
            if (!tog || !lbl) return;
            if (_privacy === null) {
                lbl.textContent = _t('status_unavailable');
                tog.disabled = true;
                return;
            }

            if (!tog.dataset.dhBound) {
                tog.dataset.dhBound = '1';
                tog.addEventListener('change', async function() {
                    tog.disabled = true;
                    lbl.textContent = _t('status_saving');
                    const ok = await _setPrivacy(tog.checked);
                    if (ok) {
                        _privacy = tog.checked;
                        lbl.textContent = tog.checked ? _t('profile_private') : _t('profile_public');
                    } else {
                        tog.checked = !tog.checked;
                        lbl.textContent = _t('status_failed_retry');
                    }
                    tog.disabled = false;
                });
            }
            tog.checked = _privacy;
            tog.disabled = false;
            lbl.textContent = _privacy ? _t('profile_private') : _t('profile_public');
        }

        function _initHideProfileToggle() {
            const tog = document.getElementById('DH_HideProfile_Toggle');
            const lbl = document.getElementById('DH_HideProfile_Status');
            if (!tog || !lbl) return;
            if (!_sub || !_hdrs) {
                lbl.textContent = _t('status_not_connected');
                tog.disabled = true;
                return;
            }
            if (_privacy !== null) {
                _applyHideProfileToggle();
                return;
            }

            lbl.textContent = _t('status_loading');
            tog.disabled = true;
            _getPrivacy().then(v => {
                _privacy = v;
                _applyHideProfileToggle();
            });
        }

        document.getElementById('DH_Settings_Btn').addEventListener('click', () => {
            _goPage(2);
            _initHideProfileToggle();
        });
        document.getElementById('DH_Back_Btn').addEventListener('click', () => _goBack());
        document.getElementById('DH_Shop_Btn').addEventListener('click', () => _goPage(3));
        document.getElementById('DH_Shop_Back_Btn').addEventListener('click', () => _goBack());
        document.getElementById('DH_LB_Nav_Btn').addEventListener('click', () => _goPage(11));
        document.getElementById('DH_LB_Back_Btn').addEventListener('click', () => _goBack());
        document.getElementById('DH_React_Back_Btn').addEventListener('click', () => _goBack());
        document.getElementById('DH_Settings_Back_Btn').addEventListener('click', () => {
            _initHideProfileToggle();
            _goBack();
        });

        document.getElementById('DH_AccSettings_Btn').addEventListener('click', () => {
            _goPage(5);
            _accRefreshAll().then(() => _renderAccounts());
        });
        document.getElementById('DH_AccMgr_Back_Btn').addEventListener('click', () => _goBack());
        document.getElementById('DH_AccSave_Btn').addEventListener('click', () => _accSaveCurrent());

        document.getElementById('DH_MonthlyQuest_Nav_Btn').addEventListener('click', e => {
            if (!e.target.closest('#DH_MonthlyQuest_Claim_Btn')) _goPage(6);
        });
        document.getElementById('DH_MonthlyQuest_Claim_Btn').addEventListener('click', e => {
            e.stopPropagation();
            _goPage(6);
        });
        document.getElementById('DH_MQ_Back_Btn').addEventListener('click', () => _goBack());
        document.getElementById('DH_Credits_Back_Btn').addEventListener('click', () => _goBack());
        document.getElementById('DH_Credits_Btn').addEventListener('click', () => {
            const container = document.getElementById('DH_Credits_Container');
            container.innerHTML = '';
            CREDITS.forEach(c => {
                const card = document.createElement('div');
                card.className = 'DH_Credit_Card';
                const header = document.createElement('div');
                header.className = 'DH_Credit_Card_Header';
                const img = document.createElement('img');
                img.className = 'DH_Credit_Thumb';
                img.src = c.thumbnail;
                img.alt = '';
                img.onerror = function() {
                    this.style.display = 'none';
                };
                const info = document.createElement('div');
                info.style.cssText = 'display:flex;flex-direction:column;gap:1px;min-width:0;';
                const name = document.createElement('p');
                name.className = 'DH_Credit_Script DH_NoSel';
                name.textContent = c.script;
                const author = document.createElement('p');
                author.className = 'DH_Credit_Author DH_NoSel';
                author.textContent = 'by ' + c.author;
                info.appendChild(name);
                info.appendChild(author);
                header.appendChild(img);
                header.appendChild(info);
                const task = document.createElement('p');
                task.className = 'DH_Credit_Task DH_NoSel';
                task.textContent = c.task;
                const link = document.createElement('a');
                link.className = 'DH_Credit_Link';
                link.href = c.url;
                link.target = '_blank';
                link.rel = 'noopener';
                link.textContent = 'View Script \u2197';
                card.appendChild(header);
                card.appendChild(task);
                card.appendChild(link);
                container.appendChild(card);
            });
            _goPage(7);
        });
        document.getElementById('DH_MQ_ClaimAll_Btn').addEventListener('click', () => _claimAllMonthly());

        const xpI = document.getElementById('DH_XP_Input'),
            xpB = document.getElementById('DH_XP_Btn');
        xpI.addEventListener('input', () => {
            xpB.disabled = !_user || !xpI.value || +xpI.value < 30;
        });
        xpB.addEventListener('click', () => {
            if (_running && _task === 'xp') {
                _run('xp', 0);
                return;
            }
            if (_running) {
                _notif('⚠️', 'Busy', 'Stop current farm first.');
                return;
            }
            const v = +xpI.value;
            if (v < 30) {
                _notif('⚠️', 'Min 30 XP', 'Enter at least 30 XP.');
                return;
            }
            _run('xp', v);
        });
        xpI.addEventListener('keydown', e => {
            if (e.key === 'Enter' && !xpB.disabled) xpB.click();
        });

        const gmB = document.getElementById('DH_Gem_Btn');
        gmB.addEventListener('click', () => {
            if (_running && _task === 'gem') {
                _run('gem', 0);
                return;
            }
            if (_running) {
                _notif('⚠️', 'Busy', 'Stop current farm first.');
                return;
            }
            _run('gem', 0);
        });

        const stI = document.getElementById('DH_Streak_Input'),
            stB = document.getElementById('DH_Streak_Btn');
        stI.addEventListener('input', () => {
            stB.disabled = !_user || !stI.value || +stI.value < 1;
        });
        stB.addEventListener('click', () => {
            if (_running && _task === 'streak') {
                _run('streak', 0);
                return;
            }
            if (_running) {
                _notif('⚠️', 'Busy', 'Stop current farm first.');
                return;
            }
            const v = +stI.value;
            if (v < 1) return;
            _run('streak', v);
        });
        stI.addEventListener('keydown', e => {
            if (e.key === 'Enter' && !stB.disabled) stB.click();
        });

        // ── Mode Buttons (# / Time / Infinity) ──────────────────────────────────
        (function _setupModeBtns() {
            // SVG icons matching DuoHacker stroke style
            const ICON_HASH = '􀆃'; // SF Symbol: number.sign (#)
            const ICON_TIME = '􀐬'; // SF Symbol: timer
            const ICON_INF  = '∞'; // SF Symbol: infinity

            // Clone a button element to strip ALL existing event listeners
            function cloneBtn(id) {
                const old = document.getElementById(id);
                if (!old) return null;
                const fresh = old.cloneNode(true);
                old.parentNode.replaceChild(fresh, old);
                return fresh;
            }

            // ── XP: hash → time → infinity ──
            let xpMode = 'hash';
            const xpModeBtn  = document.getElementById('DH_XP_Mode_Btn');
            const xpModeIcon = document.getElementById('DH_XP_Mode_Icon');
            const xpInputWrap = document.getElementById('DH_XP_Input_Wrap');
            // Clone xpB to remove old click listeners that would conflict
            const xpBtn = cloneBtn('DH_XP_Btn');

            function syncXPMode() {
                const icon = document.getElementById('DH_XP_Mode_Icon');
                if (xpMode === 'hash') {
                    icon.textContent = ICON_HASH;
                    xpModeBtn.title = 'Switch to Time Mode';
                    xpInputWrap.style.opacity = '';
                    xpI.style.pointerEvents = '';
                    xpI.readOnly = false;
                    xpI.placeholder = '0';
                    xpBtn.disabled = !_user || !xpI.value || +xpI.value < 30;
                } else if (xpMode === 'time') {
                    icon.textContent = ICON_TIME;
                    xpModeBtn.title = 'Switch to Infinite Mode';
                    xpInputWrap.style.opacity = '';
                    xpI.style.pointerEvents = '';
                    xpI.readOnly = false;
                    xpI.placeholder = 'min';
                    xpBtn.disabled = !_user || !xpI.value || +xpI.value < 1;
                } else {
                    icon.textContent = ICON_INF;
                    xpModeBtn.title = 'Switch to # Mode';
                    xpInputWrap.style.opacity = '0.35';
                    xpI.style.pointerEvents = 'none';
                    xpBtn.disabled = !_user;
                }
            }
            xpModeBtn.addEventListener('click', () => {
                xpMode = xpMode === 'hash' ? 'time' : xpMode === 'time' ? 'infinity' : 'hash';
                xpI.value = '';
                syncXPMode();
            });
            xpI.addEventListener('input', () => { syncXPMode(); });
            xpI.addEventListener('keydown', e => { if (e.key === 'Enter' && !xpBtn.disabled) xpBtn.click(); });
            xpBtn.addEventListener('click', () => {
                if (_running && _task === 'xp') {
                    if (xpMode === 'hash') { _run('xp', 0); } else { _running = false; }
                    return;
                }
                if (_running) { _notif('⚠️', 'Busy', 'Stop current farm first.'); return; }
                if (xpMode === 'infinity') {
                    _run('xp', 99999999);
                    setTimeout(() => {
                        const prog = document.getElementById('DH_XP_Prog');
                        if (prog) { prog.classList.remove('on'); }
                        const fill = document.getElementById('DH_XP_Fill');
                        if (fill) fill.style.width = '0%';
                    }, 80);
                } else if (xpMode === 'time') {
                    const mins = +xpI.value;
                    if (mins < 1) { _notif('⚠️', 'Min 1 min', 'Enter at least 1 minute.'); return; }
                    const startAt = Date.now();
                    const endAt = startAt + mins * 60000;
                    const totalMs = mins * 60000;
                    _run('xp', 99999999);
                    const progEl  = document.getElementById('DH_XP_Prog');
                    if (progEl) progEl.classList.add('on');
                    const stopTimer = setInterval(() => {
                        if (!_running || _task !== 'xp') { clearInterval(stopTimer); return; }
                        const now = Date.now();
                        const elapsed = now - startAt;
                        const pct = Math.min(100, Math.floor((elapsed / totalMs) * 100));
                        const fill = document.getElementById('DH_XP_Fill');
                        if (fill) fill.style.width = pct + '%';
                        if (now >= endAt) { clearInterval(stopTimer); _running = false; }
                    }, 500);
                } else {
                    const v = +xpI.value;
                    if (v < 30) { _notif('⚠️', 'Min 30 XP', 'Enter at least 30 XP.'); return; }
                    _run('xp', v);
                }
            });
            syncXPMode();

            // ── Streak: hash → time → infinity ──
            let stMode = 'hash';
            const stModeBtn  = document.getElementById('DH_Streak_Mode_Btn');
            const stModeIcon = document.getElementById('DH_Streak_Mode_Icon');
            const stInputWrap = document.getElementById('DH_Streak_Input_Wrap');
            // Clone stB to remove old click listeners
            const stBtn = cloneBtn('DH_Streak_Btn');

            function syncStMode() {
                const icon = document.getElementById('DH_Streak_Mode_Icon');
                if (stMode === 'hash') {
                    icon.textContent = ICON_HASH;
                    stModeBtn.title = 'Switch to Time Mode';
                    stInputWrap.style.opacity = '';
                    stI.style.pointerEvents = '';
                    stI.readOnly = false;
                    stI.placeholder = '0';
                    stBtn.disabled = !_user || !stI.value || +stI.value < 1;
                } else if (stMode === 'time') {
                    icon.textContent = ICON_TIME;
                    stModeBtn.title = 'Switch to Infinite Mode';
                    stInputWrap.style.opacity = '';
                    stI.style.pointerEvents = '';
                    stI.readOnly = false;
                    stI.placeholder = 'min';
                    stBtn.disabled = !_user || !stI.value || +stI.value < 1;
                } else {
                    icon.textContent = ICON_INF;
                    stModeBtn.title = 'Switch to # Mode';
                    stInputWrap.style.opacity = '0.35';
                    stI.style.pointerEvents = 'none';
                    stBtn.disabled = !_user;
                }
            }
            stModeBtn.addEventListener('click', () => {
                stMode = stMode === 'hash' ? 'time' : stMode === 'time' ? 'infinity' : 'hash';
                stI.value = '';
                syncStMode();
            });
            stI.addEventListener('input', () => { syncStMode(); });
            stI.addEventListener('keydown', e => { if (e.key === 'Enter' && !stBtn.disabled) stBtn.click(); });
            stBtn.addEventListener('click', () => {
                if (_running && _task === 'streak') {
                    if (stMode === 'hash') { _run('streak', 0); } else { _running = false; }
                    return;
                }
                if (_running) { _notif('⚠️', 'Busy', 'Stop current farm first.'); return; }
                if (stMode === 'infinity') {
                    _run('streak', 9999);
                    setTimeout(() => {
                        const prog = document.getElementById('DH_Streak_Prog');
                        if (prog) { prog.classList.remove('on'); }
                        const fill = document.getElementById('DH_Streak_Fill');
                        if (fill) fill.style.width = '0%';
                    }, 80);
                } else if (stMode === 'time') {
                    const mins = +stI.value;
                    if (mins < 1) { _notif('⚠️', 'Min 1 min', 'Enter at least 1 minute.'); return; }
                    const startAt = Date.now();
                    const endAt = startAt + mins * 60000;
                    const totalMs = mins * 60000;
                    _run('streak', 9999);
                    const progEl  = document.getElementById('DH_Streak_Prog');
                    if (progEl) progEl.classList.add('on');
                    const stopTimer = setInterval(() => {
                        if (!_running || _task !== 'streak') { clearInterval(stopTimer); return; }
                        const now = Date.now();
                        const elapsed = now - startAt;
                        const pct = Math.min(100, Math.floor((elapsed / totalMs) * 100));
                        const fill = document.getElementById('DH_Streak_Fill');
                        if (fill) fill.style.width = pct + '%';
                        if (now >= endAt) { clearInterval(stopTimer); _running = false; }
                    }, 500);
                } else {
                    const v = +stI.value;
                    if (v < 1) return;
                    _run('streak', v);
                }
            });
            syncStMode();

            // ── Gem: hash → infinity ──
            let gmMode = 'hash';
            const gmModeBtn  = document.getElementById('DH_Gem_Mode_Btn');
            const gmInputWrap = document.getElementById('DH_Gem_Input_Wrap');
            const gmI = document.getElementById('DH_Gem_Input');
            // Clone gmB to remove old click listeners
            const gmBtn = cloneBtn('DH_Gem_Btn');

            function syncGmMode() {
                const icon = document.getElementById('DH_Gem_Mode_Icon');
                if (gmMode === 'hash') {
                    icon.textContent = ICON_HASH;
                    gmModeBtn.title = 'Switch to Infinite Mode';
                    gmInputWrap.style.opacity = '';
                    gmI.style.pointerEvents = 'none';
                    gmI.placeholder = '0';
                } else {
                    icon.textContent = ICON_INF;
                    gmModeBtn.title = 'Switch to # Mode';
                    gmInputWrap.style.opacity = '0.35';
                    gmI.style.pointerEvents = 'none';
                }
                gmBtn.disabled = !_user;
            }
            gmModeBtn.addEventListener('click', () => {
                gmMode = gmMode === 'hash' ? 'infinity' : 'hash';
                syncGmMode();
            });
            gmBtn.addEventListener('click', () => {
                if (_running && _task === 'gem') { _run('gem', 0); return; }
                if (_running) { _notif('⚠️', 'Busy', 'Stop current farm first.'); return; }
                _run('gem', 0);
            });
            syncGmMode();
        })();
        // ────────────────────────────────────────────────────────────────────────

        const prI = document.getElementById('DH_Practice_Input'),
            prB = document.getElementById('DH_Practice_Btn');
        prI.addEventListener('input', () => {
            prB.disabled = !_user;
        });
        prB.addEventListener('click', () => {
            if (_lessonSolving) {

                _stopPractice();
                sessionStorage.removeItem('dh2_practice');
                _notif('⏹️', 'Stopped', 'Practice farm stopped.');
                return;
            }
            if (_running) {
                _notif('⚠️', 'Busy', 'Stop current farm first.');
                return;
            }
            if (!_user) {
                _notif('⚠️', 'Not connected', 'Please wait.');
                return;
            }
            const v = parseInt(prI.value) || 0;

            sessionStorage.setItem('dh2_practice', JSON.stringify({
                active: true,
                count: v,
                done: 0
            }));
            _farmPractice(v);
        });

        document.getElementById('DH_League_Btn').addEventListener('click', () => {
            if (_running && _task === 'league') {
                _run('league', 0);
                return;
            }
            if (_running) {
                _notif('⚠️', 'Busy', 'Stop current farm first.');
                return;
            }
            if (!confirm('⚠️ Warning\n\nOverusing this feature may result in your account being permanently banned from the leaderboard.\n\nDo you wish to continue?')) return;
            _run('league', 0);
        });

        document.getElementById('DH_Quest_Btn').addEventListener('click', async () => {
            if (_running) {
                _notif('⚠️', 'Busy', 'Stop current farm first.');
                return;
            }
            await _farmDailyQuest();
        });

        const delI = document.getElementById('DH_Delay_Input'),
            delB = document.getElementById('DH_Delay_Btn');
        delB.addEventListener('click', () => {
            const v = parseInt(delI.value);
            if (!isNaN(v) && v >= 0) {
                _delay = v;
                localStorage.setItem('dh2_delay', v);
                _setBtnState('DH_Delay_Btn', _C_GREEN, _t('btn_saved'));
                setTimeout(() => _setBtnState('DH_Delay_Btn', _C_BLUE, _t('btn_save')), 1500);
            }
        });

        const supT = document.getElementById('DH_Super_Toggle');
        supT.checked = localStorage.getItem('dh2_super') === 'true';
        supT.addEventListener('change', () => {
            localStorage.setItem('dh2_super', supT.checked ? 'true' : 'false');
            _notif('ℹ️', 'Reload required', 'Refresh page to apply.', 4);
        });

        const solverT = document.getElementById('DH_Solver_Toggle');
        solverT.checked = localStorage.getItem('duohacker_inject_solver') === 'true';
        _INJECT_SOLVER_ENABLED = solverT.checked;
        solverT.addEventListener('change', () => {
            _INJECT_SOLVER_ENABLED = solverT.checked;
            localStorage.setItem('duohacker_inject_solver', solverT.checked ? 'true' : 'false');
            if (!_INJECT_SOLVER_ENABLED) {
                _autoSolver.removeUI();
            } else {
                const inLesson = window.location.pathname.includes('/lesson') || window.location.pathname.includes('/practice');
                if (inLesson) setTimeout(() => _autoSolver.createUI(), 300);
            }
        });

        document.getElementById('DH_Shop_Search').addEventListener('input', e => {
            _renderShop(_allShopItems, e.target.value);
        });

        let _hideAnimObserver = null;
        const _HIDE_ANIM_STYLE_ID = 'DH_HideAnim_Style';
        const _HIDE_ANIM_KEY = 'duohacker_hide_animation';
        const _HIDE_PROTECT = ['#DH_Root', '#DH_Root *'];

        function _applyHideAnim() {
            if (document.getElementById(_HIDE_ANIM_STYLE_ID)) return;
            const s = document.createElement('style');
            s.id = _HIDE_ANIM_STYLE_ID;
            s.textContent = `
body img:not(#DH_Root img),
body svg:not(#DH_Root svg),
body [role="img"]:not(#DH_Root [role="img"]),
body canvas:not(#DH_Root canvas),
body video:not(#DH_Root video),
body lottie-player:not(#DH_Root lottie-player) {
    visibility:hidden!important;
    animation:none!important;
    transition:none!important;
}
body * {
    animation-play-state:paused!important;
    transition:none!important;
}
#DH_Root {
    visibility:visible!important;
}
#DH_Root * {
    animation-play-state:running!important;
    visibility:visible!important;
    transition:unset!important;
}`;
            document.head.appendChild(s);
        }

        function _removeHideAnim() {
            document.getElementById(_HIDE_ANIM_STYLE_ID)?.remove();
        }

        const _hideAnimT = document.getElementById('DH_HideAnim_Toggle');
        _hideAnimT.checked = localStorage.getItem(_HIDE_ANIM_KEY) === 'true';
        if (_hideAnimT.checked) _applyHideAnim();
        _hideAnimT.addEventListener('change', () => {
            localStorage.setItem(_HIDE_ANIM_KEY, _hideAnimT.checked ? 'true' : 'false');
            _hideAnimT.checked ? _applyHideAnim() : _removeHideAnim();
        });

        const CREDITS = [{
                script: 'DuoHacker V1',
                url: 'https://github.com/DuoHacker/DuoHacker/tree/main/v1',
                thumbnail: 'https://raw.githubusercontent.com/DuoHacker/DuoHacker/refs/heads/main/images/DuoHacker_Logo_NoBG_PNG.png',
                author: 'DuoHacker',
                task: 'Original script - The main cores are being used in V2'
            },
            {
                script: 'Duolingo PRO',
                url: 'https://github.com/anonymoushackerIV/Duolingo-PRO',
                thumbnail: 'https://www.duolingopro.net/static/favicons/duo/128/light/primary.png',
                author: 'anonymoushackerIV',
                task: 'The V2 UI was inspired by this script'
            }
        ];

        const main = document.getElementById('DH_Main');
        const box = document.getElementById('DH_Main_Box');

        main.style.bottom = `-${box.offsetHeight-8}px`;
        box.style.opacity = '0';
        box.style.filter = 'blur(8px)';

        _doHide(false);
        setTimeout(() => {
            main.style.transition = '0.8s cubic-bezier(0.16,1,0.32,1)';
            box.style.transition = '0.8s cubic-bezier(0.16,1,0.32,1)';
            main.style.bottom = '16px';
            box.style.opacity = '';
            box.style.filter = '';
            setTimeout(() => {
                main.style.transition = '';
                box.style.transition = '';
            }, 800);
        }, 600);
        let _licenseLoaded = false;

        function _loadLicense() {
            if (_licenseLoaded) return;
            const txt = document.getElementById('DH_License_Text');
            if (!txt) return;
            GM_xmlhttpRequest({
                method: 'GET',
                url: 'https://raw.githubusercontent.com/DuoHacker/DuoHacker/refs/heads/main/LICENSE',
                onload: r => {
                    if (!document.getElementById('DH_License_Text')) return;
                    document.getElementById('DH_License_Text').textContent =
                        r.status === 200 ? r.responseText : 'Could not load license. Please check your connection.';
                    _licenseLoaded = true;
                },
                onerror: () => {
                    const t = document.getElementById('DH_License_Text');
                    if (t) t.textContent = 'Could not load license. Please check your connection.';
                }
            });
        }
        document.getElementById('DH_License_Open_Btn').addEventListener('click', () => _goPage(9));
        document.getElementById('DH_License_Back_Btn').addEventListener('click', () => _goBack());
        document.getElementById('DH_Changelog_Back_Btn').addEventListener('click', () => _goBack());
        document.getElementById('DH_Version_Txt').addEventListener('click', () => {
            _renderChangelog();
            _goPage(10);
        });

        function _renderChangelog() {
            const list = document.getElementById('DH_Changelog_List');
            if (!list || list.dataset.rendered) return;
            list.dataset.rendered = '1';
            _CHANGELOG.forEach((entry, i) => {
                const isLatest = i === 0;
                const block = document.createElement('div');
                block.style.cssText = 'display:flex;flex-direction:column;gap:8px;align-self:stretch;';

                const vRow = document.createElement('div');
                vRow.style.cssText = 'display:flex;align-items:center;gap:8px;';
                vRow.innerHTML = `
            <p class="DH_T1 DH_NoSel" style="font-size:15px;font-weight:800;color:rgb(var(--color-eel,88,88,88));">v${entry.version}</p>
            ${isLatest ? `<span style="background:rgb(var(--DH-blue));color:#fff;font-size:10px;font-weight:700;padding:2px 8px;border-radius:20px;">${_t('changelog_current')}</span>` : ''}
        `;

                const changeList = document.createElement('div');
                changeList.style.cssText = 'display:flex;flex-direction:column;gap:6px;';
                entry.changes.forEach(c => {
                    const item = document.createElement('p');
                    item.className = 'DH_T2 DH_NoSel';
                    item.style.cssText = `
                font-size:13px;font-weight:600;line-height:1.5;
                background:rgba(var(--color-eel,88,88,88),0.06);
                border-radius:10px;padding:9px 12px;
                color:rgb(var(--color-eel,88,88,88));
            `;
                    item.textContent = c;
                    changeList.appendChild(item);
                });

                block.appendChild(vRow);
                block.appendChild(changeList);

                if (i < _CHANGELOG.length - 1) {
                    const div = document.createElement('div');
                    div.className = 'DH_Divider';
                    block.appendChild(div);
                }

                list.appendChild(block);
            });
        }

        // ── Lesson Shortener toggle ────────────────────────────────────────
        const _lsTog = document.getElementById('DH_LS_Toggle');
        _lsTog.checked = localStorage.getItem(_LS_KEY) === 'true';
        _lsTog.addEventListener('change', () => {
            localStorage.setItem(_LS_KEY, _lsTog.checked ? 'true' : 'false');
        });

        // ── Stories Shortener toggle ───────────────────────────────────────
        const _ssTog = document.getElementById('DH_SS_Toggle');
        _ssTog.checked = localStorage.getItem(_SS_KEY) === 'true';
        _ssTog.addEventListener('change', () => {
            localStorage.setItem(_SS_KEY, _ssTog.checked ? 'true' : 'false');
        });
        // ──────────────────────────────────────────────────────────────────

        // ── Safe Mode toggle ─────────────────────────────────────────────
        const _safeTog = document.getElementById('DH_SafeMode_Toggle');
        _safeTog.checked = _SAFE_MODE;
        _safeTog.addEventListener('change', () => {
            _SAFE_MODE = _safeTog.checked;
            localStorage.setItem('duohacker_safe_mode', _SAFE_MODE ? 'true' : 'false');
        });
        // ──────────────────────────────────────────────────────────────────

        _connect();

        setTimeout(() => {
            if (!_v1Mode) {
                const sb = document.getElementById('DH_SwitchV1_Btn');
                if (sb && !_hidden) sb.style.display = '';
            }
        }, 700);

        _resumePracticeIfNeeded();

        // ── Magnetic hover (giống DLP) ──
        function _initMagneticHover(el) {
            let down = false, origZ = null;
            el.addEventListener('pointermove', e => {
                const r = el.getBoundingClientRect();
                const x = e.clientX - r.left - r.width / 2;
                const y = e.clientY - r.top - r.height / 2;
                el.style.transform = `translate(${x * 0.1}px, ${y * 0.1}px) scale(${down ? 0.9 : 1.05})`;
                if (origZ === null) origZ = parseInt(el.style.zIndex) || 0;
                el.style.zIndex = origZ + 1;
            });
            el.addEventListener('pointerleave', () => {
                el.style.transform = 'translate(0,0) scale(1)';
                if (origZ !== null) el.style.zIndex = origZ;
                down = false;
            });
            el.addEventListener('pointerdown', e => {
                down = true;
                const r = el.getBoundingClientRect();
                const x = e.clientX - r.left - r.width / 2;
                const y = e.clientY - r.top - r.height / 2;
                el.style.transform = `translate(${x * 0.1}px, ${y * 0.1}px) scale(0.9)`;
            });
            el.addEventListener('pointerup', e => {
                down = false;
                const r = el.getBoundingClientRect();
                const x = e.clientX - r.left - r.width / 2;
                const y = e.clientY - r.top - r.height / 2;
                el.style.transform = `translate(${x * 0.1}px, ${y * 0.1}px) scale(1.05)`;
            });
        }
        function _initAllMagneticHovers() {
            const MARK = 'data-dh-mag';
            document.querySelectorAll('.DH_Btn').forEach(el => {
                if (!el.hasAttribute(MARK)) {
                    _initMagneticHover(el);
                    el.setAttribute(MARK, '1');
                }
            });
        }
        _initAllMagneticHovers();
        new MutationObserver(muts => {
            muts.forEach(m => m.addedNodes.forEach(n => {
                if (!(n instanceof Element)) return;
                if (n.classList.contains('DH_Btn') && !n.hasAttribute('data-dh-mag')) { _initMagneticHover(n); n.setAttribute('data-dh-mag','1'); }
                n.querySelectorAll?.('.DH_Btn').forEach(d => { if (!d.hasAttribute('data-dh-mag')) { _initMagneticHover(d); d.setAttribute('data-dh-mag','1'); } });
            }));
        }).observe(document.documentElement, { childList: true, subtree: true });

    }
    _dhMountUI();
})();