DuoGang

Advanced Duolingo automation - Auto-solve lessons, farm XP, Gems, Streaks with beautiful UI

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         DuoGang
// @license mit
// @namespace    https://github.com/duochristian
// @version      1.0.0
// @description  Advanced Duolingo automation - Auto-solve lessons, farm XP, Gems, Streaks with beautiful UI
// @author      DuoGangTeam
// @match        *://*.duolingo.com/*
// @match        *://*.duolingo.cn/*
// @icon      https://raw.githubusercontent.com/JULIANGAMER587/Duogang_icon/main/Screenshot%202026-05-07%20151236-Photoroom.png
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      duolingo.com
// @connect      stories.duolingo.com
// @connect      goals-api.duolingo.com
// @connect      duolingo-leaderboards-prod.duolingo.com
// @connect      fonts.googleapis.com
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // ==================== STYLES ====================
    const fontLink = document.createElement('link');
    fontLink.rel = 'stylesheet';
    fontLink.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap';
    document.head.appendChild(fontLink);

    GM_addStyle(`
        :root {
            --DC-gold: 255, 179, 0;
            --DC-gold-dark: 204, 143, 0;
            --DC-blue: 28, 176, 246;
            --DC-green: 88, 204, 2;
            --DC-red: 255, 75, 75;
            --DC-purple: 206, 130, 255;
            /* Light mode defaults */
            --DC-bg: rgba(255,255,255,0.95);
            --DC-surface: rgba(248,248,248,0.9);
            --DC-text: #1a1a1a;
            --DC-text-sub: #888;
            --DC-divider: rgba(0,0,0,0.08);
            --DC-outline: rgba(0,0,0,0.08);
            --DC-input-bg: rgba(255,179,0,0.05);
            --DC-input-outline: rgba(255,179,0,0.2);
            --DC-notif-bg: rgba(255,255,255,0.97);
            --DC-shadow: rgba(0,0,0,0.10);
        }
        #DC_Root.DC_Dark {
            --DC-bg: rgba(22,22,28,0.97);
            --DC-surface: rgba(30,30,38,0.95);
            --DC-text: #f0f0f0;
            --DC-text-sub: #aaa;
            --DC-divider: rgba(255,255,255,0.08);
            --DC-outline: rgba(255,255,255,0.08);
            --DC-input-bg: rgba(255,179,0,0.07);
            --DC-input-outline: rgba(255,179,0,0.25);
            --DC-notif-bg: rgba(28,28,36,0.98);
            --DC-shadow: rgba(0,0,0,0.40);
        }

        #DC_Root * { box-sizing: border-box; }
        #DC_Root p, #DC_Root span, #DC_Root button, #DC_Root input, #DC_Root label, #DC_Root div {
            font-family: 'Inter', 'din-round', -apple-system, sans-serif !important;
        }
        #DC_Root p, #DC_Root span { margin: 0; padding: 0; }

        .DC_Main {
            display: inline-flex; flex-direction: column;
            justify-content: flex-end; align-items: flex-end;
            gap: 10px; position: fixed; right: 16px; bottom: 16px;
            z-index: 2147483647; transition: bottom 0.6s cubic-bezier(0.16,1,0.32,1);
            user-select: none;
        }
        .DC_Main.DC_Dragging { transition: none !important; }
        @media (max-width: 699px) { .DC_Main { margin-bottom: 80px; } }

        .DC_Main_Box {
            display: flex; width: 320px; padding: 18px;
            flex-direction: column; gap: 12px;
            border-radius: 20px;
            outline: 2px solid var(--DC-outline); outline-offset: -2px;
            background: var(--DC-bg);
            backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
            box-shadow: 0 8px 32px var(--DC-shadow);
            transition: opacity 0.6s cubic-bezier(0.16,1,0.32,1), filter 0.6s cubic-bezier(0.16,1,0.32,1);
        }

        .DC_HStack { display:flex; align-items:center; gap:8px; align-self:stretch; }
        .DC_HStack_Between { display:flex; align-items:center; justify-content:space-between; align-self:stretch; }
        .DC_VStack { display:flex; flex-direction:column; gap:8px; align-self:stretch; }
        .DC_Divider { height:1px; background:var(--DC-divider); align-self:stretch; flex-shrink:0; }

        .DC_Title { font-size:15px; font-weight:700; color:var(--DC-text); }
        .DC_Subtitle { font-size:12px; font-weight:500; color:var(--DC-text-sub); }

        .DC_Btn {
            display:flex; height:44px; padding:10px 16px;
            align-items:center; gap:8px;
            border-radius:12px; border:none; cursor:pointer;
            font-size:14px; font-weight:700;
            transition: all 0.3s cubic-bezier(0.16,1,0.32,1);
            user-select:none; -webkit-user-select:none;
        }
        .DC_Btn:hover { filter:brightness(0.9); transform:scale(1.03); }
        .DC_Btn:active { filter:brightness(0.85); transform:scale(0.97); }

        .DC_Btn_Primary { background:rgb(var(--DC-gold)); color:#1a1a1a; }
        .DC_Btn_Blue { background:rgb(var(--DC-blue)); color:#fff; }
        .DC_Btn_Green { background:rgb(var(--DC-green)); color:#fff; }
        .DC_Btn_Red { background:rgb(var(--DC-red)); color:#fff; }
        .DC_Btn_Ghost {
            background:rgba(var(--DC-gold),0.1); color:rgb(var(--DC-gold-dark));
            outline:2px solid rgba(var(--DC-gold),0.2); outline-offset:-2px;
        }
        .DC_Btn_Icon { flex:none; width:44px; padding:10px; justify-content:center; }

        .DC_Input_Wrap {
            display:flex; height:48px; padding:0 16px;
            align-items:center; gap:8px; flex:1;
            border-radius:12px;
            outline:2px solid var(--DC-input-outline); outline-offset:-2px;
            background:var(--DC-input-bg);
        }
        .DC_Input {
            border:none; outline:none; background:none;
            font-size:16px; font-weight:600; color:rgb(var(--DC-gold-dark));
            text-align:right; width:100%; font-family:inherit;
            -moz-appearance:textfield;
        }
        .DC_Input::placeholder { color:rgba(var(--DC-gold-dark),0.4); }
        .DC_Input::-webkit-outer-spin-button,
        .DC_Input::-webkit-inner-spin-button { -webkit-appearance:none; margin:0; }

        .DC_Input_Btn {
            display:flex; height:48px; padding:0 18px;
            align-items:center; gap:6px;
            border-radius:12px; border:none; cursor:pointer;
            font-size:14px; font-weight:800; color:#1a1a1a;
            background:rgb(var(--DC-gold));
            transition: all 0.3s cubic-bezier(0.16,1,0.32,1);
            white-space:nowrap; flex-shrink:0;
        }
        .DC_Input_Btn:hover { filter:brightness(0.9); transform:scale(1.03); }
        .DC_Input_Btn:active { filter:brightness(0.85); transform:scale(0.97); }
        .DC_Input_Btn:disabled { opacity:0.4; pointer-events:none; }

        .DC_Prog_Wrap { height:0; border-radius:4px; background:rgba(var(--DC-gold),0.1); overflow:hidden; transition:height 0.4s; }
        .DC_Prog_Wrap.on { height:4px; }
        .DC_Prog_Fill { height:100%; border-radius:4px; background:linear-gradient(90deg, rgb(var(--DC-gold)), rgb(var(--DC-gold-dark))); width:0%; transition:width 0.5s; }

        .DC_Page { display:none; flex-direction:column; gap:12px; }
        .DC_Page.active { display:flex; }

        .DC_Notif {
            position:fixed; left:50%; transform:translateX(-50%); bottom:16px;
            z-index:2147483647; padding:14px 18px;
            border-radius:14px; background:var(--DC-notif-bg);
            backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px);
            box-shadow:0 8px 32px var(--DC-shadow);
            font-size:13px; font-weight:600; color:var(--DC-text);
            transition:all 0.4s cubic-bezier(0.16,1,0.32,1);
            opacity:0; pointer-events:none;
        }
        .DC_Notif.show { opacity:1; pointer-events:auto; }

        .DC_User_Row { display:none; gap:10px; }
        .DC_Avatar { width:36px; height:36px; border-radius:50%; background:rgba(var(--DC-gold),0.1); overflow:hidden; flex-shrink:0; display:flex; align-items:center; justify-content:center; font-size:18px; }
        .DC_Stat { display:flex; align-items:center; gap:4px; }
        .DC_Stat img { width:16px; height:16px; }
        .DC_Stat span { font-size:12px; font-weight:600; color:var(--DC-text-sub); }

        /* Drag handle cursor */
        .DC_Drag_Handle { cursor: grab; }
        .DC_Drag_Handle:active { cursor: grabbing; }

        /* YouTube link button */
        .DC_YT_Btn {
            display:flex; align-items:center; gap:6px; height:44px; padding:0 12px;
            border-radius:12px; border:none; cursor:pointer; text-decoration:none;
            background:rgba(255,0,0,0.1); color:#cc0000;
            outline:2px solid rgba(255,0,0,0.18); outline-offset:-2px;
            font-size:12px; font-weight:700; transition:all 0.3s cubic-bezier(0.16,1,0.32,1);
            white-space:nowrap; flex-shrink:0;
        }
        #DC_Root.DC_Dark .DC_YT_Btn { background:rgba(255,0,0,0.15); color:#ff5555; outline-color:rgba(255,80,80,0.25); }
        .DC_YT_Btn:hover { background:rgba(255,0,0,0.18); transform:scale(1.04); }
        .DC_YT_Btn:active { transform:scale(0.97); }

        /* Dark mode toggle */
        .DC_DarkToggle {
            display:flex; align-items:center; justify-content:center;
            width:44px; height:44px; border-radius:12px; border:none; cursor:pointer;
            background:rgba(var(--DC-gold),0.10); outline:2px solid rgba(var(--DC-gold),0.2); outline-offset:-2px;
            transition:all 0.3s cubic-bezier(0.16,1,0.32,1); flex-shrink:0;
        }
        .DC_DarkToggle:hover { filter:brightness(0.88); transform:scale(1.05); }
        .DC_DarkToggle:active { transform:scale(0.95); }

        /* Eye icon states */
        .DC_Eye_Open { display:block; }
        .DC_Eye_Closed { display:none; }
        .DC_Hide_Btn_Hidden .DC_Eye_Open { display:none; }
        .DC_Hide_Btn_Hidden .DC_Eye_Closed { display:block; }

        .DC_Solver_Btn {
            position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%);
            z-index: 2147483647; display: flex; gap: 10px;
            animation: DC_SlideUp 0.3s ease-out;
        }
        @keyframes DC_SlideUp {
            from { opacity:0; transform:translateX(-50%) translateY(20px); }
            to { opacity:1; transform:translateX(-50%) translateY(0); }
        }
        .DC_Solver_Btn button {
            padding: 12px 24px; border:none; border-radius: 12px;
            font-size: 14px; font-weight: 700; cursor:pointer;
            transition: all 0.2s; color:#fff;
        }
        .DC_Solver_Btn button:hover { filter:brightness(1.1); transform:translateY(-2px); }
        .DC_Solver_Btn button:active { transform:translateY(2px); }
        .DC_Solve_One { background:rgb(var(--DC-green)); border-bottom:4px solid #46a302; }
        .DC_Solve_All { background:rgb(var(--DC-gold)); border-bottom:4px solid rgb(var(--DC-gold-dark)); color:#1a1a1a; }

        .DC_Toggle { position:relative; width:44px; height:26px; flex-shrink:0; cursor:pointer; }
        .DC_Toggle input { opacity:0; width:0; height:0; }
        .DC_Toggle_Slider { position:absolute; inset:0; background:rgba(0,0,0,0.15); border-radius:26px; transition:0.3s; }
        .DC_Toggle_Slider:before { content:''; position:absolute; width:20px; height:20px; left:3px; bottom:3px; background:#fff; border-radius:50%; transition:0.3s; box-shadow:0 2px 4px rgba(0,0,0,0.2); }
        .DC_Toggle input:checked + .DC_Toggle_Slider { background:rgb(var(--DC-gold)); }
        .DC_Toggle input:checked + .DC_Toggle_Slider:before { transform:translateX(18px); }
    `);

    // ==================== HTML STRUCTURE ====================
    const wrap = document.createElement('div');
    wrap.id = 'DC_Root';
    wrap.innerHTML = `
        <div class="DC_Notif" id="DC_Notif"></div>
        <div class="DC_Main" id="DC_Main">
            <div class="DC_HStack" style="align-self:flex-end; gap:8px;">
                <!-- YouTube link -->
                <a class="DC_YT_Btn" href="https://www.youtube.com/@JULSEXE_Edits/shorts?app=desktop" target="_blank" rel="noopener noreferrer" title="Follow us on YouTube">
                    <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>
                    </svg>
                    Follow us on YouTube
                </a>
                <!-- Dark mode toggle -->
                <button class="DC_DarkToggle" id="DC_Dark_Btn" title="Toggle dark/light mode">
                    <svg id="DC_Dark_Icon_Moon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="rgb(var(--DC-gold-dark))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
                    </svg>
                    <svg id="DC_Dark_Icon_Sun" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="rgb(var(--DC-gold))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none;">
                        <circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
                    </svg>
                </button>
                <!-- Hide/Show button with eye icon -->
                <div class="DC_Btn DC_Drag_Handle" id="DC_Hide_Btn" style="background:rgb(var(--DC-gold)); height:44px; padding:0 14px; gap:8px; border-radius:12px; cursor:pointer; display:flex; align-items:center;" title="Drag to move · Click to show/hide">
                    <!-- Eye Open (menu visible) -->
                    <svg class="DC_Eye_Open" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#1a1a1a" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
                        <circle cx="12" cy="12" r="3"/>
                    </svg>
                    <!-- Eye Closed (menu hidden) -->
                    <svg class="DC_Eye_Closed" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#1a1a1a" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
                        <line x1="1" y1="1" x2="23" y2="23"/>
                    </svg>
                    <span style="color:#1a1a1a;font-size:13px;font-weight:700;" id="DC_Hide_Label">Hide</span>
                </div>
            </div>
            <div class="DC_Main_Box" id="DC_Main_Box">
                <div class="DC_Page active" id="DC_Page_Main">
                    <div class="DC_HStack">
                        <div class="DC_Btn DC_Btn_Ghost" id="DC_Conn_Btn" style="flex:1;">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" id="DC_Conn_Ico">
                                <circle cx="12" cy="12" r="10" stroke="rgb(var(--DC-gold-dark))" stroke-width="2" fill="none"/>
                            </svg>
                            <span class="DC_Title" id="DC_Conn_Txt" style="color:rgb(var(--DC-gold-dark));">Connecting</span>
                        </div>
                    </div>

                    <div class="DC_User_Row DC_HStack" id="DC_User_Row">
                        <div class="DC_Avatar" id="DC_Avatar">?</div>
                        <div style="flex:1;min-width:0;">
                            <p class="DC_Title" id="DC_UName" style="font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"></p>
                            <div class="DC_HStack" style="gap:12px;">
                                <div class="DC_Stat"><img src="https://d35aaqx5ub95lt.cloudfront.net/images/profile/01ce3a817dd01842581c3d18debcbc46.svg"><span id="DC_UXP">0</span></div>
                                <div class="DC_Stat"><img src="https://d35aaqx5ub95lt.cloudfront.net/images/gems/45c14e05be9c1af1d7d0b54c6eed7eee.svg"><span id="DC_UGems">0</span></div>
                                <div class="DC_Stat"><img src="https://d35aaqx5ub95lt.cloudfront.net/images/icons/398e4298a3b39ce566050e5c041949ef.svg"><span id="DC_UStreak">0</span></div>
                            </div>
                        </div>
                    </div>

                    <div class="DC_Divider"></div>

                    <div class="DC_VStack">
                        <p class="DC_Title">XP to farm</p>
                        <div class="DC_HStack">
                            <div class="DC_Input_Wrap">
                                <input type="number" class="DC_Input" id="DC_XP_Input" placeholder="0" min="30" max="500000">
                            </div>
                            <button class="DC_Input_Btn" id="DC_XP_Btn" disabled>
                                <span>GET XP</span>
                                <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22 12 18.56 5.82 22 7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
                            </button>
                        </div>
                        <div class="DC_Prog_Wrap" id="DC_XP_Prog"><div class="DC_Prog_Fill" id="DC_XP_Fill"></div></div>
                    </div>

                    <div class="DC_VStack">
                        <p class="DC_Title">Farm Gems</p>
                        <div class="DC_HStack">
                            <div class="DC_Input_Wrap">
                                <input type="number" class="DC_Input" id="DC_Gem_Input" placeholder="0" readonly>
                            </div>
                            <button class="DC_Input_Btn" id="DC_Gem_Btn" disabled>
                                <span>RUN</span>
                                <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
                            </button>
                        </div>
                    </div>

                    <div class="DC_VStack">
                        <p class="DC_Title">Streak days to add</p>
                        <div class="DC_HStack">
                            <div class="DC_Input_Wrap">
                                <input type="number" class="DC_Input" id="DC_Streak_Input" placeholder="0" min="1" max="3650">
                            </div>
                            <button class="DC_Input_Btn" id="DC_Streak_Btn" disabled>
                                <span>RUN</span>
                                <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/></svg>
                            </button>
                        </div>
                        <div class="DC_Prog_Wrap" id="DC_Streak_Prog"><div class="DC_Prog_Fill" id="DC_Streak_Fill"></div></div>
                    </div>

                    <div class="DC_Divider"></div>

                    <div class="DC_HStack_Between">
                        <button class="DC_Btn DC_Btn_Ghost" id="DC_Practice_Btn" style="flex:1;justify-content:center;" disabled>
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" stroke="rgb(var(--DC-gold-dark))" stroke-width="2" stroke-linecap="round"/></svg>
                            Practice Farm
                        </button>
                        <button class="DC_Btn DC_Btn_Ghost" id="DC_Super_Btn" style="flex:1;justify-content:center;" disabled>
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22 12 18.56 5.82 22 7 14.14 2 9.27l6.91-1.01L12 2z" fill="rgb(var(--DC-gold-dark))"/></svg>
                            Free Super
                        </button>
                    </div>

                    <div class="DC_HStack_Between">
                        <div class="DC_HStack" style="gap:4px;">
                            <span class="DC_Subtitle">Auto Solver</span>
                            <label class="DC_Toggle">
                                <input type="checkbox" id="DC_Solver_Toggle">
                                <span class="DC_Toggle_Slider"></span>
                            </label>
                        </div>
                        <span class="DC_Subtitle">DuoGang v2.0.2</span>
                    </div>
                </div>
            </div>
        </div>
    `;
    document.body.appendChild(wrap);

    // ==================== STATE ====================
    let jwt = null, sub = null, headers = null, user = null;
    let running = false, task = null, hidden = false;
    let delay = parseInt(localStorage.getItem('dc_delay') || '500', 10);
    let earnedGems = 0;

    const sleep = ms => new Promise(r => setTimeout(r, ms));

    // ==================== HELPERS ====================
    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 buildHeaders(j) {
        return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + j, 'User-Agent': navigator.userAgent };
    }

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

    function notif(msg, dur = 4) {
        const el = document.getElementById('DC_Notif');
        el.textContent = msg;
        el.classList.add('show');
        clearTimeout(window._dcNotifTimer);
        window._dcNotifTimer = setTimeout(() => el.classList.remove('show'), dur * 1000);
    }

    function setConn(state, label) {
        const btn = document.getElementById('DC_Conn_Btn');
        const txt = document.getElementById('DC_Conn_Txt');
        const ico = document.getElementById('DC_Conn_Ico');
        txt.textContent = label || state;
        ico.innerHTML = state === 'Connected' ?
            '<circle cx="12" cy="12" r="10" fill="rgb(var(--DC-green))"/><path d="M7 12l3 3 7-7" stroke="#fff" stroke-width="2" stroke-linecap="round"/>' :
            '<circle cx="12" cy="12" r="10" stroke="rgb(var(--DC-gold-dark))" stroke-width="2" fill="none"/>';
        if (state === 'Connected') {
            document.getElementById('DC_User_Row').style.display = 'flex';
        }
    }

    function setBtnState(btnId, cfg, label) {
        const btn = document.getElementById(btnId);
        if (!btn) return;
        btn.disabled = false;
        btn.className = cfg.cls || 'DC_Input_Btn';
        const span = btn.querySelector('span');
        if (span) span.textContent = label;
    }

    function resetBtn(btnId, label) {
        const btn = document.getElementById(btnId);
        if (!btn) return;
        btn.disabled = !user;
        setBtnState(btnId, { cls: 'DC_Input_Btn' }, 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 + '%';
        const btn = document.getElementById(btnId);
        if (btn) {
            const span = btn.querySelector('span');
            if (span) span.textContent = pct + '%';
        }
    }

    function setBtnRunning(btnId) {
        const btn = document.getElementById(btnId);
        if (!btn) return;
        btn.disabled = false;
        setBtnState(btnId, { cls: 'DC_Input_Btn DC_Btn_Red' }, 'STOP');
        const prog = document.getElementById(btnId.replace('_Btn', '_Prog'));
        if (prog) prog.classList.add('on');
    }

    function setBtnDone(btnId, label) {
        setBtnState(btnId, { cls: 'DC_Input_Btn DC_Btn_Green' }, label || 'DONE');
        const fill = document.getElementById(btnId.replace('_Btn', '_Fill'));
        if (fill) fill.style.width = '100%';
    }

    // ==================== CONNECTION ====================
    async function connect() {
        setConn('Connecting');
        jwt = getJwt();
        if (!jwt) { setConn('Not logged in'); return; }
        const dec = decodeJwt(jwt);
        if (!dec) { setConn('Invalid token'); return; }
        sub = dec.sub;
        headers = buildHeaders(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`);
            if (r.status !== 200) throw new Error(r.status);
            user = JSON.parse(r.responseText);
            setConn('Connected');
            renderUser(user);
            ['DC_XP_Btn', 'DC_Gem_Btn', 'DC_Streak_Btn', 'DC_Practice_Btn', 'DC_Super_Btn'].forEach(id => {
                const b = document.getElementById(id);
                if (b) b.disabled = false;
            });
        } catch (e) {
            setConn('Error - retrying');
            setTimeout(connect, 8000);
        }
    }

    function renderUser(u) {
        if (!u) return;
        document.getElementById('DC_UName').textContent = u.username || '';
        document.getElementById('DC_UXP').textContent = (u.totalXp || 0).toLocaleString();
        document.getElementById('DC_UGems').textContent = (u.gems || 0).toLocaleString();
        document.getElementById('DC_UStreak').textContent = (u.streak || 0).toLocaleString();
        if (u.picture) {
            let hq = u.picture.replace(/\/(medium|large|small)$/, '/xlarge');
            if (!hq.endsWith('/xlarge') && hq.includes('duolingo.com/ssr-avatars')) hq += '/xlarge';
            const av = document.getElementById('DC_Avatar');
            const img = document.createElement('img');
            img.src = hq;
            img.style.cssText = 'width:100%;height:100%;object-fit:cover;border-radius:50%;';
            img.onerror = function() { av.innerHTML = '?'; };
            av.innerHTML = '';
            av.appendChild(img);
        }
    }

    // ==================== XP FARM ====================
    async function farmXP(txp) {
        const MAX = 499;
        let loops = Math.floor(txp / MAX), rem = txp % MAX;
        if (rem > 0 && rem < 30 && loops > 0) { loops--; rem += MAX; }
        const total = loops + (rem >= 30 ? 1 : 0);
        let cur = 0, earned = 0;
        setBtnRunning('DC_XP_Btn');
        for (let i = 0; i < loops; i++) {
            if (!running) break;
            const ok = await storyXP(469);
            if (ok) { earned += MAX; cur++; }
            setBtnProgress('DC_XP_Btn', Math.floor((cur / total) * 100));
            await sleep(delay);
        }
        if (rem >= 30 && running) {
            const ok = await storyXP(Math.min(rem - 30, 469));
            if (ok) { earned += rem; cur++; }
            setBtnProgress('DC_XP_Btn', 100);
        }
        if (running) {
            setBtnDone('DC_XP_Btn', 'DONE');
            notif('Farmed ' + earned.toLocaleString() + ' XP!');
            setTimeout(connect, 1500);
            setTimeout(() => resetBtn('DC_XP_Btn', 'GET XP'), 3000);
        }
    }

    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; }
    }

    // ==================== GEM FARM ====================
    async function getGemRewards() {
        try {
            const r = await gm('GET', `https://www.duolingo.com/2023-05-23/users/${sub}?fields=rewardBundles`);
            if (r.status !== 200) return [];
            const bundles = JSON.parse(r.responseText).rewardBundles || [];
            const gems = [];
            for (const bundle of bundles) {
                for (const reward of bundle.rewards || []) {
                    if (!reward.consumed && (reward.id.includes('GEMS') || reward.currency === 'GEMS')) {
                        gems.push({ id: reward.id, amount: reward.amount || 0 });
                    }
                }
            }
            return gems;
        } catch { 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 {
            const r = await gm('PATCH', `https://www.duolingo.com/2023-05-23/users/${sub}/rewards/${rewardId}`, body);
            return r.status === 200;
        } catch { return false; }
    }

    async function getGemCount() {
        try {
            const r = await gm('GET', `https://www.duolingo.com/2023-05-23/users/${sub}?fields=gemsConfig`);
            if (r.status !== 200) return null;
            return JSON.parse(r.responseText).gemsConfig?.gems ?? null;
        } catch { return null; }
    }

    async function farmGems() {
        earnedGems = 0;
        const gemInput = document.getElementById('DC_Gem_Input');
        if (gemInput) gemInput.value = '';
        setBtnRunning('DC_Gem_Btn');

        outer: while (running && task === 'gems') {
            const rewards = await getGemRewards();
            if (rewards.length === 0) { await sleep(delay * 2); continue; }

            let gemsBefore = await getGemCount() ?? (user?.gems ?? 0);

            const THREADS = 3;
            for (let i = 0; i < rewards.length; i += THREADS) {
                if (!running || task !== 'gems') break outer;
                const batch = rewards.slice(i, i + THREADS);
                await Promise.all(batch.map(r => exploitGemReward(r.id)));
                await sleep(200);

                const now = await getGemCount();
                if (now !== null) {
                    const gained = Math.max(0, now - gemsBefore);
                    earnedGems += gained;
                    gemsBefore = now;
                    if (gemInput) gemInput.value = String(earnedGems);
                    if (user) { user.gems = now; renderUser(user); }
                }
                await sleep(Math.max(100, delay));
            }
            await sleep(delay);
        }

        resetBtn('DC_Gem_Btn', 'RUN');
        const btn = document.getElementById('DC_Gem_Btn');
        if (btn) btn.disabled = !user;
        if (earnedGems > 0) {
            notif('+' + earnedGems.toLocaleString() + ' gems gained!');
            setTimeout(connect, 1500);
        }
    }

    // ==================== STREAK FARM ====================
    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"];
        let farmStart;
        try {
            const s = new Date(user.streakData?.currentStreak?.startDate || Date.now());
            s.setDate(s.getDate() - 1);
            farmStart = s;
        } catch { const n = new Date(); n.setDate(n.getDate() - 1); farmStart = n; }
        setBtnRunning('DC_Streak_Btn');
        for (let i = 0; i < days; i++) {
            if (!running) break;
            const simDay = new Date(farmStart);
            simDay.setDate(simDay.getDate() - i);
            const end = Math.floor(simDay.getTime() / 1000);
            try {
                const sr = await gm('POST', 'https://www.duolingo.com/2023-05-23/sessions', {
                    challengeTypes: CH, fromLanguage: user.fromLanguage, isFinalLevel: false, isV2: true,
                    juicy: true, learningLanguage: user.learningLanguage, smartTipsVersion: 2, type: 'GLOBAL_PRACTICE'
                });
                if (sr.status === 200) {
                    const sess = JSON.parse(sr.responseText);
                    await new Promise((res, rej) => GM_xmlhttpRequest({
                        method: 'PUT', url: `https://www.duolingo.com/2023-05-23/sessions/${sess.id}`,
                        headers: headers, data: JSON.stringify({
                            ...sess, heartsLeft: 5, startTime: end - 1, endTime: end,
                            enableBonusPoints: false, failed: false, maxInLessonStreak: 9, shouldLearnThings: true
                        }),
                        onload: r => res(r), onerror: () => rej(), timeout: 15000, ontimeout: () => rej()
                    }));
                }
            } catch { }
            setBtnProgress('DC_Streak_Btn', Math.floor(((i + 1) / days) * 100));
            await sleep(delay);
        }
        if (running) {
            setBtnDone('DC_Streak_Btn', 'DONE');
            notif('Restored ' + days + ' streak days!');
            setTimeout(connect, 1500);
            setTimeout(() => resetBtn('DC_Streak_Btn', 'RUN'), 3000);
        }
    }

    // ==================== RUN LOGIC ====================
    async function run(type, val) {
        if (running) { running = false; notif('Farm stopped.'); return; }
        if (!user) { notif('Please wait for connection.'); return; }
        running = true; task = type;
        try {
            if (type === 'xp') await farmXP(val);
            if (type === 'gems') await farmGems();
            if (type === 'streak') await farmStreak(val);
        } catch (e) { notif('Error: ' + e.message); }
        if (!running) {
            if (type === 'xp') resetBtn('DC_XP_Btn', 'GET XP');
            if (type === 'streak') resetBtn('DC_Streak_Btn', 'RUN');
        }
        running = false; task = null;
    }

    // ==================== AUTO SOLVER ====================
    let solverUI = null;
    let isAutoMode = false;
    let solverEnabled = localStorage.getItem('dc_solver') === 'true';

    const solver = {
        findReact: (dom, traverseUp = 1) => {
            const key = Object.keys(dom).find(k => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$"));
            if (!key) return null;
            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;
        },

        determineType: () => {
            try {
                const t = window.sol?.type;
                if (!t) return false;
                if (t === 'speak' || t === 'listenSpeak' || document.querySelector('[data-test*="challenge-speak"]')) return 'Challenge Speak';
                if (t === 'listenMatch') return 'Listen Match';
                if (document.querySelector('.FmlUF')) {
                    if (t === 'arrange') return 'Story Arrange';
                    if (t === 'multiple-choice' || t === 'select-phrases') return 'Story Multiple Choice';
                    if (t === 'point-to-phrase') return 'Story Point to Phrase';
                    if (t === 'match') return 'Story Pairs';
                }
                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.querySelector('[data-test*="challenge-partialReverseTranslate"]')) return 'Partial Reverse';
                if (document.querySelector('[data-test="challenge challenge-characterWrite"]')) {
                    if (document.querySelector('g._25Ktp')) return 'Character Write Drag';
                    if (document.querySelector('path._1e5Zt')) 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.querySelector('[data-test$="challenge-tap-token"]')) return 'Pairs';
                }
                if (t === 'select' || t === 'characterSelect' || t === 'form' || t === 'readComprehension' || t === 'listenComprehension' || t === 'selectPronunciation') return 'Select Card';
                if (document.querySelector('[data-test*="challenge-name"]') && document.querySelector('[data-test="challenge-choice"]')) return 'Challenge Name';
                if (document.querySelector('[data-test="challenge-choice"]')) {
                    if (document.querySelector('[data-test="challenge-text-input"]')) return 'Challenge Choice with Text Input';
                    return 'Challenge Choice';
                }
                if (document.querySelector('[data-test$="challenge-tap-token"]')) {
                    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.querySelector('[data-test="challenge-tap-token-text"]')) return 'Fill in the Gap';
                if (document.querySelector('[data-test="challenge-text-input"]')) return 'Challenge Text Input';
                if (document.querySelector('textarea[data-test="challenge-translate-input"]')) return 'Challenge Translate Input';
                return false;
            } catch { return false; }
        },

        setValue: (el, val) => {
            const proto = el.tagName === 'TEXTAREA' ? window.HTMLTextAreaElement : window.HTMLInputElement;
            const setter = Object.getOwnPropertyDescriptor(proto.prototype, "value").set;
            setter.call(el, val);
            el.dispatchEvent(new Event('input', { bubbles: true }));
        },

        handle: async (type) => {
            const sleep = ms => new Promise(r => setTimeout(r, ms));
            try {
                switch (type) {
                    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) 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 judges = document.querySelectorAll('[data-test="challenge-judge-text"]');
                        if (judges.length) judges[idx]?.click();
                        else document.querySelectorAll('[data-test="challenge-choice"]')[idx]?.click();
                        break;
                    }
                    case 'Translate': {
                        const { correctTokens, correctSolutions } = window.sol;
                        if (correctTokens?.length) {
                            const tokens = document.querySelectorAll('[data-test$="challenge-tap-token"]');
                            const used = [];
                            for (const word of correctTokens) {
                                for (let i = 0; i < tokens.length; i++) {
                                    if (used.includes(i) || tokens[i].disabled) continue;
                                    if (tokens[i].innerText.trim() === word.trim()) {
                                        tokens[i].click();
                                        used.push(i);
                                        await sleep(40);
                                        break;
                                    }
                                }
                            }
                        } else if (correctSolutions) {
                            const ta = document.querySelector('textarea[data-test="challenge-translate-input"]');
                            if (ta) solver.setValue(ta, correctSolutions[0]);
                        }
                        break;
                    }
                    case 'Listen Tap': {
                        const tokens = document.querySelectorAll('[data-test$="challenge-tap-token"]');
                        const used = [];
                        for (const word of (window.sol.correctTokens || [])) {
                            for (let i = 0; i < tokens.length; i++) {
                                if (used.includes(i) || tokens[i].disabled) continue;
                                if (tokens[i].innerText.trim() === word.trim()) {
                                    tokens[i].click();
                                    used.push(i);
                                    await sleep(40);
                                    break;
                                }
                            }
                        }
                        break;
                    }
                    case 'Listen Type': {
                        const ans = 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) solver.setValue(ta, ans);
                        break;
                    }
                    case 'Complete Reverse': {
                        const blankTokens = window.sol.displayTokens?.filter(t => t.isBlank);
                        const inputs = document.querySelectorAll('[data-test="challenge-text-input"]');
                        inputs.forEach((inp, i) => { if (blankTokens?.[i]) solver.setValue(inp, blankTokens[i].text); });
                        break;
                    }
                    case 'Challenge Choice':
                        document.querySelectorAll("[data-test='challenge-choice']")[window.sol.correctIndex]?.click();
                        break;
                    case 'Challenge Choice with Text Input': {
                        const inp = document.querySelector('[data-test="challenge-text-input"]');
                        if (inp) {
                            const ans = window.sol.correctSolutions ? window.sol.correctSolutions[0].split(/(?<=^\S+)\s/)[1] : (window.sol.displayTokens?.find(t => t.isBlank)?.text || window.sol.prompt);
                            solver.setValue(inp, ans);
                        }
                        break;
                    }
                    case 'Challenge Text Input': {
                        const inp = document.querySelector('[data-test="challenge-text-input"]');
                        if (inp) solver.setValue(inp, window.sol.correctSolutions?.[0] || window.sol.displayTokens?.find(t => t.isBlank)?.text || window.sol.prompt);
                        break;
                    }
                    case 'Challenge Translate Input': {
                        const ta = document.querySelector('textarea[data-test="challenge-translate-input"]');
                        if (ta) solver.setValue(ta, window.sol.correctSolutions?.[0] || window.sol.prompt);
                        break;
                    }
                    case 'Partial Reverse': {
                        const el = document.querySelector('[data-test*="challenge-partialReverseTranslate"]')?.querySelector("span[contenteditable]");
                        if (el) {
                            const txt = window.sol?.displayTokens?.filter(t => t.isBlank)?.map(t => t.text)?.join('')?.trim() || '';
                            el.textContent = txt;
                            el.dispatchEvent(new Event('input', { bubbles: true }));
                        }
                        break;
                    }
                    case 'Type Cloze': {
                        const inp = document.querySelector('input[type="text"].b4jqk');
                        if (inp) {
                            const tok = window.sol.displayTokens.find(t => t.damageStart !== undefined);
                            if (tok) solver.setValue(inp, tok.text.slice(tok.damageStart));
                        }
                        break;
                    }
                    case 'Type Cloze Table': {
                        const rows = document.querySelectorAll('tbody tr');
                        window.sol.displayTableTokens.slice(1).forEach((rowToks, i) => {
                            const cell = rowToks[1]?.find(t => typeof t.damageStart === "number");
                            if (cell && rows[i]) {
                                const inp = rows[i].querySelector('input[type="text"].b4jqk');
                                if (inp) solver.setValue(inp, cell.text.slice(cell.damageStart));
                            }
                        });
                        break;
                    }
                    case 'Tap Cloze Table': {
                        const rows = document.querySelectorAll('tbody tr');
                        window.sol.displayTableTokens.slice(1).forEach((rowToks, i) => {
                            const cell = rowToks[1]?.find(t => typeof t.damageStart === "number");
                            if (!cell || !rows[i]) return;
                            const bank = document.querySelector('[data-test="word-bank"]');
                            const btns = bank ? Array.from(bank.querySelectorAll('button[data-test*="challenge-tap-token"]:not([aria-disabled="true"])')) : [];
                            const correct = cell.text.slice(cell.damageStart);
                            let matched = "";
                            for (const btn of btns) {
                                if (!correct.startsWith(matched + btn.innerText)) continue;
                                btn.click();
                                matched += btn.innerText;
                                if (matched === correct) break;
                            }
                        });
                        break;
                    }
                    case 'Type Complete Table': {
                        const rows = document.querySelectorAll('tbody tr');
                        window.sol.displayTableTokens.slice(1).forEach((rowToks, i) => {
                            const cell = rowToks[1]?.find(t => t.isBlank);
                            if (cell && rows[i]) {
                                const inp = rows[i].querySelector('input[type="text"].b4jqk');
                                if (inp) solver.setValue(inp, cell.text);
                            }
                        });
                        break;
                    }
                    case 'Pattern Tap Complete': {
                        const bank = document.querySelector('[data-test="word-bank"]');
                        if (bank) {
                            const idx = window.sol.correctIndex ?? 0;
                            const txt = window.sol.choices[idx];
                            const btn = Array.from(bank.querySelectorAll('button[data-test*="challenge-tap-token"]:not([aria-disabled="true"])')).find(b => b.innerText.trim() === txt);
                            if (btn) btn.click();
                        }
                        break;
                    }
                    case 'Story Arrange': {
                        const choices = document.querySelectorAll('[data-test*="challenge-tap-token"]:not(span)');
                        for (const idx of window.sol.phraseOrder) {
                            choices[idx]?.click();
                            await sleep(50);
                        }
                        break;
                    }
                    case 'Story Multiple Choice': {
                        const choices = document.querySelectorAll('[data-test="stories-choice"]');
                        choices[window.sol.correctAnswerIndex]?.click();
                        break;
                    }
                    case 'Story Point to Phrase': {
                        const choices = document.querySelectorAll('[data-test="challenge-tap-token-text"]');
                        let ci = -1;
                        for (let i = 0; i < window.sol.parts.length; i++) {
                            if (window.sol.parts[i].selectable) ci++;
                            if (window.sol.correctAnswerIndex === i) {
                                choices[ci]?.parentElement.click();
                                break;
                            }
                        }
                        break;
                    }
                    case 'Story Pairs': {
                        const btns = document.querySelectorAll('[data-test*="challenge-tap-token"]:not(span)');
                        const texts = document.querySelectorAll('[data-test="challenge-tap-token-text"]');
                        const map = new Map();
                        for (let i = 0; i < btns.length; i++) map.set(texts[i].innerText.toLowerCase().trim(), btns[i]);
                        for (const key in window.sol.dictionary) {
                            const val = window.sol.dictionary[key];
                            const k = key.split(":")[1].toLowerCase().trim();
                            const v = val.toLowerCase().trim();
                            map.get(k)?.click();
                            await sleep(50);
                            map.get(v)?.click();
                            await sleep(50);
                        }
                        break;
                    }
                    case 'Challenge Name': {
                        const arts = window.sol.articles;
                        const correct = window.sol.correctSolutions[0];
                        const match = arts.find(a => correct.startsWith(a));
                        if (match) {
                            const idx = arts.indexOf(match);
                            const sel = document.querySelector(`[data-test="challenge-choice"]:nth-child(${idx + 1})`);
                            if (sel) { sel.click(); await sleep(50); }
                            const inp = document.querySelector('[data-test="challenge-text-input"]');
                            if (inp) solver.setValue(inp, correct.substring(match.length).trim());
                        }
                        break;
                    }
                    case 'Pairs': {
                        const btns = document.querySelectorAll('[data-test*="challenge-tap-token"]:not(span)');
                        const texts = document.querySelectorAll('[data-test="challenge-tap-token-text"]');
                        if (texts.length !== btns.length || !btns.length) break;
                        for (const pair of window.sol.pairs || []) {
                            for (let i = 0; i < btns.length; i++) {
                                if (btns[i].disabled) continue;
                                const t = texts[i].innerText.toLowerCase().trim();
                                if (t === pair.transliteration?.toLowerCase().trim() || t === pair.character?.toLowerCase().trim() || t === pair.learningToken?.toLowerCase().trim() || t === pair.fromToken?.toLowerCase().trim()) {
                                    btns[i].click();
                                    await sleep(50);
                                }
                            }
                        }
                        break;
                    }
                    case 'Tokens Run': {
                        const all = document.querySelectorAll('[data-test$="challenge-tap-token"]');
                        const clicked = [];
                        for (const ct of window.sol.correctTokens) {
                            const match = Array.from(all).filter(el => el.textContent.trim() === ct.trim());
                            const idx = clicked.filter(t => t.textContent.trim() === ct.trim()).length;
                            const btn = match[idx] || match[0];
                            if (btn && !btn.disabled) { btn.click(); clicked.push(btn); }
                        }
                        break;
                    }
                    case 'Indices Run':
                    case 'Fill in the Gap': {
                        if (!window.sol.correctIndices) break;
                        const bank = document.querySelector('div[data-test="word-bank"]') || document.querySelector('.eSgkc');
                        if (!bank) break;
                        const btns = Array.from(bank.querySelectorAll('button[data-test*="challenge-tap-token"]:not(span)'));
                        for (const idx of window.sol.correctIndices) {
                            if (idx >= 0 && idx < btns.length && !btns[idx].disabled && btns[idx].getAttribute('aria-disabled') !== 'true') {
                                btns[idx].click();
                                await sleep(50);
                            }
                        }
                        break;
                    }
                    case 'Tap Complete Table': {
                        const rows = document.querySelectorAll('tbody tr');
                        const solRows = window.sol.displayTableTokens.slice(1);
                        const bank = document.querySelector('div[data-test="word-bank"]');
                        const bankBtns = bank ? Array.from(bank.querySelectorAll('button[data-test*="-challenge-tap-token"]')) : [];
                        const used = new Set();
                        for (let ri = 0; ri < solRows.length; ri++) {
                            const cell = solRows[ri][1]?.find(t => t.isBlank);
                            if (!cell) continue;
                            const txt = cell.text;
                            const rowBtns = rows[ri]?.querySelectorAll('button[data-test*="-challenge-tap-token"]');
                            let clicked = false;
                            if (rowBtns) {
                                for (const btn of rowBtns) {
                                    const tEl = btn.querySelector('[data-test="challenge-tap-token-text"]');
                                    if (tEl?.innerText.trim() === txt && !btn.disabled) { btn.click(); await sleep(50); clicked = true; break; }
                                }
                            }
                            if (!clicked && bankBtns.length) {
                                for (let i = 0; i < bankBtns.length; i++) {
                                    if (used.has(i)) continue;
                                    const tEl = bankBtns[i].querySelector('[data-test="challenge-tap-token-text"]');
                                    if (tEl?.innerText.trim() === txt && !bankBtns[i].disabled) { bankBtns[i].click(); await sleep(50); used.add(i); break; }
                                }
                            }
                        }
                        break;
                    }
                    case 'Syllable Tap':
                    case 'Syllable Listen Tap': {
                        const indices = window.sol.correctIndices;
                        const choices = window.sol.choices;
                        const domBtns = Array.from(document.querySelectorAll('[data-test="word-bank"] [data-test$="challenge-tap-token"]'));
                        indices.forEach(idx => {
                            const txt = choices[idx].text;
                            const btn = domBtns.find(b => b.innerText.trim() === txt);
                            if (btn) btn.click();
                        });
                        break;
                    }
                    case 'Character Write Drag': {
                        const createEvent = (t, x, y, btns) => new MouseEvent(t, { bubbles: true, clientX: x, clientY: y, buttons: btns, button: 0 });
                        const norm = s => s ? s.replace(/\s/g, '') : '';
                        for (const stroke of window.sol.strokes) {
                            const target = norm(stroke.path);
                            let path, handle;
                            while (!path || !handle) {
                                const cands = document.querySelectorAll('path._1e5Zt');
                                path = Array.from(cands).find(p => norm(p.getAttribute('d')) === target);
                                handle = document.querySelector('g._25Ktp');
                                if (!path || !handle) await sleep(10);
                            }
                            const mat = path.getScreenCTM(), len = path.getTotalLength();
                            const start = path.getPointAtLength(0).matrixTransform(mat);
                            const end = path.getPointAtLength(len).matrixTransform(mat);
                            handle.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
                            for (let s = 1; s <= 10; s++) {
                                const p = path.getPointAtLength((s / 10) * len).matrixTransform(mat);
                                const m = createEvent('mousemove', p.x, p.y, 1);
                                handle.dispatchEvent(m); document.dispatchEvent(m);
                            }
                            const fm = createEvent('mousemove', end.x, end.y, 1);
                            handle.dispatchEvent(fm); document.dispatchEvent(fm);
                            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 createEvent = (t, x, y, btns) => new MouseEvent(t, { bubbles: true, clientX: x, clientY: y, buttons: btns, button: 0 });
                        const norm = s => s ? s.replace(/\s/g, '') : '';
                        for (const stroke of window.sol.strokes) {
                            const target = norm(stroke.path);
                            let path, cursor;
                            while (!path || !cursor) {
                                const cands = document.querySelectorAll('path._1e5Zt');
                                path = Array.from(cands).find(p => norm(p.getAttribute('d')) === target);
                                cursor = document.querySelector('g._1h31R:not(._25Ktp)');
                                if (!path || !cursor) await sleep(10);
                            }
                            const mat = path.getScreenCTM(), len = path.getTotalLength();
                            const start = path.getPointAtLength(0).matrixTransform(mat);
                            const end = path.getPointAtLength(len).matrixTransform(mat);
                            cursor.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
                            document.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
                            for (let s = 1; s <= 10; s++) {
                                const p = path.getPointAtLength((s / 10) * len).matrixTransform(mat);
                                const m = createEvent('mousemove', p.x, p.y, 1);
                                cursor.dispatchEvent(m); document.dispatchEvent(m);
                            }
                            const fm = createEvent('mousemove', end.x, end.y, 1);
                            cursor.dispatchEvent(fm); document.dispatchEvent(fm);
                            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 createEvent = (t, x, y, btns) => new MouseEvent(t, { bubbles: true, clientX: x, clientY: y, buttons: btns, button: 0 });
                        const norm = s => s ? s.replace(/\s/g, '') : '';
                        const freeStrokes = window.sol.strokes.filter(s => s.strokeDrawMode === 'FREEHAND');
                        for (const stroke of freeStrokes) {
                            const target = norm(stroke.path);
                            let path, svg;
                            while (!path || !svg) {
                                const cands = document.querySelectorAll('path._22UPm');
                                path = Array.from(cands).find(p => norm(p.getAttribute('d')) === target);
                                svg = document.querySelector('svg.o1rqi');
                                if (!path || !svg) await sleep(10);
                            }
                            const mat = path.getScreenCTM(), len = path.getTotalLength();
                            const start = path.getPointAtLength(0).matrixTransform(mat);
                            const end = path.getPointAtLength(len).matrixTransform(mat);
                            svg.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
                            document.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
                            for (let s = 1; s <= 10; s++) {
                                const p = path.getPointAtLength((s / 10) * len).matrixTransform(mat);
                                const m = createEvent('mousemove', p.x, p.y, 1);
                                svg.dispatchEvent(m); document.dispatchEvent(m);
                            }
                            const fm = createEvent('mousemove', end.x, end.y, 1);
                            svg.dispatchEvent(fm); document.dispatchEvent(fm);
                            await sleep(5);
                            svg.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
                            document.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
                        }
                        break;
                    }
                }
            } catch (e) { console.error('Solver error:', e); }
        },

        clickNext: () => {
            const nb = document.querySelector('[data-test="player-next"]') || document.querySelector('[data-test="stories-player-continue"]') || document.querySelector('[data-test="stories-player-done"]');
            if (nb && nb.getAttribute('aria-disabled') !== 'true' && !nb.disabled) nb.click();
        },

        solve: async () => {
            if (solver._busy) return;
            solver._busy = true;
            try {
                const skipSel = [
                    '[data-test="practice-hub-ad-no-thanks-button"]', '[data-test="plus-no-thanks"]',
                    '[data-test="story-start"]', '.vpDIE'
                ];
                skipSel.forEach(sel => document.querySelector(sel)?.click());

                let mainEl = document.querySelector('._3yE3H') || document.querySelector('[data-test="challenge"]') || document.querySelector('[class*="challenge"]');
                if (!mainEl) { solver.clickNext(); solver._busy = false; return; }

                const ri = solver.findReact(mainEl);
                window.sol = ri?.props?.currentChallenge;
                if (!window.sol) { solver.clickNext(); solver._busy = false; return; }

                const ct = solver.determineType();
                if (ct && ct !== 'Challenge Speak' && ct !== 'Listen Match' && ct !== 'Listen Speak') {
                    await Promise.race([solver.handle(ct), new Promise(r => setTimeout(r, 2000))]);
                    await new Promise(r => setTimeout(r, 80));
                } else if (ct) {
                    const skipBtn = document.querySelector('button[data-test="player-skip"]');
                    if (!skipBtn) await new Promise(r => setTimeout(r, 500));
                    document.querySelector('button[data-test="player-skip"]')?.click();
                    await new Promise(r => setTimeout(r, 150));
                }
                solver.clickNext();
                await new Promise(r => setTimeout(r, 120));
            } catch { solver.clickNext(); }
            solver._busy = false;
        },

        _busy: false,

        createUI: () => {
            if (solverUI) return;
            solverUI = document.createElement('div');
            solverUI.className = 'DC_Solver_Btn';
            solverUI.innerHTML = `
                <button class="DC_Solve_One" id="DC_Solve_One">SOLVE</button>
                <button class="DC_Solve_All" id="DC_Solve_All">SOLVE ALL</button>
            `;
            document.body.appendChild(solverUI);
            document.getElementById('DC_Solve_One').addEventListener('click', () => solver.solve());
            document.getElementById('DC_Solve_All').addEventListener('click', () => solver.toggleAuto());
            document.addEventListener('keydown', (e) => {
                if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
                    if (e.shiftKey) solver.toggleAuto();
                    else solver.solve();
                }
            });
        },

        removeUI: () => {
            if (solverUI) { solverUI.remove(); solverUI = null; }
            isAutoMode = false;
        },

        toggleAuto: () => {
            isAutoMode = !isAutoMode;
            const btn = document.getElementById('DC_Solve_All');
            if (btn) {
                btn.textContent = isAutoMode ? 'PAUSE' : 'SOLVE ALL';
                btn.style.background = isAutoMode ? 'rgb(var(--DC-red))' : 'rgb(var(--DC-gold))';
                btn.style.borderBottomColor = isAutoMode ? '#cc0000' : 'rgb(var(--DC-gold-dark))';
            }
            if (isAutoMode) {
                (async function loop() {
                    while (isAutoMode) {
                        await solver.solve();
                        await new Promise(r => setTimeout(r, 400));
                    }
                })();
            }
        }
    };

    // Auto-solver observer
    setInterval(() => {
        const inLesson = window.location.pathname.includes('/lesson') || window.location.pathname.includes('/practice');
        if (inLesson && solverEnabled) {
            setTimeout(() => solver.createUI(), 500);
        } else {
            solver.removeUI();
        }
    }, 1000);

    // ==================== FREE SUPER ====================
    function installFakeSuper() {
        if (localStorage.getItem('dc_super') !== 'true') return;
        const RX = /https:\/\/www\.duolingo\.com\/\d{4}-\d{2}-\d{2}\/users\/.+/;
        const ITEMS = { gold_subscription: { itemName: 'gold_subscription', subscriptionInfo: { vendor: 'STRIPE', renewing: true, isFamilyPlan: true, expectedExpiration: 9999999999000 } } };
        function mod(j) {
            try {
                const d = JSON.parse(j);
                d.hasPlus = true;
                if (!d.trackingProperties) d.trackingProperties = {};
                d.trackingProperties.has_item_gold_subscription = true;
                d.shopItems = { ...d.shopItems, ...ITEMS };
                return JSON.stringify(d);
            } catch { return j; }
        }
        const uw = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
        const orig = uw.fetch;
        uw.fetch = function(resource, options) {
            const url = resource instanceof Request ? resource.url : resource;
            const method = resource instanceof Request ? resource.method : (options?.method || 'GET');
            if (method.toUpperCase() === 'GET' && RX.test(url) && !url.includes('/shop-items')) {
                return orig.apply(this, arguments).then(async r => {
                    const text = await r.clone().text();
                    return new Response(mod(text), { status: r.status, statusText: r.statusText, headers: new Headers(r.headers) });
                });
            }
            return orig.apply(this, arguments);
        };
    }
    installFakeSuper();

    // ==================== EVENT HANDLERS ====================

    // --- Dark Mode ---
    const dcRoot = document.getElementById('DC_Root');
    let isDark = localStorage.getItem('dc_dark') === 'true';
    function applyDarkMode() {
        if (isDark) {
            dcRoot.classList.add('DC_Dark');
            document.getElementById('DC_Dark_Icon_Moon').style.display = 'none';
            document.getElementById('DC_Dark_Icon_Sun').style.display = 'block';
        } else {
            dcRoot.classList.remove('DC_Dark');
            document.getElementById('DC_Dark_Icon_Moon').style.display = 'block';
            document.getElementById('DC_Dark_Icon_Sun').style.display = 'none';
        }
    }
    applyDarkMode();
    document.getElementById('DC_Dark_Btn').addEventListener('click', () => {
        isDark = !isDark;
        localStorage.setItem('dc_dark', isDark ? 'true' : 'false');
        applyDarkMode();
    });

    // --- Hide/Show with eye icon ---
    document.getElementById('DC_Hide_Btn').addEventListener('click', (e) => {
        // Prevent click firing right after drag
        if (window._dcDragMoved) return;
        hidden = !hidden;
        const main = document.getElementById('DC_Main');
        const box = document.getElementById('DC_Main_Box');
        const btn = document.getElementById('DC_Hide_Btn');
        const label = document.getElementById('DC_Hide_Label');
        box.style.transition = 'opacity 0.6s cubic-bezier(0.16,1,0.32,1), filter 0.6s cubic-bezier(0.16,1,0.32,1)';
        if (hidden) {
            box.style.opacity = '0';
            box.style.filter = 'blur(8px)';
            box.style.pointerEvents = 'none';
            label.textContent = 'Show';
            btn.classList.add('DC_Hide_Btn_Hidden');
        } else {
            box.style.opacity = '';
            box.style.filter = '';
            box.style.pointerEvents = '';
            label.textContent = 'Hide';
            btn.classList.remove('DC_Hide_Btn_Hidden');
        }
        setTimeout(() => { box.style.transition = ''; }, 600);
    });

    // --- Draggable panel ---
    (function() {
        const main = document.getElementById('DC_Main');
        let dragging = false, startX, startY, origRight, origBottom;
        window._dcDragMoved = false;

        main.addEventListener('mousedown', (e) => {
            // Only drag on the hide button (the handle) or the top bar area
            const hideBtn = document.getElementById('DC_Hide_Btn');
            if (!hideBtn.contains(e.target) && e.target !== hideBtn) return;
            dragging = true;
            window._dcDragMoved = false;
            const rect = main.getBoundingClientRect();
            startX = e.clientX;
            startY = e.clientY;
            origRight = window.innerWidth - rect.right;
            origBottom = window.innerHeight - rect.bottom;
            main.classList.add('DC_Dragging');
            e.preventDefault();
        });

        document.addEventListener('mousemove', (e) => {
            if (!dragging) return;
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;
            if (Math.abs(dx) > 4 || Math.abs(dy) > 4) window._dcDragMoved = true;
            const newRight = Math.max(0, Math.min(window.innerWidth - 50, origRight - dx));
            const newBottom = Math.max(0, Math.min(window.innerHeight - 50, origBottom - dy));
            main.style.right = newRight + 'px';
            main.style.bottom = newBottom + 'px';
        });

        document.addEventListener('mouseup', () => {
            if (!dragging) return;
            dragging = false;
            main.classList.remove('DC_Dragging');
            // Save position
            localStorage.setItem('dc_pos', JSON.stringify({ right: main.style.right, bottom: main.style.bottom }));
            setTimeout(() => { window._dcDragMoved = false; }, 50);
        });

        // Restore saved position
        try {
            const saved = JSON.parse(localStorage.getItem('dc_pos') || 'null');
            if (saved && saved.right && saved.bottom) {
                main.style.right = saved.right;
                main.style.bottom = saved.bottom;
            }
        } catch {}
    })();

    // XP button
    const xpI = document.getElementById('DC_XP_Input'), xpB = document.getElementById('DC_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('Stop current farm first.'); return; }
        const v = +xpI.value;
        if (v < 30) { notif('Min 30 XP'); return; }
        run('xp', v);
    });
    xpI.addEventListener('keydown', e => { if (e.key === 'Enter' && !xpB.disabled) xpB.click(); });

    // Gems button
    const gmB = document.getElementById('DC_Gem_Btn');
    gmB.addEventListener('click', () => {
        if (running && task === 'gems') { run('gems', 0); return; }
        if (running) { notif('Stop current farm first.'); return; }
        run('gems', 0);
    });

    // Streak button
    const stI = document.getElementById('DC_Streak_Input'), stB = document.getElementById('DC_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('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(); });

    // Practice button
    document.getElementById('DC_Practice_Btn').addEventListener('click', () => {
        if (!window.location.pathname.startsWith('/practice')) {
            window.location.assign('/practice');
        }
    });

    // Super button
    document.getElementById('DC_Super_Btn').addEventListener('click', () => {
        localStorage.setItem('dc_super', localStorage.getItem('dc_super') === 'true' ? 'false' : 'true');
        notif(localStorage.getItem('dc_super') === 'true' ? 'Free Super activated! Reload page.' : 'Free Super deactivated. Reload page.');
    });

    // Solver toggle
    const solverT = document.getElementById('DC_Solver_Toggle');
    solverT.checked = solverEnabled;
    solverT.addEventListener('change', () => {
        solverEnabled = solverT.checked;
        localStorage.setItem('dc_solver', solverEnabled ? 'true' : 'false');
        if (!solverEnabled) solver.removeUI();
    });

    // ==================== INITIALIZATION ====================
    // Initial animation
    const main = document.getElementById('DC_Main');
    const box = document.getElementById('DC_Main_Box');
    box.style.opacity = '0';
    box.style.filter = 'blur(8px)';
    setTimeout(() => {
        box.style.transition = 'opacity 0.6s cubic-bezier(0.16,1,0.32,1), filter 0.6s cubic-bezier(0.16,1,0.32,1)';
        box.style.opacity = '';
        box.style.filter = '';
        setTimeout(() => { box.style.transition = ''; }, 600);
    }, 300);

    connect();

})();