DuoAM

The best Duolingo account management tool

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

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.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

// ==UserScript==
// @name         DuoAM
// @namespace    Tampermonkey
// @version      1.2.5
// @description  The best Duolingo account management tool
// @author       kietxx_173
// @match        https://www.duolingo.com/*
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @run-at       document-idle
// ==/UserScript==
(function () {
  'use strict';
  const STORAGE_KEY       = 'duo_am_tokens_v1';
  const SETTINGS_KEY      = 'duo_am_settings_v1';
  const PENDING_LOGIN_KEY = 'duo_am_pending_login_v1';

  const LANGS = {
    vi: {
      tabAccounts:'📋 Tài khoản', tabAdd:'➕ Thêm', tabGuide:'📖 Hướng dẫn',
      searchPlaceholder:'Tìm tài khoản…',
      emptyNoAccounts:'Chưa có tài khoản nào',
      emptyNoAccountsSub:'Sang tab <b style="color:#58CC02">➕ Thêm</b> để bắt đầu',
      emptyNotFound:'Không tìm thấy',
      labelPlaceholder:'Tên hiển thị (ví dụ: main, alt1…)',
      tokenPlaceholder:'JWT Token',
      btnSave:'Lưu', btnGetToken:'⚡ Lấy token acc hiện tại', btnGetTokenSuccess:'✓ Đã điền token!',
      toastSwitching:'⏳ Đang chuyển tài khoản...', toastNoLabel:'⚠️ Nhập tên hiển thị',
      toastNoToken:'⚠️ Nhập JWT Token', toastDuplicateLabel:'⚠️ Tên đã tồn tại',
      toastSaved:n=>`✓ Đã lưu: ${n}`, toastDeleted:n=>`Đã xoá: ${n}`,
      toastNoTokenFound:'⚠️ Không tìm thấy token — bạn đang login chưa?',
      toastCookieError:'⚠️ Lỗi khi đọc cookie', toastCopyFail:'⚠️ Không copy được, hãy copy thủ công',
      toastLoginError:'⚠️ Token không hợp lệ hoặc đã hết hạn',
      toastTokenDetected:'🔴 Token lỗi — đăng nhập thất bại, vui lòng cập nhật token mới',
      toastLoginSuccess:n=>`✓ Đang dùng: ${n}`,
      toastBadFormat:'⚠️ Token sai định dạng — phải bắt đầu bằng eyJhb',
      badgeCurrent:'Current', btnLogin:'Login', btnDelete:'Xoá', btnEdit:'Sửa',
      confirmTitle:'Xác nhận đăng nhập', confirmMsg:name=>`Đăng nhập với tài khoản <strong>${name}</strong>?`,
      btnCancel:'Huỷ', btnOk:'Đăng nhập',
      editTitle:'Sửa tài khoản', editLabelField:'Tên hiển thị', editTokenField:'JWT Token',
      editPreviewField:'Xem trước token đầy đủ',
      editNote:'⚠️ Token phải bắt đầu bằng <b>eyJhb</b>. Sau khi lưu cần đăng nhập lại để áp dụng.',
      settingTheme:'🎨 Giao diện', themeAuto:'🌗 Tự động', themeDark:'🌙 Tối', themeLight:'☀️ Sáng',
      settingTransparent:'🪟 Nền trong suốt', settingTransparentDesc:'Hiệu ứng kính mờ (backdrop blur)',
      settingOpacity:'💧 Độ trong suốt', settingLanguage:'🌐 Ngôn ngữ',
      btnSettingsSave:'💾 Lưu cài đặt', toastSettingsSaved:'✓ Đã lưu cài đặt',
      langVi:'🇻🇳 Tiếng Việt', langEn:'🇬🇧 English',
      guideTitle:'📖 Cách lấy JWT Token',
      guideSteps:[
        {label:'Đăng nhập Duolingo trên trình duyệt',desc:'Vào <b>duolingo.com</b> và đăng nhập bằng tài khoản bạn muốn lưu. Đảm bảo đã vào được trang học (<b>/learn</b>).'},
        {label:'Mở DevTools (công cụ nhà phát triển)',desc:'Nhấn <em>F12</em> hoặc <em>Ctrl + Shift + I</em><br>Trên Mac: <em>Cmd + Option + I</em><br>Hoặc chuột phải vào trang → <b>Inspect / Kiểm tra</b>'},
        {label:'Chuyển sang tab Console',desc:'Trong DevTools, click vào tab <b>Console</b> ở thanh trên cùng.'},
        {label:'Bỏ qua cảnh báo "paste" (nếu có)',desc:'Chrome/Edge có thể hiện cảnh báo bảo mật yêu cầu gõ <b>allow pasting</b> rồi Enter trước khi dán lệnh. Làm theo rồi tiếp tục.'},
        {label:'Dán lệnh lấy token vào Console',desc:'Copy lệnh bên dưới, dán vào Console rồi nhấn <em>Enter</em>:',code:true,extra:'Token sẽ hiện ra trong Console — một chuỗi dài bắt đầu bằng <em>eyJ…</em>'},
        {label:"Copy token — bỏ dấu nháy đơn ' ở đầu và cuối",desc:`Kết quả trong Console trông như thế này:<br><em style="font-size:10px;word-break:break-all">'eyJhbGciOiJIUzI1NiJ9.eyJ…'</em><br><br>Bạn cần <b>bỏ dấu nháy đơn</b> <em>'</em> ở đầu và cuối chuỗi trước khi dán vào ô Token. Chỉ copy phần chữ ở giữa, ví dụ:<br><em style="font-size:10px;word-break:break-all">eyJhbGciOiJIUzI1NiJ9.eyJ…abc</em>`,warn:`⚠️ Nếu dán cả dấu <b>'</b> vào thì token sẽ không hoạt động và đăng nhập thất bại.`},
        {label:'Hoặc dùng nút tự động (nhanh hơn)',desc:'Sang tab <b>➕ Thêm</b>, nhấn nút <em>⚡ Lấy token acc hiện tại</em> — script sẽ tự đọc cookie và điền token vào ô, không cần làm thủ công bước 5–6.'},
        {label:'Điền tên và lưu tài khoản',desc:'Sang tab <b>➕ Thêm</b>, nhập <b>Tên hiển thị</b> (vd: <em>main</em>, <em>alt1</em>), dán token vào ô <b>JWT Token</b>, nhấn <em>Lưu</em>.'},
        {label:'Muốn lưu nhiều acc? Đăng xuất → đăng nhập acc khác → lặp lại',desc:'Mỗi lần đăng nhập bằng acc khác, token mới sẽ được ghi vào cookie. Lặp lại từ bước 1 để lưu thêm tài khoản.'}
      ]
    },
    en: {
      tabAccounts:'📋 Accounts', tabAdd:'➕ Add', tabGuide:'📖 Guide',
      searchPlaceholder:'Search accounts…',
      emptyNoAccounts:'No accounts yet',
      emptyNoAccountsSub:'Go to the <b style="color:#58CC02">➕ Add</b> tab to get started',
      emptyNotFound:'Nothing found',
      labelPlaceholder:'Display name (e.g. main, alt1…)',
      tokenPlaceholder:'JWT Token',
      btnSave:'Save', btnGetToken:'⚡ Grab current token', btnGetTokenSuccess:'✓ Token filled!',
      toastSwitching:'⏳ Switching account…', toastNoLabel:'⚠️ Enter a display name',
      toastNoToken:'⚠️ Enter a JWT Token', toastDuplicateLabel:'⚠️ Name already exists',
      toastSaved:n=>`✓ Saved: ${n}`, toastDeleted:n=>`Deleted: ${n}`,
      toastNoTokenFound:'⚠️ No token found — are you logged in?',
      toastCookieError:'⚠️ Error reading cookie', toastCopyFail:'⚠️ Copy failed, please copy manually',
      toastLoginError:'⚠️ Token invalid or expired',
      toastTokenDetected:'🔴 Token error — login failed, please update your token',
      toastLoginSuccess:n=>`✓ Active: ${n}`,
      toastBadFormat:'⚠️ Invalid token format — must start with eyJhb',
      badgeCurrent:'Current', btnLogin:'Login', btnDelete:'Delete', btnEdit:'Edit',
      confirmTitle:'Confirm login', confirmMsg:name=>`Log in with account <strong>${name}</strong>?`,
      btnCancel:'Cancel', btnOk:'Log in',
      editTitle:'Edit account', editLabelField:'Display name', editTokenField:'JWT Token',
      editPreviewField:'Full token preview',
      editNote:'⚠️ Token must start with <b>eyJhb</b>. Re-login after saving to apply the new token.',
      settingTheme:'🎨 Theme', themeAuto:'🌗 Auto', themeDark:'🌙 Dark', themeLight:'☀️ Light',
      settingTransparent:'🪟 Transparent background', settingTransparentDesc:'Frosted glass effect (backdrop blur)',
      settingOpacity:'💧 Opacity', settingLanguage:'🌐 Language',
      btnSettingsSave:'💾 Save settings', toastSettingsSaved:'✓ Settings saved',
      langVi:'🇻🇳 Tiếng Việt', langEn:'🇬🇧 English',
      guideTitle:'📖 How to get your JWT Token',
      guideSteps:[
        {label:'Log in to Duolingo in your browser',desc:'Go to <b>duolingo.com</b> and sign in with the account you want to save. Make sure you reach the learning page (<b>/learn</b>).'},
        {label:'Open DevTools',desc:'Press <em>F12</em> or <em>Ctrl + Shift + I</em><br>On Mac: <em>Cmd + Option + I</em><br>Or right-click the page → <b>Inspect</b>'},
        {label:'Switch to the Console tab',desc:'In DevTools, click the <b>Console</b> tab at the top.'},
        {label:'Dismiss the "paste" warning if it appears',desc:'Chrome/Edge may show a security warning asking you to type <b>allow pasting</b> and press Enter before you can paste commands. Do that first, then continue.'},
        {label:'Paste the token command into the Console',desc:'Copy the command below, paste it in the Console, then press <em>Enter</em>:',code:true,extra:'The token will appear in the Console — a long string starting with <em>eyJ…</em>'},
        {label:"Copy the token — remove the single quotes ' at both ends",desc:`The Console output will look like this:<br><em style="font-size:10px;word-break:break-all">'eyJhbGciOiJIUzI1NiJ9.eyJ…'</em><br><br>You need to <b>remove the single quotes</b> <em>'</em> at the start and end before pasting the token into the field. Copy only the middle part, e.g.:<br><em style="font-size:10px;word-break:break-all">eyJhbGciOiJIUzI1NiJ9.eyJ…abc</em>`,warn:`⚠️ If you include the <b>'</b> quotes, the token won't work and login will fail.`},
        {label:'Or use the auto button (faster)',desc:'Go to the <b>➕ Add</b> tab and click <em>⚡ Grab current token</em> — the script will read the cookie and fill in the token automatically, skipping steps 5–6.'},
        {label:'Enter a name and save the account',desc:'In the <b>➕ Add</b> tab, enter a <b>Display name</b> (e.g. <em>main</em>, <em>alt1</em>), paste the token into the <b>JWT Token</b> field, and click <em>Save</em>.'},
        {label:'Want to save more accounts? Log out → log in as another → repeat',desc:'Each time you log in as a different account, a new token is written to the cookie. Repeat from step 1 to save more accounts.'}
      ]
    }
  };

  const t = (key,...args) => { const v=LANGS[settings.lang||'vi'][key]; return typeof v==='function'?v(...args):(v||key); };
  const escHtml = s => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
  const sleep = ms => new Promise(r=>setTimeout(r,ms));
  const loadAccounts = () => { try{return JSON.parse(GM_getValue(STORAGE_KEY,'[]'));}catch{return[];} };
  const saveAccounts = list => GM_setValue(STORAGE_KEY,JSON.stringify(list));
  const loadSettings = () => { try{return Object.assign({theme:'dark',themeMode:'auto',transparent:true,opacity:85,lang:'vi'},JSON.parse(GM_getValue(SETTINGS_KEY,'{}')));}catch{return{theme:'dark',themeMode:'auto',transparent:true,opacity:85,lang:'vi'};} };
  const saveSettings = s => GM_setValue(SETTINGS_KEY,JSON.stringify(s));
  const getCurrentToken = () => { try{return document.cookie.split('; ').find(x=>x.startsWith('jwt_token='))?.split('=')[1]||null;}catch{return null;} };
  const isCurrentAccount = acc => {
    if (location.pathname.startsWith('/login') || location.search.includes('loginRedirect')) return false;
    const tk = getCurrentToken();
    return !!(tk && acc.token === tk);
  };

  // ─── Background account detection ──────────────────────────────────────────
  function detectCurrentAccount() {
    if (location.pathname.startsWith('/login') || location.search.includes('loginRedirect')) return null;
    const tk = getCurrentToken();
    if (!tk) return null;
    const list = loadAccounts();
    return list.find(a => a.token === tk) || null;
  }

  function updateFab() {
    fab.innerHTML = USER_SVG;
    fab.style.background = '#58CC02';
    fab.style.boxShadow = '0 3px 0 0 #46A302,0 4px 16px rgba(88,204,2,.35)';
    fab.style.fontSize = '26px';
    fab.style.fontWeight = '';
    fab.style.color = '#fff';
  }

  async function loginByToken(token) {
    panel.classList.add('hidden');
    showToast(t('toastSwitching'));
    document.cookie=`jwt_token=${token}; path=/; domain=.duolingo.com`;
    await sleep(400);
    const set=getCurrentToken();
    if(!set||set!==token){showToast(t('toastLoginError'));panel.classList.remove('hidden');return;}
    GM_setValue(PENDING_LOGIN_KEY, '1');
    location.replace('https://www.duolingo.com/learn');
  }

  function makeDraggable(el,handle) {
    let x=0,y=0,mx=0,my=0;
    handle.style.cursor='move';
    handle.onmousedown=e=>{
      e.preventDefault(); mx=e.clientX; my=e.clientY;
      document.onmouseup=()=>{document.onmouseup=null;document.onmousemove=null;};
      document.onmousemove=e=>{
        x=mx-e.clientX; y=my-e.clientY; mx=e.clientX; my=e.clientY;
        el.style.top=(el.offsetTop-y)+'px'; el.style.left=(el.offsetLeft-x)+'px';
        el.style.right='auto'; el.style.bottom='auto';
      };
    };
  }

  const buildAvatarEl = (acc,ci) => {
    const el=document.createElement('div');
    el.className=`dam-avatar dam-avatar-${ci%6}`;
    el.textContent=acc.label.slice(0,2).toUpperCase();
    return el;
  };

  GM_addStyle(`
    @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
    #duo-am-panel {
      --dam-r:16px; --dam-inp-color:rgba(255,255,255,0.07); --dam-acc-color:rgba(255,255,255,0.04);
      --dam-border:rgba(255,255,255,0.09); --dam-text:#E6E6E6; --dam-text-muted:#c0c0c0;
      --dam-text-dim:#999; --dam-text-dim2:#777; --dam-accent:#58CC02; --dam-accent-dk:#46A302;
      --dam-accent-lt:#61E002; --dam-red:#FF6B6B; --dam-scroll:rgba(255,255,255,0.13);
      --dam-ring-gap:#12131a; --dam-bg-solid:rgba(18,19,26,0.97); --dam-bg-glass:rgba(18,19,26,0.72);
    }
    #duo-am-panel.dam-light {
      --dam-inp-color:rgba(0,0,0,0.06); --dam-acc-color:rgba(0,0,0,0.04); --dam-border:rgba(0,0,0,0.09);
      --dam-text:#1a1a1a; --dam-text-muted:#444; --dam-text-dim:#666; --dam-text-dim2:#888;
      --dam-red:#e53935; --dam-scroll:rgba(0,0,0,0.13); --dam-ring-gap:#f0f1f5;
      --dam-bg-solid:rgba(240,241,245,0.97); --dam-bg-glass:rgba(240,241,245,0.72);
    }
    #duo-am-fab {
      position:fixed; bottom:24px; right:24px; z-index:99999;
      width:48px; height:48px; border-radius:50%; background:#58CC02; border:none;
      box-shadow:0 3px 0 0 #46A302,0 4px 16px rgba(88,204,2,.35);
      cursor:grab; display:flex; align-items:center; justify-content:center;
      font-size:26px; color:#fff; transition:background .15s,box-shadow .1s;
      touch-action:none; user-select:none;
    }
    #duo-am-fab:hover{background:#61E002;}
    #duo-am-fab.dam-fab-dragging{cursor:grabbing;box-shadow:0 6px 24px rgba(88,204,2,.5);}
    #duo-am-fab.dam-fab-pressed{transform:translateY(2px);box-shadow:0 1px 0 0 #46A302 !important;}
    #duo-am-panel {
      position:fixed; top:72px; right:20px; z-index:99999; width:360px;
      border-radius:var(--dam-r); border:1px solid var(--dam-border);
      box-shadow:0 8px 32px rgba(0,0,0,.35); font-family:'Inter',sans-serif;
      font-size:13px; color:var(--dam-text); transition:opacity .18s,transform .18s;
      transform-origin:top right; isolation:isolate; overflow:hidden;
    }
    #duo-am-panel.hidden{opacity:0;transform:scale(0.92) translateY(-6px);pointer-events:none;}
    #duo-am-panel::before {
      content:''; position:absolute; inset:0; z-index:-1;
      background:var(--dam-panel-bg,var(--dam-bg-glass));
      backdrop-filter:var(--dam-panel-filter,blur(24px) saturate(180%));
      -webkit-backdrop-filter:var(--dam-panel-filter,blur(24px) saturate(180%));
    }
    .dam-header {
      display:flex; align-items:center; gap:8px; padding:12px 14px; user-select:none;
      border-bottom:1px solid var(--dam-border); border-radius:var(--dam-r) var(--dam-r) 0 0;
    }
    .dam-header h2{margin:0;font-size:14px;font-weight:800;color:var(--dam-accent);flex:1;letter-spacing:-.3px;}
    .dam-icon-btn {
      width:26px; height:26px; border-radius:6px; border:none; cursor:pointer;
      background:transparent; color:var(--dam-text-dim); font-size:13px;
      display:flex; align-items:center; justify-content:center;
      transition:background .15s,color .15s; flex-shrink:0;
    }
    .dam-icon-btn:hover{background:var(--dam-border);color:var(--dam-text);}
    .dam-icon-btn.close:hover{background:rgba(255,107,107,.15);color:var(--dam-red);}
    .dam-icon-btn.active{background:var(--dam-accent);color:#fff;}
    .dam-tabs{display:flex;border-bottom:1px solid var(--dam-border);}
    .dam-tab {
      flex:1; padding:9px 0; font-size:11px; font-weight:700; font-family:'Inter',sans-serif;
      border:none; background:transparent; color:var(--dam-text-dim); cursor:pointer;
      border-bottom:2px solid transparent; transition:color .15s,border-color .15s;
    }
    .dam-tab.active{color:var(--dam-accent);border-bottom:2px solid var(--dam-accent);}
    .dam-tab:hover:not(.active){color:var(--dam-text-muted);}
    .dam-pane{display:none;} .dam-pane.active{display:block;}
    .dam-add{padding:12px 14px;display:flex;flex-direction:column;gap:0;}
    .dam-inp {
      flex:1; padding:8px 10px; border:1px solid var(--dam-border); border-radius:8px;
      font-size:12px; font-family:'Inter',sans-serif; font-weight:500; outline:none;
      background:var(--dam-inp-color); box-sizing:border-box; color:var(--dam-text); transition:border-color .15s;
    }
    .dam-inp:focus{border-color:var(--dam-accent);}
    .dam-inp::placeholder{color:var(--dam-text-dim2);}
    .dam-inp-full{width:100%;margin-bottom:8px;}
    .dam-token-wrap{position:relative;display:flex;align-items:center;margin-bottom:8px;}
    .dam-token-wrap .dam-inp{width:100%;padding-right:62px;}
    .dam-btn-clear-token {
      position:absolute; right:34px; top:50%; transform:translateY(-50%);
      width:22px; height:22px; border-radius:5px; border:none; cursor:pointer;
      background:transparent; color:var(--dam-text-dim); font-size:11px; font-weight:700;
      display:flex; align-items:center; justify-content:center; transition:background .15s,color .15s; flex-shrink:0; padding:0;
    }
    .dam-btn-clear-token:hover{background:rgba(255,107,107,.15);color:var(--dam-red);}
    .dam-btn-eye {
      position:absolute; right:6px; top:50%; transform:translateY(-50%);
      width:24px; height:24px; border-radius:5px; border:none; cursor:pointer;
      background:transparent; color:var(--dam-text-dim); font-size:13px;
      display:flex; align-items:center; justify-content:center; transition:background .15s,color .15s; flex-shrink:0; padding:0;
    }
    .dam-btn-eye:hover{background:var(--dam-border);color:var(--dam-text);}
    .dam-btn-row{display:flex;gap:3px;margin-top:4px;}
    .dam-btn-add {
      flex:6; height:40px; background:var(--dam-accent); color:#fff; border:none;
      border-radius:10px; font-size:13px; font-weight:800; font-family:'Inter',sans-serif; cursor:pointer;
      box-shadow:0 3px 0 0 var(--dam-accent-dk); transition:background .15s,transform .1s,box-shadow .1s;
    }
    .dam-btn-add:hover{background:var(--dam-accent-lt);}
    .dam-btn-add:active{transform:translateY(2px);box-shadow:0 1px 0 0 var(--dam-accent-dk);}
    .dam-btn-gettoken {
      flex:4; height:40px; background:var(--dam-accent); color:#fff; border:none; border-radius:10px;
      font-size:13px; font-weight:800; font-family:'Inter',sans-serif; cursor:pointer;
      white-space:nowrap; overflow:hidden; box-shadow:0 3px 0 0 var(--dam-accent-dk);
      transition:background .15s,transform .1s,box-shadow .1s;
      display:flex; align-items:center; justify-content:center; gap:5px;
    }
    .dam-btn-gettoken:hover{background:var(--dam-accent-lt);}
    .dam-btn-gettoken:active{transform:translateY(2px);box-shadow:0 1px 0 0 var(--dam-accent-dk);}
    .dam-btn-gettoken.success{background:#46A302;}
    .dam-search-wrap{padding:8px 14px;border-bottom:1px solid var(--dam-border);}
    .dam-search {
      width:100%; padding:7px 10px 7px 30px; box-sizing:border-box;
      border:1px solid var(--dam-border); border-radius:8px; font-size:12px;
      font-family:'Inter',sans-serif; outline:none; background:var(--dam-inp-color); color:var(--dam-text);
      transition:border-color .15s;
      background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'/%3E%3C/svg%3E");
      background-repeat:no-repeat; background-position:9px center;
    }
    .dam-search:focus{border-color:var(--dam-accent);}
    .dam-search::placeholder{color:var(--dam-text-dim2);}
    .dam-list{max-height:260px;overflow-y:auto;}
    .dam-list::-webkit-scrollbar{width:3px;}
    .dam-list::-webkit-scrollbar-thumb{background:var(--dam-scroll);border-radius:4px;}
    .dam-empty{padding:28px 14px;text-align:center;color:var(--dam-text-dim2);font-size:12px;line-height:1.6;}
    .dam-empty-icon{font-size:30px;display:block;margin-bottom:8px;}
    .dam-acc {
      display:flex; align-items:center; gap:8px; padding:9px 14px;
      transition:background .1s; border-top:1px solid var(--dam-border);
    }
    .dam-acc:first-child{border-top:none;}
    .dam-acc:hover{background:var(--dam-acc-color);}
    .dam-acc:hover .dam-btn-del,
    .dam-acc:hover .dam-btn-edit-inline{opacity:1;}
    .dam-acc.dam-acc-current{background:rgba(88,204,2,0.06);}
    .dam-acc.dam-acc-current:hover{background:rgba(88,204,2,0.10);}
    .dam-avatar {
      width:36px; height:36px; border-radius:50%; flex-shrink:0;
      display:flex; align-items:center; justify-content:center;
      font-weight:800; font-size:12px; overflow:hidden;
    }
    .dam-avatar-0{background:#1C3D00;color:#58CC02;} .dam-avatar-1{background:#00304A;color:#1CB0F6;}
    .dam-avatar-2{background:#3A1F52;color:#CE82FF;} .dam-avatar-3{background:#433200;color:#FFC800;}
    .dam-avatar-4{background:#4A1515;color:#FF6B6B;} .dam-avatar-5{background:#4A2900;color:#FF9600;}
    #duo-am-panel.dam-light .dam-avatar-0{background:#d4f5a8;color:#2a7000;}
    #duo-am-panel.dam-light .dam-avatar-1{background:#b3e8ff;color:#005f8a;}
    #duo-am-panel.dam-light .dam-avatar-2{background:#e8d0ff;color:#6b00b3;}
    #duo-am-panel.dam-light .dam-avatar-3{background:#fff0b3;color:#7a5c00;}
    #duo-am-panel.dam-light .dam-avatar-4{background:#ffd0d0;color:#a00000;}
    #duo-am-panel.dam-light .dam-avatar-5{background:#ffe0b3;color:#8a4500;}
    .dam-avatar-current-ring{box-shadow:0 0 0 2px var(--dam-ring-gap),0 0 0 4px #58CC02;}
    .dam-info{flex:1;min-width:0;}
    .dam-name{font-weight:700;font-size:13px;color:var(--dam-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
    .dam-sub-row{display:flex;align-items:center;gap:4px;margin-top:2px;min-width:0;}
    .dam-sub{font-size:10px;color:var(--dam-text-dim2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px;}
    /* Edit button — inline next to token, visible on hover */
    .dam-btn-edit-inline {
      width:18px; height:18px; flex-shrink:0; opacity:0;
      background:transparent; border:1px solid var(--dam-border); border-radius:4px;
      cursor:pointer; color:var(--dam-text-dim); font-size:10px;
      display:flex; align-items:center; justify-content:center; transition:all .15s; padding:0;
    }
    .dam-btn-edit-inline:hover{border-color:var(--dam-accent);color:var(--dam-accent);background:rgba(88,204,2,0.07);}
    /* Delete button */
    .dam-btn-del {
      width:24px; height:24px; flex-shrink:0; opacity:0;
      background:transparent; border:1px solid var(--dam-border); border-radius:6px;
      cursor:pointer; color:var(--dam-text-dim); font-size:10px;
      display:flex; align-items:center; justify-content:center; transition:all .15s;
    }
    .dam-btn-del:hover{border-color:var(--dam-red);color:var(--dam-red);background:rgba(255,107,107,.1);}
    /* Login button */
    .dam-btn-login {
      padding:0 10px; height:28px; background:var(--dam-accent); color:#fff;
      border:none; border-radius:6px; font-size:11px; font-weight:700;
      font-family:'Inter',sans-serif; cursor:pointer; flex-shrink:0;
      box-shadow:0 2px 0 0 var(--dam-accent-dk); transition:background .15s,transform .1s,box-shadow .1s;
    }
    .dam-btn-login:hover{background:var(--dam-accent-lt);}
    .dam-btn-login:active{transform:translateY(1px);box-shadow:none;}
    .dam-badge-current {
      display:flex; align-items:center; gap:4px; padding:0 9px; height:28px; flex-shrink:0;
      background:rgba(88,204,2,0.12); border:1px solid rgba(88,204,2,0.35); border-radius:6px;
      font-size:10px; font-weight:800; font-family:'Inter',sans-serif; color:var(--dam-accent);
      letter-spacing:.3px; pointer-events:none; white-space:nowrap;
    }
    .dam-badge-current::before {
      content:''; width:6px; height:6px; border-radius:50%; background:var(--dam-accent);
      box-shadow:0 0 5px rgba(88,204,2,.7); animation:dam-pulse 1.8s ease-in-out infinite; flex-shrink:0;
    }
    @keyframes dam-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.7)}}
    .dam-settings{padding:14px;display:flex;flex-direction:column;gap:14px;}
    .dam-setting-row{display:flex;flex-direction:column;gap:6px;}
    .dam-setting-label{font-size:11px;font-weight:700;color:var(--dam-text-dim);text-transform:uppercase;letter-spacing:.6px;}
    .dam-theme-pills,.dam-lang-pills{display:flex;gap:6px;}
    .dam-pill,.dam-lang-pill {
      flex:1; height:32px; border-radius:8px; border:1px solid var(--dam-border);
      background:var(--dam-inp-color); color:var(--dam-text-muted); font-size:11px; font-weight:700;
      font-family:'Inter',sans-serif; cursor:pointer; transition:all .15s;
      display:flex; align-items:center; justify-content:center; gap:4px;
    }
    .dam-pill:hover,.dam-lang-pill:hover{border-color:var(--dam-accent);color:var(--dam-accent);}
    .dam-pill.active,.dam-lang-pill.active{background:var(--dam-accent);border-color:var(--dam-accent-dk);color:#fff;box-shadow:0 2px 0 0 var(--dam-accent-dk);}
    .dam-toggle-row{display:flex;align-items:center;justify-content:space-between;}
    .dam-toggle-title{font-size:12px;font-weight:600;color:var(--dam-text);}
    .dam-toggle-desc{font-size:10px;color:var(--dam-text-dim2);margin-top:2px;}
    .dam-switch{position:relative;width:40px;height:22px;flex-shrink:0;}
    .dam-switch input{opacity:0;width:0;height:0;position:absolute;}
    .dam-switch-track{position:absolute;inset:0;background:var(--dam-border);border-radius:11px;cursor:pointer;transition:background .2s;}
    .dam-switch-track::before{content:'';position:absolute;width:16px;height:16px;border-radius:50%;background:#fff;top:3px;left:3px;transition:transform .2s;box-shadow:0 1px 3px rgba(0,0,0,.3);}
    .dam-switch input:checked+.dam-switch-track{background:var(--dam-accent);}
    .dam-switch input:checked+.dam-switch-track::before{transform:translateX(18px);}
    .dam-slider-row{display:flex;align-items:center;gap:10px;}
    .dam-slider{-webkit-appearance:none;appearance:none;flex:1;height:4px;border-radius:4px;background:var(--dam-border);outline:none;cursor:pointer;}
    .dam-slider::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;border-radius:50%;background:var(--dam-accent);cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.4);transition:background .15s,transform .1s;}
    .dam-slider::-webkit-slider-thumb:hover{background:var(--dam-accent-lt);transform:scale(1.15);}
    .dam-slider::-moz-range-thumb{width:16px;height:16px;border-radius:50%;border:none;background:var(--dam-accent);cursor:pointer;}
    .dam-slider-val{font-size:11px;font-weight:700;color:var(--dam-accent);width:32px;text-align:right;flex-shrink:0;}
    .dam-settings-divider{height:1px;background:var(--dam-border);margin:2px 0;}
    .dam-opacity-row-disabled{opacity:0.35;pointer-events:none;}
    .dam-settings-footer{padding:12px 14px;border-top:1px solid var(--dam-border);}
    .dam-btn-settings-save {
      width:100%; height:38px; background:var(--dam-accent); color:#fff; border:none; border-radius:10px;
      font-size:13px; font-weight:800; font-family:'Inter',sans-serif; cursor:pointer;
      box-shadow:0 3px 0 0 var(--dam-accent-dk); transition:background .15s,transform .1s,box-shadow .1s;
    }
    .dam-btn-settings-save:hover{background:var(--dam-accent-lt);}
    .dam-btn-settings-save:active{transform:translateY(2px);box-shadow:0 1px 0 0 var(--dam-accent-dk);}
    .dam-btn-settings-save.saved{background:#46A302;}
    .dam-guide{padding:12px 14px 16px;display:flex;flex-direction:column;gap:0;max-height:340px;overflow-y:auto;}
    .dam-guide::-webkit-scrollbar{width:3px;}
    .dam-guide::-webkit-scrollbar-thumb{background:var(--dam-scroll);border-radius:4px;}
    .dam-guide-title{font-size:11px;font-weight:800;color:var(--dam-accent);text-transform:uppercase;letter-spacing:.7px;margin-bottom:10px;}
    .dam-step{display:flex;gap:10px;padding:9px 0;border-bottom:1px solid var(--dam-border);}
    .dam-step:last-child{border-bottom:none;}
    .dam-step-num {
      width:20px; height:20px; border-radius:50%; flex-shrink:0;
      background:rgba(88,204,2,0.13); border:1px solid rgba(88,204,2,0.3);
      color:var(--dam-accent); font-size:10px; font-weight:800;
      display:flex; align-items:center; justify-content:center; margin-top:1px;
    }
    .dam-step-body{flex:1;min-width:0;}
    .dam-step-label{font-size:12px;font-weight:700;color:var(--dam-text);line-height:1.4;margin-bottom:4px;}
    .dam-step-desc{font-size:11px;color:var(--dam-text-muted);line-height:1.55;}
    .dam-step-desc b{color:var(--dam-text);font-weight:700;}
    .dam-step-desc em{font-style:normal;color:var(--dam-accent);background:rgba(88,204,2,0.1);border-radius:3px;padding:0 4px;font-size:10.5px;font-weight:600;}
    .dam-code-block {
      margin-top:6px; position:relative; background:rgba(0,0,0,0.28);
      border:1px solid var(--dam-border); border-radius:7px; padding:8px 34px 8px 10px;
      font-family:'Consolas','Fira Code',monospace; font-size:10.5px; color:#b5f5a0;
      line-height:1.5; word-break:break-all; cursor:pointer; transition:border-color .15s;
    }
    .dam-code-block:hover{border-color:rgba(88,204,2,0.4);}
    .dam-code-block:hover .dam-copy-hint{opacity:1;}
    .dam-copy-hint {
      position:absolute; top:50%; right:7px; transform:translateY(-50%);
      font-size:9px; font-weight:800; color:var(--dam-accent); opacity:0; transition:opacity .15s;
      pointer-events:none; text-transform:uppercase; letter-spacing:.4px;
      background:rgba(0,0,0,0.4); padding:2px 5px; border-radius:4px;
    }
    .dam-copy-hint.copied{opacity:1;color:#fff;background:var(--dam-accent);}
    .dam-warn-box{margin-top:8px;padding:8px 10px;background:rgba(255,200,0,0.07);border:1px solid rgba(255,200,0,0.2);border-radius:7px;font-size:11px;color:#d4b000;line-height:1.55;}
    #duo-am-panel.dam-light .dam-warn-box{color:#8a7000;background:rgba(255,200,0,0.08);}
    #duo-am-panel.dam-light .dam-code-block{color:#1a5c00;background:rgba(0,0,0,0.06);}
    #duo-am-panel.dam-light .dam-step-desc em{background:rgba(88,204,2,0.15);}
    /* Confirm modal */
    #duo-am-confirm {
      position:fixed; inset:0; z-index:9999999; background:rgba(0,0,0,.65);
      backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px);
      display:flex; align-items:center; justify-content:center; font-family:'Inter',sans-serif;
    }
    #duo-am-confirm.hidden{display:none;}
    .dam-modal{position:relative;isolation:isolate;border-radius:16px;border:1px solid rgba(255,255,255,0.09);box-shadow:0 16px 48px rgba(0,0,0,.7);width:260px;overflow:hidden;}
    .dam-modal::before{content:'';position:absolute;inset:0;z-index:-1;background:rgba(18,19,22,0.92);backdrop-filter:blur(24px) saturate(200%);-webkit-backdrop-filter:blur(24px) saturate(200%);}
    .dam-modal-head{padding:16px;background:rgba(0,0,0,0.22);border-bottom:1px solid rgba(255,255,255,0.09);display:flex;align-items:center;gap:10px;}
    .dam-modal-head span{font-size:24px;display:flex;align-items:center;color:#58CC02;}
    .dam-modal-head h3{margin:0;font-size:14px;font-weight:800;color:#58CC02;}
    .dam-modal-body{padding:16px;}
    .dam-modal-body p{margin:0 0 16px;font-size:13px;color:#aaa;line-height:1.6;}
    .dam-modal-body p strong{color:#E6E6E6;font-weight:700;}
    .dam-modal-btns{display:flex;gap:8px;}
    .dam-modal-btns button{flex:1;height:36px;border-radius:8px;font-size:13px;font-weight:700;font-family:'Inter',sans-serif;cursor:pointer;border:none;transition:all .15s;}
    .dam-btn-cancel{background:rgba(255,255,255,0.07);color:#888;border:1px solid rgba(255,255,255,0.09) !important;}
    .dam-btn-cancel:hover{background:rgba(255,255,255,0.12);color:#ccc;}
    .dam-btn-ok{background:#58CC02;color:#fff;box-shadow:0 3px 0 0 #46A302;}
    .dam-btn-ok:hover{background:#61E002;}
    .dam-btn-ok:active{transform:translateY(2px);box-shadow:0 1px 0 0 #46A302;}
    #duo-am-confirm.dam-light .dam-modal{border-color:rgba(0,0,0,0.10);box-shadow:0 16px 48px rgba(0,0,0,.18);}
    #duo-am-confirm.dam-light .dam-modal::before{background:rgba(240,241,245,0.96);}
    #duo-am-confirm.dam-light .dam-modal-head{background:rgba(0,0,0,0.04);border-bottom-color:rgba(0,0,0,0.08);}
    #duo-am-confirm.dam-light .dam-modal-head h3{color:#2a7000;}
    #duo-am-confirm.dam-light .dam-modal-body p{color:#555;}
    #duo-am-confirm.dam-light .dam-modal-body p strong{color:#1a1a1a;}
    #duo-am-confirm.dam-light .dam-btn-cancel{background:rgba(0,0,0,0.06) !important;color:#555 !important;border-color:rgba(0,0,0,0.10) !important;}
    #duo-am-confirm.dam-light .dam-btn-cancel:hover{background:rgba(0,0,0,0.11) !important;color:#222 !important;}
    .dam-confirm-avatar{width:48px;height:48px;border-radius:50%;overflow:hidden;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:16px;margin:0 auto 12px;}
    /* Edit modal */
    #duo-am-edit {
      position:fixed; inset:0; z-index:9999999; background:rgba(0,0,0,.65);
      backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px);
      display:flex; align-items:center; justify-content:center; font-family:'Inter',sans-serif;
    }
    #duo-am-edit.hidden{display:none;}
    .dam-edit-modal{position:relative;isolation:isolate;border-radius:16px;border:1px solid rgba(255,255,255,0.09);box-shadow:0 16px 48px rgba(0,0,0,.7);width:300px;overflow:hidden;}
    .dam-edit-modal::before{content:'';position:absolute;inset:0;z-index:-1;background:rgba(18,19,22,0.94);backdrop-filter:blur(24px) saturate(200%);-webkit-backdrop-filter:blur(24px) saturate(200%);}
    .dam-edit-head{padding:12px 14px;background:rgba(0,0,0,.22);border-bottom:1px solid rgba(255,255,255,0.09);display:flex;align-items:center;gap:8px;}
    .dam-edit-head h3{margin:0;font-size:14px;font-weight:800;color:#58CC02;flex:1;}
    .dam-edit-body{padding:14px;display:flex;flex-direction:column;gap:10px;}
    .dam-edit-field-label{font-size:10px;font-weight:700;color:#777;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px;}
    .dam-edit-token-wrap{position:relative;}
    .dam-edit-token-wrap .dam-inp{width:100%;padding-right:30px;font-family:'Consolas','Fira Code',monospace;font-size:11px;}
    .dam-edit-eye{position:absolute;right:6px;top:50%;transform:translateY(-50%);width:22px;height:22px;border-radius:5px;border:none;cursor:pointer;background:transparent;color:#777;font-size:13px;display:flex;align-items:center;justify-content:center;padding:0;transition:background .15s,color .15s;}
    .dam-edit-eye:hover{background:rgba(255,255,255,0.09);color:#E6E6E6;}
    .dam-token-preview{background:rgba(0,0,0,.25);border:1px solid rgba(255,255,255,0.09);border-radius:7px;padding:7px 10px;font-family:'Consolas','Fira Code',monospace;font-size:10px;color:#b5f5a0;line-height:1.5;word-break:break-all;max-height:64px;overflow-y:auto;}
    .dam-token-preview::-webkit-scrollbar{width:3px;}
    .dam-token-preview::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.13);border-radius:4px;}
    .dam-edit-note{font-size:10px;color:#666;line-height:1.55;}
    .dam-edit-note b{color:#888;font-weight:700;}
    .dam-edit-footer{padding:12px 14px;border-top:1px solid rgba(255,255,255,0.09);display:flex;gap:8px;}
    .dam-edit-footer button{flex:1;height:36px;border-radius:8px;font-size:13px;font-weight:700;font-family:'Inter',sans-serif;cursor:pointer;border:none;transition:all .15s;}
    .dam-btn-edit-cancel{background:rgba(255,255,255,0.07);color:#888;border:1px solid rgba(255,255,255,0.09) !important;}
    .dam-btn-edit-cancel:hover{background:rgba(255,255,255,0.12);color:#ccc;}
    .dam-btn-edit-save{background:#58CC02;color:#fff;box-shadow:0 3px 0 0 #46A302;}
    .dam-btn-edit-save:hover{background:#61E002;}
    .dam-btn-edit-save:active{transform:translateY(2px);box-shadow:0 1px 0 0 #46A302;}
    #duo-am-edit.dam-light .dam-edit-modal{border-color:rgba(0,0,0,0.10);box-shadow:0 16px 48px rgba(0,0,0,.18);}
    #duo-am-edit.dam-light .dam-edit-modal::before{background:rgba(240,241,245,0.96);}
    #duo-am-edit.dam-light .dam-edit-head{background:rgba(0,0,0,0.04);border-bottom-color:rgba(0,0,0,0.08);}
    #duo-am-edit.dam-light .dam-edit-head h3{color:#2a7000;}
    #duo-am-edit.dam-light .dam-edit-field-label{color:#888;}
    #duo-am-edit.dam-light .dam-edit-eye{color:#888;}
    #duo-am-edit.dam-light .dam-edit-eye:hover{background:rgba(0,0,0,0.07);color:#333;}
    #duo-am-edit.dam-light .dam-token-preview{color:#1a5c00;background:rgba(0,0,0,0.06);border-color:rgba(0,0,0,0.09);}
    #duo-am-edit.dam-light .dam-token-preview::-webkit-scrollbar-thumb{background:rgba(0,0,0,0.13);}
    #duo-am-edit.dam-light .dam-edit-note{color:#888;}
    #duo-am-edit.dam-light .dam-edit-note b{color:#666;}
    #duo-am-edit.dam-light .dam-edit-footer{border-top-color:rgba(0,0,0,0.08);}
    #duo-am-edit.dam-light .dam-btn-edit-cancel{background:rgba(0,0,0,0.06) !important;color:#555 !important;border-color:rgba(0,0,0,0.10) !important;}
    #duo-am-edit.dam-light .dam-btn-edit-cancel:hover{background:rgba(0,0,0,0.11) !important;color:#222 !important;}
    /* Toast */
    #duo-am-toast {
      position:fixed; bottom:84px; right:24px; z-index:9999999; isolation:isolate;
      border-radius:10px; border:1px solid rgba(255,255,255,0.09); box-shadow:0 4px 16px rgba(0,0,0,.5);
      color:#E6E6E6; padding:9px 16px; font-size:12px; font-weight:600; font-family:'Inter',sans-serif;
      opacity:0; transform:translateY(10px); transition:opacity .2s,transform .2s;
      pointer-events:none; white-space:nowrap;
    }
    #duo-am-toast::before{content:'';position:absolute;inset:0;z-index:-1;border-radius:10px;background:rgba(18,19,22,0.92);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);}
    #duo-am-toast.show{opacity:1;transform:translateY(0);}
    #duo-am-toast.dam-light{color:#1a1a1a;border-color:rgba(0,0,0,0.10);box-shadow:0 4px 16px rgba(0,0,0,.12);}
    #duo-am-toast.dam-light::before{background:rgba(240,241,245,0.96);}
  `);

  // ─── SVG Icons ───────────────────────────────────────────────────────────────
  const USER_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" style="width:1em;height:1em;display:block"><circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/></svg>`;
  const EYE_SVG  = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:14px;height:14px;display:block"><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>`;
  const EYE_OFF  = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:14px;height:14px;display:block"><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>`;
  // Pencil-line icon (edit) — different from the old ✏️ emoji
  const EDIT_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:10px;height:10px;display:block"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>`;

  // ─── DOM elements ─────────────────────────────────────────────────────────────
  const fab       = Object.assign(document.createElement('button'), {id:'duo-am-fab',title:'DuoAM',innerHTML:USER_SVG});
  const panel     = Object.assign(document.createElement('div'),   {id:'duo-am-panel'}); panel.classList.add('hidden');
  const confirmEl = Object.assign(document.createElement('div'),   {id:'duo-am-confirm'}); confirmEl.classList.add('hidden');
  const editEl    = Object.assign(document.createElement('div'),   {id:'duo-am-edit'});    editEl.classList.add('hidden');
  const toast     = Object.assign(document.createElement('div'),   {id:'duo-am-toast'});
  document.body.append(fab, panel, confirmEl, editEl, toast);

  const TOKEN_SNIPPET = "document.cookie.split('; ').find(x=>x.startsWith('jwt_token='))?.split('=')[1]";

  let settings = loadSettings();

  function buildUI() {
    panel.innerHTML = `
      <div class="dam-header" id="dam-drag-handle">
        <span style="font-size:17px;display:flex;align-items:center;color:var(--dam-accent)">${USER_SVG}</span>
        <h2>DuoAM - v1.4.0</h2>
        <button class="dam-icon-btn" id="dam-feedback-btn" title="Feedback">💬</button>
        <button class="dam-icon-btn" id="dam-settings-btn" title="Settings">⚙️</button>
        <button class="dam-icon-btn close" id="dam-close">✕</button>
      </div>
      <div class="dam-tabs" id="dam-tabs">
        <button class="dam-tab active" data-tab="accounts">${t('tabAccounts')}</button>
        <button class="dam-tab" data-tab="add">${t('tabAdd')}</button>
        <button class="dam-tab" data-tab="guide">${t('tabGuide')}</button>
      </div>
      <div class="dam-pane active" id="dam-pane-accounts">
        <div class="dam-search-wrap">
          <input class="dam-search" id="dam-search" type="text" placeholder="${t('searchPlaceholder')}" autocomplete="off"/>
        </div>
        <div class="dam-list" id="dam-list"></div>
      </div>
      <div class="dam-pane" id="dam-pane-add">
        <div class="dam-add">
          <input class="dam-inp dam-inp-full" type="text" id="dam-inp-label" placeholder="${t('labelPlaceholder')}" autocomplete="off"/>
          <div class="dam-token-wrap">
            <input class="dam-inp" type="password" id="dam-inp-token" placeholder="${t('tokenPlaceholder')}" autocomplete="off"/>
            <button class="dam-btn-clear-token" id="dam-btn-clear-token" title="Xóa token" style="display:none">✕</button>
            <button class="dam-btn-eye" id="dam-btn-eye" title="Show/hide token">${EYE_SVG}</button>
          </div>
          <div class="dam-btn-row">
            <button class="dam-btn-add" id="dam-btn-add">💾 ${t('btnSave')}</button>
            <button class="dam-btn-gettoken" id="dam-btn-gettoken" title="${t('btnGetToken')}">⚡ ${settings.lang==='en'?'Grab token':'Lấy token'}</button>
          </div>
        </div>
      </div>
      <div class="dam-pane" id="dam-pane-guide">
        <div class="dam-guide" id="dam-guide-content"></div>
      </div>
      <div class="dam-pane" id="dam-pane-settings">
        <div class="dam-settings">
          <div class="dam-setting-row">
            <div class="dam-setting-label">${t('settingTheme')}</div>
            <div class="dam-theme-pills">
              <button class="dam-pill" data-theme="auto">${t('themeAuto')}</button>
              <button class="dam-pill" data-theme="dark">${t('themeDark')}</button>
              <button class="dam-pill" data-theme="light">${t('themeLight')}</button>
            </div>
          </div>
          <div class="dam-settings-divider"></div>
          <div class="dam-setting-row">
            <div class="dam-toggle-row">
              <div>
                <div class="dam-toggle-title">${t('settingTransparent')}</div>
                <div class="dam-toggle-desc">${t('settingTransparentDesc')}</div>
              </div>
              <label class="dam-switch">
                <input type="checkbox" id="dam-toggle-transparent"/>
                <span class="dam-switch-track"></span>
              </label>
            </div>
          </div>
          <div class="dam-setting-row" id="dam-opacity-row">
            <div class="dam-setting-label">${t('settingOpacity')}</div>
            <div class="dam-slider-row">
              <input type="range" class="dam-slider" id="dam-slider-opacity" min="5" max="98" step="1"/>
              <span class="dam-slider-val" id="dam-opacity-val"></span>
            </div>
          </div>
          <div class="dam-settings-divider"></div>
          <div class="dam-setting-row">
            <div class="dam-setting-label">${t('settingLanguage')}</div>
            <div class="dam-lang-pills">
              <button class="dam-lang-pill" data-lang="vi">${t('langVi')}</button>
              <button class="dam-lang-pill" data-lang="en">${t('langEn')}</button>
            </div>
          </div>
        </div>
        <div class="dam-settings-footer">
          <button class="dam-btn-settings-save" id="dam-btn-settings-save">${t('btnSettingsSave')}</button>
        </div>
      </div>
    `;
    confirmEl.innerHTML = `
      <div class="dam-modal">
        <div class="dam-modal-head"><span>${USER_SVG}</span><h3>${t('confirmTitle')}</h3></div>
        <div class="dam-modal-body">
          <p id="dam-confirm-msg"></p>
          <div class="dam-modal-btns">
            <button class="dam-btn-cancel" id="dam-cancel">${t('btnCancel')}</button>
            <button class="dam-btn-ok" id="dam-ok">${t('btnOk')}</button>
          </div>
        </div>
      </div>
    `;
    editEl.innerHTML = `
      <div class="dam-edit-modal">
        <div class="dam-edit-head">
          <h3>✏️ ${t('editTitle')}</h3>
          <button class="dam-icon-btn close" id="dam-edit-close" style="color:#777">✕</button>
        </div>
        <div class="dam-edit-body">
          <div>
            <div class="dam-edit-field-label">${t('editLabelField')}</div>
            <input class="dam-inp dam-inp-full" type="text" id="dam-edit-label" autocomplete="off" style="margin-bottom:0"/>
          </div>
          <div>
            <div class="dam-edit-field-label">${t('editTokenField')}</div>
            <div class="dam-edit-token-wrap">
              <input class="dam-inp" type="password" id="dam-edit-token" autocomplete="off"/>
              <button class="dam-edit-eye" id="dam-edit-eye">${EYE_SVG}</button>
            </div>
          </div>
          <div>
            <div class="dam-edit-field-label">${t('editPreviewField')}</div>
            <div class="dam-token-preview" id="dam-token-preview">—</div>
          </div>
          <div class="dam-edit-note">${t('editNote')}</div>
        </div>
        <div class="dam-edit-footer">
          <button class="dam-btn-edit-cancel" id="dam-edit-cancel">${t('btnCancel')}</button>
          <button class="dam-btn-edit-save"   id="dam-edit-save">💾 ${t('btnSave')}</button>
        </div>
      </div>
    `;

    // ─── Wire up events ───────────────────────────────────────────────────────
    document.getElementById('dam-close').addEventListener('click', () => panel.classList.add('hidden'));
    document.getElementById('dam-settings-btn').addEventListener('click', () => switchTab('settings'));
    document.getElementById('dam-feedback-btn').addEventListener('click', () => window.open('https://greasyfork.org/en/scripts/581910-duoam/feedback','_blank'));
    document.getElementById('dam-tabs').addEventListener('click', e => {
      const tab = e.target.closest('.dam-tab');
      if (tab) switchTab(tab.dataset.tab);
    });
    document.getElementById('dam-search').addEventListener('input', e => render(e.target.value));
    document.getElementById('dam-btn-add').addEventListener('click', addAccount);
    document.getElementById('dam-btn-gettoken').addEventListener('click', grabCurrentToken);

    const tokenInp  = document.getElementById('dam-inp-token');
    const clearBtn  = document.getElementById('dam-btn-clear-token');
    const eyeBtn    = document.getElementById('dam-btn-eye');
    const toggleClearBtn = show => { clearBtn.style.display = show ? 'flex' : 'none'; };
    tokenInp.addEventListener('input', () => toggleClearBtn(!!tokenInp.value));
    clearBtn.addEventListener('click', () => { tokenInp.value=''; tokenInp.type='password'; eyeBtn.innerHTML=EYE_SVG; toggleClearBtn(false); tokenInp.focus(); });
    eyeBtn.addEventListener('click', () => {
      const isPass = tokenInp.type==='password';
      tokenInp.type = isPass?'text':'password';
      eyeBtn.innerHTML = isPass?EYE_OFF:EYE_SVG;
    });

    document.getElementById('dam-cancel').addEventListener('click',  () => confirmEl.classList.add('hidden'));
    document.getElementById('dam-ok').addEventListener('click', () => { confirmEl.classList.add('hidden'); if(pendingToken) loginByToken(pendingToken); });
    document.getElementById('dam-edit-close').addEventListener('click',  closeEdit);
    document.getElementById('dam-edit-cancel').addEventListener('click', closeEdit);
    document.getElementById('dam-edit-save').addEventListener('click',   saveEdit);
    document.getElementById('dam-edit-eye').addEventListener('click', () => {
      const inp = document.getElementById('dam-edit-token');
      const isPass = inp.type==='password';
      inp.type = isPass?'text':'password';
      document.getElementById('dam-edit-eye').innerHTML = isPass?EYE_OFF:EYE_SVG;
    });
    document.getElementById('dam-edit-token').addEventListener('input', () => {
      const prev = document.getElementById('dam-token-preview');
      if (prev) prev.textContent = document.getElementById('dam-edit-token').value || '—';
    });

    // Settings
    document.querySelectorAll('.dam-pill').forEach(p => p.addEventListener('click', () => updateSetting({themeMode:p.dataset.theme,theme:p.dataset.theme})));
    document.querySelectorAll('.dam-lang-pill').forEach(p => p.addEventListener('click', () => updateSetting({lang:p.dataset.lang})));
    document.getElementById('dam-toggle-transparent').addEventListener('change', e => updateSetting({transparent:e.target.checked}));
    document.getElementById('dam-slider-opacity').addEventListener('input', e => {
      document.getElementById('dam-opacity-val').textContent = e.target.value + '%';
      updateSetting({opacity:+e.target.value});
    });
    document.getElementById('dam-btn-settings-save').addEventListener('click', () => {
      if (!pendingSettings) return;
      settings = pendingSettings; pendingSettings = null;
      saveSettings(settings);
      applyTheme(settings);
      buildUI(); applyTheme(settings); render();
      const btn = document.getElementById('dam-btn-settings-save');
      if (btn){ btn.classList.add('saved'); btn.textContent='✓'; setTimeout(()=>{btn.classList.remove('saved');btn.textContent=t('btnSettingsSave');},1200); }
      showToast(t('toastSettingsSaved'));
    });

    makeDraggable(panel, document.getElementById('dam-drag-handle'));
    applyTheme(settings);
    syncSettingsUI(settings);
    buildGuide();
    render();
  }

  // ─── Tab switch ───────────────────────────────────────────────────────────────
  function switchTab(name) {
    document.querySelectorAll('.dam-tab').forEach(t => t.classList.toggle('active', t.dataset.tab===name));
    document.querySelectorAll('.dam-pane').forEach(p => p.classList.toggle('active', p.id===`dam-pane-${name}`));
    document.getElementById('dam-settings-btn')?.classList.toggle('active', name==='settings');
    if (name==='accounts') render(document.getElementById('dam-search')?.value||'');
  }

  // ─── Guide ────────────────────────────────────────────────────────────────────
  function buildGuide() {
    const el = document.getElementById('dam-guide-content'); if (!el) return;
    const steps = t('guideSteps');
    el.innerHTML = `<div class="dam-guide-title">${t('guideTitle')}</div>` +
      steps.map((s,i) => `
        <div class="dam-step">
          <div class="dam-step-num">${i+1}</div>
          <div class="dam-step-body">
            <div class="dam-step-label">${s.label}</div>
            <div class="dam-step-desc">${s.desc}</div>
            ${s.code?`<div class="dam-code-block" id="dam-code-block"><span>${escHtml(TOKEN_SNIPPET)}</span><span class="dam-copy-hint">COPY</span></div>`:''}
            ${s.extra?`<div class="dam-step-desc" style="margin-top:6px">${s.extra}</div>`:''}
            ${s.warn?`<div class="dam-warn-box">${s.warn}</div>`:''}
          </div>
        </div>`).join('');
    document.getElementById('dam-code-block')?.addEventListener('click', function() {
      navigator.clipboard.writeText(TOKEN_SNIPPET).then(()=>{
        const hint=this.querySelector('.dam-copy-hint');
        hint.textContent='✓'; hint.classList.add('copied');
        setTimeout(()=>{hint.textContent='COPY';hint.classList.remove('copied');},1500);
      }).catch(()=>showToast(t('toastCopyFail')));
    });
  }

  // ─── Grab current token ───────────────────────────────────────────────────────
  function grabCurrentToken() {
    const btn = document.getElementById('dam-btn-gettoken');
    let token;
    try { token = getCurrentToken(); } catch { showToast(t('toastCookieError')); return; }
    if (!token) { showToast(t('toastNoTokenFound')); return; }
    document.getElementById('dam-inp-token').value = token;
    document.getElementById('dam-inp-token').type  = 'password';
    document.getElementById('dam-btn-eye').innerHTML = EYE_SVG;
    document.getElementById('dam-btn-clear-token').style.display = 'flex';
    btn.classList.add('success'); btn.textContent = t('btnGetTokenSuccess');
    setTimeout(()=>{ btn.classList.remove('success'); btn.textContent=`⚡ ${settings.lang==='en'?'Grab token':'Lấy token'}`; }, 2000);
  }

  // ─── Theme ────────────────────────────────────────────────────────────────────
  function detectDuoTheme() {
    const html = document.documentElement;
    const body = document.body;
    // 1. Explicit class/attribute markers
    const darkMarkers = [
      ()=>html.classList.contains('dark')||html.classList.contains('night'),
      ()=>body&&(body.classList.contains('dark')||body.classList.contains('night')),
      ()=>html.dataset.colorScheme?.includes('dark'),
      ()=>html.dataset.theme?.includes('dark'),
      ()=>body&&body.dataset.colorScheme?.includes('dark'),
      ()=>body&&body.dataset.theme?.includes('dark'),
      ()=>html.style.colorScheme?.includes('dark'),
      ()=>getComputedStyle(html).colorScheme?.includes('dark'),
    ];
    const lightMarkers = [
      ()=>html.classList.contains('light'),
      ()=>body&&body.classList.contains('light'),
      ()=>html.dataset.colorScheme?.includes('light'),
      ()=>html.dataset.theme?.includes('light'),
      ()=>body&&body.dataset.colorScheme?.includes('light'),
      ()=>body&&body.dataset.theme?.includes('light'),
      ()=>html.style.colorScheme?.includes('light'),
      ()=>getComputedStyle(html).colorScheme?.includes('light'),
    ];
    if (darkMarkers.some(fn=>{try{return fn();}catch{return false;}})) return 'dark';
    if (lightMarkers.some(fn=>{try{return fn();}catch{return false;}})) return 'light';
    // 2. Fallback: measure actual page background luminance
    try {
      const bg = getComputedStyle(body||html).backgroundColor;
      const m = bg.match(/\d+/g);
      if (m && m.length >= 3) {
        const lum = 0.299*+m[0] + 0.587*+m[1] + 0.114*+m[2];
        if (lum > 10) return lum < 128 ? 'dark' : 'light';
      }
    } catch {}
    // 3. Last resort: OS preference
    return window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';
  }
  function applyTheme(s) {
    const eff = s.themeMode==='auto'?detectDuoTheme():s.theme;
    panel.classList.toggle('dam-light', eff==='light');
    toast.classList.toggle('dam-light',  eff==='light');
    confirmEl.classList.toggle('dam-light', eff==='light');
    editEl.classList.toggle('dam-light',    eff==='light');
    if (s.transparent) {
      const op = Math.round((s.opacity||85)/100*255).toString(16).padStart(2,'0');
      panel.style.setProperty('--dam-panel-bg',     eff==='light'?`rgba(240,241,245,0.${Math.round((s.opacity||85)*0.72)})`:`rgba(18,19,26,0.${Math.round((s.opacity||85)*0.72)})`);
      panel.style.setProperty('--dam-panel-filter', 'blur(24px) saturate(180%)');
    } else {
      panel.style.setProperty('--dam-panel-bg',     eff==='light'?'rgba(240,241,245,0.98)':'rgba(18,19,26,0.98)');
      panel.style.setProperty('--dam-panel-filter', 'none');
    }
  }
  function syncSettingsUI(s) {
    document.querySelectorAll('.dam-pill').forEach(p=>p.classList.toggle('active',p.dataset.theme===s.themeMode));
    document.querySelectorAll('.dam-lang-pill').forEach(p=>p.classList.toggle('active',p.dataset.lang===s.lang));
    const tog = document.getElementById('dam-toggle-transparent');
    if (tog) tog.checked = !!s.transparent;
    const slider = document.getElementById('dam-slider-opacity');
    const val    = document.getElementById('dam-opacity-val');
    if (slider) slider.value = s.opacity||85;
    if (val)    val.textContent = (s.opacity||85)+'%';
    const opRow = document.getElementById('dam-opacity-row');
    if (opRow) opRow.classList.toggle('dam-opacity-row-disabled', !s.transparent);
    document.getElementById('dam-toggle-transparent')?.addEventListener('change', e=>{
      document.getElementById('dam-opacity-row')?.classList.toggle('dam-opacity-row-disabled',!e.target.checked);
    });
  }
  function effectiveTheme() { return settings.themeMode==='auto'?detectDuoTheme():settings.theme; }

  const _mq = window.matchMedia('(prefers-color-scheme:dark)');
  _mq.addEventListener('change', ()=>{ if(settings.themeMode==='auto') applyTheme(settings); });
  new MutationObserver(()=>{ if(settings.themeMode==='auto') applyTheme(settings); })
    .observe(document.documentElement,{attributes:true,attributeFilter:['class','data-color-scheme','data-theme','style']});
  if(document.body) new MutationObserver(()=>{ if(settings.themeMode==='auto') applyTheme(settings); })
    .observe(document.body,{attributes:true,attributeFilter:['class','data-color-scheme','data-theme','style']});
  setInterval(()=>{ if(settings.themeMode==='auto') applyTheme(settings); }, 2000);

  let pendingSettings = null;
  const updateSetting = patch => {
    if (!pendingSettings) pendingSettings = Object.assign({},settings);
    Object.assign(pendingSettings,patch);
    applyThemePreview(pendingSettings);
    syncSettingsUI(pendingSettings);
  };
  function applyThemePreview(s) { const saved=settings; settings=s; applyTheme(s); settings=saved; }

  // ─── Avatar palettes ──────────────────────────────────────────────────────────
  const AV_BG_DARK  = ['#1C3D00','#00304A','#3A1F52','#433200','#4A1515','#4A2900'];
  const AV_FG_DARK  = ['#58CC02','#1CB0F6','#CE82FF','#FFC800','#FF6B6B','#FF9600'];
  const AV_BG_LIGHT = ['#d4f5a8','#b3e8ff','#e8d0ff','#fff0b3','#ffd0d0','#ffe0b3'];
  const AV_FG_LIGHT = ['#2a7000','#005f8a','#6b00b3','#7a5c00','#a00000','#8a4500'];

  // ─── Render account list ──────────────────────────────────────────────────────
  function render(query='') {
    const listEl = document.getElementById('dam-list'); if (!listEl) return;
    const all   = loadAccounts();
    const q     = query.trim().toLowerCase();
    const shown = q ? all.filter(a=>a.label.toLowerCase().includes(q)) : all;
    if (!all.length) {
      listEl.innerHTML=`<div class="dam-empty"><span class="dam-empty-icon" style="display:flex;justify-content:center;color:var(--dam-text-dim2)">${USER_SVG}</span>${t('emptyNoAccounts')}<br>${t('emptyNoAccountsSub')}</div>`;
      return;
    }
    if (!shown.length) {
      listEl.innerHTML=`<div class="dam-empty"><span class="dam-empty-icon">🔍</span>${t('emptyNotFound')}</div>`;
      return;
    }
    listEl.innerHTML='';
    shown.forEach(a => {
      const realIdx = all.indexOf(a);
      const ci      = realIdx % 6;
      const isCur   = isCurrentAccount(a);
      const row = document.createElement('div');
      row.className = 'dam-acc'+(isCur?' dam-acc-current':'');

      const avatarEl = buildAvatarEl(a,ci);
      if (isCur) avatarEl.classList.add('dam-avatar-current-ring');

      // Info: name + sub row (token snippet + edit button)
      const info    = document.createElement('div'); info.className='dam-info';
      const nameEl  = document.createElement('div'); nameEl.className='dam-name'; nameEl.textContent=a.label;
      const subRow  = document.createElement('div'); subRow.className='dam-sub-row';
      const subEl   = document.createElement('div'); subEl.className='dam-sub'; subEl.textContent='⚡ '+a.token.slice(0,18)+'…';

      // Edit button — next to the token snippet
      const btnEdit = document.createElement('button');
      btnEdit.className='dam-btn-edit-inline';
      btnEdit.title=t('btnEdit');
      btnEdit.innerHTML=EDIT_SVG;
      btnEdit.addEventListener('click', e=>{e.stopPropagation();openEdit(realIdx);});

      subRow.append(subEl, btnEdit);
      info.append(nameEl, subRow);

      // Delete button
      const btnDel = document.createElement('button');
      btnDel.className='dam-btn-del';
      btnDel.title=t('btnDelete');
      btnDel.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:11px;height:11px;display:block"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4h6v2"/></svg>';
      btnDel.addEventListener('click', e=>{e.stopPropagation();deleteAcc(realIdx);});

      // Login / Current badge — far right
      let actionEl;
      if (isCur) {
        actionEl = document.createElement('div');
        actionEl.className='dam-badge-current';
        actionEl.textContent=t('badgeCurrent');
      } else {
        actionEl = document.createElement('button');
        actionEl.className='dam-btn-login';
        actionEl.textContent=t('btnLogin');
        actionEl.addEventListener('click', e=>{e.stopPropagation();openConfirm(realIdx);});
      }

      // Order: avatar | info (name + token+editBtn) | delete | login
      row.append(avatarEl, info, btnDel, actionEl);
      listEl.appendChild(row);
    });
  }

  // ─── Account actions ──────────────────────────────────────────────────────────
  function addAccount() {
    const label = document.getElementById('dam-inp-label').value.trim();
    const token = document.getElementById('dam-inp-token').value.trim();
    if (!label) return showToast(t('toastNoLabel'));
    if (!token) return showToast(t('toastNoToken'));
    if (!token.startsWith('eyJhb')) return showToast(t('toastBadFormat'));
    const list = loadAccounts();
    if (list.find(a=>a.label===label)) return showToast(t('toastDuplicateLabel'));
    list.push({label,token});
    saveAccounts(list);
    document.getElementById('dam-inp-label').value='';
    document.getElementById('dam-inp-token').value='';
    document.getElementById('dam-inp-token').type='password';
    document.getElementById('dam-btn-eye').innerHTML=EYE_SVG;
    document.getElementById('dam-btn-clear-token').style.display='none';
    showToast(t('toastSaved',label));
    switchTab('accounts');
    render();
  }
  function deleteAcc(i) {
    const list=loadAccounts();
    const name=list[i]?.label||'';
    list.splice(i,1);
    saveAccounts(list);
    render(document.getElementById('dam-search')?.value||'');
    showToast(t('toastDeleted',name));
  }

  // ─── Confirm login modal ──────────────────────────────────────────────────────
  let pendingToken=null;
  function openConfirm(i) {
    const a=loadAccounts()[i]; if (!a) return;
    pendingToken=a.token;
    const msgEl=document.getElementById('dam-confirm-msg');
    const ci=i%6, isLight=effectiveTheme()==='light';
    msgEl.innerHTML='';
    const avWrap=document.createElement('div');
    avWrap.className='dam-confirm-avatar';
    avWrap.style.background=(isLight?AV_BG_LIGHT:AV_BG_DARK)[ci];
    avWrap.style.color=(isLight?AV_FG_LIGHT:AV_FG_DARK)[ci];
    avWrap.textContent=a.label.slice(0,2).toUpperCase();
    const txt=document.createElement('span');
    txt.innerHTML=t('confirmMsg',escHtml(a.label));
    msgEl.append(avWrap,txt);
    confirmEl.classList.remove('hidden');
  }

  // ─── Edit modal ───────────────────────────────────────────────────────────────
  let editingIndex=-1;
  function openEdit(i) {
    const list=loadAccounts(), a=list[i]; if (!a) return;
    editingIndex=i;
    document.getElementById('dam-edit-label').value=a.label;
    const tokenInp=document.getElementById('dam-edit-token');
    tokenInp.value=a.token; tokenInp.type='password';
    document.getElementById('dam-edit-eye').innerHTML=EYE_SVG;
    const prev=document.getElementById('dam-token-preview');
    if (prev) prev.textContent=a.token||'—';
    editEl.classList.toggle('dam-light',effectiveTheme()==='light');
    editEl.classList.remove('hidden');
  }
  function closeEdit() { editEl.classList.add('hidden'); editingIndex=-1; }
  function saveEdit() {
    const label=document.getElementById('dam-edit-label').value.trim();
    const token=document.getElementById('dam-edit-token').value.trim();
    if (!label) return showToast(t('toastNoLabel'));
    if (!token) return showToast(t('toastNoToken'));
    if (!token.startsWith('eyJhb')) return showToast(t('toastBadFormat'));
    const list=loadAccounts();
    if (list.findIndex((a,i)=>a.label===label&&i!==editingIndex)!==-1) return showToast(t('toastDuplicateLabel'));
    list[editingIndex]={label,token};
    saveAccounts(list);
    closeEdit();
    showToast(t('toastSaved',label));
    render(document.getElementById('dam-search')?.value||'');
  }

  // ─── Draggable FAB ────────────────────────────────────────────────────────────
  (function makeFabDraggable() {
    let dragging=false,moved=false,startX=0,startY=0,origLeft=0,origTop=0;
    fab.addEventListener('pointerdown',e=>{
      dragging=true; moved=false; startX=e.clientX; startY=e.clientY;
      const r=fab.getBoundingClientRect(); origLeft=r.left; origTop=r.top;
      fab.setPointerCapture(e.pointerId); fab.classList.add('dam-fab-pressed');
    });
    fab.addEventListener('pointermove',e=>{
      if (!dragging) return;
      const dx=e.clientX-startX,dy=e.clientY-startY;
      if (!moved&&Math.abs(dx)<4&&Math.abs(dy)<4) return;
      if (!moved){fab.classList.remove('dam-fab-pressed');fab.style.transition='none';}
      moved=true; fab.classList.add('dam-fab-dragging');
      fab.style.right='auto'; fab.style.bottom='auto';
      fab.style.left=Math.max(8,Math.min(origLeft+dx,window.innerWidth-fab.offsetWidth-8))+'px';
      fab.style.top=Math.max(8,Math.min(origTop+dy,window.innerHeight-fab.offsetHeight-8))+'px';
    });
    fab.addEventListener('pointerup',()=>{
      if (!dragging) return; dragging=false;
      fab.classList.remove('dam-fab-dragging','dam-fab-pressed'); fab.style.transition='';
      if (!moved){
        panel.classList.toggle('hidden');
        if (!panel.classList.contains('hidden')){applyTheme(settings);render(document.getElementById('dam-search')?.value||'');}
      }
    });
    fab.addEventListener('pointercancel',()=>{
      dragging=false; fab.classList.remove('dam-fab-dragging','dam-fab-pressed'); fab.style.transition='';
    });
  })();

  // ─── Toast ────────────────────────────────────────────────────────────────────
  let toastTimer;
  function showToast(msg) {
    toast.textContent=msg; toast.classList.add('show');
    clearTimeout(toastTimer);
    toastTimer=setTimeout(()=>toast.classList.remove('show'),2500);
  }

  buildUI();

  // ─── Detect failed/successful login on page load ──────────────────────────────
  (function checkPendingLogin() {
    const pending = GM_getValue(PENDING_LOGIN_KEY, '');
    if (!pending) return;
    GM_setValue(PENDING_LOGIN_KEY, '');
    const path = location.pathname;
    const search = location.search;
    const failed = path.startsWith('/login') || search.includes('loginRedirect') || path === '/';
    if (failed) {
      setTimeout(() => {
        panel.classList.remove('hidden');
        applyTheme(settings);
        render();
        showToast(t('toastTokenDetected'));
      }, 600);
    } else {
      // Login redirect landed on /learn — identify which account is now active
      setTimeout(() => {
        updateFab();
        const acc = detectCurrentAccount();
        if (acc) showToast(t('toastLoginSuccess', acc.label));
        render();
      }, 800);
    }
  })();


})();