ASSs MultiTools

удобный мультитул для AnimeSSS

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

Advertisement:

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

Advertisement:

// ==UserScript==
// @name         ASSs MultiTools
// @namespace    https://animesss.com/
// @version      1.3
// @description  удобный мультитул для AnimeSSS
// @author       SoulUA
// @license      MIT
// @match        *://animesss.tv/*
// @match        *://animesss.com/*
// @run-at       document-start
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function () {
  'use strict';

  const SCRIPT_NAME = 'ASS TM Combo';
  const SETTING_PREFIX = 'ass_tm_combo_';

  const DEFAULT_PROFILE_BUTTONS = [
    { id: 'need', enabled: true, text: '', icon: 'fal fa-search', url: '/user/cards/need/?name={USERNAME}' },
    { id: 'in-list', enabled: true, text: '', icon: 'fal fa-heart', url: '/user/cards/?name={USERNAME}&in_list=1' },
    { id: 'unlocked', enabled: true, text: '', icon: 'fal fa-unlock', url: '/user/cards/?name={USERNAME}&locked=0' },
    { id: 'club', enabled: true, text: '', icon: 'fal fa-users', url: '/clubs/{CLUB_ID}/' },
    ...['s_plus', 's', 'a_plus', 'a', 'b_plus', 'b', 'c_plus', 'c', 'd_plus', 'd', 'e_plus', 'e', 'ass'].map((rank) => ({
      id: `rank-${rank}`,
      enabled: true,
      text: rank.replace('_plus', '+').toUpperCase(),
      icon: '',
      url: `/user/cards/?name={USERNAME}&locked=0&rank=${rank}`,
    })),
  ];

  const DEFAULT_QN_LINKS = [
    { id: 'qn-1', title: 'Лента карт', url: '/cards' },
    { id: 'qn-2', title: 'Паки карт', url: '/cards/pack/' },
    { id: 'qn-3', title: 'Трейды', url: '/trades/offers/' }
  ];

  const FONT_OPTIONS = [
    { value: 'system-ui, sans-serif', label: 'Системный (По умолчанию)' },
    { value: 'Georgia, serif', label: 'Georgia (Элегантный)' },
    { value: '"Palatino Linotype", "Book Antiqua", serif', label: 'Palatino (Книжный)' },
    { value: '"Trebuchet MS", sans-serif', label: 'Trebuchet MS (Компактный)' },
    { value: 'Impact, sans-serif', label: 'Impact (Массивный)' },
    { value: '"Arial Black", sans-serif', label: 'Arial Black (Жирный)' },
    { value: '"Comic Sans MS", cursive', label: 'Comic Sans (Неформальный)' },
    { value: '"Courier New", monospace', label: 'Courier New (Печатная машинка)' },
    { value: '"Lucida Console", monospace', label: 'Lucida Console (Терминал)' }
  ];

  function cloneValue(value) {
    try { return JSON.parse(JSON.stringify(value)); } catch (_) { return value; }
  }

  const DEFAULTS = {
    profileButtons: true,
    cardNeedButtons: true,
    modalStarButton: true,

    takeCinemaStone: false,
    takeSnowStone: false,
    takeHeavenlyStone: false,
    stoneBaseDelayMs: 900,
    stoneGrowthDelayMs: 250,

    quickNavEnabled: true,
    qnBgColor: '#121212',
    qnBgOpacity: 0.0,
    qnBlur: 0,
    qnBtnBgColor: '#212121',
    qnBtnTextColor: '#e0e0e0',
    qnFontFamily: 'system-ui, sans-serif',
    qnBtnFontSize: 13,
    qnBtnPadY: 10,
    qnBtnPadX: 16,

    profBtnBgColor: '#2b2b2b',
    profBtnHoverBgColor: '#9e294f',
    profBtnTextColor: '#e0e0e0',
    profFontFamily: 'system-ui, sans-serif',
    profBtnFontSize: 12,
    profBtnPadX: 8,
    profBtnHeight: 28,

    profileButtonsConfig: cloneValue(DEFAULT_PROFILE_BUTTONS),
    qnLinks: cloneValue(DEFAULT_QN_LINKS),
  };

  const state = {};
  const clickedCinemaCodes = new Set();
  let qnResizeObserver = null;
  let qnIsScrollListenerAttached = false;
  let stoneClickQueue = 0;

  // ---------------------------------------------------------------------------
  // Core Utilities & Toast UI
  // ---------------------------------------------------------------------------

  function showToast(msg) {
    let toast = document.getElementById('ass-tm-toast');
    if (!toast) {
      toast = document.createElement('div');
      toast.id = 'ass-tm-toast';
      toast.style.cssText = 'position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,0.85); color:#fff; padding:10px 20px; border-radius:12px; z-index:9999999; font-size:13px; font-family:sans-serif; pointer-events:none; opacity:0; transition:opacity 0.3s ease; box-shadow: 0 4px 12px rgba(0,0,0,0.3); font-weight: 600; text-align: center; white-space: nowrap;';
      document.body.appendChild(toast);
    }
    toast.textContent = msg;
    toast.style.opacity = '1';
    clearTimeout(toast.timer);
    toast.timer = setTimeout(() => { toast.style.opacity = '0'; }, 3000);
  }

  function gmGet(key, fallback) {
    try { if (typeof GM_getValue === 'function') return GM_getValue(SETTING_PREFIX + key, fallback); } catch (_) { }
    try { const raw = localStorage.getItem(SETTING_PREFIX + key); return raw == null ? fallback : JSON.parse(raw); } catch (_) { return fallback; }
  }

  function gmSet(key, value) {
    state[key] = value;
    try { if (typeof GM_setValue === 'function') { GM_setValue(SETTING_PREFIX + key, value); return; } } catch (_) { }
    try { localStorage.setItem(SETTING_PREFIX + key, JSON.stringify(value)); } catch (_) { }
  }

  function loadSettings() {
    for (const [key, value] of Object.entries(DEFAULTS)) {
      state[key] = gmGet(key, cloneValue(value));
      if ((key === 'profileButtonsConfig' || key === 'qnLinks') && !Array.isArray(state[key])) {
        state[key] = cloneValue(value);
      }
    }
  }

  function onBodyReady(fn) {
    if (document.body) return fn();
    const timer = setInterval(() => { if (!document.body) return; clearInterval(timer); fn(); }, 50);
  }

  // ---------------------------------------------------------------------------
  // Styles
  // ---------------------------------------------------------------------------

  function injectStyle() {
    const css = `
      .user-card-buttons { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-left: 0; width: 100%; justify-content: flex-start; }
      .user-card-buttons a {
        display: inline-flex; align-items: center; justify-content: center; min-width: 28px;
        height: var(--prof-btn-h, 28px); padding: 0 var(--prof-btn-padx, 8px);
        border-radius: 8px; background: var(--prof-btn-bg, rgba(255,255,255,.08));
        color: var(--prof-btn-text, inherit); font-family: var(--prof-btn-font, system-ui, sans-serif);
        text-decoration: none; font-size: var(--prof-btn-fs, 12px); font-weight: 600; line-height: 1; transition: background-color 0.2s ease;
      }
      .user-card-buttons a:hover { background: var(--prof-btn-hover, rgba(158,41,79,.85)); }
      .as-ext-star-meta-item { flex: 0 0 auto; }

      #ass-tm-settings-fab { display: inline-flex; align-items: center; justify-content: center; min-width: 38px; height: 38px; border: 0; border-radius: 12px; background: rgba(255,255,255,.10); color: #fff; font: 18px/1 Arial, sans-serif; cursor: pointer; text-decoration: none; box-sizing: border-box; transition: background 0.2s; }
      #ass-tm-settings-fab:hover { background: rgba(158,41,79,.92); }
      #ass-tm-settings-fab.ass-tm-settings-fallback { position: fixed; z-index: 999998; right: 12px; top: 88px; box-shadow: 0 8px 24px rgba(0,0,0,.28); }

      .ass-tm-settings-backdrop { position: fixed; inset: 0; z-index: 999999; display: none; align-items: center; justify-content: center; background: rgba(0,0,0,.55); padding: 14px; box-sizing: border-box; backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); }
      .ass-tm-settings-backdrop.ass-tm-settings-open { display: flex; }
      .ass-tm-settings-panel { width: min(760px, 100%); max-height: min(86vh, 760px); overflow: auto; border-radius: 14px; background: #171719; color: #f4f4f4; box-shadow: 0 18px 56px rgba(0,0,0,.45); font: 13px/1.35 Arial, sans-serif; }
      .ass-tm-settings-head { position: sticky; top: 0; z-index: 10; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 14px; background: #202024; border-bottom: 1px solid rgba(255,255,255,.08); }
      .ass-tm-settings-title { font-weight: 700; font-size: 15px; }
      .ass-tm-settings-close { border: 0; border-radius: 8px; background: rgba(255,255,255,.08); color: #fff; min-width: 34px; height: 32px; cursor: pointer; transition: background 0.2s; }
      .ass-tm-settings-close:hover { background: rgba(255,255,255,.15); }
      .ass-tm-settings-body { padding: 12px 14px 16px; }

      .ass-tm-settings-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
      .ass-tm-settings-section { border: 1px solid rgba(255,255,255,.08); border-radius: 12px; padding: 10px; background: rgba(255,255,255,.035); }
      .ass-tm-settings-section h3 { margin: 0 0 8px; font-size: 13px; color: #fff; }
      .ass-tm-setting-row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; min-height: 34px; border-top: 1px solid rgba(255,255,255,.06); padding: 7px 0; }
      .ass-tm-setting-row:first-of-type { border-top: 0; }
      .ass-tm-setting-label { flex: 1 1 auto; min-width: 50%; }

      .ass-tm-setting-row input[type="number"], .ass-tm-setting-row input[type="text"], .ass-tm-editor-row input[type="text"] { border: 1px solid rgba(255,255,255,.14); border-radius: 8px; background: #0f0f11; color: #fff; padding: 6px 8px; box-sizing: border-box; }
      .ass-tm-setting-row select {
      border: 1px solid rgba(255,255,255,.14);
      border-radius: 8px;
      background-color: #0f0f11 !important;
      background-image: none !important;
      color: #fff;
      padding: 6px 36px 6px 8px;
      box-sizing: border-box;
      width: 140px;
      outline: none;
      font-size: 12px;
      cursor: pointer;
      appearance: none;
      -webkit-appearance: none;
      -moz-appearance: none;
     }
      .ass-tm-setting-row {
      position: relative;
     }
      .ass-tm-setting-row select {
      background-repeat: no-repeat !important;
      background-size: 0 0 !important;
     }
      .ass-tm-setting-row input[type="number"] { width: 104px; }
      .ass-tm-setting-row input[type="color"] { background: none; border: none; width: 32px; height: 32px; cursor: pointer; padding: 0; border-radius: 6px; }
      .ass-tm-setting-row input[type="checkbox"], .ass-tm-editor-row input[type="checkbox"] { width: 18px; height: 18px; accent-color: #9e294f; cursor: pointer; margin: 0; }
      .ass-tm-editor { margin-top: 12px; border: 1px solid rgba(255,255,255,.08); border-radius: 12px; padding: 10px; background: rgba(255,255,255,.035); }
      .ass-tm-editor h3 { margin: 0 0 8px; font-size: 13px; }
      .ass-tm-editor-list { display: flex; flex-direction: column; gap: 8px; }

      /* ========================================================= */
      /* --- ПУЛЕНЕПРОБИВАЕМЫЙ CSS (из скриншотов) --- */
      .ass-tm-editor-row {
        display: grid;
        gap: 8px;
        padding: 10px;
        border-radius: 8px;
        background: rgba(255,255,255,.04);
        border: 1px solid rgba(255,255,255,.05);
        align-items: center;

        grid-template-columns: 24px minmax(0, 1fr) minmax(0, 1fr) 34px;
        grid-template-rows: auto auto;
      }

      .ass-tm-editor-row > [data-f="en"] {
        grid-column: 1;
        grid-row: 1;
        justify-self: center;
        margin: 0;
      }

      .ass-tm-editor-row > [data-f="txt"] {
        grid-column: 2;
        grid-row: 1;
        width: 100%;
        min-width: 0;
        margin: 0;
      }

      .ass-tm-editor-row > [data-f="icn"] {
        grid-column: 3;
        grid-row: 1;
        width: 100%;
        min-width: 0;
        margin: 0;
      }

      .ass-tm-editor-row > .ass-tm-editor-remove {
        grid-column: 4;
        grid-row: 1;
        width: 100%;
        height: 34px;
        margin: 0;
        border: 0;
        border-radius: 8px;
        background: rgba(255,255,255,.08);
        color: #fff;
        cursor: pointer;
        transition: background 0.2s;
      }

      .ass-tm-editor-row > .ass-tm-editor-remove:hover {
        background: rgba(190,40,40,.9);
      }

      .ass-tm-editor-row > [data-f="url"] {
        grid-column: 1 / -1;
        grid-row: 2;
        width: 100%;
        min-width: 0;
        margin: 0;
      }

      /* Стили для ссылок Quick Nav (у них нет галочки и иконки) */
      .ass-tm-editor-row.qn-link {
        grid-template-columns: minmax(0, 1fr) 34px;
      }
      .ass-tm-editor-row.qn-link > [data-f="ttl"] {
        grid-column: 1;
        grid-row: 1;
        width: 100%;
        min-width: 0;
        margin: 0;
      }
      .ass-tm-editor-row.qn-link > .ass-tm-editor-remove {
        grid-column: 2;
        grid-row: 1;
      }

      @media (min-width: 681px) {
        .ass-tm-editor-row {
          grid-template-columns: 24px 1fr 1fr 2fr 34px;
          grid-template-rows: auto;
          padding: 6px;
          background: rgba(255,255,255,.02);
          border: none;
        }

        .ass-tm-editor-row > [data-f="en"] {
          grid-column: 1;
          grid-row: 1;
        }

        .ass-tm-editor-row > [data-f="txt"] {
          grid-column: 2;
          grid-row: 1;
        }

        .ass-tm-editor-row > [data-f="icn"] {
          grid-column: 3;
          grid-row: 1;
        }

        .ass-tm-editor-row > .ass-tm-editor-remove {
          grid-column: 5;
          grid-row: 1;
        }

        .ass-tm-editor-row > [data-f="url"] {
          grid-column: 4;
          grid-row: 1;
        }

        .ass-tm-editor-row.qn-link {
          grid-template-columns: 1fr 2fr 34px;
        }
        .ass-tm-editor-row.qn-link > [data-f="ttl"] {
          grid-column: 1;
          grid-row: 1;
        }
        .ass-tm-editor-row.qn-link > [data-f="url"] {
          grid-column: 2;
          grid-row: 1;
        }
        .ass-tm-editor-row.qn-link > .ass-tm-editor-remove {
          grid-column: 3;
          grid-row: 1;
        }
      }

      /* Mobile overrides for standard settings */
      @media (max-width: 680px) {
        .ass-tm-settings-grid { grid-template-columns: 1fr; }
        .ass-tm-settings-backdrop { padding: 8px; }
        .ass-tm-settings-panel { max-height: 95vh; }
        .ass-tm-settings-body { padding: 12px 10px; }

        .ass-tm-setting-row { flex-direction: column; align-items: flex-start; gap: 6px; }
        .ass-tm-setting-row input[type="number"], .ass-tm-setting-row input[type="text"], .ass-tm-setting-row select {
          flex: 0 0 100%; max-width: none; width: 100%; margin-top: 4px;
        }
      }
      /* ========================================================= */

      .ass-tm-editor-actions, .ass-tm-settings-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
      .ass-tm-settings-actions { margin-top: 12px; }
      .ass-tm-editor-actions button, .ass-tm-settings-actions button { border: 0; border-radius: 9px; background: rgba(255,255,255,.1); color: #fff; padding: 8px 10px; cursor: pointer; font-weight: 600; transition: background 0.2s;}
      .ass-tm-editor-actions button:hover, .ass-tm-settings-actions button:hover { background: rgba(158,41,79,.85); }
      .ass-tm-save-status { margin-top: 10px; min-height: 16px; color: rgba(255,255,255,.62); font-size: 11px; }

      /* Quick Nav UI Styles */
      #custom-quick-nav {
        display: flex; gap: 8px; padding: 12px 16px; overflow-x: auto; white-space: nowrap; scrollbar-width: none;
        -ms-overflow-style: none; border-bottom: none; box-shadow: none; position: fixed; box-sizing: border-box;
        z-index: 998; justify-content: safe center; pointer-events: none; transition: background-color 0.3s ease, backdrop-filter 0.3s ease;
      }
      #custom-quick-nav > * { pointer-events: auto; }
      #custom-quick-nav::-webkit-scrollbar { display: none; }
      #aqn-spacer { width: 100%; display: block; flex-shrink: 0; }

      .custom-nav-btn {
        background-color: var(--btn-bg, #212121); color: var(--btn-text, #e0e0e0); font-family: var(--btn-font, system-ui, sans-serif);
        font-size: var(--btn-fs, 13px); padding: var(--btn-pad, 10px 16px); text-decoration: none; border-radius: 6px;
        font-weight: 600; letter-spacing: 0.5px; transition: opacity 0.2s ease; flex-shrink: 0; border: none; cursor: pointer;
        display: flex; align-items: center; justify-content: center; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.5);
      }
      .custom-nav-btn:hover, .custom-nav-btn:active { opacity: 0.8; }
    `;
    const style = document.createElement('style');
    style.id = 'ass-tm-opt-combo-style';
    style.textContent = css;
    (document.head || document.documentElement).appendChild(style);
  }

  // ---------------------------------------------------------------------------
  // Profile & Quick Nav Visual Styling Engine
  // ---------------------------------------------------------------------------

  function hexToRgb(hex) {
    let c;
    if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
      c = hex.substring(1).split('');
      if (c.length === 3) c = [c[0], c[0], c[1], c[1], c[2], c[2]];
      c = '0x' + c.join('');
      return [(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',');
    }
    return '18,18,18';
  }

  function applyQnVisualSettings() {
    const nav = document.getElementById('custom-quick-nav');
    if (!nav) return;
    const rgb = hexToRgb(state.qnBgColor || '#121212');
    nav.style.backgroundColor = `rgba(${rgb}, ${state.qnBgOpacity})`;
    nav.style.backdropFilter = `blur(${state.qnBlur}px)`;
    nav.style.webkitBackdropFilter = `blur(${state.qnBlur}px)`;
    nav.style.setProperty('--btn-bg', state.qnBtnBgColor || '#212121');
    nav.style.setProperty('--btn-text', state.qnBtnTextColor || '#e0e0e0');
    nav.style.setProperty('--btn-font', state.qnFontFamily || 'system-ui, sans-serif');
    nav.style.setProperty('--btn-fs', `${state.qnBtnFontSize || 13}px`);
    nav.style.setProperty('--btn-pad', `${state.qnBtnPadY || 10}px ${state.qnBtnPadX || 16}px`);
  }

  function applyProfVisualSettings() {
    const root = document.documentElement;
    root.style.setProperty('--prof-btn-bg', state.profBtnBgColor || 'rgba(255,255,255,.08)');
    root.style.setProperty('--prof-btn-hover', state.profBtnHoverBgColor || 'rgba(158,41,79,.85)');
    root.style.setProperty('--prof-btn-text', state.profBtnTextColor || 'inherit');
    root.style.setProperty('--prof-btn-font', state.profFontFamily || 'system-ui, sans-serif');
    root.style.setProperty('--prof-btn-fs', `${state.profBtnFontSize || 12}px`);
    root.style.setProperty('--prof-btn-padx', `${state.profBtnPadX || 8}px`);
    root.style.setProperty('--prof-btn-h', `${state.profBtnHeight || 28}px`);
  }

  function renderQnButtons() {
    const nav = document.getElementById('custom-quick-nav');
    if (!nav) return;
    nav.innerHTML = '';
    const fragment = document.createDocumentFragment();
    (state.qnLinks || []).forEach(link => {
      if (!link.url || !link.title) return;
      const btn = document.createElement('a');
      btn.href = link.url;
      btn.textContent = link.title;
      btn.className = 'custom-nav-btn';
      fragment.appendChild(btn);
    });
    nav.appendChild(fragment);
  }

  function setupQnPositioning(targetElement, navContainer, spacer) {
    if (qnResizeObserver) qnResizeObserver.disconnect();
    const updateStickyPosition = () => {
      if (!document.body.contains(targetElement) || !document.body.contains(navContainer)) return;
      const rect = targetElement.getBoundingClientRect();
      navContainer.style.top = `${rect.bottom}px`;
      navContainer.style.left = `${rect.left}px`;
      navContainer.style.width = `${rect.width}px`;
      if (spacer) spacer.style.height = `${navContainer.offsetHeight}px`;
    };
    updateStickyPosition();
    if (window.ResizeObserver) {
      qnResizeObserver = new ResizeObserver(() => requestAnimationFrame(updateStickyPosition));
      qnResizeObserver.observe(targetElement);
      qnResizeObserver.observe(navContainer);
    }
    if (!qnIsScrollListenerAttached) {
      let ticking = false;
      window.addEventListener('scroll', () => {
        if (!ticking) { window.requestAnimationFrame(() => { updateStickyPosition(); ticking = false; }); ticking = true; }
      }, { passive: true });
      qnIsScrollListenerAttached = true;
    }
  }

  function injectQuickNav() {
    const existingNav = document.getElementById('custom-quick-nav');
    const existingSpacer = document.getElementById('aqn-spacer');

    if (!state.quickNavEnabled) {
      if (existingNav) existingNav.remove();
      if (existingSpacer) existingSpacer.remove();
      if (qnResizeObserver) qnResizeObserver.disconnect();
      return;
    }

    if (existingNav) return;
    const targetElement = document.querySelector('header');
    if (!targetElement) return;

    const spacer = document.createElement('div');
    spacer.id = 'aqn-spacer';
    const navContainer = document.createElement('div');
    navContainer.id = 'custom-quick-nav';

    targetElement.insertAdjacentElement('afterend', spacer);
    targetElement.insertAdjacentElement('afterend', navContainer);

    renderQnButtons();
    applyQnVisualSettings();
    setupQnPositioning(targetElement, navContainer, spacer);
  }

  // ---------------------------------------------------------------------------
  // Settings Logic & UI Schema
  // ---------------------------------------------------------------------------

  const SETTINGS_SCHEMA = [
    {
      title: 'Панель быстрого доступа',
      items: [
        { key: 'quickNavEnabled', type: 'bool', label: 'Включить панель' },
        { key: 'qnBgColor', type: 'color', label: 'Цвет подложки' },
        { key: 'qnBgOpacity', type: 'number', step: '0.05', label: 'Непрозрачность (0-1)', min: 0, max: 1 },
        { key: 'qnBlur', type: 'number', label: 'Размытие фона (px)', min: 0, max: 20 },
        { key: 'qnBtnBgColor', type: 'color', label: 'Фон кнопок' },
        { key: 'qnBtnTextColor', type: 'color', label: 'Текст кнопок' },
        { key: 'qnFontFamily', type: 'select', label: 'Шрифт кнопок', options: FONT_OPTIONS },
        { key: 'qnBtnFontSize', type: 'number', label: 'Размер шрифта', min: 10, max: 24 },
        { key: 'qnBtnPadY', type: 'number', label: 'Отступ (вертикаль)', min: 4, max: 24 },
        { key: 'qnBtnPadX', type: 'number', label: 'Отступ (горизонталь)', min: 4, max: 32 },
      ],
    },
    {
      title: 'Кнопки профиля / рангов (Внешний вид)',
      items: [
        { key: 'profBtnBgColor', type: 'color', label: 'Фон кнопок' },
        { key: 'profBtnHoverBgColor', type: 'color', label: 'Фон при наведении' },
        { key: 'profBtnTextColor', type: 'color', label: 'Текст кнопок' },
        { key: 'profFontFamily', type: 'select', label: 'Шрифт кнопок', options: FONT_OPTIONS },
        { key: 'profBtnFontSize', type: 'number', label: 'Размер шрифта', min: 8, max: 24 },
        { key: 'profBtnPadX', type: 'number', label: 'Отступ (горизонталь)', min: 0, max: 32 },
        { key: 'profBtnHeight', type: 'number', label: 'Высота кнопки', min: 16, max: 48 },
      ],
    },
    {
      title: 'Прочее',
      items: [
        { key: 'profileButtons', type: 'bool', label: 'Включить кнопки в профиле' },
        { key: 'cardNeedButtons', type: 'bool', label: 'Кнопки need на карточках' },
        { key: 'modalStarButton', type: 'bool', label: 'Кнопка звезды в модалке' },
      ],
    },
    {
      title: 'Камни (Автосбор)',
      items: [
        { key: 'takeCinemaStone', type: 'bool', label: 'Автозабор cinema stone' },
        { key: 'takeSnowStone', type: 'bool', label: 'Автозабор snow stone' },
        { key: 'takeHeavenlyStone', type: 'bool', label: 'Автозабор heavenly stone' },
        { key: 'stoneBaseDelayMs', type: 'number', label: 'Базовая задержка клика, ms', min: 10, max: 5000 },
        { key: 'stoneGrowthDelayMs', type: 'number', label: 'Рост задержки, ms', min: 10, max: 3000 },
      ],
    }
  ];

  function getSettingDef(key) {
    for (const section of SETTINGS_SCHEMA) {
      const found = section.items.find((item) => item.key === key);
      if (found) return found;
    }
    return null;
  }

  function clampNumberSetting(key, rawValue, min = 10, max = 999999) {
    const fallback = Number(DEFAULTS[key]);
    const n = parseFloat(rawValue);
    const base = Number.isFinite(n) ? n : (Number.isFinite(fallback) ? fallback : min);
    return Math.min(max, Math.max(min, base));
  }

  function removeProfileButtons() { document.querySelectorAll('.user-card-buttons').forEach((el) => el.remove()); }
  function remountProfileButtons() { removeProfileButtons(); mountProfileButtons(); }

  function showSettingsStatus(text) {
    const el = document.querySelector('.ass-tm-save-status');
    if (!el) return;
    el.textContent = text;
    window.clearTimeout(showSettingsStatus._timer);
    showSettingsStatus._timer = window.setTimeout(() => { if (el.textContent === text) el.textContent = ''; }, 1800);
  }

  function applyRuntimeSettingChange(key) {
    if (key === 'profileButtons' || key === 'profileButtonsConfig') {
      remountProfileButtons();
    } else if (key === 'cardNeedButtons') {
      if (state.cardNeedButtons) scanCardNeedButtons();
      else document.querySelectorAll('.show-need_button').forEach((el) => el.classList.remove('show-need_button'));
    } else if (key === 'modalStarButton') {
      if (state.modalStarButton) processExistingDialogs();
      else document.querySelectorAll('.as-ext-star-meta-item').forEach((el) => el.remove());
    } else if (key.startsWith('qn') || key === 'quickNavEnabled') {
      if (key === 'quickNavEnabled') injectQuickNav();
      else if (key === 'qnLinks') renderQnButtons();
      else applyQnVisualSettings();
    } else if (key.startsWith('profBtn') || key === 'profFontFamily') {
      applyProfVisualSettings();
    }
  }

  function setSettingFromUi(key, rawValue) {
    const def = getSettingDef(key);
    if (!def) return;
    let next;
    if (def.type === 'bool') next = !!rawValue;
    else if (def.type === 'color' || def.type === 'text' || def.type === 'select') next = String(rawValue);
    else next = clampNumberSetting(key, rawValue, def.min ?? 10, def.max ?? 999999);
    gmSet(key, next);
    applyRuntimeSettingChange(key);
  }

  function findTelegramHeaderButton() {
    const candidates = Array.from(document.querySelectorAll('a, button')).filter((el) => el.id !== 'ass-tm-settings-fab');
    const topRight = candidates
      .map((el) => ({ el, rect: el.getBoundingClientRect?.() }))
      .filter(({ rect }) => rect && rect.width > 0 && rect.height > 0 && rect.top >= 0 && rect.top < 190 && rect.right > Math.max(260, window.innerWidth * 0.55));
    const isTelegram = (el) => {
      const text = [el.getAttribute('href'), el.getAttribute('title'), el.className, el.textContent].join(' ').toLowerCase();
      if (text.includes('telegram') || text.includes('t.me') || text.includes('tg://')) return true;
      const icon = el.querySelector?.('i, svg');
      return icon && (icon.className.includes('telegram') || icon.outerHTML.includes('paper-plane'));
    };
    const direct = topRight.find(({ el }) => isTelegram(el));
    if (direct) return direct.el;
    const smallButtons = topRight.filter(({ rect }) => rect.width <= 56 && rect.height <= 56).sort((a, b) => b.rect.right - a.rect.right || a.rect.top - b.rect.top);
    return smallButtons[0]?.el || null;
  }

  function makeSettingsButton(replaceTarget = null) {
    const btn = document.createElement('button');
    btn.id = 'ass-tm-settings-fab';
    btn.type = 'button';
    btn.title = 'ASS Tools настройки';
    btn.innerHTML = '<i class="fal fa-cog" aria-hidden="true"></i>';
    if (replaceTarget) {
      const cls = String(replaceTarget.getAttribute('class') || '').trim();
      if (cls) btn.className = cls;
      btn.classList.add('ass-tm-settings-header-btn');
    } else {
      btn.className = 'ass-tm-settings-fallback';
    }
    btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); openSettingsPanel(); });
    return btn;
  }

  function ensureSettingsFab() {
    const existing = document.getElementById('ass-tm-settings-fab');
    if (existing && !existing.classList.contains('ass-tm-settings-fallback')) return;
    const target = findTelegramHeaderButton();
    if (target && target.id !== 'ass-tm-settings-fab') {
      const btn = makeSettingsButton(target);
      target.replaceWith(btn);
      if (existing && existing !== btn) existing.remove();
      return;
    }
    if (existing) return;
    document.body.appendChild(makeSettingsButton(null));
  }

  // --- Double-Click Confirm Utility ---
  function addDoubleConfirm(btn, normalText, onConfirm) {
    let clicks = 0;
    btn.addEventListener('click', (e) => {
      e.preventDefault();
      if (clicks === 0) {
        clicks++;
        const originalBg = btn.style.background;
        btn.textContent = 'Уверены? (Нажмите еще раз)';
        btn.style.background = '#d93838';
        setTimeout(() => {
          if (clicks > 0) {
            clicks = 0;
            btn.textContent = normalText;
            btn.style.background = originalBg;
          }
        }, 3000);
      } else {
        clicks = 0;
        btn.textContent = normalText;
        btn.style.background = '';
        onConfirm();
      }
    });
  }

  // --- List Editors (Profile + Quick Nav Links) ---
  function renderListEditor(panel, listClass, dataKey, defaults, rowBuilder, readRow) {
    const list = panel.querySelector(listClass);
    if (!list) return;
    list.innerHTML = '';
    const items = Array.isArray(state[dataKey]) && state[dataKey].length ? state[dataKey] : defaults;
    items.forEach((item, index) => list.appendChild(rowBuilder(item, index)));

    const saveFn = () => {
      const config = Array.from(list.children).map((row, i) => readRow(row, i)).filter(Boolean);
      gmSet(dataKey, config.length ? config : cloneValue(defaults));
      applyRuntimeSettingChange(dataKey);
      showSettingsStatus('Сохранено');
    };

    let timer = null;
    const schedule = () => { clearTimeout(timer); timer = setTimeout(saveFn, 250); };

    list.addEventListener('input', schedule);
    list.addEventListener('change', schedule);

    const wrapper = list.closest('.ass-tm-editor');
    wrapper.addEventListener('click', (e) => {
      if (e.target.closest('.ass-tm-editor-remove')) {
        e.target.closest('.ass-tm-editor-row').remove();
        saveFn();
      } else if (e.target.dataset.action === 'add') {
        list.appendChild(rowBuilder({}, Date.now()));
        saveFn();
      }
    });

    const resetBtn = wrapper.querySelector('[data-action="reset"]');
    if (resetBtn && !resetBtn.dataset.bound) {
      resetBtn.dataset.bound = '1';
      addDoubleConfirm(resetBtn, 'Сбросить', () => {
        gmSet(dataKey, cloneValue(defaults));
        applyRuntimeSettingChange(dataKey);
        renderListEditor(panel, listClass, dataKey, defaults, rowBuilder, readRow);
        showToast('Элементы сброшены');
      });
    }
  }

  function buildProfileRow(item, i) {
    const row = document.createElement('div'); row.className = 'ass-tm-editor-row'; row.dataset.id = item.id || `p-${i}`;
    const enabled = document.createElement('input'); enabled.type = 'checkbox'; enabled.checked = item.enabled !== false; enabled.dataset.f = 'en'; enabled.title = 'Включено';
    const text = document.createElement('input'); text.type = 'text'; text.value = item.text || ''; text.placeholder = 'Текст'; text.dataset.f = 'txt';
    const icon = document.createElement('input'); icon.type = 'text'; icon.value = item.icon || ''; icon.placeholder = 'Icon'; icon.dataset.f = 'icn';
    const rm = document.createElement('button'); rm.type = 'button'; rm.className = 'ass-tm-editor-remove'; rm.textContent = '×'; rm.title = 'Удалить';
    const url = document.createElement('input'); url.type = 'text'; url.value = item.url || ''; url.placeholder = 'URL'; url.dataset.f = 'url';

    // ВАЖНО: Добавление элементов ровно так, как указано на скриншоте пользователя
    row.append(enabled, text, icon, rm, url);

    return row;
  }

  function readProfileRow(row, i) {
    const q = (s) => row.querySelector(`[data-f="${s}"]`)?.value?.trim() || '';
    const url = q('url'), txt = q('txt'), icn = q('icn');
    if (!url && !txt && !icn) return null;
    return { id: row.dataset.id || `p-${i}`, enabled: !!row.querySelector('[data-f="en"]')?.checked, text: txt, icon: icn, url: url };
  }

  function buildQnRow(item, i) {
    const row = document.createElement('div'); row.className = 'ass-tm-editor-row qn-link'; row.dataset.id = item.id || `q-${i}`;
    const text = document.createElement('input'); text.type = 'text'; text.value = item.title || ''; text.placeholder = 'Название'; text.dataset.f = 'ttl';
    const rm = document.createElement('button'); rm.type = 'button'; rm.className = 'ass-tm-editor-remove'; rm.textContent = '×';
    const url = document.createElement('input'); url.type = 'text'; url.value = item.url || ''; url.placeholder = 'URL'; url.dataset.f = 'url';

    // По аналогии: текст, удаление, url
    row.append(text, rm, url);

    return row;
  }

  function readQnRow(row, i) {
    const url = row.querySelector('[data-f="url"]')?.value?.trim() || '';
    const ttl = row.querySelector('[data-f="ttl"]')?.value?.trim() || '';
    if (!url && !ttl) return null;
    return { id: row.dataset.id || `q-${i}`, title: ttl || 'Без названия', url: url || '#' };
  }

  function openSettingsPanel() {
    if (!document.body) return;
    let backdrop = document.querySelector('.ass-tm-settings-backdrop');
    if (!backdrop) {
      backdrop = document.createElement('div');
      backdrop.className = 'ass-tm-settings-backdrop';
      document.body.appendChild(backdrop);
    }
    backdrop.innerHTML = '';
    const panel = document.createElement('div');
    panel.className = 'ass-tm-settings-panel';
    panel.innerHTML = `
      <div class="ass-tm-settings-head">
        <div class="ass-tm-settings-title">ASS Tools — настройки</div>
        <button type="button" class="ass-tm-settings-close" title="Закрыть">×</button>
      </div>
      <div class="ass-tm-settings-body">
        <div class="ass-tm-settings-grid"></div>

        <div class="ass-tm-editor" id="qn-editor">
          <h3>Панель быстрого доступа (Ссылки)</h3>
          <div class="ass-tm-editor-list"></div>
          <div class="ass-tm-editor-actions">
            <button type="button" data-action="add">Добавить ссылку</button>
            <button type="button" data-action="reset">Сбросить</button>
          </div>
        </div>

        <div class="ass-tm-editor" id="prof-editor">
          <h3>Список кнопок профиля (включая ранги)</h3>
          <p style="margin:0 0 8px; color:#999; font-size:11px;">Плейсхолдеры: {USERNAME}, {CLUB_ID}.</p>
          <div class="ass-tm-editor-list"></div>
          <div class="ass-tm-editor-actions">
            <button type="button" data-action="add">Добавить кнопку</button>
            <button type="button" data-action="reset">Сбросить</button>
          </div>
        </div>

        <div class="ass-tm-save-status"></div>
        <div class="ass-tm-settings-actions">
          <button type="button" data-action="save">Сохранить всё</button>
          <button type="button" data-action="reset-all" style="background:rgba(255,255,255,.05); color:#999;">Полный сброс настроек</button>
        </div>
      </div>
    `;

    const grid = panel.querySelector('.ass-tm-settings-grid');
    SETTINGS_SCHEMA.forEach((section) => {
      const sectionEl = document.createElement('section'); sectionEl.className = 'ass-tm-settings-section';
      const h = document.createElement('h3'); h.textContent = section.title; sectionEl.appendChild(h);
      section.items.forEach((item) => {
        const row = document.createElement('label'); row.className = 'ass-tm-setting-row';
        const text = document.createElement('span'); text.className = 'ass-tm-setting-label'; text.textContent = item.label;
        let input;

        if (item.type === 'select') {
          input = document.createElement('select');
          item.options.forEach(opt => {
            const o = document.createElement('option');
            o.value = opt.value;
            o.textContent = opt.label;
            input.appendChild(o);
          });
          input.value = state[item.key] || item.options[0].value;
        } else {
          input = document.createElement('input');
          if (item.type === 'bool') {
            input.type = 'checkbox'; input.checked = !!state[item.key];
          } else if (item.type === 'color' || item.type === 'text') {
            input.type = item.type; input.value = state[item.key] || '';
          } else {
            input.type = 'number'; input.min = String(item.min ?? 10); input.max = String(item.max ?? 999999);
            if (item.step) input.step = item.step; else input.step = '1';
            input.value = String(state[item.key]);
          }
        }

        input.setAttribute('data-setting-key', item.key);
        row.appendChild(text); row.appendChild(input); sectionEl.appendChild(row);
      });
      grid.appendChild(sectionEl);
    });

    renderListEditor(panel.querySelector('#qn-editor'), '.ass-tm-editor-list', 'qnLinks', DEFAULT_QN_LINKS, buildQnRow, readQnRow);
    renderListEditor(panel.querySelector('#prof-editor'), '.ass-tm-editor-list', 'profileButtonsConfig', DEFAULT_PROFILE_BUTTONS, buildProfileRow, readProfileRow);

    let numTimer = null;
    panel.querySelectorAll('[data-setting-key]').forEach((input) => {
      const saveOne = () => { setSettingFromUi(input.getAttribute('data-setting-key'), input.type === 'checkbox' ? input.checked : input.value); showSettingsStatus('Сохранено'); };
      if (input.type === 'checkbox' || input.tagName === 'SELECT' || input.type === 'color') input.addEventListener('change', saveOne);
      else { input.addEventListener('input', () => { window.clearTimeout(numTimer); numTimer = window.setTimeout(saveOne, 300); }); input.addEventListener('change', saveOne); }
    });

    panel.querySelector('.ass-tm-settings-close')?.addEventListener('click', () => backdrop.classList.remove('ass-tm-settings-open'));
    panel.querySelector('[data-action="save"]')?.addEventListener('click', () => showSettingsStatus('Сохранено'));

    const resetAllBtn = panel.querySelector('[data-action="reset-all"]');
    if (resetAllBtn) {
      addDoubleConfirm(resetAllBtn, 'Полный сброс настроек', () => {
        Object.keys(DEFAULTS).forEach((k) => { gmSet(k, cloneValue(DEFAULTS[k])); applyRuntimeSettingChange(k); });
        backdrop.classList.remove('ass-tm-settings-open');
        showToast('Все настройки полностью сброшены');
        openSettingsPanel();
      });
    }

    backdrop.addEventListener('click', (e) => { if (e.target === backdrop) backdrop.classList.remove('ass-tm-settings-open'); });
    backdrop.appendChild(panel); backdrop.classList.add('ass-tm-settings-open');
  }

  // ---------------------------------------------------------------------------
  // Profile buttons
  // ---------------------------------------------------------------------------

  function getProfileUsername() { return document.querySelector('.usn__name > h1')?.textContent?.trim?.() || ''; }
  function getUserClubId() { const u = document.querySelector('.usn__club-item-top a')?.href; return u && u.includes('clubs/') ? u.replace(/\/$/, '').split('/').pop() : '1'; }
  function resolveIconClass(ic) { return ic ? (ic.startsWith('fas ') ? `fal ${ic.slice(4)}` : ic) : ''; }

  function mountProfileButtons() {
    if (!state.profileButtons) return;
    const username = getProfileUsername();
    if (!username) return;
    const header = document.querySelector('.usn-sect__header');
    if (!header || header.querySelector('.user-card-buttons')) return;

    const clubId = getUserClubId();
    const box = document.createElement('div'); box.className = 'user-card-buttons';

    (state.profileButtonsConfig || []).forEach((btn) => {
      if (!btn.enabled) return;
      const text = String(btn.text || '').trim(), iconClass = String(btn.icon || '').trim();
      let url = String(btn.url || '').replace(/\{USERNAME\}/g, username).replace(/\{USER\}/g, username).replace(/\{CLUB_ID\}/g, clubId);
      if (!url || (!text && !iconClass)) return;
      if (!/^https?:\/\//i.test(url) && !url.startsWith('/')) url = `/${url.replace(/^\/+/, '')}`;

      const link = document.createElement('a'); link.href = url; link.title = text || '';
      if (iconClass) {
        const i = document.createElement('i'); i.className = resolveIconClass(iconClass); link.appendChild(i);
        if (text && text.length <= 3) link.appendChild(document.createTextNode(text));
      } else link.textContent = text;
      box.appendChild(link);
    });

    if (box.childNodes.length) header.appendChild(box);
  }

  // ---------------------------------------------------------------------------
  // Card need buttons / class marker
  // ---------------------------------------------------------------------------

  const CARD_CONTAINER_SELECTOR = '.lootbox__card, .anime-cards__item, a.trade__main-item, a.history__body-item, .trade__inventory-item, div.trade__main-item, .remelt__inventory-item, .remelt__item, .anime-cards__placeholder, .stone__inventory-item';

  function applyNeedButtonMarker(elm) {
    if (!state.cardNeedButtons || !elm || elm.classList.contains('show-need_button')) return;
    if (elm.classList.contains('show-trade_button') || elm.dataset?.canTrade === '1') return;
    elm.classList.add('show-need_button');
  }
  function scanCardNeedButtons(root = document) { if (state.cardNeedButtons) root.querySelectorAll?.(CARD_CONTAINER_SELECTOR).forEach(applyNeedButtonMarker); }

  // ---------------------------------------------------------------------------
  // Modal star button
  // ---------------------------------------------------------------------------

  function addStarButton(modalContent) {
    if (!state.modalStarButton || !modalContent) return;
    const meta = modalContent.querySelector('.ncard__meta');
    if (!meta || meta.querySelector('.as-ext-star-meta-item')) return;
    const rankElement = modalContent.querySelector('.ncard__meta-item.ncard__rank');
    if (!rankElement) return;

    const rankClass = Array.from(rankElement.classList).find((c) => c.startsWith('rank-'));
    const rank = rankClass ? rankClass.split('-').slice(1).join('-') : null;
    const cardName = modalContent.querySelector('.anime-cards__name')?.textContent?.trim() || null;
    if (!rank || !cardName) return;

    const starLink = document.createElement('a');
    starLink.href = `/update_stars/?rank=${encodeURIComponent(rank)}&search=${encodeURIComponent(cardName)}`;
    starLink.className = 'ncard__meta-item as-ext-star-meta-item';

    // Идеальная, залитая по центру SVG звезда (гарантированно работает без внешних шрифтов)
    starLink.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" width="16" height="16" fill="gold"><path d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"/></svg>';

    starLink.style.cssText = 'display:flex; align-items:center; justify-content:center; width:36px; min-width:36px; height:36px; border-radius:50%; text-decoration:none; padding:0; margin:0; box-sizing:border-box; background-color:transparent; border:1px solid #555; transition:all .2s ease;';

    starLink.onmouseenter = () => { starLink.style.backgroundColor = 'rgba(158,41,79,.9)'; starLink.style.borderColor = 'rgba(158,41,79,.9)'; };
    starLink.onmouseleave = () => { starLink.style.backgroundColor = 'transparent'; starLink.style.borderColor = '#555'; };

    meta.style.columnGap = '5px';
    meta.insertBefore(starLink, rankElement);
  }

  function processDialogElement(dialogElement) {
    if (state.modalStarButton) setTimeout(() => addStarButton(dialogElement.querySelector?.('#card-modal .modal__content')), 50);
  }
  function processExistingDialogs() { document.querySelectorAll('.ui-dialog').forEach(processDialogElement); }

  // ---------------------------------------------------------------------------
  // Stones (Event-Driven Auto-collect)
  // ---------------------------------------------------------------------------

  function clickStoneWithDelay(stoneElement) {
    const baseDelay = clampNumberSetting('stoneBaseDelayMs', state.stoneBaseDelayMs, 10, 5000);
    const growthDelay = clampNumberSetting('stoneGrowthDelayMs', state.stoneGrowthDelayMs, 10, 3000);
    const currentDelay = baseDelay + (growthDelay * stoneClickQueue);
    stoneClickQueue++;

    setTimeout(() => {
      if (stoneElement && document.body.contains(stoneElement)) {
        stoneElement.click();
      }
      stoneClickQueue--;
      if (stoneClickQueue < 0) stoneClickQueue = 0;
    }, currentDelay);
  }

  function scanExistingStones() {
    if (state.takeCinemaStone) {
      const diamonds = Array.from(document.querySelectorAll('#diamonds-chat[data-code]'));
      diamonds.reverse().forEach((diamond) => {
        if (diamond.dataset.assQueued === '1') return;
        const code = diamond.getAttribute('data-code');
        if (!code || clickedCinemaCodes.has(code)) return;
        clickedCinemaCodes.add(code);
        diamond.dataset.assQueued = '1';
        clickStoneWithDelay(diamond);
      });
    }
    if (state.takeSnowStone) {
      const snow = document.querySelector('#snow-stone-gift');
      if (snow && snow.dataset.assQueued !== '1') {
        snow.dataset.assQueued = '1';
        clickStoneWithDelay(snow);
      }
    }
    if (state.takeHeavenlyStone) {
      const heavenly = document.querySelector('#gift-icon');
      if (heavenly && heavenly.dataset.assQueued !== '1') {
        heavenly.dataset.assQueued = '1';
        clickStoneWithDelay(heavenly);
      }
    }
  }

  function startInitialStoneScanner() {
    let ticks = 0;
    const maxTicks = 60;

    scanExistingStones();
    const scanInterval = setInterval(() => {
      scanExistingStones();
      ticks++;
      if (ticks >= maxTicks) clearInterval(scanInterval);
    }, 3000);
  }

  function interactionWithChat() {
    if (!state.takeCinemaStone) return;
    const idle = document.querySelector('#animesssChatIdle');
    if (idle && idle.style.display !== 'none') {
      document.querySelector('#animesssChatIdleBack')?.click();
      document.activeElement?.focus?.();
    }
  }

  // ---------------------------------------------------------------------------
  // Main Observers & Init
  // ---------------------------------------------------------------------------

  function setupObservers() {
    new MutationObserver((mutations) => {
      for (const m of mutations) {
        m.addedNodes.forEach((node) => {
          if (node.nodeType !== Node.ELEMENT_NODE) return;

          if (node.matches?.(CARD_CONTAINER_SELECTOR)) applyNeedButtonMarker(node);
          scanCardNeedButtons(node);

          if (node.classList?.contains('ui-dialog')) processDialogElement(node);
          const nestedDialog = node.querySelector?.('.ui-dialog');
          if (nestedDialog) processDialogElement(nestedDialog);

          if (state.takeCinemaStone) {
            let diamonds = [];
            if (node.id === 'diamonds-chat' && node.hasAttribute('data-code')) {
              diamonds.push(node);
            } else if (node.querySelectorAll) {
              diamonds = Array.from(node.querySelectorAll('#diamonds-chat[data-code]'));
            }

            diamonds.reverse().forEach((diamond) => {
              if (diamond.dataset.assQueued === '1') return;
              const code = diamond.getAttribute('data-code');
              if (!code || clickedCinemaCodes.has(code)) return;
              clickedCinemaCodes.add(code);
              diamond.dataset.assQueued = '1';
              clickStoneWithDelay(diamond);
            });
          }

          if (state.takeSnowStone) {
            const snow = node.id === 'snow-stone-gift' ? node : node.querySelector?.('#snow-stone-gift');
            if (snow && snow.dataset.assQueued !== '1') {
              snow.dataset.assQueued = '1';
              clickStoneWithDelay(snow);
            }
          }

          if (state.takeHeavenlyStone) {
            const heavenly = node.id === 'gift-icon' ? node : node.querySelector?.('#gift-icon');
            if (heavenly && heavenly.dataset.assQueued !== '1') {
              heavenly.dataset.assQueued = '1';
              clickStoneWithDelay(heavenly);
            }
          }
        });
      }

      mountProfileButtons();
      ensureSettingsFab();
      if (state.quickNavEnabled && !document.getElementById('custom-quick-nav') && document.querySelector('header')) {
        injectQuickNav();
      }
    }).observe(document.body, { childList: true, subtree: true });
  }

  function main() {
    loadSettings();
    injectStyle();

    onBodyReady(() => {
      injectQuickNav();
      applyProfVisualSettings();
      ensureSettingsFab();

      setupObservers();
      mountProfileButtons();
      scanCardNeedButtons();
      processExistingDialogs();

      startInitialStoneScanner();
      setInterval(interactionWithChat, 10000);
    });
  }

  main();
})();