Pluscord (Discord Enhancer)

Message logger, custom backgrounds, themes, quick menu, QoL tweaks, CSS editor, and image uploads for Discord. Local-only; DOM/CSS enhancements.

// ==UserScript==
// @name         Pluscord (Discord Enhancer)
// @namespace    https://pluscord.local
// @version      2.0.0
// @description  Message logger, custom backgrounds, themes, quick menu, QoL tweaks, CSS editor, and image uploads for Discord. Local-only; DOM/CSS enhancements.
// @author       pietrodelfiore_
// @match        https://discord.com/*
// @match        https://ptb.discord.com/*
// @match        https://canary.discord.com/*
// @run-at       document-idle
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_xmlhttpRequest
// @grant        GM_notification
// @grant        GM_uploadFile
// ==/UserScript==

(function () {
  'use strict';

  const APP_NAME = 'Pluscord';
  const VERSION = '2.0.0';

  // --- Load Discord Font ---
  function loadDiscordFont() {
    const fontLink = document.createElement('link');
    fontLink.href = 'https://cdn.jsdelivr.net/gh/googlefonts/noto-fonts@main/fonts/latin/NotoSans-Regular.ttf';
    fontLink.rel = 'preload';
    fontLink.as = 'font';
    fontLink.type = 'font/ttf';
    fontLink.crossOrigin = 'anonymous';
    document.head.appendChild(fontLink);

    const fontFace = document.createElement('style');
    fontFace.textContent = `
      @font-face {
        font-family: 'gg sans';
        font-style: normal;
        font-weight: 400;
        src: url('https://cdn.jsdelivr.net/gh/googlefonts/noto-fonts@main/fonts/latin/NotoSans-Regular.ttf') format('truetype');
      }
      @font-face {
        font-family: 'gg sans';
        font-style: normal;
        font-weight: 500;
        src: url('https://cdn.jsdelivr.net/gh/googlefonts/noto-fonts@main/fonts/latin/NotoSans-Medium.ttf') format('truetype');
      }
      @font-face {
        font-family: 'gg sans';
        font-style: normal;
        font-weight: 600;
        src: url('https://cdn.jsdelivr.net/gh/googlefonts/noto-fonts@main/fonts/latin/NotoSans-SemiBold.ttf') format('truetype');
      }
      @font-face {
        font-family: 'gg sans';
        font-style: normal;
        font-weight: 700;
        src: url('https://cdn.jsdelivr.net/gh/googlefonts/noto-fonts@main/fonts/latin/NotoSans-Bold.ttf') format('truetype');
      }
    `;
    document.head.appendChild(fontFace);
  }

  // --- Defaults ---
  const DEFAULTS = {
    theme: 'amoled',
    customTheme: { bg: '#0b0d10', panelBg: '#111318', text: '#e7e7e7', accent: '#5b9fff', link: '#7aa8ff', mention: '#243343' },
    appearance: { imageUrl: '', appOpacity: 1.0, avatarShape: 'default', customScrollbar: true, avatarFrames: false },
    compactMode: false,
    codeCopyButton: true,
    msgCharCounter: true,
    charLimit: 2000,
    absoluteTimestamps: false,
    disableAnimations: false,
    revealSpoilers: false,
    uiCleanup: { hideGift: true, hideGIF: false, hideSticker: false, hideTyping: false },
    highlight: { keywords: [], myName: '', color: '#2db879', useRegex: false, caseSensitive: false },
    floatingButton: true,
    performanceBoost: false,
    boost: { pauseGIFs: true, collapseEmbeds: false, collapseAttachments: true, hideAvatars: false, hideMemberList: false, reduceTransparency: true, simplifyShadows: true },
    messageLogger: { enabled: false, logDeletes: true, logEdits: true, maxLogs: 200 },
    quickActions: { autoScroll: true, jumpToBottom: true, markUnread: true },
    experimental: { customCSS: '', devMode: false },
    // New features
    quickReplies: { enabled: false, templates: [] },
    formattingShortcuts: { enabled: true, bold: 'Ctrl+B', italic: 'Ctrl+I', strikethrough: 'Ctrl+Shift+S', code: 'Ctrl+Shift+C' },
    quickReactions: { enabled: false, emojis: ['👍', '❤️', '😂', '😮', '😢', '👎'] },
    translation: { enabled: false, autoDetect: true, defaultLang: 'en' },
    textToSpeech: { enabled: false, voice: 'default', rate: 1.0, volume: 1.0 },
    notifications: { customSounds: false, soundFile: '', suppressMentions: false },
    serverFolders: { enabled: false, folders: [] },
    pinnedChannels: { enabled: false, channels: [] },
    userNotes: { enabled: false, notes: {} },
    moderation: { autoMod: false, filters: [], warningTemplates: [] },
    integrations: { spotify: false, github: false, calendar: false },
    cssEditor: { theme: 'monokai', fontSize: 14, wordWrap: true, livePreview: true },
    imageUploads: { backgrounds: [], avatars: [] }
  };

  // --- Storage & Utils ---
  function loadSettings() {
    let s; try { s = GM_getValue('settings', null); } catch { s = null; }
    if (!s) return structuredClone(DEFAULTS);
    return deepMerge(structuredClone(DEFAULTS), s);
  }
  function saveSettings(settings) { GM_setValue('settings', settings); }
  function deepMerge(base, update) {
    for (const k in update) {
      if (update[k] && typeof update[k] === 'object' && !Array.isArray(update[k])) {
        base[k] = deepMerge(base[k] || {}, update[k]);
      } else {
        base[k] = update[k];
      }
    }
    return base;
  }
  const clamp = (n, min, max) => Math.min(Math.max(n, min), max);

  let S = loadSettings();
  let styleTag = null;
  let messageCache = new Map();
  let logCache = [];
  let mainObserver = null;
  let mediaObserver = null;
  let managedVideos = new Set();
  let seenSpoilers = new WeakSet();
  let quickReplyMenu = null;
  let reactionMenu = null;
  let userNotesMenu = null;
  let serverFoldersContainer = null;
  let pinnedChannelsList = null;
  let cssEditor = null;
  let imageUploader = null;

  // --- Image Upload Handler ---
  function handleImageUpload(type, callback) {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'image/*';
    input.onchange = (e) => {
      const file = e.target.files[0];
      if (!file) return;

      const reader = new FileReader();
      reader.onload = (event) => {
        const dataUrl = event.target.result;

        // Store the image
        if (!S.imageUploads[type]) S.imageUploads[type] = [];
        S.imageUploads[type].push({
          name: file.name,
          data: dataUrl,
          size: file.size,
          type: file.type,
          uploaded: new Date().toISOString()
        });

        // Keep only last 10 images per type
        if (S.imageUploads[type].length > 10) {
          S.imageUploads[type] = S.imageUploads[type].slice(-10);
        }

        saveSettings(S);
        if (callback) callback(dataUrl);
        toast(`Image uploaded: ${file.name}`);
      };
      reader.readAsDataURL(file);
    };
    input.click();
  }

  // --- CSS Editor with Syntax Highlighting ---
  function createCSSEditor() {
    const editor = document.createElement('div');
    editor.id = 'pluscord-css-editor';
    editor.innerHTML = `
      <div class="editor-header">
        <div class="editor-title">CSS Editor</div>
        <div class="editor-controls">
          <select id="editor-theme">
            <option value="monokai">Monokai</option>
            <option value="github">GitHub</option>
            <option value="dracula">Dracula</option>
            <option value="discord">Discord</option>
          </select>
          <select id="editor-font-size">
            <option value="12">12px</option>
            <option value="14" selected>14px</option>
            <option value="16">16px</option>
            <option value="18">18px</option>
          </select>
          <button id="editor-word-wrap">Word Wrap</button>
          <button id="editor-live-preview">Live Preview</button>
          <button id="editor-format">Format</button>
          <button id="editor-close">×</button>
        </div>
      </div>
      <div class="editor-content">
        <div class="editor-line-numbers"></div>
        <div class="editor-code" contenteditable="true" spellcheck="false"></div>
      </div>
      <div class="editor-footer">
        <div class="editor-status"></div>
        <div class="editor-actions">
          <button id="editor-apply">Apply</button>
          <button id="editor-save">Save</button>
        </div>
      </div>
    `;

    // Add styles
    const editorStyles = document.createElement('style');
    editorStyles.textContent = `
      #pluscord-css-editor {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 80vw;
        height: 80vh;
        background: var(--background-secondary, #2f3136);
        border: 1px solid var(--background-tertiary, #202225);
        border-radius: 8px;
        box-shadow: 0 8px 16px rgba(0, 0, 0, 0.24);
        display: none;
        flex-direction: column;
        font-family: 'gg sans', 'Noto Sans', sans-serif;
        z-index: 2147483647;
      }

      #pluscord-css-editor .editor-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 16px;
        border-bottom: 1px solid var(--background-tertiary, #202225);
      }

      #pluscord-css-editor .editor-title {
        font-size: 16px;
        font-weight: 600;
        color: var(--header-primary, #ffffff);
      }

      #pluscord-css-editor .editor-controls {
        display: flex;
        gap: 8px;
        align-items: center;
      }

      #pluscord-css-editor select {
        background: var(--background-primary, #36393f);
        color: var(--text-normal, #dcddde);
        border: 1px solid var(--background-tertiary, #202225);
        border-radius: 4px;
        padding: 6px 8px;
        font-size: 14px;
      }

      #pluscord-css-editor button {
        background: var(--background-modifier-hover, #4f545c);
        color: var(--text-normal, #dcddde);
        border: none;
        border-radius: 4px;
        padding: 6px 12px;
        font-size: 14px;
        font-weight: 500;
        cursor: pointer;
        transition: background-color 0.17s ease;
      }

      #pluscord-css-editor button:hover {
        background: var(--background-modifier-active, #7289da);
      }

      #pluscord-css-editor .editor-content {
        display: flex;
        flex: 1;
        overflow: hidden;
      }

      #pluscord-css-editor .editor-line-numbers {
        background: var(--background-tertiary, #202225);
        color: var(--text-muted, #72767d);
        padding: 16px 8px;
        text-align: right;
        font-family: 'Consolas', 'Monaco', monospace;
        font-size: 14px;
        line-height: 1.5;
        user-select: none;
        overflow: hidden;
      }

      #pluscord-css-editor .editor-code {
        flex: 1;
        padding: 16px;
        font-family: 'Consolas', 'Monaco', monospace;
        font-size: 14px;
        line-height: 1.5;
        color: var(--text-normal, #dcddde);
        background: var(--background-primary, #36393f);
        overflow: auto;
        white-space: pre;
        outline: none;
      }

      #pluscord-css-editor .editor-footer {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 16px;
        border-top: 1px solid var(--background-tertiary, #202225);
      }

      #pluscord-css-editor .editor-status {
        color: var(--text-muted, #72767d);
        font-size: 14px;
      }

      #pluscord-css-editor .editor-actions {
        display: flex;
        gap: 8px;
      }

      #pluscord-css-editor .editor-actions button {
        background: var(--brand-experiment, #5865f2);
        color: white;
      }

      #pluscord-css-editor .editor-actions button:hover {
        background: var(--brand-experiment-hover, #4752c4);
      }

      /* Syntax highlighting themes */
      .theme-monokai .keyword { color: #f92672; }
      .theme-monokai .string { color: #a6e22e; }
      .theme-monokai .number { color: #ae81ff; }
      .theme-monokai .comment { color: #75715e; }
      .theme-monokai .selector { color: #66d9ef; }
      .theme-monokai .property { color: #fd971f; }
      .theme-monokai .value { color: #e6db74; }

      .theme-github .keyword { color: #d73a49; }
      .theme-github .string { color: #032f62; }
      .theme-github .number { color: #005cc5; }
      .theme-github .comment { color: #6a737d; }
      .theme-github .selector { color: #6f42c1; }
      .theme-github .property { color: #e36209; }
      .theme-github .value { color: #22863a; }

      .theme-dracula .keyword { color: #ff79c6; }
      .theme-dracula .string { color: #f1fa8c; }
      .theme-dracula .number { color: #bd93f9; }
      .theme-dracula .comment { color: #6272a4; }
      .theme-dracula .selector { color: #8be9fd; }
      .theme-dracula .property { color: #50fa7b; }
      .theme-dracula .value { color: #ffb86c; }

      .theme-discord .keyword { color: #eb459e; }
      .theme-discord .string { color: #f6c177; }
      .theme-discord .number { color: #31748f; }
      .theme-discord .comment { color: #9ccfd8; }
      .theme-discord .selector { color: #c4a7e7; }
      .theme-discord .property { color: #ebbcba; }
      .theme-discord .value { color: #90b99f; }
    `;
    document.head.appendChild(editorStyles);

    document.body.appendChild(editor);
    return editor;
  }

  function highlightCSS(code, theme) {
    // Simple CSS syntax highlighting
    return code
      .replace(/([{}])/g, '<span class="bracket">$1</span>')
      .replace(/([:;,])/g, '<span class="punctuation">$1</span>')
      .replace(/\/\*[\s\S]*?\*\//g, '<span class="comment">$&</span>')
      .replace(/([a-zA-Z-]+)(\s*:)/g, '<span class="property">$1</span>$2')
      .replace(/:(\s*)([^;]+)(;)/g, ':$1<span class="value">$2</span>$3')
      .replace(/^([^.]+)(?=\s*{)/gm, '<span class="selector">$1</span>')
      .replace(/\b(important|initial|inherit|unset|revert|auto|none|hidden|visible|block|inline|flex|grid|absolute|relative|fixed|sticky|static|center|left|right|top|bottom|justify|align|baseline|middle|sub|super|text-top|text-bottom|uppercase|lowercase|capitalize|underline|overline|line-through|italic|oblique|normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|solid|dotted|dashed|double|groove|ridge|inset|outset|none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset|transparent|rgba?|hsla?|url|var|calc|attr|counter|counters|cross-fade|element|image|paint|linear-gradient|radial-gradient|conic-gradient|repeating-linear-gradient|repeating-radial-gradient|repeating-conic-gradient|color|color-mix|color-adjust|opacity|filter|drop-shadow|blur|brightness|contrast|grayscale|hue-rotate|invert|saturate|sepia|transform|rotate|scale|skew|translate|perspective|matrix|matrix3d|rotateX|rotateY|rotateZ|rotate3d|scaleX|scaleY|scaleZ|scale3d|skewX|skewY|translateX|translateY|translateZ|translate3d|transition|animation|delay|duration|timing-function|iteration-count|direction|fill-mode|play-state|keyframes|media|supports|document|namespace|page|font-face|charset|import|layer|container|scope|starting-style|property|scroll-timeline|view-timeline|counter-style|font-palette|position-try|position-fallback)\b/g, '<span class="keyword">$1</span>')
      .replace(/\b(\d+\.?\d*%?|#[0-9a-fA-F]{3,6}|[a-zA-Z-]+)\b/g, '<span class="number">$1</span>');
  }

  function setupCSSEditor() {
    if (!cssEditor) cssEditor = createCSSEditor();

    const codeEl = cssEditor.querySelector('.editor-code');
    const lineNumbersEl = cssEditor.querySelector('.editor-line-numbers');
    const statusEl = cssEditor.querySelector('.editor-status');
    const themeSelect = cssEditor.querySelector('#editor-theme');
    const fontSizeSelect = cssEditor.querySelector('#editor-font-size');
    const wordWrapBtn = cssEditor.querySelector('#editor-word-wrap');
    const livePreviewBtn = cssEditor.querySelector('#editor-live-preview');
    const formatBtn = cssEditor.querySelector('#editor-format');
    const closeBtn = cssEditor.querySelector('#editor-close');
    const applyBtn = cssEditor.querySelector('#editor-apply');
    const saveBtn = cssEditor.querySelector('#editor-save');

    // Load current CSS
    codeEl.textContent = S.experimental.customCSS || '';

    // Update line numbers
    function updateLineNumbers() {
      const lines = codeEl.textContent.split('\n').length;
      lineNumbersEl.innerHTML = Array.from({ length: lines }, (_, i) => i + 1).join('<br>');
    }

    updateLineNumbers();

    // Syntax highlighting
    function applySyntaxHighlighting() {
      const theme = themeSelect.value;
      codeEl.innerHTML = highlightCSS(codeEl.textContent, theme);
      codeEl.classList.remove('theme-monokai', 'theme-github', 'theme-dracula', 'theme-discord');
      codeEl.classList.add(`theme-${theme}`);
    }

    // Event listeners
    codeEl.addEventListener('input', () => {
      updateLineNumbers();
      applySyntaxHighlighting();

      if (S.cssEditor.livePreview) {
        S.experimental.customCSS = codeEl.textContent;
        refreshGlobalStyles();
      }

      // Update status
      const lines = codeEl.textContent.split('\n').length;
      const chars = codeEl.textContent.length;
      statusEl.textContent = `Lines: ${lines} | Characters: ${chars}`;
    });

    codeEl.addEventListener('scroll', () => {
      lineNumbersEl.scrollTop = codeEl.scrollTop;
    });

    themeSelect.addEventListener('change', () => {
      S.cssEditor.theme = themeSelect.value;
      saveSettings(S);
      applySyntaxHighlighting();
    });

    fontSizeSelect.addEventListener('change', () => {
      S.cssEditor.fontSize = parseInt(fontSizeSelect.value);
      saveSettings(S);
      codeEl.style.fontSize = `${S.cssEditor.fontSize}px`;
      lineNumbersEl.style.fontSize = `${S.cssEditor.fontSize}px`;
    });

    wordWrapBtn.addEventListener('click', () => {
      S.cssEditor.wordWrap = !S.cssEditor.wordWrap;
      saveSettings(S);
      codeEl.style.whiteSpace = S.cssEditor.wordWrap ? 'pre-wrap' : 'pre';
      wordWrapBtn.textContent = S.cssEditor.wordWrap ? 'No Wrap' : 'Word Wrap';
    });

    livePreviewBtn.addEventListener('click', () => {
      S.cssEditor.livePreview = !S.cssEditor.livePreview;
      saveSettings(S);
      livePreviewBtn.textContent = S.cssEditor.livePreview ? 'No Preview' : 'Live Preview';
    });

    formatBtn.addEventListener('click', () => {
      // Simple CSS formatting
      let formatted = codeEl.textContent
        .replace(/\s*{\s*/g, ' {\n  ')
        .replace(/;\s*/g, ';\n  ')
        .replace(/\s*}\s*/g, '\n}\n')
        .replace(/\n\s*\n/g, '\n')
        .trim();

      codeEl.textContent = formatted;
      updateLineNumbers();
      applySyntaxHighlighting();
    });

    closeBtn.addEventListener('click', () => {
      cssEditor.style.display = 'none';
    });

    applyBtn.addEventListener('click', () => {
      S.experimental.customCSS = codeEl.textContent;
      refreshGlobalStyles();
      toast('CSS applied');
    });

    saveBtn.addEventListener('click', () => {
      S.experimental.customCSS = codeEl.textContent;
      saveSettings(S);
      toast('CSS saved');
    });

    // Initialize settings
    themeSelect.value = S.cssEditor.theme;
    fontSizeSelect.value = S.cssEditor.fontSize;
    codeEl.style.fontSize = `${S.cssEditor.fontSize}px`;
    lineNumbersEl.style.fontSize = `${S.cssEditor.fontSize}px`;
    codeEl.style.whiteSpace = S.cssEditor.wordWrap ? 'pre-wrap' : 'pre';
    wordWrapBtn.textContent = S.cssEditor.wordWrap ? 'No Wrap' : 'Word Wrap';
    livePreviewBtn.textContent = S.cssEditor.livePreview ? 'No Preview' : 'Live Preview';

    applySyntaxHighlighting();
  }

  // --- Image Uploader ---
  function createImageUploader() {
    const uploader = document.createElement('div');
    uploader.id = 'pluscord-image-uploader';
    uploader.innerHTML = `
      <div class="uploader-header">
        <div class="uploader-title">Image Manager</div>
        <button id="uploader-close">×</button>
      </div>
      <div class="uploader-tabs">
        <button class="tab active" data-tab="backgrounds">Backgrounds</button>
        <button class="tab" data-tab="avatars">Avatars</button>
        <button class="tab" data-tab="upload">Upload</button>
      </div>
      <div class="uploader-content">
        <div class="tab-content active" id="tab-backgrounds">
          <div class="image-grid" id="backgrounds-grid"></div>
        </div>
        <div class="tab-content" id="tab-avatars">
          <div class="image-grid" id="avatars-grid"></div>
        </div>
        <div class="tab-content" id="tab-upload">
          <div class="upload-area" id="upload-area">
            <div class="upload-icon">📁</div>
            <div class="upload-text">Drag & drop images here or click to browse</div>
            <input type="file" id="file-input" accept="image/*" multiple style="display: none;">
          </div>
          <div class="upload-options">
            <select id="upload-type">
              <option value="backgrounds">Background</option>
              <option value="avatars">Avatar</option>
            </select>
            <button id="upload-btn">Upload Images</button>
          </div>
        </div>
      </div>
    `;

    // Add styles
    const uploaderStyles = document.createElement('style');
    uploaderStyles.textContent = `
      #pluscord-image-uploader {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 80vw;
        height: 80vh;
        background: var(--background-secondary, #2f3136);
        border: 1px solid var(--background-tertiary, #202225);
        border-radius: 8px;
        box-shadow: 0 8px 16px rgba(0, 0, 0, 0.24);
        display: none;
        flex-direction: column;
        font-family: 'gg sans', 'Noto Sans', sans-serif;
        z-index: 2147483647;
      }

      #pluscord-image-uploader .uploader-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 16px;
        border-bottom: 1px solid var(--background-tertiary, #202225);
      }

      #pluscord-image-uploader .uploader-title {
        font-size: 16px;
        font-weight: 600;
        color: var(--header-primary, #ffffff);
      }

      #pluscord-image-uploader .uploader-header button {
        background: none;
        border: none;
        color: var(--text-normal, #dcddde);
        font-size: 20px;
        cursor: pointer;
        padding: 0;
        width: 24px;
        height: 24px;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      #pluscord-image-uploader .uploader-tabs {
        display: flex;
        border-bottom: 1px solid var(--background-tertiary, #202225);
      }

      #pluscord-image-uploader .tab {
        background: none;
        border: none;
        color: var(--text-muted, #72767d);
        padding: 12px 16px;
        font-size: 14px;
        font-weight: 500;
        cursor: pointer;
        position: relative;
      }

      #pluscord-image-uploader .tab.active {
        color: var(--header-primary, #ffffff);
      }

      #pluscord-image-uploader .tab.active::after {
        content: '';
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        height: 2px;
        background: var(--brand-experiment, #5865f2);
      }

      #pluscord-image-uploader .uploader-content {
        flex: 1;
        overflow: hidden;
        display: flex;
        flex-direction: column;
      }

      #pluscord-image-uploader .tab-content {
        display: none;
        flex: 1;
        overflow: auto;
        padding: 16px;
      }

      #pluscord-image-uploader .tab-content.active {
        display: block;
      }

      #pluscord-image-uploader .image-grid {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
        gap: 16px;
      }

      #pluscord-image-uploader .image-item {
        background: var(--background-primary, #36393f);
        border-radius: 8px;
        overflow: hidden;
        position: relative;
        aspect-ratio: 16/9;
      }

      #pluscord-image-uploader .image-item img {
        width: 100%;
        height: 100%;
        object-fit: cover;
      }

      #pluscord-image-uploader .image-item .image-overlay {
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        background: linear-gradient(to top, rgba(0,0,0,0.7), transparent);
        padding: 8px;
        color: white;
        font-size: 12px;
        display: flex;
        justify-content: space-between;
        align-items: center;
      }

      #pluscord-image-uploader .image-item .image-actions {
        display: flex;
        gap: 8px;
      }

      #pluscord-image-uploader .image-item button {
        background: rgba(255,255,255,0.2);
        border: none;
        color: white;
        border-radius: 4px;
        padding: 4px 8px;
        font-size: 12px;
        cursor: pointer;
      }

      #pluscord-image-uploader .upload-area {
        border: 2px dashed var(--background-tertiary, #202225);
        border-radius: 8px;
        padding: 40px;
        text-align: center;
        margin-bottom: 16px;
        cursor: pointer;
        transition: border-color 0.2s;
      }

      #pluscord-image-uploader .upload-area:hover {
        border-color: var(--brand-experiment, #5865f2);
      }

      #pluscord-image-uploader .upload-area.dragover {
        border-color: var(--brand-experiment, #5865f2);
        background: rgba(88, 101, 242, 0.1);
      }

      #pluscord-image-uploader .upload-icon {
        font-size: 48px;
        margin-bottom: 16px;
      }

      #pluscord-image-uploader .upload-text {
        color: var(--text-muted, #72767d);
        font-size: 14px;
      }

      #pluscord-image-uploader .upload-options {
        display: flex;
        gap: 12px;
        align-items: center;
      }

      #pluscord-image-uploader .upload-options select {
        background: var(--background-primary, #36393f);
        color: var(--text-normal, #dcddde);
        border: 1px solid var(--background-tertiary, #202225);
        border-radius: 4px;
        padding: 8px 12px;
        font-size: 14px;
      }

      #pluscord-image-uploader .upload-options button {
        background: var(--brand-experiment, #5865f2);
        color: white;
        border: none;
        border-radius: 4px;
        padding: 8px 16px;
        font-size: 14px;
        font-weight: 500;
        cursor: pointer;
      }
    `;
    document.head.appendChild(uploaderStyles);

    document.body.appendChild(uploader);
    return uploader;
  }

  function setupImageUploader() {
    if (!imageUploader) imageUploader = createImageUploader();

    const tabs = imageUploader.querySelectorAll('.tab');
    const tabContents = imageUploader.querySelectorAll('.tab-content');
    const uploadArea = imageUploader.querySelector('#upload-area');
    const fileInput = imageUploader.querySelector('#file-input');
    const uploadType = imageUploader.querySelector('#upload-type');
    const uploadBtn = imageUploader.querySelector('#upload-btn');
    const closeBtn = imageUploader.querySelector('#uploader-close');

    // Tab switching
    tabs.forEach(tab => {
      tab.addEventListener('click', () => {
        const targetTab = tab.dataset.tab;

        tabs.forEach(t => t.classList.remove('active'));
        tabContents.forEach(tc => tc.classList.remove('active'));

        tab.classList.add('active');
        imageUploader.querySelector(`#tab-${targetTab}`).classList.add('active');

        // Load images for the selected tab
        if (targetTab === 'backgrounds') {
          loadImages('backgrounds');
        } else if (targetTab === 'avatars') {
          loadImages('avatars');
        }
      });
    });

    // Load images
    function loadImages(type) {
      const grid = imageUploader.querySelector(`#${type}-grid`);
      const images = S.imageUploads[type] || [];

      grid.innerHTML = images.map((img, index) => `
        <div class="image-item">
          <img src="${img.data}" alt="${img.name}">
          <div class="image-overlay">
            <span>${img.name}</span>
            <div class="image-actions">
              <button data-action="use" data-index="${index}">Use</button>
              <button data-action="delete" data-index="${index}">Delete</button>
            </div>
          </div>
        </div>
      `).join('');

      // Add event listeners to buttons
      grid.querySelectorAll('button').forEach(btn => {
        btn.addEventListener('click', (e) => {
          e.stopPropagation();
          const action = btn.dataset.action;
          const index = parseInt(btn.dataset.index);

          if (action === 'use') {
            const img = images[index];
            if (type === 'backgrounds') {
              S.appearance.imageUrl = img.data;
              saveSettings(S);
              refreshGlobalStyles();
              toast('Background applied');
            } else if (type === 'avatars') {
              // TODO: Implement avatar functionality
              toast('Avatar feature coming soon');
            }
          } else if (action === 'delete') {
            if (confirm('Delete this image?')) {
              S.imageUploads[type].splice(index, 1);
              saveSettings(S);
              loadImages(type);
              toast('Image deleted');
            }
          }
        });
      });
    }

    // File upload
    uploadArea.addEventListener('click', () => fileInput.click());

    uploadArea.addEventListener('dragover', (e) => {
      e.preventDefault();
      uploadArea.classList.add('dragover');
    });

    uploadArea.addEventListener('dragleave', () => {
      uploadArea.classList.remove('dragover');
    });

    uploadArea.addEventListener('drop', (e) => {
      e.preventDefault();
      uploadArea.classList.remove('dragover');
      handleFiles(e.dataTransfer.files);
    });

    fileInput.addEventListener('change', (e) => {
      handleFiles(e.target.files);
    });

    uploadBtn.addEventListener('click', () => {
      fileInput.click();
    });

    function handleFiles(files) {
      const type = uploadType.value;

      Array.from(files).forEach(file => {
        if (!file.type.startsWith('image/')) {
          toast(`${file.name} is not an image`);
          return;
        }

        const reader = new FileReader();
        reader.onload = (e) => {
          const dataUrl = e.target.result;

          if (!S.imageUploads[type]) S.imageUploads[type] = [];
          S.imageUploads[type].push({
            name: file.name,
            data: dataUrl,
            size: file.size,
            type: file.type,
            uploaded: new Date().toISOString()
          });

          // Keep only last 10 images per type
          if (S.imageUploads[type].length > 10) {
            S.imageUploads[type] = S.imageUploads[type].slice(-10);
          }

          saveSettings(S);
          loadImages(type);
          toast(`Uploaded: ${file.name}`);
        };
        reader.readAsDataURL(file);
      });
    }

    closeBtn.addEventListener('click', () => {
      imageUploader.style.display = 'none';
    });

    // Load initial images
    loadImages('backgrounds');
  }

  // --- Theming and CSS ---
  function buildGlobalCSS() {
    const T = {
      discord: { bg: 'transparent', panelBg: 'rgba(32,34,37,.98)', text: 'var(--text-normal, #dbdee1)', accent: '#5865f2', link: '#00a8fc', highlight: '#4cc38a' },
      amoled: { bg: '#0b0d10', panelBg: '#111318', text: '#e7e7e7', accent: '#5b9fff', link: '#7aa8ff', highlight: '#2db879' },
      solarized: { bg: '#002b36', panelBg: '#073642', text: '#eee8d5', accent: '#268bd2', link: '#2aa198', highlight: '#b58900' },
      nord: { bg: '#2e3440', panelBg: '#3b4252', text: '#eceff4', accent: '#88c0d0', link: '#81a1c1', highlight: '#a3be8c' },
      custom: S.customTheme,
      dark: { bg: '#1a1d1f', panelBg: '#202225', text: '#dcddde', accent: '#5865f2', link: '#00b0f4', highlight: '#3ba55d' },
      light: { bg: '#f2f3f5', panelBg: '#ffffff', text: '#2e3338', accent: '#5865f2', link: '#00b0f4', highlight: '#3ba55d' },
      purple: { bg: '#1e1b2e', panelBg: '#2d2a3e', text: '#e0e0e0', accent: '#b794f6', link: '#d4a5ff', highlight: '#ab46bc' },
      green: { bg: '#1d281f', panelBg: '#2d3729', text: '#e0e0e0', accent: '#a9d34a', link: '#c5e88a', highlight: '#73c936' }
    };
    const t = T[S.theme] || T.amoled;
    const AP = S.appearance;

    return `
/* ${APP_NAME} ${VERSION} */
:root {
  --pc-bg: ${t.bg}; --pc-panel-bg: ${t.panelBg}; --pc-text: ${t.text};
  --pc-accent: ${t.accent}; --pc-link: ${t.link};
  --pc-highlight: ${S.highlight.color || t.highlight};
  --pc-bg-image: ${AP.imageUrl ? `url("${AP.imageUrl.replace(/"/g, '\\"')}")` : 'none'};
}

body.pluscord-on #app-mount::before {
  content: ""; position: fixed; inset: 0; background-color: var(--pc-bg);
  background-image: var(--pc-bg-image); background-size: cover; background-position: center;
  z-index: -1;
}
/* App backdrop opacity */
body.pluscord-on [class*="app-"] {
  background: color-mix(in srgb, var(--pc-panel-bg) ${clamp(AP.appOpacity, 0, 1) * 100}%, transparent);
}
/* Keep app content positioned above */
#app-mount > * { position: relative; }

/* Avatar shape (best-effort) */
 ${AP.avatarShape === 'rounded' ? 'img[class*="avatar-"], [class*="avatarDecoration-"] { border-radius: 8px !important; }' : ''}
 ${AP.avatarShape === 'square' ? 'img[class*="avatar-"], [class*="avatarDecoration-"] { border-radius: 0 !important; }' : ''}

/* Avatar frames */
 ${AP.avatarFrames ? `
img[class*="avatar-"] {
  box-shadow: 0 0 0 2px var(--pc-accent);
  position: relative;
}
img[class*="avatar-"]:after {
  content: '';
  position: absolute;
  inset: -4px;
  border: 1px solid rgba(255,255,255,0.2);
  border-radius: inherit;
  pointer-events: none;
}
` : ''}

/* Custom scrollbars */
 ${AP.customScrollbar ? `
::-webkit-scrollbar {
  width: 10px;
}
::-webkit-scrollbar-track {
  background: var(--pc-panel-bg);
}
::-webkit-scrollbar-thumb {
  background: var(--pc-accent);
  border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--pc-accent) 80%, black);
}
` : ''}

/* Compact mode */
body.pluscord-compact article[aria-roledescription="Message"] { margin: 2px 0 !important; padding: 2px 6px !important; }

/* UI cleanup */
body.pluscord-hide-gift [aria-label="Send a gift" i] { display: none !important; }
body.pluscord-hide-gif [aria-label*="GIF Picker" i] { display: none !important; }
body.pluscord-hide-sticker [aria-label*="Sticker Picker" i] { display: none !important; }
body.pluscord-hide-typing [class*="typing-"] { display: none !important; }

/* Motion + Boost */
body.pluscord-reduce-motion *, body.pluscord-boost * { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
body.pluscord-boost-reduce-transparency * { backdrop-filter: none !important; filter: none !important; }
body.pluscord-boost-simplify-shadows * { box-shadow: none !important; text-shadow: none !important; }
body.pluscord-boost-hide-avatars [class*="avatar"] { visibility: hidden !important; }
body.pluscord-boost-hide-members [aria-label="Members"] { display: none !important; }

/* Collapse heavy content */
body.pluscord-boost-collapse-embeds [class*="embed"] { max-height: 28px !important; overflow: hidden !important; }
body.pluscord-boost-collapse-attachments [class*="mediaAttachmentsContainer-"] { max-height: 48px !important; overflow: hidden !important; position: relative; }
body.pluscord-boost-collapse-attachments .pluscord-expand-btn {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  background: rgba(0,0,0,.7); color: #fff; padding: 4px 8px;
  border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer;
  user-select: none; border: 1px solid rgba(255,255,255,.2); z-index: 2;
}
body.pluscord-boost-collapse-attachments .pluscord-expanded { max-height: none !important; }

/* Enhanced keyword highlighting */
.pluscord-keyword {
  box-shadow: inset 0 0 0 2px var(--pc-highlight);
  background: color-mix(in srgb, var(--pc-highlight) 12%, transparent);
  border-radius: 4px;
  padding: 0 2px;
}

/* Code block copy button */
.pluscord-pre-wrap { position: relative; }
.pluscord-copy {
  position: absolute; top: 6px; right: 6px; padding: 3px 6px;
  font-size: 12px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.15);
  background: rgba(0,0,0,0.55); color: #fff; cursor: pointer;
  user-select: none; transition: all 0.2s ease;
}
.pluscord-pre-wrap:hover .pluscord-copy { opacity: 1; transform: scale(1.05); }
.pluscord-copied { background: rgba(45,184,121,.8) !important; border-color: rgba(45,184,121,1) !important; }

/* Composer counter */
form[class^="form-"] { position: relative; }
.pluscord-counter {
  position: absolute; right: 8px; bottom: 6px; padding: 2px 6px;
  border-radius: 6px; background: rgba(0,0,0,0.35); font-size: 12px;
  color: var(--pc-text); opacity: .75; pointer-events: none;
}
.pluscord-counter.warn { background: rgba(255,165,0,.25); color: #ffcb6b; }
.pluscord-counter.error { background: rgba(220,20,60,.25); color: #ff6b6b; }

/* Floating button (+) — higher z-index and self-healing */
#pluscord-fab {
  position: fixed; right: 16px; bottom: 16px; width: 40px; height: 40px;
  border-radius: 12px; background: var(--pc-panel-bg); color: var(--pc-text);
  border: 1px solid rgba(255,255,255,.1);
  box-shadow: 0 6px 18px rgba(0,0,0,.35);
  display: grid; place-items: center; font-weight: 700;
  font-family: 'gg sans', 'Noto Sans', sans-serif; cursor: pointer;
  z-index: 2147483647; user-select: none;
  transition: all 0.2s ease;
}
#pluscord-fab:hover { transform: scale(1.1); box-shadow: 0 8px 24px rgba(0,0,0,0.5); }

/* Settings panel host */
#pluscord-panel-host { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; z-index: 2147483646; }
#pluscord-panel-host.open { display: flex; }

/* Custom CSS injection */
 ${S.experimental.customCSS}
    `;
  }

  function refreshGlobalStyles() {
    if (!styleTag) {
      styleTag = document.createElement('style');
      styleTag.id = 'pluscord-style';
      document.head.appendChild(styleTag);
    }
    styleTag.textContent = buildGlobalCSS();
  }

  function applyBodyClasses() {
    const cl = document.body.classList;
    cl.toggle('pluscord-on', true);
    cl.toggle('pluscord-compact', !!S.compactMode);
    cl.toggle('pluscord-reduce-motion', !!S.disableAnimations || !!S.performanceBoost);
    cl.toggle('pluscord-hide-gift', !!S.uiCleanup.hideGift);
    cl.toggle('pluscord-hide-gif', !!S.uiCleanup.hideGIF);
    cl.toggle('pluscord-hide-sticker', !!S.uiCleanup.hideSticker);
    cl.toggle('pluscord-hide-typing', !!S.uiCleanup.hideTyping);

    const B = S.boost || {};
    cl.toggle('pluscord-boost', !!S.performanceBoost);
    cl.toggle('pluscord-boost-collapse-embeds', !!S.performanceBoost && !!B.collapseEmbeds);
    cl.toggle('pluscord-boost-collapse-attachments', !!S.performanceBoost && !!B.collapseAttachments);
    cl.toggle('pluscord-boost-hide-avatars', !!S.performanceBoost && !!B.hideAvatars);
    cl.toggle('pluscord-boost-hide-members', !!S.performanceBoost && !!B.hideMemberList);
    cl.toggle('pluscord-boost-reduce-transparency', !!S.performanceBoost && !!B.reduceTransparency);
    cl.toggle('pluscord-boost-simplify-shadows', !!S.performanceBoost && !!B.simplifyShadows);
  }

  // --- Settings Panel ---
  let panelHost, panelShadow, fabBtn;

  function ensureFab(force = false) {
    if (!S.floatingButton) {
      document.getElementById('pluscord-fab')?.remove();
      return null;
    }
    let btn = document.getElementById('pluscord-fab');
    if (!btn || force) {
      btn?.remove();
      btn = document.createElement('div');
      btn.id = 'pluscord-fab';
      btn.setAttribute('role', 'button');
      btn.title = 'Pluscord Quick Menu (Alt+M). Right-click for Settings.';
      btn.textContent = '+';
      document.body.appendChild(btn);
      btn.onclick = toggleQuickMenu;
      btn.oncontextmenu = (e) => { e.preventDefault(); togglePanel(); };
    }
    return btn;
  }

  function buildPanel() {
    ensureFab(true);

    panelHost = document.createElement('div');
    panelHost.id = 'pluscord-panel-host';
    document.body.appendChild(panelHost);

    panelShadow = panelHost.attachShadow({ mode: 'open' });
    const wrap = document.createElement('div');
    wrap.innerHTML = `
      <style>
        @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;600;700&display=swap');

        :host {
          --bg: var(--pc-panel-bg);
          --text: var(--pc-text);
          --accent: var(--pc-accent);
          --background-primary: #36393f;
          --background-secondary: #2f3136;
          --background-tertiary: #202225;
          --background-accent: #5865f2;
          --header-primary: #ffffff;
          --header-secondary: #b9bbbe;
          --text-normal: #dcddde;
          --text-muted: #72767d;
          --text-link: #00b0f4;
          --interactive-normal: #b9bbbe;
          --interactive-hover: #dcddde;
          --interactive-active: #ffffff;
          --interactive-muted: #4f545c;
          --brand-experiment: #5865f2;
          --brand-experiment-hover: #4752c4;
        }

        * {
          font-family: 'gg sans', 'Noto Sans', sans-serif;
        }

        .backdrop {
          position: absolute;
          inset: 0;
          background: rgba(0,0,0,.45);
        }

        .panel {
          position: relative;
          width: min(900px, 95vw);
          max-height: 90vh;
          display: grid;
          grid-template-rows: auto 1fr auto;
          background: var(--background-secondary);
          color: var(--text-normal);
          border: 1px solid var(--background-tertiary);
          border-radius: 8px;
          box-shadow: 0 8px 16px rgba(0,0,0,.24);
          overflow: hidden;
        }

        header {
          padding: 16px 20px;
          border-bottom: 1px solid var(--background-tertiary);
          display: flex;
          align-items: center;
          justify-content: space-between;
        }

        h1 {
          margin: 0;
          font-size: 20px;
          font-weight: 600;
          color: var(--header-primary);
        }

        h3 {
          margin: 16px 0 8px;
          font-size: 14px;
          font-weight: 600;
          color: var(--header-secondary);
          text-transform: uppercase;
          letter-spacing: 0.5px;
        }

        main {
          display: grid;
          grid-template-columns: 240px 1fr;
          min-height: 400px;
          overflow: hidden;
        }

        nav {
          background: var(--background-primary);
          padding: 8px;
          border-right: 1px solid var(--background-tertiary);
          display: flex;
          flex-direction: column;
          gap: 2px;
          overflow-y: auto;
        }

        nav button {
          background: transparent;
          color: var(--interactive-normal);
          border: none;
          border-radius: 4px;
          padding: 8px 12px;
          cursor: pointer;
          font-size: 14px;
          font-weight: 500;
          text-align: left;
          transition: background-color 0.17s ease, color 0.17s ease;
        }

        nav button:hover {
          background: var(--background-modifier-hover);
          color: var(--interactive-hover);
        }

        nav button.active {
          background: var(--brand-experiment);
          color: white;
        }

        section {
          padding: 20px;
          overflow-y: auto;
          display: none;
        }

        section.active {
          display: block;
        }

        .row {
          display: flex;
          gap: 16px;
          align-items: center;
          margin: 16px 0;
        }

        .row > label {
          width: 260px;
          color: var(--header-secondary);
          font-size: 14px;
          font-weight: 500;
          flex-shrink: 0;
        }

        input[type="text"], input[type="number"], select, textarea {
          background: var(--background-primary);
          color: var(--text-normal);
          border: 1px solid var(--background-tertiary);
          border-radius: 4px;
          padding: 8px 12px;
          font-size: 14px;
          transition: border-color 0.17s ease;
        }

        input[type="text"]:focus, input[type="number"]:focus, select:focus, textarea:focus {
          border-color: var(--brand-experiment);
          outline: none;
        }

        input[type="color"] {
          width: 48px;
          height: 32px;
          border: none;
          background: transparent;
          padding: 0;
          border-radius: 4px;
          cursor: pointer;
        }

        input[type="range"] {
          flex-grow: 1;
        }

        .switch {
          display: flex;
          align-items: center;
          gap: 8px;
          cursor: pointer;
        }

        .switch input[type="checkbox"] {
          width: 44px;
          height: 24px;
          appearance: none;
          background: var(--background-modifier-accent);
          border-radius: 12px;
          position: relative;
          cursor: pointer;
          transition: background-color 0.17s ease;
        }

        .switch input[type="checkbox"]:checked {
          background: var(--brand-experiment);
        }

        .switch input[type="checkbox"]::after {
          content: '';
          position: absolute;
          top: 2px;
          left: 2px;
          width: 20px;
          height: 20px;
          background: white;
          border-radius: 50%;
          transition: transform 0.17s ease;
        }

        .switch input[type="checkbox"]:checked::after {
          transform: translateX(20px);
        }

        .hint {
          color: var(--text-muted);
          font-size: 12px;
          margin-top: 4px;
        }

        .danger {
          color: #faa61a;
          font-weight: 600;
        }

        .primary {
          background: var(--brand-experiment);
          color: white;
          border: none;
          padding: 10px 16px;
          border-radius: 4px;
          cursor: pointer;
          font-size: 14px;
          font-weight: 500;
          transition: background-color 0.17s ease;
        }

        .primary:hover {
          background: var(--brand-experiment-hover);
        }

        .ghost {
          background: transparent;
          color: var(--interactive-normal);
          border: 1px solid var(--background-tertiary);
          padding: 10px 16px;
          border-radius: 4px;
          cursor: pointer;
          font-size: 14px;
          font-weight: 500;
          transition: all 0.17s ease;
        }

        .ghost:hover {
          background: var(--background-modifier-hover);
          color: var(--interactive-hover);
          border-color: var(--background-accent);
        }

        footer {
          padding: 16px 20px;
          border-top: 1px solid var(--background-tertiary);
          display: flex;
          justify-content: space-between;
          align-items: center;
        }

        .log-entry {
          background: var(--background-primary);
          border: 1px solid var(--background-tertiary);
          border-radius: 4px;
          margin-bottom: 8px;
          padding: 12px;
          font-size: 14px;
        }

        .log-header {
          display: flex;
          justify-content: space-between;
          color: var(--header-secondary);
          font-size: 12px;
          margin-bottom: 8px;
        }

        .log-content p {
          margin: 4px 0;
          white-space: pre-wrap;
          word-break: break-word;
        }

        .log-old {
          color: var(--text-muted);
        }

        .log-delete {
          border-left: 3px solid #ed4245;
        }

        .log-edit {
          border-left: 3px solid #faa61a;
        }

        .color-row {
          display: grid;
          grid-template-columns: auto 1fr auto;
          gap: 12px;
          align-items: center;
        }

        .color-row label {
          width: auto;
        }

        .color-preview {
          width: 32px;
          height: 32px;
          border-radius: 4px;
          border: 1px solid var(--background-tertiary);
        }

        .template-list, .folder-list, .channel-list {
          max-height: 200px;
          overflow-y: auto;
          border: 1px solid var(--background-tertiary);
          border-radius: 4px;
          margin: 8px 0;
        }

        .template-item, .folder-item, .channel-item {
          padding: 12px;
          border-bottom: 1px solid var(--background-tertiary);
          display: flex;
          justify-content: space-between;
          align-items: center;
        }

        .template-item:last-child, .folder-item:last-child, .channel-item:last-child {
          border-bottom: none;
        }

        .template-name, .folder-name, .channel-name {
          font-weight: 600;
        }

        .template-content {
          font-size: 12px;
          color: var(--text-muted);
          margin-top: 4px;
        }

        .template-actions, .folder-actions, .channel-actions {
          display: flex;
          gap: 8px;
        }

        .template-actions button, .folder-actions button, .channel-actions button {
          font-size: 12px;
          padding: 4px 8px;
        }

        .divider {
          height: 1px;
          background: var(--background-tertiary);
          margin: 16px 0;
        }
      </style>
      <div class="backdrop"></div>
      <div class="panel">
        <header>
          <h1>Pluscord Settings</h1>
          <div style="color: var(--text-muted); font-size: 14px;">v${VERSION}</div>
        </header>
        <main>
            <nav>
                <button data-tab="appearance" class="active">Appearance</button>
                <button data-tab="messages">Messages</button>
                <button data-tab="cleanup">UI Cleanup</button>
                <button data-tab="performance">Performance</button>
                <button data-tab="quickactions">Quick Actions</button>
                <button data-tab="logger">Message Logger</button>
                <button data-tab="communication">Communication</button>
                <button data-tab="server">Server Tools</button>
                <button data-tab="css">CSS Editor</button>
                <button data-tab="images">Images</button>
                <button data-tab="experimental">Experimental</button>
                <button data-tab="data">Data</button>
            </nav>
            <section id="tab-appearance" class="active">
                <h3>Theme</h3>
                <div class="row">
                  <label>Theme</label>
                  <select id="pc-theme">
                    <option value="discord">Discord</option>
                    <option value="amoled">AMOLED</option>
                    <option value="solarized">Solarized</option>
                    <option value="nord">Nord</option>
                    <option value="dark">Dark</option>
                    <option value="light">Light</option>
                    <option value="purple">Purple</option>
                    <option value="green">Green</option>
                    <option value="custom">Custom</option>
                  </select>
                </div>
                <div id="pc-custom-colors" style="display:none;">
                  <div class="color-row">
                    <label>Background</label>
                    <input type="color" id="pc-bg">
                    <div class="color-preview" id="pc-bg-preview"></div>
                  </div>
                  <div class="color-row">
                    <label>Panel</label>
                    <input type="color" id="pc-panel">
                    <div class="color-preview" id="pc-panel-preview"></div>
                  </div>
                </div>
                <h3>Background</h3>
                <div class="row">
                  <label>Background Image</label>
                  <div style="display: flex; gap: 8px; flex: 1;">
                    <input type="text" id="pc-bg-image" placeholder="Enter URL or upload image">
                    <button class="ghost" id="pc-upload-bg">Upload</button>
                  </div>
                </div>
                <div class="row">
                  <label>App Opacity</label>
                  <div style="display: flex; align-items: center; gap: 12px; flex: 1;">
                    <input type="range" id="pc-opacity" min="0.1" max="1" step="0.05">
                    <span id="pc-opacity-val" style="min-width: 40px;">100%</span>
                  </div>
                </div>
                <h3>Layout</h3>
                <div class="row">
                  <label>Avatar Shape</label>
                  <select id="pc-avatar-shape">
                    <option value="default">Default</option>
                    <option value="rounded">Rounded</option>
                    <option value="square">Square</option>
                  </select>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-avatar-frames">
                  <label for="pc-avatar-frames">Avatar frames</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-custom-scrollbar">
                  <label for="pc-custom-scrollbar">Custom scrollbars</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-compact">
                  <label for="pc-compact">Compact mode</label>
                </div>
            </section>
            <section id="tab-messages">
                <h3>Display</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-abs-ts">
                  <label for="pc-abs-ts">Absolute timestamps</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-spoilers">
                  <label for="pc-spoilers">Auto-reveal spoilers</label>
                </div>
                <h3>Highlighting</h3>
                <div class="row">
                  <label>Highlight keywords</label>
                  <input type="text" id="pc-keywords" placeholder="word, another word">
                </div>
                <div class="row">
                  <label>Highlight my name</label>
                  <input type="text" id="pc-myname" placeholder="Your display name">
                </div>
                <div class="color-row">
                  <label>Highlight color</label>
                  <input type="color" id="pc-hl-color">
                  <div class="color-preview" id="pc-hl-preview"></div>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-regex">
                  <label for="pc-regex">Use regex for keywords</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-case-sensitive">
                  <label for="pc-case-sensitive">Case sensitive keywords</label>
                </div>
                <h3>Tools</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-copy">
                  <label for="pc-copy">Show "Copy" on code blocks</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-counter">
                  <label for="pc-counter">Show character counter</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-tts">
                  <label for="pc-tts">Text-to-speech button</label>
                </div>
            </section>
            <section id="tab-cleanup">
                <h3>Chat Input</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-hide-gift">
                  <label for="pc-hide-gift">Hide "Send a gift" button</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-hide-gif">
                  <label for="pc-hide-gif">Hide GIF button</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-hide-sticker">
                  <label for="pc-hide-sticker">Hide Sticker button</label>
                </div>
                <h3>General UI</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-hide-typing">
                  <label for="pc-hide-typing">Hide typing indicator</label>
                </div>
            </section>
            <section id="tab-performance">
                <h3>General</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-reduce">
                  <label for="pc-reduce">Reduce animations</label>
                </div>
                <h3>Boost Mode</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-boost">
                  <label for="pc-boost">Enable Boost Mode (reduce lag)</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-boost-gif">
                  <label for="pc-boost-gif">Pause GIFs/videos until hover</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-boost-embed">
                  <label for="pc-boost-embed">Collapse link embeds</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-boost-attach">
                  <label for="pc-boost-attach">Collapse attachments (click to expand)</label>
                </div>
            </section>
            <section id="tab-quickactions">
                <h3>Quick Actions</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-auto-scroll">
                  <label for="pc-auto-scroll">Auto-scroll to bottom</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-jump-bottom">
                  <label for="pc-jump-bottom">Jump to bottom button</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-mark-unread">
                  <label for="pc-mark-unread">Mark as unread button</label>
                </div>
            </section>
            <section id="tab-logger">
                <h3>Message Logger</h3>
                <p class="hint"><span class="danger">Disclaimer:</span> Use at your own risk. Storing message content may be against Discord's ToS. This log is <strong>cleared when you refresh or close the tab.</strong></p>
                <div class="row switch">
                  <input type="checkbox" id="pc-log-enabled">
                  <label for="pc-log-enabled">Enable Message Logger</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-log-deletes">
                  <label for="pc-log-deletes">Log deleted messages</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-log-edits">
                  <label for="pc-log-edits">Log edited messages</label>
                </div>
                <div class="row">
                  <label>Max log entries</label>
                  <input type="number" id="pc-log-max" min="10" max="1000" value="${S.messageLogger.maxLogs}">
                </div>
                <div class="row">
                  <button class="ghost" id="pc-log-clear">Clear Log</button>
                </div>
                <div class="divider"></div>
                <div id="pc-log-container" style="max-height: 250px; overflow-y: auto;"></div>
            </section>
            <section id="tab-communication">
                <h3>Quick Replies</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-qr-enabled">
                  <label for="pc-qr-enabled">Enable quick replies</label>
                </div>
                <div class="template-list" id="pc-qr-list"></div>
                <div class="row">
                  <button class="ghost" id="pc-qr-add">Add Template</button>
                </div>
                <h3>Formatting Shortcuts</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-fs-enabled">
                  <label for="pc-fs-enabled">Enable formatting shortcuts</label>
                </div>
                <div class="row">
                  <label>Bold</label>
                  <input type="text" id="pc-fs-bold" value="${S.formattingShortcuts.bold}">
                </div>
                <div class="row">
                  <label>Italic</label>
                  <input type="text" id="pc-fs-italic" value="${S.formattingShortcuts.italic}">
                </div>
                <div class="row">
                  <label>Strikethrough</label>
                  <input type="text" id="pc-fs-strikethrough" value="${S.formattingShortcuts.strikethrough}">
                </div>
                <div class="row">
                  <label>Code</label>
                  <input type="text" id="pc-fs-code" value="${S.formattingShortcuts.code}">
                </div>
                <h3>Quick Reactions</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-reactions-enabled">
                  <label for="pc-reactions-enabled">Enable quick reactions</label>
                </div>
                <div class="row">
                  <label>Reaction emojis</label>
                  <input type="text" id="pc-reactions-emojis" value="${S.quickReactions.emojis.join(' ')}">
                </div>
                <h3>Translation</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-translation-enabled">
                  <label for="pc-translation-enabled">Enable translation</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-translation-auto">
                  <label for="pc-translation-auto">Auto-detect language</label>
                </div>
                <div class="row">
                  <label>Default language</label>
                  <select id="pc-translation-lang">
                    <option value="en">English</option>
                    <option value="es">Spanish</option>
                    <option value="fr">French</option>
                    <option value="de">German</option>
                    <option value="ja">Japanese</option>
                    <option value="zh">Chinese</option>
                    <option value="ru">Russian</option>
                  </select>
                </div>
                <h3>Text-to-Speech</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-tts-enabled">
                  <label for="pc-tts-enabled">Enable text-to-speech</label>
                </div>
                <div class="row">
                  <label>Voice</label>
                  <select id="pc-tts-voice"></select>
                </div>
                <div class="row">
                  <label>Rate</label>
                  <div style="display: flex; align-items: center; gap: 12px; flex: 1;">
                    <input type="range" id="pc-tts-rate" min="0.5" max="2" step="0.1" value="${S.textToSpeech.rate}">
                    <span id="pc-tts-rate-val" style="min-width: 40px;">${S.textToSpeech.rate}x</span>
                  </div>
                </div>
                <div class="row">
                  <label>Volume</label>
                  <div style="display: flex; align-items: center; gap: 12px; flex: 1;">
                    <input type="range" id="pc-tts-volume" min="0" max="1" step="0.1" value="${S.textToSpeech.volume}">
                    <span id="pc-tts-volume-val" style="min-width: 40px;">${Math.round(S.textToSpeech.volume * 100)}%</span>
                  </div>
                </div>
            </section>
            <section id="tab-server">
                <h3>Server Folders</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-folders-enabled">
                  <label for="pc-folders-enabled">Enable server folders</label>
                </div>
                <div class="folder-list" id="pc-folders-list"></div>
                <div class="row">
                  <button class="ghost" id="pc-folders-add">Add Folder</button>
                </div>
                <h3>Pinned Channels</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-pinned-enabled">
                  <label for="pc-pinned-enabled">Enable pinned channels</label>
                </div>
                <div class="channel-list" id="pc-pinned-list"></div>
                <div class="row">
                  <button class="ghost" id="pc-pinned-add">Pin Current Channel</button>
                </div>
                <h3>User Notes</h3>
                <div class="row switch">
                  <input type="checkbox" id="pc-notes-enabled">
                  <label for="pc-notes-enabled">Enable user notes</label>
                </div>
            </section>
            <section id="tab-css">
                <h3>CSS Editor</h3>
                <p class="hint">Edit custom CSS with syntax highlighting and live preview.</p>
                <div class="row">
                  <button class="primary" id="pc-open-css-editor">Open CSS Editor</button>
                </div>
                <h3>Editor Settings</h3>
                <div class="row">
                  <label>Theme</label>
                  <select id="pc-css-theme">
                    <option value="monokai">Monokai</option>
                    <option value="github">GitHub</option>
                    <option value="dracula">Dracula</option>
                    <option value="discord">Discord</option>
                  </select>
                </div>
                <div class="row">
                  <label>Font Size</label>
                  <select id="pc-css-font-size">
                    <option value="12">12px</option>
                    <option value="14">14px</option>
                    <option value="16">16px</option>
                    <option value="18">18px</option>
                  </select>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-css-word-wrap">
                  <label for="pc-css-word-wrap">Word wrap</label>
                </div>
                <div class="row switch">
                  <input type="checkbox" id="pc-css-live-preview">
                  <label for="pc-css-live-preview">Live preview</label>
                </div>
            </section>
            <section id="tab-images">
                <h3>Image Manager</h3>
                <p class="hint">Upload and manage images for backgrounds and avatars.</p>
                <div class="row">
                  <button class="primary" id="pc-open-image-uploader">Open Image Manager</button>
                </div>
                <h3>Storage</h3>
                <div class="row">
                  <label>Background images</label>
                  <span>${(S.imageUploads.backgrounds || []).length}/10</span>
                </div>
                <div class="row">
                  <label>Avatar images</label>
                  <span>${(S.imageUploads.avatars || []).length}/10</span>
                </div>
            </section>
            <section id="tab-experimental">
                <h3>Experimental Features</h3>
                <p class="hint">These features are unstable and may break with Discord updates.</p>
                <div class="row switch">
                  <input type="checkbox" id="pc-dev-mode">
                  <label for="pc-dev-mode">Developer mode (shows debug info)</label>
                </div>
                <h3>Custom CSS</h3>
                <div class="row" style="align-items:flex-start;">
                  <label>Custom CSS</label>
                  <textarea id="pc-custom-css" placeholder="Enter custom CSS here..." style="min-height: 120px;"></textarea>
                </div>
                <div class="row">
                  <button class="ghost" id="pc-preview-css">Preview</button>
                  <button class="primary" id="pc-apply-css">Apply</button>
                </div>
            </section>
            <section id="tab-data">
                <h3>Manage Settings</h3>
                <div class="row" style="align-items:flex-start;">
                  <label>Export</label>
                  <textarea id="pc-export" readonly style="min-height: 120px;"></textarea>
                </div>
                <div class="row" style="align-items:flex-start;">
                  <label>Import</label>
                  <textarea id="pc-import" placeholder='Paste settings here' style="min-height: 120px;"></textarea>
                </div>
                <div class="row">
                  <button class="ghost" id="pc-copy-export">Copy</button>
                  <button class="primary" id="pc-do-import">Import</button>
                </div>
                <div class="divider"></div>
                <button class="ghost danger" id="pc-reset">Reset All Settings</button>
            </section>
        </main>
        <footer>
            <div class="hint">Local-only. May break with Discord updates.</div>
            <div style="display:flex; gap:8px;">
              <button class="ghost" id="pc-close">Close</button>
              <button class="primary" id="pc-apply">Apply & Close</button>
            </div>
        </footer>
      </div>
    `;
    panelShadow.appendChild(wrap);

    const I = Object.fromEntries(
      Array.from(panelShadow.querySelectorAll('[id^="pc-"]'))
      .map(el => [el.id.replace('pc-', '').replace(/-(\w)/g, (_, c) => c.toUpperCase()), el])
    );

    const close = () => panelHost.classList.remove('open');
    panelShadow.querySelector('.backdrop').onclick = close;
    I.close.onclick = close;

    panelShadow.querySelectorAll('nav button').forEach(btn => {
      btn.onclick = () => {
        panelShadow.querySelectorAll('nav button').forEach(b => b.classList.remove('active'));
        btn.classList.add('active');
        panelShadow.querySelectorAll('section').forEach(s => s.classList.toggle('active', s.id === `tab-${btn.dataset.tab}`));
      };
    });

    // Color preview updates
    ['bg', 'panel', 'hl'].forEach(colorId => {
      const input = panelShadow.getElementById(`pc-${colorId}`);
      const preview = panelShadow.getElementById(`pc-${colorId}-preview`);
      if (input && preview) {
        input.addEventListener('input', () => {
          preview.style.backgroundColor = input.value;
        });
        preview.style.backgroundColor = input.value;
      }
    });

    I.opacity.oninput = () => {
      I.opacityVal.textContent = `${Math.round(I.opacity.value * 100)}%`;
    };

    I.logMax.oninput = () => {
      I.logMax.value = clamp(parseInt(I.logMax.value || '200'), 10, 1000);
    };

    I.logClear.onclick = () => {
      logCache.length = 0;
      updateLoggerPanel();
    };

    // Custom CSS preview
    I.previewCss.onclick = () => {
      const tempSettings = structuredClone(S);
      tempSettings.experimental.customCSS = I.customCss.value;
      saveSettings(tempSettings);
      refreshGlobalStyles();
      toast('Custom CSS previewed. Click Apply to save.');
    };

    I.applyCss.onclick = () => {
      S.experimental.customCSS = I.customCss.value;
      saveSettings(S);
      refreshGlobalStyles();
      toast('Custom CSS applied');
    };

    // Background upload
    I.uploadBg.onclick = () => {
      handleImageUpload('backgrounds', (dataUrl) => {
        I.bgImage.value = dataUrl;
        S.appearance.imageUrl = dataUrl;
        saveSettings(S);
        refreshGlobalStyles();
      });
    };

    // CSS Editor
    I.openCssEditor.onclick = () => {
      setupCSSEditor();
      cssEditor.style.display = 'flex';
    };

    // Image Uploader
    I.openImageUploader.onclick = () => {
      setupImageUploader();
      imageUploader.style.display = 'flex';
    };

    // TTS rate and volume
    I.ttsRate.oninput = () => {
      I.ttsRateVal.textContent = `${I.ttsRate.value}x`;
    };

    I.ttsVolume.oninput = () => {
      I.ttsVolumeVal.textContent = `${Math.round(I.ttsVolume.value * 100)}%`;
    };

    // Quick replies
    I.qrEnabled.onchange = () => {
      S.quickReplies.enabled = I.qrEnabled.checked;
      saveSettings(S);
      updateQuickReplyMenu();
    };

    I.qrAdd.onclick = () => {
      const name = prompt('Template name:');
      if (!name) return;
      const content = prompt('Template content:');
      if (!content) return;

      S.quickReplies.templates.push({ name, content });
      saveSettings(S);
      updateQuickReplyTemplates();
      updateQuickReplyMenu();
    };

    function updateQuickReplyTemplates() {
      const container = panelShadow.getElementById('pc-qr-list');
      if (!container) return;

      container.innerHTML = S.quickReplies.templates.map((template, index) => `
        <div class="template-item">
          <div>
            <div class="template-name">${template.name}</div>
            <div class="template-content">${template.content}</div>
          </div>
          <div class="template-actions">
            <button class="ghost" data-index="${index}" data-action="edit">Edit</button>
            <button class="ghost danger" data-index="${index}" data-action="delete">Delete</button>
          </div>
        </div>
      `).join('');

      container.querySelectorAll('button').forEach(btn => {
        btn.onclick = () => {
          const index = parseInt(btn.dataset.index);
          const action = btn.dataset.action;

          if (action === 'edit') {
            const name = prompt('Template name:', S.quickReplies.templates[index].name);
            if (!name) return;
            const content = prompt('Template content:', S.quickReplies.templates[index].content);
            if (!content) return;

            S.quickReplies.templates[index] = { name, content };
            saveSettings(S);
            updateQuickReplyTemplates();
            updateQuickReplyMenu();
          } else if (action === 'delete') {
            if (!confirm('Delete this template?')) return;
            S.quickReplies.templates.splice(index, 1);
            saveSettings(S);
            updateQuickReplyTemplates();
            updateQuickReplyMenu();
          }
        };
      });
    }

    // Server folders
    I.foldersEnabled.onchange = () => {
      S.serverFolders.enabled = I.foldersEnabled.checked;
      saveSettings(S);
      updateServerFolders();
    };

    I.foldersAdd.onclick = () => {
      const name = prompt('Folder name:');
      if (!name) return;

      S.serverFolders.folders.push({ name, servers: [], collapsed: false });
      saveSettings(S);
      updateServerFoldersList();
      updateServerFolders();
    };

    function updateServerFoldersList() {
      const container = panelShadow.getElementById('pc-folders-list');
      if (!container) return;

      container.innerHTML = S.serverFolders.folders.map((folder, index) => `
        <div class="folder-item">
          <div class="folder-name">${folder.name}</div>
          <div class="folder-actions">
            <button class="ghost" data-index="${index}" data-action="edit">Edit</button>
            <button class="ghost danger" data-index="${index}" data-action="delete">Delete</button>
          </div>
        </div>
      `).join('');

      container.querySelectorAll('button').forEach(btn => {
        btn.onclick = () => {
          const index = parseInt(btn.dataset.index);
          const action = btn.dataset.action;

          if (action === 'edit') {
            const name = prompt('Folder name:', S.serverFolders.folders[index].name);
            if (!name) return;

            S.serverFolders.folders[index].name = name;
            saveSettings(S);
            updateServerFoldersList();
            updateServerFolders();
          } else if (action === 'delete') {
            if (!confirm('Delete this folder?')) return;
            S.serverFolders.folders.splice(index, 1);
            saveSettings(S);
            updateServerFoldersList();
            updateServerFolders();
          }
        };
      });
    }

    // Pinned channels
    I.pinnedEnabled.onchange = () => {
      S.pinnedChannels.enabled = I.pinnedEnabled.checked;
      saveSettings(S);
      updatePinnedChannels();
    };

    I.pinnedAdd.onclick = () => {
      const channelName = document.querySelector('[class*="title-"] [class*="name-"]')?.innerText;
      if (!channelName) {
        toast('Could not identify current channel');
        return;
      }

      const channelId = window.location.pathname.split('/').pop();
      if (!channelId) {
        toast('Could not identify current channel');
        return;
      }

      if (S.pinnedChannels.channels.some(c => c.id === channelId)) {
        toast('Channel already pinned');
        return;
      }

      S.pinnedChannels.channels.push({ id: channelId, name: channelName });
      saveSettings(S);
      updatePinnedChannelsList();
      updatePinnedChannels();
      toast(`Pinned #${channelName}`);
    };

    function updatePinnedChannelsList() {
      const container = panelShadow.getElementById('pc-pinned-list');
      if (!container) return;

      container.innerHTML = S.pinnedChannels.channels.map((channel, index) => `
        <div class="channel-item">
          <div class="channel-name">#${channel.name}</div>
          <div class="channel-actions">
            <button class="ghost danger" data-index="${index}" data-action="unpin">Unpin</button>
          </div>
        </div>
      `).join('');

      container.querySelectorAll('button').forEach(btn => {
        btn.onclick = () => {
          const index = parseInt(btn.dataset.index);
          const action = btn.dataset.action;

          if (action === 'unpin') {
            if (!confirm('Unpin this channel?')) return;
            S.pinnedChannels.channels.splice(index, 1);
            saveSettings(S);
            updatePinnedChannelsList();
            updatePinnedChannels();
          }
        };
      });
    }

    // TTS voices
    function updateTTSVoices() {
      const voiceSelect = panelShadow.getElementById('pc-tts-voice');
      if (!voiceSelect) return;

      voiceSelect.innerHTML = '';

      if ('speechSynthesis' in window) {
        const voices = speechSynthesis.getVoices();
        voices.forEach(voice => {
          const option = document.createElement('option');
          option.value = voice.name;
          option.textContent = `${voice.name} (${voice.lang})`;
          if (voice.name === S.textToSpeech.voice) option.selected = true;
          voiceSelect.appendChild(option);
        });
      }
    }

    if ('speechSynthesis' in window) {
      if (speechSynthesis.onvoiceschanged !== undefined) {
        speechSynthesis.onvoiceschanged = updateTTSVoices;
      }
      updateTTSVoices();
    }

    function syncPanelFromSettings() {
      I.theme.value = S.theme;
      I.bgImage.value = S.appearance.imageUrl;
      I.opacity.value = S.appearance.appOpacity;
      I.opacityVal.textContent = `${Math.round(S.appearance.appOpacity * 100)}%`;
      I.avatarShape.value = S.appearance.avatarShape;
      I.avatarFrames.checked = !!S.appearance.avatarFrames;
      I.customScrollbar.checked = !!S.appearance.customScrollbar;
      panelShadow.getElementById('pc-custom-colors').style.display = (S.theme === 'custom') ? 'block' : 'none';
      if (S.theme === 'custom') {
        const bg = S.customTheme.bg || '#0b0d10';
        const panel = S.customTheme.panelBg || '#111318';
        panelShadow.getElementById('pc-bg').value = bg;
        panelShadow.getElementById('pc-panel').value = panel;
        panelShadow.getElementById('pc-bg-preview').style.backgroundColor = bg;
        panelShadow.getElementById('pc-panel-preview').style.backgroundColor = panel;
      }
      I.compact.checked = !!S.compactMode;
      I.absTs.checked = !!S.absoluteTimestamps;
      I.spoilers.checked = !!S.revealSpoilers;
      I.keywords.value = (S.highlight.keywords || []).join(', ');
      I.myname.value = S.highlight.myName || '';
      I.hlColor.value = S.highlight.color || '#2db879';
      panelShadow.getElementById('pc-hl-preview').style.backgroundColor = I.hlColor.value;
      I.regex.checked = !!S.highlight.useRegex;
      I.caseSensitive.checked = !!S.highlight.caseSensitive;
      I.copy.checked = !!S.codeCopyButton;
      I.counter.checked = !!S.msgCharCounter;
      I.tts.checked = !!S.textToSpeech.enabled;
      I.hideGift.checked = !!S.uiCleanup.hideGift;
      I.hideGif.checked = !!S.uiCleanup.hideGIF;
      I.hideSticker.checked = !!S.uiCleanup.hideSticker;
      I.hideTyping.checked = !!S.uiCleanup.hideTyping;
      I.reduce.checked = !!S.disableAnimations;
      I.boost.checked = !!S.performanceBoost;
      I.boostGif.checked = !!S.boost.pauseGIFs;
      I.boostEmbed.checked = !!S.boost.collapseEmbeds;
      I.boostAttach.checked = !!S.boost.collapseAttachments;
      I.autoScroll.checked = !!S.quickActions.autoScroll;
      I.jumpBottom.checked = !!S.quickActions.jumpToBottom;
      I.markUnread.checked = !!S.quickActions.markUnread;
      I.devMode.checked = !!S.experimental.devMode;
      I.customCss.value = S.experimental.customCSS || '';
      I.export.value = JSON.stringify(S, null, 2);

      // Communication settings
      I.qrEnabled.checked = !!S.quickReplies.enabled;
      updateQuickReplyTemplates();
      I.fsEnabled.checked = !!S.formattingShortcuts.enabled;
      I.fsBold.value = S.formattingShortcuts.bold;
      I.fsItalic.value = S.formattingShortcuts.italic;
      I.fsStrikethrough.value = S.formattingShortcuts.strikethrough;
      I.fsCode.value = S.formattingShortcuts.code;
      I.reactionsEnabled.checked = !!S.quickReactions.enabled;
      I.reactionsEmojis.value = S.quickReactions.emojis.join(' ');
      I.translationEnabled.checked = !!S.translation.enabled;
      I.translationAuto.checked = !!S.translation.autoDetect;
      I.translationLang.value = S.translation.defaultLang;
      I.ttsEnabled.checked = !!S.textToSpeech.enabled;
      I.ttsRate.value = S.textToSpeech.rate;
      I.ttsVolume.value = S.textToSpeech.volume;

      // Server settings
      I.foldersEnabled.checked = !!S.serverFolders.enabled;
      updateServerFoldersList();
      I.pinnedEnabled.checked = !!S.pinnedChannels.enabled;
      updatePinnedChannelsList();
      I.notesEnabled.checked = !!S.userNotes.enabled;

      // CSS Editor settings
      I.cssTheme.value = S.cssEditor.theme;
      I.cssFontSize.value = S.cssEditor.fontSize;
      I.cssWordWrap.checked = !!S.cssEditor.wordWrap;
      I.cssLivePreview.checked = !!S.cssEditor.livePreview;
    }

    function applyPanelToSettings() {
      S.theme = I.theme.value;
      S.appearance.imageUrl = I.bgImage.value.trim();
      S.appearance.appOpacity = clamp(parseFloat(I.opacity.value || '1'), 0.1, 1);
      S.appearance.avatarShape = I.avatarShape.value;
      S.appearance.avatarFrames = I.avatarFrames.checked;
      S.appearance.customScrollbar = I.customScrollbar.checked;
      if (S.theme === 'custom') {
        const bg = panelShadow.getElementById('pc-bg').value || '#0b0d10';
        const panel = panelShadow.getElementById('pc-panel').value || '#111318';
        S.customTheme = { ...S.customTheme, bg, panelBg: panel };
      }
      S.compactMode = I.compact.checked;
      S.absoluteTimestamps = I.absTs.checked;
      S.revealSpoilers = I.spoilers.checked;
      S.highlight.keywords = I.keywords.value.split(',').map(s => s.trim()).filter(Boolean);
      S.highlight.myName = I.myname.value.trim();
      S.highlight.color = I.hlColor.value || '#2db879';
      S.highlight.useRegex = I.regex.checked;
      S.highlight.caseSensitive = I.caseSensitive.checked;
      S.codeCopyButton = I.copy.checked;
      S.msgCharCounter = I.counter.checked;
      S.textToSpeech.enabled = I.tts.checked;
      S.uiCleanup = { hideGift: I.hideGift.checked, hideGIF: I.hideGif.checked, hideSticker: I.hideSticker.checked, hideTyping: I.hideTyping.checked };
      S.disableAnimations = I.reduce.checked;
      S.performanceBoost = I.boost.checked;
      S.boost = { ...S.boost, pauseGIFs: I.boostGif.checked, collapseEmbeds: I.boostEmbed.checked, collapseAttachments: I.boostAttach.checked };
      S.quickActions = { autoScroll: I.autoScroll.checked, jumpToBottom: I.jumpBottom.checked, markUnread: I.markUnread.checked };
      S.messageLogger = { enabled: I.logEnabled.checked, logDeletes: I.logDeletes.checked, logEdits: I.logEdits.checked, maxLogs: parseInt(I.logMax.value || '200') };
      S.experimental.devMode = I.devMode.checked;
      S.experimental.customCSS = I.customCss.value;

      // Communication settings
      S.quickReplies.enabled = I.qrEnabled.checked;
      S.formattingShortcuts.enabled = I.fsEnabled.checked;
      S.formattingShortcuts.bold = I.fsBold.value;
      S.formattingShortcuts.italic = I.fsItalic.value;
      S.formattingShortcuts.strikethrough = I.fsStrikethrough.value;
      S.formattingShortcuts.code = I.fsCode.value;
      S.quickReactions.enabled = I.reactionsEnabled.checked;
      S.quickReactions.emojis = I.reactionsEmojis.value.trim().split(/\s+/).filter(Boolean);
      S.translation.enabled = I.translationEnabled.checked;
      S.translation.autoDetect = I.translationAuto.checked;
      S.translation.defaultLang = I.translationLang.value;
      S.textToSpeech.enabled = I.ttsEnabled.checked;
      S.textToSpeech.voice = I.ttsVoice.value;
      S.textToSpeech.rate = parseFloat(I.ttsRate.value);
      S.textToSpeech.volume = parseFloat(I.ttsVolume.value);

      // Server settings
      S.serverFolders.enabled = I.foldersEnabled.checked;
      S.pinnedChannels.enabled = I.pinnedEnabled.checked;
      S.userNotes.enabled = I.notesEnabled.checked;

      // CSS Editor settings
      S.cssEditor.theme = I.cssTheme.value;
      S.cssEditor.fontSize = parseInt(I.cssFontSize.value);
      S.cssEditor.wordWrap = I.cssWordWrap.checked;
      S.cssEditor.livePreview = I.cssLivePreview.checked;

      saveSettings(S);
      fullRefresh();
    }

    I.apply.onclick = () => { applyPanelToSettings(); close(); };
    I.copyExport.onclick = async () => {
      try {
        await navigator.clipboard.writeText(I.export.value);
        toast('Settings copied');
      } catch {
        alert('Clipboard permission denied.');
      }
    };
    I.doImport.onclick = () => {
      try {
        const data = JSON.parse(I.import.value);
        S = deepMerge(structuredClone(DEFAULTS), data);
        saveSettings(S);
        syncPanelFromSettings();
        fullRefresh();
        toast('Imported settings');
      } catch {
        alert('Invalid JSON.');
      }
    };
    I.reset.onclick = () => {
      if (!confirm('Reset all Pluscord settings to defaults?')) return;
      S = structuredClone(DEFAULTS);
      saveSettings(S);
      syncPanelFromSettings();
      fullRefresh();
      toast('Settings reset');
    };
    I.theme.onchange = () => {
      panelShadow.getElementById('pc-custom-colors').style.display = (I.theme.value === 'custom') ? 'block' : 'none';
    };

    syncPanelFromSettings();
  }

  function togglePanel() {
    if (!panelHost) buildPanel();
    panelHost.classList.toggle('open');
    if (panelHost.classList.contains('open')) {
      const exp = panelShadow.getElementById('pc-export');
      if (exp) exp.value = JSON.stringify(S, null, 2);
      updateLoggerPanel();
      setQuickMenuOpen(false);
    }
  }

  // --- Quick Menu ---
  let qmHost, qmShadow, qmOpen = false;
  function buildQuickMenu() {
    qmHost = document.createElement('div');
    qmHost.id = 'pluscord-qm-host';
    qmHost.style.cssText = 'position:fixed; inset:0; display:none; z-index:2147483646;';
    document.body.appendChild(qmHost);

    qmShadow = qmHost.attachShadow({ mode: 'open' });
    const wrap = document.createElement('div');
    wrap.innerHTML = `
      <style>
        @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;600;700&display=swap');

        :host {
          --bg: var(--pc-panel-bg);
          --text: var(--pc-text);
          --accent: var(--pc-accent);
          --background-primary: #36393f;
          --background-secondary: #2f3136;
          --background-tertiary: #202225;
          --background-accent: #5865f2;
          --header-primary: #ffffff;
          --header-secondary: #b9bbbe;
          --text-normal: #dcddde;
          --text-muted: #72767d;
          --text-link: #00b0f4;
          --interactive-normal: #b9bbbe;
          --interactive-hover: #dcddde;
          --interactive-active: #ffffff;
          --interactive-muted: #4f545c;
          --brand-experiment: #5865f2;
          --brand-experiment-hover: #4752c4;
        }

        * {
          font-family: 'gg sans', 'Noto Sans', sans-serif;
        }

        .backdrop {
          position: absolute;
          inset: 0;
          background: transparent;
        }

        .menu {
          position: absolute;
          right: 16px;
          bottom: 64px;
          background: var(--background-secondary);
          color: var(--text-normal);
          border: 1px solid var(--background-tertiary);
          border-radius: 8px;
          box-shadow: 0 8px 16px rgba(0,0,0,.24);
          min-width: 300px;
          max-width: 340px;
          overflow: hidden;
        }

        header {
          padding: 12px 16px;
          display: flex;
          align-items: center;
          justify-content: space-between;
          border-bottom: 1px solid var(--background-tertiary);
          font-weight: 600;
          font-size: 14px;
        }

        .grid {
          display: grid;
          grid-template-columns: repeat(2, 1fr);
          gap: 8px;
          padding: 12px;
        }

        button.toggle, button.ghost {
          background: var(--background-primary);
          color: var(--interactive-normal);
          border: 1px solid var(--background-tertiary);
          border-radius: 4px;
          padding: 10px;
          cursor: pointer;
          text-align: left;
          font-size: 14px;
          font-weight: 500;
          transition: all 0.17s ease;
        }

        button.toggle:hover, button.ghost:hover {
          background: var(--background-modifier-hover);
          color: var(--interactive-hover);
          border-color: var(--background-accent);
        }

        button.toggle.active {
          background: var(--brand-experiment);
          color: white;
          border-color: var(--brand-experiment);
        }

        .swatches {
          display: flex;
          gap: 6px;
        }

        .swatch {
          width: 24px;
          height: 24px;
          border-radius: 6px;
          border: 1px solid var(--background-tertiary);
          cursor: pointer;
          transition: transform 0.17s ease;
        }

        .swatch:hover {
          transform: scale(1.1);
        }

        .quick-actions {
          padding: 12px;
          border-top: 1px solid var(--background-tertiary);
        }

        .quick-actions h4 {
          margin: 0 0 8px 0;
          font-size: 12px;
          font-weight: 600;
          color: var(--header-secondary);
          text-transform: uppercase;
          letter-spacing: 0.5px;
        }

        .quick-action {
          display: flex;
          align-items: center;
          gap: 8px;
          padding: 8px;
          border-radius: 4px;
          cursor: pointer;
          transition: background-color 0.17s ease;
        }

        .quick-action:hover {
          background: var(--background-modifier-hover);
        }

        .quick-action-icon {
          width: 20px;
          height: 20px;
          display: flex;
          align-items: center;
          justify-content: center;
        }

        footer {
          display: flex;
          gap: 8px;
          padding: 12px;
          border-top: 1px solid var(--background-tertiary);
        }

        button.ghost {
          background: transparent;
          font-weight: 500;
        }
      </style>
      <div class="backdrop"></div>
      <div class="menu" role="dialog" aria-label="Pluscord Quick Menu">
        <header>
          <span>Pluscord</span>
          <div class="swatches">
            <div class="swatch" data-theme="amoled" style="background:#0b0d10" title="AMOLED"></div>
            <div class="swatch" data-theme="nord" style="background:#3b4252" title="Nord"></div>
            <div class="swatch" data-theme="solarized" style="background:#073642" title="Solarized"></div>
            <div class="swatch" data-theme="discord" style="background:#202225" title="Discord"></div>
          </div>
        </header>
        <div class="grid">
          <button class="toggle" id="qm-boost">🚀 Boost</button>
          <button class="toggle" id="qm-compact">🗜️ Compact</button>
          <button class="toggle" id="qm-reduce">🎞️ Animations</button>
          <button class="toggle" id="qm-copy">📋 Copy buttons</button>
          <button class="toggle" id="qm-abs">🕒 Absolute TS</button>
          <button class="toggle" id="qm-clean-gift">🎁 Hide Gift</button>
          <button class="toggle" id="qm-qr">💬 Quick Replies</button>
          <button class="toggle" id="qm-reactions">😀 Reactions</button>
        </div>
        <div class="quick-actions">
          <h4>Quick Actions</h4>
          <div class="quick-action" id="qm-jump-bottom">
            <div class="quick-action-icon">⬇️</div>
            <span>Jump to Bottom</span>
          </div>
          <div class="quick-action" id="qm-mark-unread">
            <div class="quick-action-icon">📌</div>
            <span>Mark as Unread</span>
          </div>
          <div class="quick-action" id="qm-pin-channel">
            <div class="quick-action-icon">📌</div>
            <span>Pin Channel</span>
          </div>
        </div>
        <footer>
          <button class="ghost" id="qm-settings">⚙️ Settings</button>
          <button class="ghost" id="qm-close">Close</button>
        </footer>
      </div>
    `;
    qmShadow.appendChild(wrap);

    qmShadow.querySelector('.backdrop').addEventListener('click', () => setQuickMenuOpen(false));
    qmShadow.getElementById('qm-close').addEventListener('click', () => setQuickMenuOpen(false));
    qmShadow.getElementById('qm-settings').addEventListener('click', () => { setQuickMenuOpen(false); togglePanel(); });

    // Toggles
    qmShadow.getElementById('qm-boost').addEventListener('click', () => {
      S.performanceBoost = !S.performanceBoost;
      saveSettings(S);
      applyBodyClasses();
      syncBoostObservers();
      syncQuickMenuFromSettings();
      toast(S.performanceBoost ? 'Boost ON' : 'Boost OFF');
    });
    qmShadow.getElementById('qm-compact').addEventListener('click', () => {
      S.compactMode = !S.compactMode;
      saveSettings(S);
      applyBodyClasses();
      syncQuickMenuFromSettings();
    });
    qmShadow.getElementById('qm-reduce').addEventListener('click', () => {
      S.disableAnimations = !S.disableAnimations;
      saveSettings(S);
      applyBodyClasses();
      syncQuickMenuFromSettings();
    });
    qmShadow.getElementById('qm-copy').addEventListener('click', () => {
      S.codeCopyButton = !S.codeCopyButton;
      saveSettings(S);
      rescanAll();
      syncQuickMenuFromSettings();
    });
    qmShadow.getElementById('qm-abs').addEventListener('click', () => {
      S.absoluteTimestamps = !S.absoluteTimestamps;
      saveSettings(S);
      rescanAll();
      syncQuickMenuFromSettings();
    });
    qmShadow.getElementById('qm-clean-gift').addEventListener('click', () => {
      S.uiCleanup.hideGift = !S.uiCleanup.hideGift;
      saveSettings(S);
      applyBodyClasses();
      syncQuickMenuFromSettings();
    });
    qmShadow.getElementById('qm-qr').addEventListener('click', () => {
      S.quickReplies.enabled = !S.quickReplies.enabled;
      saveSettings(S);
      updateQuickReplyMenu();
      syncQuickMenuFromSettings();
    });
    qmShadow.getElementById('qm-reactions').addEventListener('click', () => {
      S.quickReactions.enabled = !S.quickReactions.enabled;
      saveSettings(S);
      updateReactionMenu();
      syncQuickMenuFromSettings();
    });

    // Quick actions
    qmShadow.getElementById('qm-jump-bottom').addEventListener('click', () => {
      const messagesContainer = document.querySelector('[class*="messages-"]');
      if (messagesContainer) {
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
        toast('Jumped to bottom');
      }
    });

    qmShadow.getElementById('qm-mark-unread').addEventListener('click', () => {
      const unreadBtn = document.querySelector('[class*="markUnread-"]');
      if (unreadBtn) {
        unreadBtn.click();
        toast('Marked as unread');
      } else {
        toast('No unread messages to mark');
      }
    });

    qmShadow.getElementById('qm-pin-channel').addEventListener('click', () => {
      const channelName = document.querySelector('[class*="title-"] [class*="name-"]')?.innerText;
      if (!channelName) {
        toast('Could not identify current channel');
        return;
      }

      const channelId = window.location.pathname.split('/').pop();
      if (!channelId) {
        toast('Could not identify current channel');
        return;
      }

      if (S.pinnedChannels.channels.some(c => c.id === channelId)) {
        toast('Channel already pinned');
        return;
      }

      S.pinnedChannels.channels.push({ id: channelId, name: channelName });
      saveSettings(S);
      updatePinnedChannels();
      toast(`Pinned #${channelName}`);
    });

    // Theme swatches
    qmShadow.querySelectorAll('.swatch').forEach(sw => {
      sw.addEventListener('click', () => {
        S.theme = sw.dataset.theme;
        saveSettings(S);
        refreshGlobalStyles();
        rescanAll();
        syncQuickMenuFromSettings();
      });
    });

    syncQuickMenuFromSettings();
  }

  function setQuickMenuOpen(isOpen) {
    qmOpen = !!isOpen;
    if (!qmHost) return;
    qmHost.style.display = qmOpen ? 'block' : 'none';
  }
  function toggleQuickMenu() {
    if (!qmHost) buildQuickMenu();
    setQuickMenuOpen(!qmOpen);
    if (qmOpen) syncQuickMenuFromSettings();
    if (panelHost?.classList.contains('open')) panelHost.classList.remove('open');
  }
  function syncQuickMenuFromSettings() {
    if (!qmShadow) return;
    const setActive = (id, on) => qmShadow.getElementById(id)?.classList.toggle('active', !!on);
    setActive('qm-boost', !!S.performanceBoost);
    setActive('qm-compact', !!S.compactMode);
    setActive('qm-reduce', !!S.disableAnimations || !!S.performanceBoost);
    setActive('qm-copy', !!S.codeCopyButton);
    setActive('qm-abs', !!S.absoluteTimestamps);
    setActive('qm-clean-gift', !!S.uiCleanup.hideGift);
    setActive('qm-qr', !!S.quickReplies.enabled);
    setActive('qm-reactions', !!S.quickReactions.enabled);
  }

  // --- Toast ---
  let toastTimer = 0;
  function toast(msg = 'Done') {
    let el = document.getElementById('pluscord-toast');
    if (!el) {
      el = document.createElement('div');
      el.id = 'pluscord-toast';
      el.style.cssText = `position:fixed; bottom:24px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,.75); color:#fff; padding:10px 14px; border-radius:10px; z-index:2147483647; font-weight:600; transition: opacity .2s; font-family: 'gg sans', 'Noto Sans', sans-serif;`;
      document.body.appendChild(el);
    }
    el.textContent = msg;
    el.style.opacity = '1';
    clearTimeout(toastTimer);
    toastTimer = setTimeout(() => { el.style.opacity = '0'; }, 1400);
  }

  // --- Quick Reply Menu ---
  function updateQuickReplyMenu() {
    if (!S.quickReplies.enabled) {
      if (quickReplyMenu) {
        quickReplyMenu.style.display = 'none';
      }
      return;
    }

    if (!quickReplyMenu) {
      quickReplyMenu = document.createElement('div');
      quickReplyMenu.id = 'pluscord-quick-reply';
      document.body.appendChild(quickReplyMenu);
    }

    quickReplyMenu.innerHTML = `
      <header>
        <span>Quick Replies</span>
        <button id="qr-close">×</button>
      </header>
      <div class="templates">
        ${S.quickReplies.templates.map((template, index) => `
          <div class="template" data-index="${index}">
            <div class="template-name">${template.name}</div>
            <div class="template-content">${template.content}</div>
          </div>
        `).join('')}
      </div>
      <footer>
        <button id="qr-manage">Manage</button>
      </footer>
    `;

    quickReplyMenu.querySelector('#qr-close').addEventListener('click', () => {
      quickReplyMenu.style.display = 'none';
    });

    quickReplyMenu.querySelector('#qr-manage').addEventListener('click', () => {
      quickReplyMenu.style.display = 'none';
      togglePanel();
      // Switch to communication tab
      if (panelHost) {
        panelHost.classList.add('open');
        panelShadow.querySelector('nav button[data-tab="communication"]').click();
      }
    });

    quickReplyMenu.querySelectorAll('.template').forEach(template => {
      template.addEventListener('click', () => {
        const index = parseInt(template.dataset.index);
        const content = S.quickReplies.templates[index].content;
        const chatInput = document.querySelector('div[role="textbox"]') || document.querySelector('textarea');
        if (chatInput) {
          chatInput.focus();
          chatInput.innerText = content;
          // Trigger input event
          const event = new Event('input', { bubbles: true });
          chatInput.dispatchEvent(event);
        }
        quickReplyMenu.style.display = 'none';
      });
    });
  }

  // --- Quick Reactions Menu ---
  function updateReactionMenu() {
    if (!S.quickReactions.enabled) {
      if (reactionMenu) {
        reactionMenu.style.display = 'none';
      }
      return;
    }

    if (!reactionMenu) {
      reactionMenu = document.createElement('div');
      reactionMenu.id = 'pluscord-reactions';
      document.body.appendChild(reactionMenu);
    }

    reactionMenu.innerHTML = S.quickReactions.emojis.map(emoji => `
      <div class="reaction" data-emoji="${emoji}">${emoji}</div>
    `).join('');

    reactionMenu.querySelectorAll('.reaction').forEach(reaction => {
      reaction.addEventListener('click', () => {
        const emoji = reaction.dataset.emoji;
        // Find the currently focused message and add reaction
        const focusedMessage = document.activeElement?.closest('article[aria-roledescription="Message"]');
        if (focusedMessage) {
          const reactButton = focusedMessage.querySelector('[aria-label="Add Reaction"]');
          if (reactButton) {
            reactButton.click();
            // Wait for emoji picker to open
            setTimeout(() => {
              const emojiButton = document.querySelector(`[aria-label="${emoji}"]`);
              if (emojiButton) emojiButton.click();
            }, 100);
          }
        }
        reactionMenu.style.display = 'none';
      });
    });
  }

  // --- User Notes Menu ---
  function updateUserNotesMenu(userId, userName, userAvatar) {
    if (!S.userNotes.enabled) return;

    if (!userNotesMenu) {
      userNotesMenu = document.createElement('div');
      userNotesMenu.id = 'pluscord-user-notes';
      document.body.appendChild(userNotesMenu);
    }

    const userNote = S.userNotes.notes[userId] || '';

    userNotesMenu.innerHTML = `
      <header>
        <span>User Notes</span>
        <button id="notes-close">×</button>
      </header>
      <div class="user-info">
        <img class="user-avatar" src="${userAvatar}" alt="${userName}">
        <div class="user-name">${userName}</div>
      </div>
      <div class="note-content">
        <textarea id="user-note-textarea" placeholder="Add a private note about this user...">${userNote}</textarea>
      </div>
      <footer>
        <button id="notes-save">Save</button>
      </footer>
    `;

    userNotesMenu.querySelector('#notes-close').addEventListener('click', () => {
      userNotesMenu.style.display = 'none';
    });

    userNotesMenu.querySelector('#notes-save').addEventListener('click', () => {
      const noteText = userNotesMenu.querySelector('#user-note-textarea').value;
      if (noteText.trim()) {
        S.userNotes.notes[userId] = noteText;
      } else {
        delete S.userNotes.notes[userId];
      }
      saveSettings(S);
      userNotesMenu.style.display = 'none';
      toast('Note saved');
    });

    userNotesMenu.style.display = 'block';
  }

  // --- Server Folders ---
  function updateServerFolders() {
    if (!S.serverFolders.enabled) {
      if (serverFoldersContainer) {
        serverFoldersContainer.style.display = 'none';
      }
      return;
    }

    if (!serverFoldersContainer) {
      serverFoldersContainer = document.createElement('div');
      serverFoldersContainer.id = 'pluscord-server-folders';

      // Insert before the server list
      const serverList = document.querySelector('[class*="scroller-"]');
      if (serverList) {
        serverList.parentNode.insertBefore(serverFoldersContainer, serverList);
      }
    }

    serverFoldersContainer.innerHTML = S.serverFolders.folders.map((folder, index) => `
      <div class="folder ${folder.collapsed ? 'collapsed' : ''}" data-index="${index}">
        <div class="folder-header">
          ${folder.collapsed ? '▶' : '▼'} ${folder.name}
        </div>
        <div class="folder-servers">
          ${folder.servers.map(serverId => {
            const server = document.querySelector(`[data-dnd-item-id="${serverId}"]`);
            if (!server) return '';
            const serverIcon = server.querySelector('img')?.src || '';
            return `<div class="folder-server" style="background-image: url('${serverIcon}')" data-server-id="${serverId}"></div>`;
          }).join('')}
        </div>
      </div>
    `).join('');

    serverFoldersContainer.querySelectorAll('.folder-header').forEach(header => {
      header.addEventListener('click', () => {
        const folderEl = header.parentElement;
        const index = parseInt(folderEl.dataset.index);
        S.serverFolders.folders[index].collapsed = !S.serverFolders.folders[index].collapsed;
        saveSettings(S);
        updateServerFolders();
      });
    });

    serverFoldersContainer.querySelectorAll('.folder-server').forEach(serverEl => {
      serverEl.addEventListener('click', () => {
        const serverId = serverEl.dataset.serverId;
        const serverLink = document.querySelector(`a[href*="/channels/${serverId}"]`);
        if (serverLink) serverLink.click();
      });
    });
  }

  // --- Pinned Channels ---
  function updatePinnedChannels() {
    if (!S.pinnedChannels.enabled) {
      if (pinnedChannelsList) {
        pinnedChannelsList.style.display = 'none';
      }
      return;
    }

    if (!pinnedChannelsList) {
      pinnedChannelsList = document.createElement('div');
      pinnedChannelsList.id = 'pluscord-pinned-channels';

      // Insert at the top of the channel list
      const channelList = document.querySelector('[class*="container-"]');
      if (channelList) {
        channelList.insertBefore(pinnedChannelsList, channelList.firstChild);
      }
    }

    pinnedChannelsList.innerHTML = `
      <div class="header">
        <span>Pinned Channels</span>
        <button id="pinned-toggle">−</button>
      </div>
      <div class="channels">
        ${S.pinnedChannels.channels.map(channel => `
          <div class="channel" data-channel-id="${channel.id}">
            <div class="channel-icon">#</div>
            <div class="channel-name">${channel.name}</div>
            <div class="unpin">✕</div>
          </div>
        `).join('')}
      </div>
    `;

    pinnedChannelsList.querySelector('#pinned-toggle').addEventListener('click', () => {
      const channels = pinnedChannelsList.querySelector('.channels');
      const toggle = pinnedChannelsList.querySelector('#pinned-toggle');
      if (channels.style.display === 'none') {
        channels.style.display = 'block';
        toggle.textContent = '−';
      } else {
        channels.style.display = 'none';
        toggle.textContent = '+';
      }
    });

    pinnedChannelsList.querySelectorAll('.channel').forEach(channelEl => {
      channelEl.addEventListener('click', (e) => {
        if (e.target.classList.contains('unpin')) return;
        const channelId = channelEl.dataset.channelId;
        window.location.href = `/channels/${window.location.pathname.split('/')[2]}/${channelId}`;
      });
    });

    pinnedChannelsList.querySelectorAll('.unpin').forEach(unpinBtn => {
      unpinBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        const channelEl = unpinBtn.parentElement;
        const channelId = channelEl.dataset.channelId;
        const channelIndex = S.pinnedChannels.channels.findIndex(c => c.id === channelId);
        if (channelIndex !== -1) {
          S.pinnedChannels.channels.splice(channelIndex, 1);
          saveSettings(S);
          updatePinnedChannels();
          toast('Channel unpinned');
        }
      });
    });
  }

  // --- Core Enhancements & Observers ---
  function enhanceMessageArticle(article) {
    if (!(article instanceof HTMLElement)) return;
    const msgId = article.id?.split('-').pop();

    try {
      const kw = collectKeywords();
      const txt = (article.querySelector('[id^="message-content-"]')?.innerText || '');

      if (Array.isArray(kw) && kw.length > 0) {
        const hasMatch = kw.some(keyword => {
          if (keyword instanceof RegExp) {
            return keyword.test(txt);
          }
          return S.highlight.caseSensitive ? txt.includes(keyword) : txt.toLowerCase().includes(keyword.toLowerCase());
        });

        article.classList.toggle('pluscord-keyword', hasMatch);
      }
    } catch {}

    if (S.absoluteTimestamps) {
      article.querySelectorAll('time[datetime]').forEach(t => {
        if (!t.dataset.pcOriginalText) t.dataset.pcOriginalText = t.textContent || '';
        const dt = t.getAttribute('datetime');
        if (dt) { try { t.textContent = new Date(dt).toLocaleString(); } catch {} }
      });
    } else {
      article.querySelectorAll('time[datetime][data-pc-original-text]').forEach(t => {
        t.textContent = t.dataset.pcOriginalText;
        delete t.dataset.pcOriginalText;
      });
    }

    if (S.codeCopyButton) {
      article.querySelectorAll('pre:not(.pluscord-pre-wrap)').forEach(pre => {
        pre.classList.add('pluscord-pre-wrap');
        const code = pre.querySelector('code'); if (!code) return;
        if (pre.querySelector(':scope > .pluscord-copy')) return;
        const btn = document.createElement('button');
        btn.className = 'pluscord-copy'; btn.textContent = 'Copy';
        btn.onclick = async e => {
          e.stopPropagation();
          try {
            await navigator.clipboard.writeText(code.innerText);
            btn.textContent = 'Copied';
            btn.classList.add('pluscord-copied');
            setTimeout(() => {
              btn.textContent = 'Copy';
              btn.classList.remove('pluscord-copied');
            }, 900);
          }
          catch { alert('Clipboard permission denied.'); }
        };
        pre.appendChild(btn);
      });
    } else {
      article.querySelectorAll('.pluscord-copy').forEach(b => b.remove());
    }

    if (S.textToSpeech.enabled) {
      if (!article.querySelector('.pluscord-tts')) {
        const ttsBtn = document.createElement('div');
        ttsBtn.className = 'pluscord-tts';
        ttsBtn.textContent = '🔊';
        ttsBtn.onclick = () => {
          const content = article.querySelector('[id^="message-content-"]')?.innerText || '';
          if ('speechSynthesis' in window) {
            const utterance = new SpeechSynthesisUtterance(content);
            utterance.voice = speechSynthesis.getVoices().find(v => v.name === S.textToSpeech.voice) || null;
            utterance.rate = S.textToSpeech.rate;
            utterance.volume = S.textToSpeech.volume;
            speechSynthesis.speak(utterance);
          }
        };
        article.appendChild(ttsBtn);
      }
    } else {
      article.querySelectorAll('.pluscord-tts').forEach(b => b.remove());
    }

    if (S.translation.enabled) {
      const contentEl = article.querySelector('[id^="message-content-"]');
      if (contentEl && !contentEl.querySelector('.pluscord-translate')) {
        const translateBtn = document.createElement('div');
        translateBtn.className = 'pluscord-translate';
        translateBtn.textContent = '🌐';
        translateBtn.style.cssText = 'position: absolute; top: 6px; right: 30px; width: 20px; height: 20px; background: rgba(0,0,0,0.55); color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 12px; opacity: 0; transition: opacity 0.2s;';
        translateBtn.onclick = () => {
          const text = contentEl.innerText;
          // Use a simple translation API (in a real implementation, you'd use a proper API)
          GM_xmlhttpRequest({
            method: 'GET',
            url: `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${S.translation.defaultLang}&dt=t&q=${encodeURIComponent(text)}`,
            onload: (response) => {
              try {
                const data = JSON.parse(response.responseText);
                const translatedText = data[0].map(item => item[0]).join('');
                contentEl.innerText = translatedText;
                article.classList.add('pluscord-translated');
                toast('Message translated');
              } catch (e) {
                toast('Translation failed');
              }
            },
            onerror: () => toast('Translation failed')
          });
        };
        article.appendChild(translateBtn);
      }
    }

    if (S.userNotes.enabled) {
      const avatarEl = article.querySelector('img[class*="avatar-"]');
      if (avatarEl && !avatarEl.dataset.pcNotesEnabled) {
        avatarEl.dataset.pcNotesEnabled = 'true';
        avatarEl.style.cursor = 'pointer';
        avatarEl.addEventListener('click', (e) => {
          e.stopPropagation();
          const userId = article.id?.split('-').pop();
          const userName = article.querySelector('[id^="message-username-"]')?.innerText || 'Unknown';
          const userAvatar = avatarEl.src;
          if (userId) updateUserNotesMenu(userId, userName, userAvatar);
        });
      }
    }

    if (S.performanceBoost && S.boost.collapseAttachments) {
      article.querySelectorAll('[class*="mediaAttachmentsContainer-"]:not([data-pc-expanded])').forEach(container => {
        container.dataset.pcExpanded = '0';
        if (!container.querySelector('.pluscord-expand-btn')) {
          const btn = document.createElement('div');
          btn.className = 'pluscord-expand-btn';
          btn.textContent = 'Click to Expand';
          btn.onclick = () => {
            container.classList.add('pluscord-expanded');
            btn.remove();
            container.dataset.pcExpanded = '1';
          };
          container.appendChild(btn);
        }
      });
    }

    if (S.messageLogger.enabled && msgId) {
      const contentEl = article.querySelector('[id^="message-content-"]');
      if (contentEl) messageCache.set(msgId, { content: contentEl.innerText, element: article });
    }
  }

  function collectKeywords() {
    const kws = (S.highlight.keywords || []).map(s => s.trim()).filter(Boolean);
    if (S.highlight.myName) kws.push(S.highlight.myName.trim());

    if (S.highlight.useRegex) {
      return kws.map(k => {
        try {
          return new RegExp(k, S.highlight.caseSensitive ? 'g' : 'gi');
        } catch {
          return k.toLowerCase();
        }
      });
    }

    return S.highlight.caseSensitive ? kws : kws.map(k => k.toLowerCase());
  }

  function startObservers() {
    if (mainObserver) mainObserver.disconnect();
    mainObserver = new MutationObserver(mutList => {
      for (const mut of mutList) {
        for (const n of mut.addedNodes) {
          if (!(n instanceof HTMLElement)) continue;
          if (n.matches?.('article[aria-roledescription="Message"]')) enhanceMessageArticle(n);
          else n.querySelectorAll?.('article[aria-roledescription="Message"]').forEach(enhanceMessageArticle);
          if (S.revealSpoilers) n.querySelectorAll?.('[class*="spoiler"]').forEach(sp => {
            if (!seenSpoilers.has(sp)) {
              sp.click?.();
              seenSpoilers.add(sp);
            }
          });
        }

        if (!S.messageLogger.enabled) continue;

        if (S.messageLogger.logDeletes && mut.removedNodes.length > 0) {
          for (const n of mut.removedNodes) {
            if (n instanceof HTMLElement && n.matches?.('article[aria-roledescription="Message"]')) {
              const msgId = n.id?.split('-').pop();
              const cached = msgId ? messageCache.get(msgId) : null;
              if (cached) logEvent('delete', cached.element);
            }
          }
        }

        if (S.messageLogger.logEdits) {
          const target = mut.target;
          if (target instanceof HTMLElement && target.matches?.('[id^="message-content-"]')) {
            const article = target.closest('article[aria-roledescription="Message"]');
            if (article) {
              const msgId = article.id?.split('-').pop();
              const cached = msgId ? messageCache.get(msgId) : null;
              const editedFlag = article.querySelector('[class*="edited-"]');
              if (cached && editedFlag) {
                logEvent('edit', article, cached.content);
                const newContent = article.querySelector('[id^="message-content-"]')?.innerText || '';
                messageCache.set(msgId, { content: newContent, element: article });
              }
            }
          }
        }
      }
    });

    mainObserver.observe(document.body, { childList: true, subtree: true, characterData: true });

    document.querySelectorAll('article[aria-roledescription="Message"]').forEach(enhanceMessageArticle);
    if (S.revealSpoilers) document.querySelectorAll('[class*="spoiler"]').forEach(sp => sp.click?.());
  }

  function logEvent(type, element, oldContent = '') {
    const msgId = element?.id?.split('-').pop();
    if (!msgId) return;
    if (logCache.some(log => log.id === msgId && log.type === type)) return;

    const author = element.querySelector('[id^="message-username-"]')?.innerText || 'Unknown';
    const content = element.querySelector('[id^="message-content-"]')?.innerText || '(no text content)';
    const channel = document.querySelector('[class*="title-"] [class*="name-"]')?.innerText || 'Unknown Channel';
    const timestamp = new Date().toISOString();

    logCache.unshift({
      id: msgId,
      type,
      author,
      content,
      oldContent,
      channel,
      time: timestamp
    });

    if (logCache.length > S.messageLogger.maxLogs) logCache.pop();
    updateLoggerPanel();

    if (type === 'delete') {
      messageCache.delete(msgId);
    }
  }

  function updateLoggerPanel() {
    if (!panelShadow) return;
    const container = panelShadow.getElementById('pc-log-container');
    if (!container) return;

    if (!logCache.length) {
      container.innerHTML = '<p class="hint">No events logged yet.</p>';
      return;
    }

    container.innerHTML = logCache.map(log => `
      <div class="log-entry log-${log.type}">
        <div class="log-header">
          <div><strong>${log.type === 'delete' ? 'Deleted' : 'Edited'}</strong> by ${escapeHtml(log.author)} in #${escapeHtml(log.channel)}</div>
          <small>${log.time}</small>
        </div>
        <div class="log-content">
          ${log.type === 'edit' ? `<p class="log-old"><em>Old:</em> ${escapeHtml(log.oldContent)}</p>` : ''}
          <p><em>${log.type === 'edit' ? 'New:' : ''}</em> ${escapeHtml(log.content)}</p>
        </div>
      </div>
    `).join('');
  }

  function escapeHtml(s) {
    return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
  }

  // Media observer
  function setupHoverPlay(video) {
    if (!(video instanceof HTMLVideoElement) || managedVideos.has(video)) return;
    managedVideos.add(video);
    try {
      video.dataset.pcHoverplay = '1';
      video.autoplay = false;
      video.muted = true;
      video.preload = 'metadata';
      const enter = () => { if (S.performanceBoost && S.boost.pauseGIFs) { video.play().catch(() => {}); } };
      const leave = () => { if (S.performanceBoost && S.boost.pauseGIFs) { video.pause(); } };
      video.addEventListener('mouseenter', enter);
      video.addEventListener('mouseleave', leave);
      video.addEventListener('focusin', enter);
      video.addEventListener('focusout', leave);
      video.pause();
    } catch {}
  }

  function startMediaObserver() {
    if (mediaObserver) mediaObserver.disconnect();
    mediaObserver = new MutationObserver((mutList) => {
      for (const mut of mutList) {
        for (const n of mut.addedNodes) {
          if (!(n instanceof HTMLElement)) continue;
          if (n instanceof HTMLVideoElement) setupHoverPlay(n);
          else n.querySelectorAll?.('video').forEach(setupHoverPlay);
        }
      }
    });
    mediaObserver.observe(document.body, { childList: true, subtree: true });
    document.querySelectorAll('video').forEach(setupHoverPlay);
  }

  function stopMediaObserver() {
    if (mediaObserver) mediaObserver.disconnect();
    mediaObserver = null;
    document.querySelectorAll('video[data-pc-hoverplay]').forEach(v => {
      try {
        v.removeAttribute('data-pc-hoverplay');
        v.preload = 'auto';
      } catch {}
    });
    managedVideos.clear();
  }

  function syncBoostObservers() {
    if (S.performanceBoost && S.boost.pauseGIFs) startMediaObserver();
    else stopMediaObserver();
  }

  function rescanAll() {
    document.querySelectorAll('article[aria-roledescription="Message"]').forEach(enhanceMessageArticle);
  }

  function fullRefresh() {
    refreshGlobalStyles();
    applyBodyClasses();
    startObservers();
    syncBoostObservers();
    ensureFab();
    updateQuickReplyMenu();
    updateReactionMenu();
    updateServerFolders();
    updatePinnedChannels();
  }

  // Router + self-heal hooks
  function hookRouter() {
    const origPush = history.pushState;
    const origReplace = history.replaceState;
    history.pushState = function (...a) {
      const r = origPush.apply(this, a);
      queueMicrotask(() => ensureFab());
      return r;
    };
    history.replaceState = function (...a) {
      const r = origReplace.apply(this, a);
      queueMicrotask(() => ensureFab());
      return r;
    };
    window.addEventListener('popstate', () => ensureFab());
    setInterval(() => ensureFab(), 3000);
  }

  // Formatting shortcuts
  function setupFormattingShortcuts() {
    if (!S.formattingShortcuts.enabled) return;

    document.addEventListener('keydown', (e) => {
      const chatInput = document.querySelector('div[role="textbox"]') || document.querySelector('textarea');
      if (!chatInput || document.activeElement !== chatInput) return;

      const selection = window.getSelection();
      if (!selection.rangeCount) return;

      const range = selection.getRangeAt(0);
      const selectedText = range.toString();

      let formattedText = '';

      if (e.ctrlKey && e.key === 'b' && !e.shiftKey && !e.altKey) { // Bold
        e.preventDefault();
        formattedText = `**${selectedText}**`;
      } else if (e.ctrlKey && e.key === 'i' && !e.shiftKey && !e.altKey) { // Italic
        e.preventDefault();
        formattedText = `*${selectedText}*`;
      } else if (e.ctrlKey && e.shiftKey && e.key === 'S' && !e.altKey) { // Strikethrough
        e.preventDefault();
        formattedText = `~~${selectedText}~~`;
      } else if (e.ctrlKey && e.shiftKey && e.key === 'C' && !e.altKey) { // Code
        e.preventDefault();
        formattedText = `\`${selectedText}\``;
      }

      if (formattedText) {
        range.deleteContents();
        range.insertNode(document.createTextNode(formattedText));
        range.collapse(false);
        selection.removeAllRanges();
        selection.addRange(range);

        // Trigger input event
        const event = new Event('input', { bubbles: true });
        chatInput.dispatchEvent(event);
      }
    });
  }

  // Spotify integration
  function setupSpotifyIntegration() {
    if (!S.integrations.spotify) return;

    // Create Spotify controls
    const spotifyControls = document.createElement('div');
    spotifyControls.id = 'pluscord-spotify-controls';
    spotifyControls.style.cssText = `
      position: fixed; bottom: 70px; left: 16px;
      background: var(--pc-panel-bg); color: var(--pc-text);
      border: 1px solid rgba(255,255,255,.12); border-radius: 12px;
      box-shadow: 0 18px 40px rgba(0,0,0,.5);
      padding: 10px; display: flex; align-items: center; gap: 8px;
      font-family: ui-sans-serif, system-ui, sans-serif;
      z-index: 2147483646;
    `;

    spotifyControls.innerHTML = `
      <div style="font-weight: 600; font-size: 12px;">Spotify</div>
      <button id="spotify-prev" style="background: none; border: none; color: var(--pc-text); cursor: pointer;">⏮</button>
      <button id="spotify-play" style="background: none; border: none; color: var(--pc-text); cursor: pointer;">⏯</button>
      <button id="spotify-next" style="background: none; border: none; color: var(--pc-text); cursor: pointer;">⏭</button>
      <div id="spotify-track" style="font-size: 12px; opacity: 0.8; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">Not playing</div>
    `;

    document.body.appendChild(spotifyControls);

    // Hide controls by default
    spotifyControls.style.display = 'none';

    // Check if Spotify is connected
    function checkSpotifyStatus() {
      GM_xmlhttpRequest({
        method: 'GET',
        url: 'https://api.spotify.com/v1/me/player/currently-playing',
        headers: {
          'Authorization': 'Bearer ' + (localStorage.getItem('spotify_token') || '')
        },
        onload: (response) => {
          if (response.status === 200) {
            const data = JSON.parse(response.responseText);
            if (data.item) {
              const trackName = data.item.name;
              const artistName = data.item.artists.map(a => a.name).join(', ');
              document.getElementById('spotify-track').textContent = `${trackName} - ${artistName}`;
              document.getElementById('spotify-play').textContent = data.is_playing ? '⏸' : '▶';
              spotifyControls.style.display = 'flex';
            } else {
              spotifyControls.style.display = 'none';
            }
          } else {
            spotifyControls.style.display = 'none';
          }
        },
        onerror: () => {
          spotifyControls.style.display = 'none';
        }
      });
    }

    // Set up button event listeners
    document.getElementById('spotify-prev').addEventListener('click', () => {
      GM_xmlhttpRequest({
        method: 'POST',
        url: 'https://api.spotify.com/v1/me/player/previous',
        headers: {
          'Authorization': 'Bearer ' + (localStorage.getItem('spotify_token') || '')
        },
        onload: () => setTimeout(checkSpotifyStatus, 500)
      });
    });

    document.getElementById('spotify-play').addEventListener('click', () => {
      const isPlaying = document.getElementById('spotify-play').textContent === '⏸';
      GM_xmlhttpRequest({
        method: 'POST',
        url: `https://api.spotify.com/v1/me/player/${isPlaying ? 'pause' : 'play'}`,
        headers: {
          'Authorization': 'Bearer ' + (localStorage.getItem('spotify_token') || '')
        },
        onload: () => setTimeout(checkSpotifyStatus, 500)
      });
    });

    document.getElementById('spotify-next').addEventListener('click', () => {
      GM_xmlhttpRequest({
        method: 'POST',
        url: 'https://api.spotify.com/v1/me/player/next',
        headers: {
          'Authorization': 'Bearer ' + (localStorage.getItem('spotify_token') || '')
        },
        onload: () => setTimeout(checkSpotifyStatus, 500)
      });
    });

    // Check status every 5 seconds
    setInterval(checkSpotifyStatus, 5000);
    checkSpotifyStatus();
  }

  // Custom notification sounds
  function setupCustomNotificationSounds() {
    if (!S.notifications.customSounds || !S.notifications.soundFile) return;

    // Override Discord's notification sound
    const originalPlay = Audio.prototype.play;
    Audio.prototype.play = function() {
      if (this.src.includes('notification')) {
        // Play custom sound instead
        const customAudio = new Audio(S.notifications.soundFile);
        customAudio.volume = this.volume;
        return customAudio.play();
      }
      return originalPlay.apply(this, arguments);
    };
  }

  // Main Init
  function init() {
    loadDiscordFont();
    fullRefresh();
    hookRouter();
    setupFormattingShortcuts();

    if (S.integrations.spotify) setupSpotifyIntegration();
    if (S.notifications.customSounds) setupCustomNotificationSounds();

    // Keyboard shortcuts
    document.addEventListener('keydown', e => {
      if (e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
        const k = e.key.toLowerCase();
        if (k === 'p') { e.preventDefault(); togglePanel(); }
        if (k === 'b') { e.preventDefault(); S.performanceBoost = !S.performanceBoost; saveSettings(S); applyBodyClasses(); syncBoostObservers(); toast(`Boost: ${S.performanceBoost ? 'ON' : 'OFF'}`); }
        if (k === 'm') { e.preventDefault(); toggleQuickMenu(); }
        if (k === 'r' && S.quickReactions.enabled) { e.preventDefault(); toggleReactionMenu(); }
        if (k === 'q' && S.quickReplies.enabled) { e.preventDefault(); toggleQuickReplyMenu(); }
      }
    });

    try {
      GM_registerMenuCommand('Pluscord: Quick Menu', toggleQuickMenu);
      GM_registerMenuCommand('Pluscord: Open Settings', togglePanel);
      GM_registerMenuCommand('Pluscord: Toggle Boost', () => { S.performanceBoost = !S.performanceBoost; saveSettings(S); applyBodyClasses(); syncBoostObservers(); });
      GM_registerMenuCommand('Pluscord: Show Floating Button', () => { S.floatingButton = true; saveSettings(S); ensureFab(true); });
    } catch {}

    window.Pluscord = {
      version: VERSION,
      settings: () => structuredClone(S),
      set: (next) => { S = deepMerge(S, next); saveSettings(S); fullRefresh(); },
      fullRefresh,
      togglePanel,
      toggleQuickMenu,
      updateQuickReplyMenu,
      updateReactionMenu,
      updateServerFolders,
      updatePinnedChannels
    };
    console.log(`[${APP_NAME}] v${VERSION} loaded`);
  }

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init, { once: true }); else init();

})();