CharFlow - Character AI Customization

Complete Character.AI UI Customization - 35+ settings, presets, HTML export, per-character background and per-element control

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         CharFlow - Character AI Customization
// @namespace    http://tampermonkey.net/
// @version      1.4.2
// @description  Complete Character.AI UI Customization - 35+ settings, presets, HTML export, per-character background and per-element control
// @match        *://character.ai/chat/*
// @match        *://www.character.ai/chat/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  if (!window.location.pathname.includes('/chat/')) return;

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

    function init() {
    // Add viewport meta tag for mobile optimization
    if (!document.querySelector('meta[name="viewport"]')) {
      const viewport = document.createElement('meta');
      viewport.name = 'viewport';
      viewport.content = 'width=device-width, initial-scale=1.0, user-scalable=yes, viewport-fit=cover';
      document.head.appendChild(viewport);
    }

    // --- Storage Keys ---
    const STORAGE = {
      bgType: 'cai_bg_type',
      bgUrl: 'cai_bg_url',
      bgFile: 'cai_bg_file',
      bgBlur: 'cai_bg_blur',
      bgBrightness: 'cai_bg_brightness',
      bgOverlayOpacity: 'cai_bg_overlay_opacity',
      bgOverlayColor: 'cai_bg_overlay_color',
      bubbleMode: 'cai_bubble_mode',
      bubbleGlobal: 'cai_bubble_global',
      bubbleAi: 'cai_bubble_ai',
      bubbleUser: 'cai_bubble_user',
      cornerMode: 'cai_corner_mode',
      cornerTopLeft: 'cai_corner_tl',
      cornerTopRight: 'cai_corner_tr',
      cornerBottomRight: 'cai_corner_br',
      cornerBottomLeft: 'cai_corner_bl',
      bubbleSpacing: 'cai_bubble_spacing',
      fontFamily: 'cai_font_family',
      fontCustomUrl: 'cai_font_custom_url',
      fontSize: 'cai_font_size',
      fontWeight: 'cai_font_weight',
      lineHeight: 'cai_line_height',
      textColorMode: 'cai_text_color_mode',
      textColorGlobal: 'cai_text_color_global',
      textColorAi: 'cai_text_color_ai',
      textColorUser: 'cai_text_color_user',
      textItalicColor: 'cai_text_italic_color',
      textFormats: 'cai_text_formats',
      textBoldColor: 'cai_text_bold_color',
      textQuoteColor: 'cai_text_quote_color',
      textItalicEnabled: 'cai_text_italic_enabled',
      textBoldEnabled: 'cai_text_bold_enabled',
      textQuoteEnabled: 'cai_text_quote_enabled',
      shadowEnabled: 'cai_shadow_enabled',
      sessionTimerEnabled: 'cai_session_timer_enabled',
      sessionTimerPrefix: 'cai_session_timer_prefix',
      conversationStartTimes: 'cai_conv_start_times',
      shadowBlur: 'cai_shadow_blur',
      shadowSpread: 'cai_shadow_spread',
      shadowOffsetX: 'cai_shadow_offset_x',
      shadowOffsetY: 'cai_shadow_offset_y',
      shadowOpacity: 'cai_shadow_opacity',
      shadowColor: 'cai_shadow_color',
      glassEnabled: 'cai_glass_enabled',
      glassBlur: 'cai_glass_blur',
      glassOpacity: 'cai_glass_opacity',
      borderEnabled: 'cai_border_enabled',
      borderWidth: 'cai_border_width',
      borderColor: 'cai_border_color',
      perCharBgEnabled: 'cai_per_char_bg_enabled',
    };

    const PRESET_PREFIX = 'cai_preset_';
    const CHAR_BG_PREFIX = 'cai_char_bg_';
    let lastCharId = null;

    // ============================================
    // STORAGE MANAGEMENT UTILITIES
    // ============================================

    function updateStorageWarning() {
      const warningDiv = document.getElementById('cai-storage-warning');
      const usageSpan = document.getElementById('cai-storage-usage');

      if (!warningDiv || !usageSpan) return;

      let totalSize = 0;
      const allKeys = GM_listValues();

      // Calculate total size of all character backgrounds
      allKeys.forEach((key) => {
        if (key.startsWith(CHAR_BG_PREFIX)) {
          const value = GM_getValue(key, '');
          totalSize += new Blob([value]).size;
        }
      });

      const totalMB = (totalSize / (1024 * 1024)).toFixed(2);
      const limitMB = 10; // Tampermonkey typical limit

      usageSpan.textContent = `Current storage used: ${totalMB} MB / ${limitMB} MB`;

      // Show warning if over 3MB (approaching limit)
      if (totalSize > 3 * 1024 * 1024) {
        warningDiv.style.display = 'block';
        if (totalSize > 8 * 1024 * 1024) {
          warningDiv.style.background = 'rgba(239,68,68,0.15)';
          warningDiv.style.borderLeftColor = '#ef4444';
          usageSpan.style.color = '#ef4444';
        } else {
          warningDiv.style.background = 'rgba(245,158,11,0.1)';
          warningDiv.style.borderLeftColor = '#f59e0b';
        }
      } else {
        warningDiv.style.display = 'none';
      }
    }

    function getTotalStorageUsage() {
      let totalSize = 0;
      const allKeys = GM_listValues();
      allKeys.forEach((key) => {
        if (key.startsWith(CHAR_BG_PREFIX)) {
          const value = GM_getValue(key, '');
          totalSize += new Blob([value]).size;
        }
      });
      return totalSize;
    }

    // ============================================
    // CHARACTER BACKGROUND MANAGER
    // ============================================

    function loadCharacterBackgroundsList() {
      const listContainer = document.getElementById('cai-char-bg-list');
      if (!listContainer) return;

      const allKeys = GM_listValues();
      const bgKeys = allKeys.filter((key) => key.startsWith(CHAR_BG_PREFIX));

      if (bgKeys.length === 0) {
        listContainer.innerHTML = `
            <div class="cai-empty-state" style="padding:30px 20px;">
                <div class="cai-empty-icon">📭</div>
                <div>No saved character backgrounds</div>
                <div class="cai-empty-sub">Save a background for a character to see it here.</div>
            </div>
        `;
        return;
      }

      // Load all backgrounds and sort by saved date (newest first)
      const backgrounds = [];
      let totalSize = 0;

      for (const key of bgKeys) {
        try {
          const data = JSON.parse(GM_getValue(key, '{}'));
          const charId = key.substring(CHAR_BG_PREFIX.length);
          const size = new Blob([GM_getValue(key, '')]).size;
          totalSize += size;

          backgrounds.push({
            key: key,
            charId: charId,
            characterName: data.characterName || 'Unknown Character',
            bgType: data.bgType || 'none',
            bgUrl: data.bgUrl,
            bgFile: data.bgFile,
            bgBlur: data.bgBlur,
            bgBrightness: data.bgBrightness,
            bgOverlayOpacity: data.bgOverlayOpacity,
            bgOverlayColor: data.bgOverlayColor,
            size: size,
            savedAt: data.savedAt || 'Unknown',
          });
        } catch (e) {
          console.error('[CharFlow] Failed to parse background:', key, e);
        }
      }

      // Sort by savedAt (newest first)
      backgrounds.sort((a, b) => {
        if (!a.savedAt) return 1;
        if (!b.savedAt) return -1;
        return new Date(b.savedAt) - new Date(a.savedAt);
      });

      // Update storage info
      const storageUsedSpan = document.getElementById(
        'cai-manager-storage-used',
      );
      if (storageUsedSpan) {
        storageUsedSpan.textContent = (totalSize / (1024 * 1024)).toFixed(2);
      }
      const storageLimitSpan = document.getElementById(
        'cai-manager-storage-limit',
      );
      if (storageLimitSpan) {
        storageLimitSpan.textContent = '10';
      }

      // Render the list
      listContainer.innerHTML = backgrounds
        .map((bg) => {
          const savedDate =
            bg.savedAt !== 'Unknown'
              ? new Date(bg.savedAt).toLocaleDateString()
              : 'Unknown date';
          const typeDisplay =
            bg.bgType === 'url'
              ? '🔗 URL'
              : bg.bgType === 'file'
                ? '📁 File'
                : '🚫 None';
          const sizeDisplay =
            bg.bgType === 'file' ? ` (${(bg.size / 1024).toFixed(0)} KB)` : '';

          return `
            <div class="cai-manager-item" data-charid="${bg.charId}" style="background:#12121c;border-radius:8px;padding:12px;margin-bottom:8px;">
                <div style="display:flex;justify-content:space-between;align-items:flex-start;">
                    <div style="flex:1;">
                        <div style="font-weight:600;color:#d0d0d8;margin-bottom:4px;">${escapeHtml(bg.characterName)}</div>
                        <div style="font-size:10px;color:#555568;">ID: ${bg.charId.substring(0, 12)}...</div>
                        <div style="font-size:10px;color:#555568;margin-top:4px;">
                            ${typeDisplay}${sizeDisplay} • Saved: ${savedDate}
                        </div>
                    </div>
                    <div style="display:flex;gap:6px;flex-shrink:0;">
                        <button class="cai-manager-preview" data-charid="${bg.charId}" data-name="${escapeHtml(bg.characterName)}" data-bgtype="${bg.bgType}" data-bgurl="${bg.bgUrl || ''}" data-bgfile="${(bg.bgFile || '').substring(0, 100)}" data-blur="${bg.bgBlur || '0px'}" data-brightness="${bg.bgBrightness || '100%'}" data-overlayopacity="${bg.bgOverlayOpacity || '0'}" data-overlaycolor="${bg.bgOverlayColor || '#000000'}" style="padding:4px 8px;background:#1e1e2e;border:1px solid rgba(255,255,255,0.08);border-radius:6px;color:#8888a0;font-size:10px;cursor:pointer;">👁️ Preview</button>
                        <button class="cai-manager-delete" data-key="${bg.key}" data-name="${escapeHtml(bg.characterName)}" style="padding:4px 8px;background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.2);border-radius:6px;color:#cc7070;font-size:10px;cursor:pointer;">Delete</button>
                    </div>
                </div>
            </div>
        `;
        })
        .join('');

      // Attach event listeners to buttons
      document.querySelectorAll('.cai-manager-preview').forEach((btn) => {
        btn.addEventListener('click', (e) => {
          e.stopPropagation();
          showBackgroundPreview({
            name: btn.dataset.name,
            bgType: btn.dataset.bgtype,
            bgUrl: btn.dataset.bgurl,
            bgFile: btn.dataset.bgfile,
            blur: btn.dataset.blur,
            brightness: btn.dataset.brightness,
            overlayOpacity: btn.dataset.overlayopacity,
            overlayColor: btn.dataset.overlaycolor,
          });
        });
      });

      document.querySelectorAll('.cai-manager-delete').forEach((btn) => {
        btn.addEventListener('click', (e) => {
          e.stopPropagation();
          const key = btn.dataset.key;
          const name = btn.dataset.name;
          if (confirm(`Delete saved background for "${name}"?`)) {
            GM_deleteValue(key);
            showNotification(`Deleted background for "${name}"`, 'success');
            loadCharacterBackgroundsList();
            updateStorageWarning();
          }
        });
      });
    }

      function updateFileStatusDisplay() {
          const fileStatusDiv = document.getElementById('cai-file-status');
          const fileNameSpan = document.getElementById('cai-file-name');

          if (!fileStatusDiv || !fileNameSpan) return;

          const bgFile = STATE.bgFile || '';
          const bgType = STATE.bgType || 'none';

          if (bgType === 'file' && bgFile && bgFile.startsWith('data:image')) {
              // Show file info when a file is selected
              const sizeInKB = Math.round(new Blob([bgFile]).size / 1024);
              const displayName = `📁 Image (${sizeInKB} KB)`;
              fileNameSpan.textContent = displayName;
              fileNameSpan.style.color = '#8888a0';
              fileStatusDiv.style.display = 'flex';
          } else {
              // Hide the status div when no file is selected
              fileStatusDiv.style.display = 'none';
          }
      }

      function clearLoadedFile() {
          saveState('bgFile', '');
          // Keep bgType as 'file' so the file input remains visible
          // Just clear the file data
          updateFileStatusDisplay();
          applyBackground();
          showNotification('File cleared', 'info');

          // Reset the file input element value so the same file can be selected again
          const fileInput = document.getElementById('cai-bg-file');
          if (fileInput) fileInput.value = '';
      }

    function showBackgroundPreview(data) {
      // Create modal overlay
      const modal = document.createElement('div');
      modal.style.cssText = `
        position:fixed;top:0;left:0;width:100%;height:100%;
        background:rgba(0,0,0,0.8);z-index:10002;
        display:flex;align-items:center;justify-content:center;
        font-family:'Inter',system-ui;
    `;

      let imageContent = '';
      if (data.bgType === 'url' && data.bgUrl) {
        imageContent = `<img src="${data.bgUrl}" style="max-width:300px;max-height:300px;border-radius:8px;object-fit:contain;">`;
      } else if (
        data.bgType === 'file' &&
        data.bgFile &&
        data.bgFile.startsWith('data:image')
      ) {
        imageContent = `<img src="${data.bgFile}" style="max-width:300px;max-height:300px;border-radius:8px;object-fit:contain;">`;
      } else {
        imageContent = `<div style="width:200px;height:200px;background:#1e1e2e;border-radius:8px;display:flex;align-items:center;justify-content:center;color:#555568;">No image preview</div>`;
      }

      modal.innerHTML = `
        <div style="background:#16161e;border-radius:16px;padding:20px;max-width:400px;width:90%;border:1px solid rgba(255,255,255,0.1);">
            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
                <h3 style="margin:0;font-size:16px;color:#e0e0f0;">Preview: ${escapeHtml(data.name)}</h3>
                <button id="cai-preview-close" style="background:none;border:none;color:#8888a0;font-size:20px;cursor:pointer;">✕</button>
            </div>
            <div style="text-align:center;margin-bottom:16px;">
                ${imageContent}
            </div>
            <div style="font-size:11px;color:#8888a0;margin-bottom:8px;">
                <div>Type: ${data.bgType === 'url' ? 'URL' : data.bgType === 'file' ? 'Local File' : 'None'}</div>
                <div>Blur: ${data.blur}</div>
                <div>Brightness: ${data.brightness}</div>
                <div>Overlay: ${data.overlayColor} at ${parseInt(data.overlayOpacity) || 0}%</div>
            </div>
        </div>
    `;

      document.body.appendChild(modal);

      const closeBtn = modal.querySelector('#cai-preview-close');
      closeBtn.addEventListener('click', () => modal.remove());
      modal.addEventListener('click', (e) => {
        if (e.target === modal) modal.remove();
      });
    }

    function deleteAllCharacterBackgrounds() {
      const confirmText = 'DELETE';
      const userInput = prompt(
        `⚠️ DANGER: This will delete ALL saved character backgrounds.\n\nType "${confirmText}" to confirm.`,
      );

      if (userInput !== confirmText) {
        showNotification('Deletion cancelled', 'info');
        return;
      }

      const allKeys = GM_listValues();
      let deletedCount = 0;

      for (const key of allKeys) {
        if (key.startsWith(CHAR_BG_PREFIX)) {
          GM_deleteValue(key);
          deletedCount++;
        }
      }

      showNotification(
        `Deleted ${deletedCount} character backgrounds`,
        'success',
      );
      loadCharacterBackgroundsList();
      updateStorageWarning();
    }

    function exportAllCharacterBackgrounds() {
      const allKeys = GM_listValues();
      const bgKeys = allKeys.filter((key) => key.startsWith(CHAR_BG_PREFIX));

      if (bgKeys.length === 0) {
        showNotification('No character backgrounds to export', 'warning');
        return;
      }

      const exportData = {
        exportDate: new Date().toISOString(),
        version: '1.0',
        type: 'charflow_character_backups',
        backgrounds: {},
      };

      for (const key of bgKeys) {
        try {
          const charId = key.substring(CHAR_BG_PREFIX.length);
          const data = JSON.parse(GM_getValue(key, '{}'));
          exportData.backgrounds[charId] = data;
        } catch (e) {
          console.error('[CharFlow] Failed to export:', key, e);
        }
      }

      const blob = new Blob([JSON.stringify(exportData, null, 2)], {
        type: 'application/json',
      });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `charflow_backgrounds_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`;
      a.click();
      URL.revokeObjectURL(url);

      showNotification(
        `Exported ${bgKeys.length} character backgrounds`,
        'success',
      );
    }

    function importCharacterBackgrounds(file) {
      const reader = new FileReader();
      reader.onload = function (e) {
        try {
          const data = JSON.parse(e.target.result);

          // Validate import format
          if (!data.backgrounds || data.type !== 'charflow_character_backups') {
            showNotification('Invalid backup file format', 'error');
            return;
          }

          const confirmReplace = confirm(
            `Import ${Object.keys(data.backgrounds).length} character backgrounds?\n\nClick OK to merge (existing backgrounds with same character ID will be overwritten).`,
          );

          if (!confirmReplace) return;

          let importedCount = 0;
          let overwrittenCount = 0;

          for (const [charId, bgData] of Object.entries(data.backgrounds)) {
            const existing = GM_getValue(CHAR_BG_PREFIX + charId, null);
            if (existing) overwrittenCount++;

            GM_setValue(CHAR_BG_PREFIX + charId, JSON.stringify(bgData));
            importedCount++;
          }

          showNotification(
            `Imported ${importedCount} backgrounds (${overwrittenCount} overwritten)`,
            'success',
          );
          loadCharacterBackgroundsList();
          updateStorageWarning();
        } catch (err) {
          showNotification('Invalid backup file: ' + err.message, 'error');
        }
      };
      reader.readAsText(file);
    }

    // Helper function to escape HTML
    function escapeHtml(str) {
      if (!str) return '';
      return str.replace(/[&<>]/g, function (m) {
        if (m === '&') return '&amp;';
        if (m === '<') return '&lt;';
        if (m === '>') return '&gt;';
        return m;
      });
    }

    // ============================================
    // SETUP CHARACTER BACKGROUND MANAGER
    // ============================================

    function setupCharacterBackgroundManager() {
      loadCharacterBackgroundsList();

      // Export All button
      const exportBtn = document.getElementById('cai-manager-export-all');
      if (exportBtn) {
        // Remove existing listeners to avoid duplicates
        const newExportBtn = exportBtn.cloneNode(true);
        exportBtn.parentNode.replaceChild(newExportBtn, exportBtn);
        newExportBtn.addEventListener('click', exportAllCharacterBackgrounds);
      }

      // Delete All button
      const deleteAllBtn = document.getElementById('cai-manager-delete-all');
      if (deleteAllBtn) {
        const newDeleteBtn = deleteAllBtn.cloneNode(true);
        deleteAllBtn.parentNode.replaceChild(newDeleteBtn, deleteAllBtn);
        newDeleteBtn.addEventListener('click', deleteAllCharacterBackgrounds);
      }

      // Import button
      const importBtn = document.getElementById('cai-manager-import');
      const importFile = document.getElementById('cai-manager-import-file');
      if (importBtn && importFile) {
        const newImportBtn = importBtn.cloneNode(true);
        importBtn.parentNode.replaceChild(newImportBtn, importBtn);

        // Remove existing file input listener
        const newImportFile = importFile.cloneNode(true);
        importFile.parentNode.replaceChild(newImportFile, importFile);

        newImportBtn.addEventListener('click', () => newImportFile.click());
        newImportFile.addEventListener('change', (e) => {
          if (e.target.files && e.target.files[0]) {
            importCharacterBackgrounds(e.target.files[0]);
            newImportFile.value = '';
          }
        });
      }
    }

    // Canonical defaults — used for reset AND preset loading fallback
    const DEFAULTS = {
      bgType: 'none',
      bgBlur: '0px',
      bgBrightness: '100%',
      bgOverlayOpacity: '0',
      bgOverlayColor: '#000000',
      bubbleMode: 'global',
      bubbleGlobal: '#2d2d3d',
      bubbleAi: '#2d2d3d',
      bubbleUser: '#1a1a2e',
      cornerMode: 'uniform',
      cornerTopLeft: '18',
      cornerTopRight: '18',
      cornerBottomRight: '18',
      cornerBottomLeft: '18',
      bubbleSpacing: '8px',
      fontFamily: 'Inter',
      fontCustomUrl: '',
      fontSize: '14px',
      fontWeight: '400',
      lineHeight: '1.5',
      textColorMode: 'global',
      textColorGlobal: '#e0e0e0',
      textColorAi: '#e0e0e0',
      textColorUser: '#e0e0e0',
      textItalicColor: '#a855f7',
      textBoldColor: '#f59e0b',
      textQuoteColor: '#e0df7f',
      textItalicEnabled: false,
      textBoldEnabled: false,
      textQuoteEnabled: false,
      shadowEnabled: false,
      shadowBlur: '12',
      shadowSpread: '0',
      shadowOffsetX: '0',
      shadowOffsetY: '4',
      shadowOpacity: '0.3',
      shadowColor: '#000000',
      glassEnabled: false,
      glassBlur: '10',
      glassOpacity: '0.7',
      borderEnabled: false,
      borderWidth: '2',
      borderColor: '#ffffff',
      sessionTimerEnabled: false,
      sessionTimerPrefix: 'Talking for',
      perCharBgEnabled: false,
    };

    const PRESET_VERSION = '4.0';

    const GOOGLE_FONTS = {
      Inter:
        'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap',
      Poppins:
        'https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap',
      Roboto:
        'https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap',
      'Open Sans':
        'https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&display=swap',
      Montserrat:
        'https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap',
      Lato: 'https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap',
      Raleway:
        'https://fonts.googleapis.com/css2?family=Raleway:wght@300;400;500;600;700&display=swap',
      Nunito:
        'https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;500;600;700&display=swap',
      Merriweather:
        'https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&display=swap',
      'Playfair Display':
        'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700&display=swap',
      'Source Code Pro':
        'https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@300;400;500;600;700&display=swap',
      'Comic Neue':
        'https://fonts.googleapis.com/css2?family=Comic+Neue:wght@300;400;700&display=swap',
      Oswald:
        'https://fonts.googleapis.com/css2?family=Oswald:wght@300;400;500;600;700&display=swap',
      Quicksand:
        'https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap',
      'Josefin Sans':
        'https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@300;400;500;600;700&display=swap',
    };

    let loadedFonts = new Set();
    let activeFontLinks = new Set();
    let globalStyleElement = null;
    let lastStyleOutput = '';
    let backgroundLayer = null;
    let observer = null;

    // ============================================
    // IN-MEMORY STATE (single source of truth)
    // ============================================
    let STATE = {};

    function loadState() {
      for (const [key, storageKey] of Object.entries(STORAGE)) {
        STATE[key] = GM_getValue(
          storageKey,
          DEFAULTS[key] !== undefined ? DEFAULTS[key] : '',
        );
      }
    }

    function saveState(key, value) {
      try {
        STATE[key] = value;
        GM_setValue(STORAGE[key], value);
      } catch (e) {
        console.error('[CharFlow] Failed to save:', key, e);
        showNotification(
          'Failed to save setting - storage may be full',
          'warning',
        );
        return false;
      }
      return true;
    }

    // ============================================
    // NOTIFICATION SYSTEM
    // ============================================
    let notificationTimeout = null;
    let notificationElement = null;

    function showNotification(message, type = 'info') {
      if (notificationElement) {
        notificationElement.remove();
        if (notificationTimeout) clearTimeout(notificationTimeout);
      }
      notificationElement = document.createElement('div');
      notificationElement.className = `cai-notification cai-notification-${type}`;
      const icons = {success: '✓', error: '✕', warning: '⚠', info: 'ℹ'};
      notificationElement.innerHTML = `<span class="cai-notification-icon">${icons[type] || icons.info}</span><span class="cai-notification-message">${message}</span><button class="cai-notification-close">×</button>`;
      document.body.appendChild(notificationElement);
      notificationElement
        .querySelector('.cai-notification-close')
        .addEventListener('click', () => {
          notificationElement.classList.add('cai-notification-hide');
          setTimeout(() => {
            if (notificationElement) notificationElement.remove();
            notificationElement = null;
          }, 300);
        });
      notificationTimeout = setTimeout(() => {
        if (notificationElement) {
          notificationElement.classList.add('cai-notification-hide');
          setTimeout(() => {
            if (notificationElement) notificationElement.remove();
            notificationElement = null;
          }, 300);
        }
      }, 4000);
    }

    // ============================================
    // FONT MANAGEMENT
    // ============================================
    function cleanupOldFonts() {
      activeFontLinks.forEach((link) => {
        if (link && link.parentNode) link.remove();
      });
      activeFontLinks.clear();
      loadedFonts.clear();
    }

    function loadGoogleFont(fontName, customUrl = null) {
      if (!fontName) return;
      let fontUrl =
        customUrl && customUrl.trim() !== ''
          ? customUrl.trim()
          : GOOGLE_FONTS[fontName] || null;
      if (!fontUrl) return;
      const isCustom = !!(customUrl && customUrl.trim() !== '');
      const fontId = isCustom
        ? `custom-${btoa(fontUrl).slice(0, 20)}`
        : fontName;
      if (loadedFonts.has(fontId)) return;
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.href = fontUrl;
      link.id = `cai-font-${fontId.replace(/\s/g, '-')}`;
      link.onerror = () => {
        showNotification(
          'Your custom font URL could not be loaded. Check that the link is a valid Google Fonts URL and try again.',
          'error',
        );
        loadedFonts.delete(fontId);
        activeFontLinks.delete(link);
        link.remove();
      };
      document.head.appendChild(link);
      activeFontLinks.add(link);
      loadedFonts.add(fontId);
    }

    function getFontFamily() {
      const fontName = STATE.fontFamily || 'Inter';
      const customUrl = STATE.fontCustomUrl || '';
      if (customUrl && customUrl.trim() !== '')
        return `'Custom Font', ${fontName}, system-ui, sans-serif`;
      return `${fontName}, system-ui, -apple-system, sans-serif`;
    }

    // ============================================
    // CSS GENERATION (no gradient)
    // ============================================
    function generateStyles() {
      const mode = STATE.bubbleMode;
      const globalColor = STATE.bubbleGlobal;
      const aiColor = STATE.bubbleAi;
      const userColor = STATE.bubbleUser;
      const spacing = STATE.bubbleSpacing;
      const fontFamily = getFontFamily();
      const fontSize = STATE.fontSize;
      const fontWeight = STATE.fontWeight;
      const lineHeight = STATE.lineHeight;
      const shadowEnabled = STATE.shadowEnabled;
      const shadowBlur = STATE.shadowBlur;
      const shadowSpread = STATE.shadowSpread;
      const shadowOffsetX = STATE.shadowOffsetX;
      const shadowOffsetY = STATE.shadowOffsetY;
      const shadowOpacity = STATE.shadowOpacity;
      const shadowColor = STATE.shadowColor;
      const r = parseInt(shadowColor.slice(1, 3), 16),
        g = parseInt(shadowColor.slice(3, 5), 16),
        b_c = parseInt(shadowColor.slice(5, 7), 16);
      const boxShadow = shadowEnabled
        ? `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px ${shadowSpread}px rgba(${r},${g},${b_c},${shadowOpacity})`
        : 'none';
      const glassEnabled = STATE.glassEnabled;
      const glassBlur = STATE.glassBlur;
      const glassOpacity = STATE.glassOpacity;
      const borderEnabled = STATE.borderEnabled;
      const borderWidth = STATE.borderWidth;
      const borderColor = STATE.borderColor;
      const borderStyle = borderEnabled
        ? `border: ${borderWidth}px solid ${borderColor} !important;`
        : '';
      const cornerMode = STATE.cornerMode;
      const tl = STATE.cornerTopLeft;
      const tr = STATE.cornerTopRight;
      const br = STATE.cornerBottomRight;
      const bl = STATE.cornerBottomLeft;
      const textColorMode = STATE.textColorMode;
      const textColorGlobal = STATE.textColorGlobal;
      const textColorAi = STATE.textColorAi;
      const textColorUser = STATE.textColorUser;
      const italicColor = STATE.textItalicColor;
      const boldColor = STATE.textBoldColor;
      const italicEnabled = STATE.textItalicEnabled;
      const boldEnabled = STATE.textBoldEnabled;

      const MSG =
        '[data-testid="completed-message"],[data-testid="active-message"],[data-testid="generating-message"],[data-testid="streaming-message"],[class*="message-bubble"]';
      const AI = '.group.relative:not(:has(.flex-row-reverse))';
      const USER = '.group.relative:has(.flex-row-reverse)';

      let css = `
                .group.relative.max-w-3xl.m-auto.w-full { margin-bottom: ${spacing} !important; }
                ${MSG} { transition: all 0.1s ease; box-shadow: ${boxShadow}; ${borderStyle} }
${MSG} p,
${MSG} span,
${MSG} div,
${MSG} li,
${MSG} .prose,
${MSG} .prose *,
[data-testid="completed-message"] .prose,
[data-testid="completed-message"] .prose *,
[data-testid="active-message"] .prose,
[data-testid="active-message"] .prose *,
[data-testid="generating-message"] .prose,
[data-testid="generating-message"] .prose *,
[data-testid="streaming-message"] .prose,
[data-testid="streaming-message"] .prose * {
    font-family: ${fontFamily} !important;
    font-size: ${fontSize} !important;
    font-weight: ${fontWeight} !important;
    line-height: ${lineHeight} !important;
}
            `;

      // Background / color
      if (glassEnabled) {
        css += `${MSG} { background: rgba(255,255,255,${glassOpacity}) !important; backdrop-filter: blur(${glassBlur}px) !important; -webkit-backdrop-filter: blur(${glassBlur}px) !important; background-image: none !important; }`;
      } else if (mode === 'global') {
        css += `${MSG} { background-color: ${globalColor} !important; background-image: none !important; }`;
      } else {
        css += `${AI} [data-testid="completed-message"],${AI} [data-testid="active-message"],${AI} [data-testid="generating-message"],${AI} [data-testid="streaming-message"],${AI} [class*="message-bubble"] { background-color: ${aiColor} !important; background-image: none !important; }
                ${USER} [data-testid="completed-message"],${USER} [data-testid="active-message"],${USER} [data-testid="generating-message"],${USER} [data-testid="streaming-message"],${USER} [class*="message-bubble"] { background-color: ${userColor} !important; background-image: none !important; }`;
      }

      // Corners
      if (cornerMode === 'uniform') {
        css += `${MSG} { border-radius: ${tl}px !important; }`;
      } else {
        css += `${AI} [data-testid="completed-message"],${AI} [data-testid="active-message"],${AI} [data-testid="generating-message"],${AI} [data-testid="streaming-message"],${AI} [class*="message-bubble"] { border-radius: ${tl}px ${tr}px ${br}px ${bl}px !important; }
                ${USER} [data-testid="completed-message"],${USER} [data-testid="active-message"],${USER} [data-testid="generating-message"],${USER} [data-testid="streaming-message"],${USER} [class*="message-bubble"] { border-radius: ${tr}px ${tl}px ${bl}px ${br}px !important; }`;
      }

      // Text colors
      if (textColorMode === 'global') {
        css += `
        ${AI} [data-testid="completed-message"] p, ${AI} [data-testid="active-message"] p,
        ${AI} [data-testid="generating-message"] p, ${AI} [data-testid="streaming-message"] p,
        ${AI} [class*="message-bubble"] p,
        ${USER} [data-testid="completed-message"] p, ${USER} [data-testid="active-message"] p,
        ${USER} [data-testid="generating-message"] p, ${USER} [data-testid="streaming-message"] p,
        ${USER} [class*="message-bubble"] p { color: ${textColorGlobal} !important; }
    `;
      } else {
        css += `${AI} [data-testid="completed-message"] p,${AI} [data-testid="active-message"] p,${AI} [data-testid="generating-message"] p,${AI} [data-testid="streaming-message"] p,${AI} [class*="message-bubble"] p { color: ${textColorAi} !important; }
    ${USER} [data-testid="completed-message"] p,${USER} [data-testid="active-message"] p,${USER} [data-testid="generating-message"] p,${USER} [data-testid="streaming-message"] p,${USER} [class*="message-bubble"] p { color: ${textColorUser} !important; }`;
      }

      // Italic
      const ITALIC_SEL = `[data-testid="completed-message"] em,[data-testid="completed-message"] i,
                [data-testid="active-message"] em,[data-testid="active-message"] i,
                [data-testid="generating-message"] em,[data-testid="generating-message"] i,
                [data-testid="streaming-message"] em,[data-testid="streaming-message"] i`;
      if (italicEnabled) {
        css += `${ITALIC_SEL} { color: ${italicColor} !important; font-style: italic !important; }`;
      } else {
        css += `${ITALIC_SEL} { font-style: italic !important; }`;
      }

      // Bold
      const BOLD_SEL = `[data-testid="completed-message"] strong,[data-testid="completed-message"] b,
                [data-testid="active-message"] strong,[data-testid="active-message"] b,
                [data-testid="generating-message"] strong,[data-testid="generating-message"] b,
                [data-testid="streaming-message"] strong,[data-testid="streaming-message"] b`;
      if (boldEnabled) {
        css += `${BOLD_SEL} { color: ${boldColor} !important; font-weight: 700 !important; }`;
      } else {
        css += `${BOLD_SEL} { font-weight: 700 !important; }`;
      }

      // Quote
      const quoteEnabled = STATE.textQuoteEnabled;
      const quoteColor = STATE.textQuoteColor || '#e0df7f';
      if (quoteEnabled) {
        css += `.cai-quote-colored, .cai-quote-colored em, .cai-quote-colored span { color: ${quoteColor} !important; }`;
      }

      return css;
    }

    function applyStyles() {
      const fontName = STATE.fontFamily || 'Inter';
      const customUrl = STATE.fontCustomUrl || '';
      if (customUrl && customUrl.trim() !== '')
        loadGoogleFont('Custom Font', customUrl);
      else if (GOOGLE_FONTS[fontName]) loadGoogleFont(fontName);

      const css = generateStyles();
      if (css === lastStyleOutput) return;
      lastStyleOutput = css;

      if (!globalStyleElement || !globalStyleElement.parentNode) {
        globalStyleElement = document.createElement('style');
        globalStyleElement.id = 'cai-global-styles';
        document.head.appendChild(globalStyleElement);
      }
      globalStyleElement.textContent = css;
    }

    function cleanupBackground() {
      if (backgroundLayer && backgroundLayer.parentNode)
        backgroundLayer.remove();
      backgroundLayer = null;
    }

    function ensureBackgroundLayer() {
      if (!backgroundLayer || !backgroundLayer.parentNode) {
        backgroundLayer = document.createElement('div');
        backgroundLayer.id = 'cai-background-layer';
        backgroundLayer.style.cssText = `position:fixed;top:0;left:0;width:100%;height:100%;z-index:-2;pointer-events:none;background-size:cover;background-position:center;background-repeat:no-repeat;background-attachment:fixed;transition:opacity 0.6s ease, filter 0.6s ease;`;
        document.body.insertBefore(backgroundLayer, document.body.firstChild);
      }
      return backgroundLayer;
    }

    function applyBackground() {
      // Check if per-character backgrounds are enabled
      const usePerChar = STATE.perCharBgEnabled && getCurrentCharacterId();
      let bgType, bgUrl, bgFile, blur, brightness, overlayColor, overlayOpacity;

      const settingsPanelEl = document.getElementById('cai-settings-panel');
      const isEditingPreview =
        usePerChar &&
        settingsPanelEl &&
        settingsPanelEl.classList.contains('open');

      if (usePerChar) {
        const charBg = loadCharBackground();
        if (charBg && charBg.bgType && charBg.bgType !== 'none') {
          // Use character-specific background
          bgType = charBg.bgType;
          bgUrl = charBg.bgUrl || '';
          bgFile = charBg.bgFile || '';
          blur = charBg.bgBlur || '0px';
          brightness = charBg.bgBrightness || '100%';
          overlayColor = charBg.bgOverlayColor || '#000000';
          overlayOpacity = charBg.bgOverlayOpacity || '0';

          // Update status display
          const statusEl = document.getElementById('cai-char-bg-status');
          if (statusEl) {
            statusEl.style.display = 'block';
            statusEl.innerHTML = `🎨 Using custom background for this character (saved ${new Date(charBg.savedAt).toLocaleDateString()})`;
            statusEl.style.color = '#6655bb';
          }
        } else if (isEditingPreview) {
          // PREVIEW MODE: Show current STATE values temporarily
          bgType = STATE.bgType || 'none';
          bgUrl = STATE.bgUrl || '';
          bgFile = STATE.bgFile || '';
          blur = STATE.bgBlur || '0px';
          brightness = STATE.bgBrightness || '100%';
          overlayColor = STATE.bgOverlayColor || '#000000';
          overlayOpacity = STATE.bgOverlayOpacity || '0';

          const statusEl = document.getElementById('cai-char-bg-status');
          if (statusEl) {
            statusEl.style.display = 'block';
            statusEl.innerHTML = `🎨 Preview mode - save to keep this background`;
            statusEl.style.color = '#f59e0b';
          }
        } else {
          // NO BACKGROUND SAVED FOR THIS CHARACTER - show nothing
          bgType = 'none';
          bgUrl = '';
          bgFile = '';
          blur = '0px';
          brightness = '100%';
          overlayColor = '#000000';
          overlayOpacity = '0';

          const statusEl = document.getElementById('cai-char-bg-status');
          if (statusEl) statusEl.style.display = 'none';
        }
      } else {
        // Use global settings (per-character mode is OFF)
        bgType = STATE.bgType || 'none';
        bgUrl = STATE.bgUrl || '';
        bgFile = STATE.bgFile || '';
        blur = STATE.bgBlur || '0px';
        brightness = STATE.bgBrightness || '100%';
        overlayColor = STATE.bgOverlayColor || '#000000';
        overlayOpacity = STATE.bgOverlayOpacity || '0';

        const statusEl = document.getElementById('cai-char-bg-status');
        if (statusEl) statusEl.style.display = 'none';
      }

      let bgImage = '';
      if (bgType === 'url') bgImage = bgUrl || '';
      if (bgType === 'file') bgImage = bgFile || '';
      const layer = ensureBackgroundLayer();

      // Crossfade: clone current layer as outgoing snapshot
      const outgoing = layer.cloneNode(false);
      outgoing.style.cssText = layer.style.cssText;
      outgoing.style.transition = 'opacity 0.6s ease';
      outgoing.style.opacity = '1';
      outgoing.style.zIndex = '-2';
      layer.style.zIndex = '-3';
      layer.parentNode.insertBefore(outgoing, layer.nextSibling);

      // Apply new image to real layer (hidden behind outgoing clone)
      layer.style.backgroundImage = bgImage ? `url(${bgImage})` : 'none';
      if (bgImage) {
        layer.style.backgroundSize = 'cover';
        layer.style.backgroundPosition = 'center';
        layer.style.backgroundAttachment = 'fixed';
      }
      layer.style.filter = `blur(${parseInt(blur) || 0}px) brightness(${parseInt(brightness) || 100}%)`;

      // Force reflow so transition triggers, then fade out the old snapshot
      void outgoing.offsetHeight;
      outgoing.style.opacity = '0';
      outgoing.addEventListener(
        'transitionend',
        () => {
          outgoing.remove();
          layer.style.zIndex = '-2';
        },
        {once: true},
      );
      document.body.style.filter = 'none';
      const existingOverlay = document.getElementById('cai-custom-overlay');
      if (existingOverlay) {
        existingOverlay.style.transition = 'opacity 0.6s ease';
        existingOverlay.style.opacity = '0';
        existingOverlay.addEventListener(
          'transitionend',
          () => existingOverlay.remove(),
          {once: true},
        );
      }
      if (parseFloat(overlayOpacity) > 0) {
        const overlay = document.createElement('div');
        overlay.id = 'cai-custom-overlay';
        overlay.style.cssText = `position:fixed;top:0;left:0;width:100%;height:100%;background:${overlayColor};opacity:0;pointer-events:none;z-index:-1;transition:opacity 0.6s ease;`;
        document.body.insertBefore(overlay, document.body.firstChild);
        void overlay.offsetHeight; // force reflow
        overlay.style.opacity = `${overlayOpacity / 100}`;
      }
      document.body.style.position = 'relative';
      document.body.style.zIndex = '1';

      // Store original color before we change it (first time only)
      if (!document.body.hasAttribute('data-cai-original-bg')) {
        const computedBg = window.getComputedStyle(document.body).backgroundColor;
        document.body.setAttribute('data-cai-original-bg', computedBg);
      }

      // Set background color properly
      if (bgImage) {
        document.body.style.backgroundColor = 'transparent';
      } else {
        // Use the original Character.AI background color
        const originalBg = document.body.getAttribute('data-cai-original-bg');
        if (originalBg && originalBg !== 'rgba(0, 0, 0, 0)') {
          document.body.style.backgroundColor = originalBg;
        } else {
          document.body.style.backgroundColor = '#1a1a24';
        }
      }
    }

    // ============================================
    // PER-CHARACTER BACKGROUND HELPERS
    // ============================================

    function getCurrentCharacterId() {
      const match = window.location.pathname.match(/\/chat\/([^\/]+)/);
      return match ? match[1] : null;
    }

    function loadCharBackground() {
      const charId = getCurrentCharacterId();
      if (!charId) return null;

      const stored = GM_getValue(CHAR_BG_PREFIX + charId, null);
      if (stored) {
        try {
          return JSON.parse(stored);
        } catch (e) {
          return null;
        }
      }
      return null;
    }

    function saveCharBackground(charId, bgData) {
      if (!charId) return;

      // Check if this is a file upload (base64)
      if (
        bgData.bgType === 'file' &&
        bgData.bgFile &&
        bgData.bgFile.startsWith('data:image')
      ) {
        // Calculate size of base64 string
        const base64Size = new Blob([bgData.bgFile]).size;
        const limitBytes = 500 * 1024; // 500KB limit

        if (base64Size > limitBytes) {
          const sizeMB = (base64Size / (1024 * 1024)).toFixed(2);
          showNotification(
            `Image too large (${sizeMB} MB). Maximum 500KB for character backgrounds. Please use a URL or compress the image.`,
            'error',
          );
          return false;
        }

        // Warn if approaching limit (over 300KB)
        if (base64Size > 300 * 1024) {
          showNotification(
            `This image is ${(base64Size / 1024).toFixed(0)}KB. Large images consume storage quickly. Consider using a URL instead.`,
            'warning',
          );
        }

        // Check overall storage limit before saving
        const currentUsage = getTotalStorageUsage();
        const estimatedNewUsage = currentUsage + base64Size;
        const limitMB = 10;

        if (estimatedNewUsage > limitMB * 1024 * 1024) {
          showNotification(
            `Cannot save: Would exceed storage limit (${(estimatedNewUsage / (1024 * 1024)).toFixed(2)} MB / ${limitMB} MB). Delete some character backgrounds or use URLs instead.`,
            'error',
          );
          return false;
        }
      }

      // If it's a URL background, also check overall storage (URLs are tiny, but we still check)
      if (bgData.bgType === 'url') {
        const currentUsage = getTotalStorageUsage();
        const limitMB = 10;

        if (currentUsage > limitMB * 1024 * 1024) {
          showNotification(
            `Storage limit reached (${(currentUsage / (1024 * 1024)).toFixed(2)} MB / ${limitMB} MB). Delete some character backgrounds before saving new ones.`,
            'error',
          );
          return false;
        }
      }

      GM_setValue(CHAR_BG_PREFIX + charId, JSON.stringify(bgData));
      updateStorageWarning(); // Update the warning display
      return true;
    }

    function clearCharBackground(charId) {
      if (!charId) return;
      GM_deleteValue(CHAR_BG_PREFIX + charId);
      updateStorageWarning(); // Update the warning display
      showNotification('Character background cleared', 'info');
    }

      function watchCharacterNavigation() {
      setInterval(() => {
        const currentCharId = getCurrentCharacterId();
        if (currentCharId !== lastCharId) {
          lastCharId = currentCharId;
          if (STATE.perCharBgEnabled) {
            applyBackground(); // Reload with new character's background
            // Update UI to show status
            const toggle = document.getElementById('cai-per-char-bg-toggle');
            if (toggle && toggle.checked) {
              const statusEl = document.getElementById('cai-char-bg-status');
              const charBg = loadCharBackground();
              if (statusEl) {
                if (charBg && charBg.bgType && charBg.bgType !== 'none') {
                  statusEl.style.display = 'block';
                  statusEl.innerHTML = `🎨 Using custom background for this character (saved ${new Date(charBg.savedAt).toLocaleDateString()})`;
                } else {
                  statusEl.style.display = 'none';
                }
              }
                // Handle save button state based on background type
                const saveBtn = document.getElementById('cai-save-char-bg');
                if (saveBtn) {
                    const bgType = STATE.bgType || 'none';
                    if (bgType === 'none') {
                        saveBtn.disabled = true;
                        saveBtn.style.opacity = '0.5';
                        saveBtn.style.cursor = 'not-allowed';
                    } else {
                        saveBtn.disabled = false;
                        saveBtn.style.opacity = '1';
                        saveBtn.style.cursor = 'pointer';
                    }
                }
            }
          }
        }
      }, 500);
    }

    // ============================================
    // CONVERSATION START TIME MANAGEMENT
    // ============================================

    function getConversationStartTimes() {
      let times = GM_getValue(STORAGE.conversationStartTimes, null);
      if (times) {
        try {
          return JSON.parse(times);
        } catch (e) {
          return {};
        }
      }
      return {};
    }

    function saveConversationStartTimes(times) {
      GM_setValue(STORAGE.conversationStartTimes, JSON.stringify(times));
    }

    function getConversationStartTime(charId) {
      if (!charId) return null;
      const times = getConversationStartTimes();
      return times[charId] || null;
    }

    function setConversationStartTime(charId, timestamp) {
      if (!charId) return;
      const times = getConversationStartTimes();
      times[charId] = timestamp;
      saveConversationStartTimes(times);
    }

    function resetConversationStartTime(charId) {
      if (!charId) return;
      const times = getConversationStartTimes();
      delete times[charId];
      saveConversationStartTimes(times);
      // Set to current time as new start
      setConversationStartTime(charId, Date.now());
    }

    function formatTimestamp(timestamp) {
      if (!timestamp) return '--';
      const date = new Date(timestamp);
      return date.toLocaleString();
    }

    // Track last activity for "Active" mode
    let lastActivityTime = Date.now();

    function updateLastActivity() {
      lastActivityTime = Date.now();
      if (STATE.sessionTimerEnabled && STATE.sessionTimerPrefix === 'Active') {
        applySessionTimer();
      }
    }

    // Watch for new messages to update activity
    function watchForNewMessages() {
      const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
          if (mutation.addedNodes.length) {
            // Check if any new message was added
            const hasNewMessage = Array.from(mutation.addedNodes).some(
              (node) =>
                node.nodeType === 1 &&
                (node.matches?.('[data-testid="completed-message"]') ||
                  node.querySelector?.('[data-testid="completed-message"]')),
            );
            if (hasNewMessage) {
              updateLastActivity();

              // For non-Active mode: ensure start time exists on first message
              if (
                STATE.sessionTimerEnabled &&
                STATE.sessionTimerPrefix !== 'Active'
              ) {
                const charId = getCurrentCharacterId();
                if (charId && !getConversationStartTime(charId)) {
                  setConversationStartTime(charId, Date.now());
                  if (STATE.sessionTimerEnabled) applySessionTimer();
                }
              }
              break;
            }
          }
        }
      });

      observer.observe(document.body, {childList: true, subtree: true});
      return observer;
    }

    let messageObserver = null;

    function setupCharBackgroundListeners() {
      const toggle = document.getElementById('cai-per-char-bg-toggle');
      const actions = document.getElementById('cai-per-char-bg-actions');
      const saveBtn = document.getElementById('cai-save-char-bg');
      const clearBtn = document.getElementById('cai-clear-char-bg');

      if (toggle) {
        toggle.checked = STATE.perCharBgEnabled;
        if (actions)
          actions.style.display = STATE.perCharBgEnabled ? 'block' : 'none';

        toggle.addEventListener('change', (e) => {
          saveState('perCharBgEnabled', e.target.checked);
          if (actions)
            actions.style.display = e.target.checked ? 'block' : 'none';
          applyBackground();
          if (e.target.checked) {
            showNotification(
              'Per-character backgrounds enabled. Save a background for the current character!',
              'info',
            );
          }
        });
      }

                if (saveBtn) {
        // Helper function to check if a background is already saved for current character
        function isBackgroundAlreadySaved() {
          const charId = getCurrentCharacterId();
          if (!charId) return false;
          const savedBg = loadCharBackground();
          return savedBg && savedBg.bgType && savedBg.bgType !== 'none';
        }

        // Function to update save button state based on saved background AND current bgType
          function updateSaveButtonState() {
          const bgType = STATE.bgType || 'none';
          const alreadySaved = isBackgroundAlreadySaved();

          // Check if URL is selected but empty
          const isUrlEmpty = (bgType === 'url' && (!STATE.bgUrl || STATE.bgUrl.trim() === ''));

          // Check if File is selected but no file loaded
          const isFileEmpty = (bgType === 'file' && (!STATE.bgFile || STATE.bgFile === ''));

          if (alreadySaved) {
            saveBtn.disabled = true;
            saveBtn.style.opacity = '0.5';
            saveBtn.style.cursor = 'not-allowed';
            saveBtn.title = 'Background already saved for this character. Use Clear button first to change it.';
          } else if (bgType === 'none') {
            saveBtn.disabled = true;
            saveBtn.style.opacity = '0.5';
            saveBtn.style.cursor = 'not-allowed';
            saveBtn.title = 'Select a URL or File background first';
          } else if (isUrlEmpty) {
            saveBtn.disabled = true;
            saveBtn.style.opacity = '0.5';
            saveBtn.style.cursor = 'not-allowed';
            saveBtn.title = 'Enter an image URL first';
          } else if (isFileEmpty) {
            saveBtn.disabled = true;
            saveBtn.style.opacity = '0.5';
            saveBtn.style.cursor = 'not-allowed';
            saveBtn.title = 'Select an image file first';
          } else {
            saveBtn.disabled = false;
            saveBtn.style.opacity = '1';
            saveBtn.style.cursor = 'pointer';
            saveBtn.title = 'Save this background for the character';
          }
        }

        // Initial state
        updateSaveButtonState();

        saveBtn.addEventListener('click', () => {
          // Check again before saving (in case something changed)
          if (isBackgroundAlreadySaved()) {
            showNotification('Background already saved for this character. Clear it first to save a new one.', 'warning');
            return;
          }

          const charId = getCurrentCharacterId();
          if (!charId) {
            showNotification('Not in a character chat', 'error');
            return;
          }

            // Double-check that background type is not 'none'
            if (STATE.bgType === 'none') {
                showNotification('No background to save - select a URL or File first', 'warning');
                return;
            }

            // Check if URL mode but URL is empty
            if (STATE.bgType === 'url' && (!STATE.bgUrl || STATE.bgUrl.trim() === '')) {
                showNotification('No image URL to save - enter a URL first', 'warning');
                return;
            }

            // Check if File mode but no file loaded
            if (STATE.bgType === 'file' && (!STATE.bgFile || STATE.bgFile === '')) {
                showNotification('No image file to save - select a file first', 'warning');
                return;
            }

          // Try to get character name from the page
          let characterName = 'Unknown Character';
          try {
            // Common selectors where character names appear on Character.AI
            const nameSelectors = [
              'p.font-semi-bold.line-clamp-1.text-ellipsis.break-anywhere.overflow-hidden.whitespace-normal', // Current Character.AI format
              'h1', // Often the character name
              '[data-testid="character-name"]',
              'h2.text-2xl',
              '.text-2xl.font-bold',
              'main h1',
              'header h1',
              '.character-name',
              '[class*="character-name"]',
            ];

            for (const selector of nameSelectors) {
              const element = document.querySelector(selector);
              if (element && element.textContent.trim()) {
                characterName = element.textContent.trim();
                break;
              }
            }

            // If name is too long, truncate it
            if (characterName.length > 50) {
              characterName = characterName.substring(0, 47) + '...';
            }
          } catch (e) {
            console.warn('[CharFlow] Could not read character name:', e);
            characterName = 'Unknown Character';
          }

          // Warn if saving a file background
          if (STATE.bgType === 'file' && STATE.bgFile) {
            const fileSize = new Blob([STATE.bgFile]).size;
            const sizeKB = (fileSize / 1024).toFixed(0);

            if (fileSize > 300 * 1024) {
              if (
                !confirm(
                  `⚠️ This image is ${sizeKB}KB and will consume significant storage space.\n\nRecommended: Use an image URL instead.\n\nSave anyway?`,
                )
              ) {
                return;
              }
            } else if (fileSize > 100 * 1024) {
              if (
                !confirm(
                  `This image is ${sizeKB}KB. Large images use storage quickly.\n\nConsider using a URL. Save anyway?`,
                )
              ) {
                return;
              }
            }
          }

          // Check if this would exceed storage limit
          const currentUsage = getTotalStorageUsage();
          let estimatedNewSize = 0;

          if (STATE.bgType === 'file' && STATE.bgFile) {
            estimatedNewSize = new Blob([STATE.bgFile]).size;
          } else if (STATE.bgType === 'url') {
            estimatedNewSize = 500; // URLs are tiny, estimate 500 bytes
          }

          if (currentUsage + estimatedNewSize > 9 * 1024 * 1024) {
            // 9MB warning threshold
            if (
              !confirm(
                `⚠️ Storage is nearly full (${(currentUsage / (1024 * 1024)).toFixed(1)} MB used).\n\nSaving this background may hit the storage limit. Consider deleting old character backgrounds or using URLs.\n\nSave anyway?`,
              )
            ) {
              return;
            }
          }

          const charBgData = {
            bgType: STATE.bgType,
            bgUrl: STATE.bgUrl,
            bgFile: STATE.bgFile,
            bgBlur: STATE.bgBlur,
            bgBrightness: STATE.bgBrightness,
            bgOverlayOpacity: STATE.bgOverlayOpacity,
            bgOverlayColor: STATE.bgOverlayColor,
            characterName: characterName,
            savedAt: new Date().toISOString(),
          };

          const success = saveCharBackground(charId, charBgData);
          if (success) {
            showNotification(
              `✅ Background saved for "${characterName}"!`,
              'success',
            );
            updateStorageWarning();
            // SHOW the clear button after successful save
            if (clearBtn) clearBtn.style.display = 'block';

            // Update save button state (should now be disabled)
            updateSaveButtonState();
          }

          const statusEl = document.getElementById('cai-char-bg-status');
          if (statusEl && success) {
            statusEl.style.display = 'block';
            statusEl.innerHTML = `🎨 Custom background saved for "${characterName}"`;
          }
        });
      }

        if (clearBtn) {
            // Check if a background is already saved for this character
            const charId = getCurrentCharacterId();
            const savedBg = charId ? loadCharBackground() : null;
            const hasSavedBg = savedBg && savedBg.bgType && savedBg.bgType !== 'none';
            clearBtn.style.display = hasSavedBg ? 'block' : 'none';

            clearBtn.addEventListener('click', () => {
                const charId = getCurrentCharacterId();
                if (charId) {
                    clearCharBackground(charId);
                    showNotification(
                        `🗑️ Character background cleared, using global settings`,
                        'success',
                    );
                    applyBackground();
                    // Hide the button again after clearing
                    clearBtn.style.display = 'none';

                    const statusEl = document.getElementById('cai-char-bg-status');
                    if (statusEl) statusEl.style.display = 'none';
                }
            });
        }
    }

    function applyAll() {
      applyBackground();
      applyStyles();
      syncUIWithSettings();
      updateCornerPreviews();
    }

    // ============================================
    // PRESET MANAGER
    // ============================================
    function getCurrentSettings() {
      const settings = {};
      for (const [key, storageKey] of Object.entries(STORAGE)) {
        settings[key] = GM_getValue(
          storageKey,
          DEFAULTS[key] !== undefined ? DEFAULTS[key] : '',
        );
      }
      return settings;
    }

    function applySettings(settings) {
      // List of background keys to skip when loading presets
      const backgroundKeys = [
        'bgType',
        'bgUrl',
        'bgFile',
        'bgBlur',
        'bgBrightness',
        'bgOverlayOpacity',
        'bgOverlayColor',
        'perCharBgEnabled',
      ];

      for (const [key, storageKey] of Object.entries(STORAGE)) {
        // Skip ALL background-related keys and the per-character toggle
        if (backgroundKeys.includes(key)) continue;

        const value = settings[key];
        const resolved =
          value !== undefined && value !== null
            ? value
            : DEFAULTS[key] !== undefined
              ? DEFAULTS[key]
              : '';
        GM_setValue(storageKey, resolved);
        STATE[key] = resolved;
      }
      applyAll();
      showNotification(
        'Preset loaded successfully! (background settings preserved)',
        'success',
      );
    }

    function savePreset(name) {
      if (!name || name.trim() === '') {
        showNotification('Please enter a preset name', 'error');
        return false;
      }
      const presetKey = PRESET_PREFIX + name.trim();
      if (GM_getValue(presetKey, null)) {
        if (!confirm(`Preset "${name}" already exists. Overwrite?`))
          return false;
      }

      const settings = getCurrentSettings();

      delete settings.perCharBgEnabled;
      delete settings.bgType;
      delete settings.bgUrl;
      delete settings.bgFile;
      delete settings.bgBlur;
      delete settings.bgBrightness;
      delete settings.bgOverlayOpacity;
      delete settings.bgOverlayColor;

      const presetData = {
        version: PRESET_VERSION,
        name: name.trim(),
        createdAt: new Date().toISOString(),
        settings: settings,
      };
      try {
        GM_setValue(presetKey, JSON.stringify(presetData));
        loadPresetList();
        showNotification(`Preset "${name}" saved!`, 'success');
        return true;
      } catch (e) {
        showNotification('Error saving: ' + e.message, 'error');
        return false;
      }
    }

    function loadPreset(name) {
      const presetData = GM_getValue(PRESET_PREFIX + name, null);
      if (presetData) {
        try {
          const data = JSON.parse(presetData);
          applySettings(data.settings || data);
          return true;
        } catch (e) {
          showNotification('Error loading preset', 'error');
          return false;
        }
      }
      showNotification(`Preset "${name}" not found`, 'error');
      return false;
    }

    function deletePreset(name) {
      if (GM_getValue(PRESET_PREFIX + name, null)) {
        GM_deleteValue(PRESET_PREFIX + name);
        loadPresetList();
        showNotification(`Preset "${name}" deleted`, 'success');
      } else showNotification(`Preset "${name}" not found`, 'error');
    }

    function exportPreset(name) {
      const presetData = GM_getValue(PRESET_PREFIX + name, null);
      if (presetData) {
        try {
          const parsed = JSON.parse(presetData);
          const exportSettings = {...parsed.settings};

          // Remove background settings from export (they don't belong in presets)
          delete exportSettings.bgType;
          delete exportSettings.bgUrl;
          delete exportSettings.bgFile;
          delete exportSettings.bgBlur;
          delete exportSettings.bgBrightness;
          delete exportSettings.bgOverlayOpacity;
          delete exportSettings.bgOverlayColor;

          if (parsed.settings?.bgFile) {
            showNotification(
              'Background settings were excluded from this preset export',
              'warning',
            );
          }

          const blob = new Blob(
            [
              JSON.stringify(
                {
                  exportDate: new Date().toISOString(),
                  ...parsed,
                  settings: exportSettings,
                },
                null,
                2,
              ),
            ],
            {type: 'application/json'},
          );
          const url = URL.createObjectURL(blob);
          const a = document.createElement('a');
          a.href = url;
          a.download = `${name.replace(/[^a-z0-9]/gi, '_')}_preset.json`;
          a.click();
          URL.revokeObjectURL(url);
          showNotification(
            `Preset "${name}" exported! (background settings excluded)`,
            'success',
          );
        } catch (e) {
          showNotification('Export error: ' + e.message, 'error');
        }
      } else showNotification(`Preset "${name}" not found`, 'error');
    }

    function importPreset(file) {
      const reader = new FileReader();
      reader.onload = function (e) {
        try {
          const data = JSON.parse(e.target.result);
          let presetName = (
            data.name ||
            data._presetName ||
            `imported_${Date.now()}`
          ).replace(/[^a-zA-Z0-9\s_-]/g, '');
          const settings = data.settings || data;
          [
            '_presetName',
            '_createdAt',
            'name',
            'version',
            'exportDate',
          ].forEach((k) => delete settings[k]);
          const presetKey = PRESET_PREFIX + presetName;
          if (GM_getValue(presetKey, null)) {
            if (!confirm(`Preset "${presetName}" already exists. Overwrite?`))
              return;
          }
          GM_setValue(
            presetKey,
            JSON.stringify({
              version: PRESET_VERSION,
              name: presetName,
              createdAt: new Date().toISOString(),
              settings,
            }),
          );
          loadPresetList();
          showNotification(`Preset "${presetName}" imported!`, 'success');
        } catch (err) {
          showNotification('Invalid preset file: ' + err.message, 'error');
        }
      };
      reader.readAsText(file);

      if (file.size > 1024 * 1024) {
        showNotification('Preset file too large (max 1MB)', 'error');
        return;
      }

      if (!file.name.endsWith('.json')) {
        showNotification('Invalid preset file - expected .json', 'error');
        return;
      }
    }

    function loadPresetList() {
      const presetList = document.getElementById('cai-preset-list');
      if (!presetList) return;
      const presetKeys = GM_listValues().filter((k) =>
        k.startsWith(PRESET_PREFIX),
      );
      if (presetKeys.length === 0) {
        presetList.innerHTML =
          '<div class="cai-empty-presets">No saved presets yet.</div>';
        return;
      }
      const presets = presetKeys.map((key) => {
        try {
          const d = JSON.parse(GM_getValue(key, '{}'));
          return {name: d.name || key.substring(PRESET_PREFIX.length)};
        } catch (e) {
          return {name: key.substring(PRESET_PREFIX.length)};
        }
      });
      presetList.innerHTML = presets
        .map(
          (p) => `
                <div class="cai-preset-item">
                    <span class="cai-preset-name">${p.name}</span>
                    <div class="cai-preset-dot-menu">
                        <button class="cai-preset-dot-btn" data-preset="${p.name.replace(/"/g, '&quot;')}">•••</button>
                        <div class="cai-preset-dropdown" id="cai-dd-${p.name.replace(/[^a-zA-Z0-9]/g, '_')}">
                            <button class="cai-preset-load" data-preset="${p.name.replace(/"/g, '&quot;')}">Load</button>
                            <button class="cai-preset-delete" data-preset="${p.name.replace(/"/g, '&quot;')}">Delete</button>
                            <button class="cai-preset-export" data-preset="${p.name.replace(/"/g, '&quot;')}">Export</button>
                        </div>
                    </div>
                </div>
            `,
        )
        .join('');

      presetList.querySelectorAll('.cai-preset-dot-btn').forEach((btn) => {
        btn.addEventListener('click', (e) => {
          e.stopPropagation();
          const dd = document.getElementById(
            `cai-dd-${btn.dataset.preset.replace(/[^a-zA-Z0-9]/g, '_')}`,
          );
          document
            .querySelectorAll('.cai-preset-dropdown.open')
            .forEach((d) => {
              if (d !== dd) d.classList.remove('open');
            });
          dd?.classList.toggle('open');
        });
      });
      document.addEventListener('click', () =>
        document
          .querySelectorAll('.cai-preset-dropdown.open')
          .forEach((d) => d.classList.remove('open')),
      );
      presetList.querySelectorAll('.cai-preset-load').forEach((btn) =>
        btn.addEventListener('click', (e) => {
          e.stopPropagation();
          loadPreset(btn.dataset.preset);
          document
            .querySelectorAll('.cai-preset-dropdown.open')
            .forEach((d) => d.classList.remove('open'));
        }),
      );
      presetList.querySelectorAll('.cai-preset-delete').forEach((btn) =>
        btn.addEventListener('click', (e) => {
          e.stopPropagation();
          deletePreset(btn.dataset.preset);
        }),
      );
      presetList.querySelectorAll('.cai-preset-export').forEach((btn) =>
        btn.addEventListener('click', (e) => {
          e.stopPropagation();
          exportPreset(btn.dataset.preset);
          document
            .querySelectorAll('.cai-preset-dropdown.open')
            .forEach((d) => d.classList.remove('open'));
        }),
      );
    }

    // ============================================
    // UI SYNC
    // ============================================
    function updateColorBtnBorder(btnId, color) {
      const btn = document.getElementById(btnId);
      if (btn) btn.style.borderColor = color;
    }

    function setEl(id, prop, value) {
      const el = document.getElementById(id);
      if (!el) return;
      if (prop === 'value') el.value = value;
      else if (prop === 'checked') el.checked = value;
      else if (prop === 'text') el.textContent = value;
    }

    function syncUIWithSettings() {
      const s = STATE;

      // Bubble - Update chip active states
      document
        .querySelectorAll('.cai-chip[data-bubble-mode]')
        .forEach((chip) => {
          if (chip.dataset.bubbleMode === s.bubbleMode) {
            chip.classList.add('active');
          } else {
            chip.classList.remove('active');
          }
        });

      setEl('cai-global-color', 'value', s.bubbleGlobal);
      setEl('cai-ai-color', 'value', s.bubbleAi);
      setEl('cai-user-color', 'value', s.bubbleUser);
      updateColorBtnBorder('cai-global-color-btn', s.bubbleGlobal);
      updateColorBtnBorder('cai-ai-color-btn', s.bubbleAi);
      updateColorBtnBorder('cai-user-color-btn', s.bubbleUser);
      document
        .getElementById('cai-global-color-group')
        ?.classList.toggle('cai-hidden', s.bubbleMode !== 'global');
      document
        .getElementById('cai-separate-color-group')
        ?.classList.toggle('cai-hidden', s.bubbleMode !== 'separate');

      // Spacing
      const spacing = parseInt(s.bubbleSpacing) || 8;
      setEl('cai-message-spacing', 'value', spacing);
      setEl('cai-spacing-val', 'text', spacing);

      // Glass
      setEl('cai-glass-enabled', 'checked', s.glassEnabled);
      setEl('cai-glass-blur', 'value', s.glassBlur);
      setEl('cai-glass-blur-val', 'text', s.glassBlur + '');
      setEl('cai-glass-opacity', 'value', s.glassOpacity);
      setEl(
        'cai-glass-opacity-val',
        'text',
        Math.round(parseFloat(s.glassOpacity) * 100) + '%',
      );

      // Border
      setEl('cai-border-enabled', 'checked', s.borderEnabled);
      setEl('cai-border-width', 'value', s.borderWidth);
      setEl('cai-border-width-val', 'text', s.borderWidth + '');
      setEl('cai-border-color', 'value', s.borderColor);
      updateColorBtnBorder('cai-border-color-btn', s.borderColor);

      // Shadow
      setEl('cai-shadow-enabled', 'checked', s.shadowEnabled);
      setEl('cai-shadow-blur', 'value', s.shadowBlur);
      setEl('cai-shadow-blur-val', 'text', s.shadowBlur + '');
      setEl('cai-shadow-offset-y', 'value', s.shadowOffsetY);
      setEl('cai-shadow-offset-y-val', 'text', s.shadowOffsetY + '');
      setEl('cai-shadow-opacity', 'value', s.shadowOpacity);
      setEl(
        'cai-shadow-opacity-val',
        'text',
        Math.round(parseFloat(s.shadowOpacity) * 100) + '%',
      );
      setEl('cai-shadow-color', 'value', s.shadowColor);
      updateColorBtnBorder('cai-shadow-color-btn', s.shadowColor);

      // Font
      setEl('cai-font-family', 'value', s.fontFamily);
      setEl('cai-custom-font-url', 'value', s.fontCustomUrl);
      setEl('cai-font-size', 'value', parseInt(s.fontSize) || 14);
      setEl('cai-fontsize-val', 'text', (parseInt(s.fontSize) || 14) + '');
      setEl('cai-font-weight', 'value', s.fontWeight);
      setEl('cai-line-height', 'value', s.lineHeight);
      setEl('cai-lineheight-val', 'text', s.lineHeight);
      document
        .getElementById('cai-custom-font-group')
        ?.classList.toggle('cai-hidden', s.fontFamily !== 'custom');

      // Text colors - Update chip active states
      document.querySelectorAll('.cai-chip[data-text-mode]').forEach((chip) => {
        if (chip.dataset.textMode === s.textColorMode) {
          chip.classList.add('active');
        } else {
          chip.classList.remove('active');
        }
      });

      setEl('cai-text-global-color', 'value', s.textColorGlobal);
      setEl('cai-text-ai-color', 'value', s.textColorAi);
      setEl('cai-text-user-color', 'value', s.textColorUser);
      updateColorBtnBorder('cai-text-global-color-btn', s.textColorGlobal);
      updateColorBtnBorder('cai-text-ai-color-btn', s.textColorAi);
      updateColorBtnBorder('cai-text-user-color-btn', s.textColorUser);
      document
        .getElementById('cai-text-global-group')
        ?.classList.toggle('cai-hidden', s.textColorMode !== 'global');
      document
        .getElementById('cai-text-separate-group')
        ?.classList.toggle('cai-hidden', s.textColorMode !== 'separate');

      // Italic / Bold
        setEl('cai-italic-enabled', 'checked', s.textItalicEnabled);
        setEl('cai-bold-enabled', 'checked', s.textBoldEnabled);
        setEl('cai-quote-enabled', 'checked', s.textQuoteEnabled);
        setEl('cai-italic-color', 'value', s.textItalicColor);
        setEl('cai-bold-color', 'value', s.textBoldColor);
        setEl('cai-quote-color', 'value', s.textQuoteColor);
      updateColorBtnBorder('cai-italic-color-btn', s.textItalicColor);
      updateColorBtnBorder('cai-bold-color-btn', s.textBoldColor);

      // Corners
      const cornerMode = s.cornerMode || 'uniform';
      const uniformBtn = document.getElementById('cai-corner-uniform');
      const customBtn = document.getElementById('cai-corner-custom');
      if (cornerMode === 'uniform') {
        uniformBtn?.classList.add('active');
        customBtn?.classList.remove('active');
        document
          .getElementById('cai-uniform-group')
          ?.classList.remove('cai-hidden');
        document
          .getElementById('cai-custom-group')
          ?.classList.add('cai-hidden');
      } else {
        customBtn?.classList.add('active');
        uniformBtn?.classList.remove('active');
        document
          .getElementById('cai-custom-group')
          ?.classList.remove('cai-hidden');
        document
          .getElementById('cai-uniform-group')
          ?.classList.add('cai-hidden');
      }
      setEl('cai-uniform-radius', 'value', s.cornerTopLeft);
      setEl('cai-uniform-val', 'text', s.cornerTopLeft + '');
      setEl('cai-corner-tl', 'value', s.cornerTopLeft);
      setEl('cai-tl-val', 'text', s.cornerTopLeft + '');
      setEl('cai-corner-tr', 'value', s.cornerTopRight);
      setEl('cai-tr-val', 'text', s.cornerTopRight + '');
      setEl('cai-corner-br', 'value', s.cornerBottomRight);
      setEl('cai-br-val', 'text', s.cornerBottomRight + '');
      setEl('cai-corner-bl', 'value', s.cornerBottomLeft);
      setEl('cai-bl-val', 'text', s.cornerBottomLeft + '');

      // Background - Update chip active states
      document.querySelectorAll('.cai-chip[data-bg-type]').forEach((chip) => {
        if (chip.dataset.bgType === s.bgType) {
          chip.classList.add('active');
        } else {
          chip.classList.remove('active');
        }
      });

      setEl('cai-bg-blur', 'value', parseInt(s.bgBlur) || 0);
      setEl('cai-blur-val', 'text', (parseInt(s.bgBlur) || 0) + '');
      setEl('cai-bg-brightness', 'value', parseInt(s.bgBrightness) || 100);
      setEl('cai-bright-val', 'text', (parseInt(s.bgBrightness) || 100) + '');
      setEl(
        'cai-overlay-opacity',
        'value',
        parseFloat(s.bgOverlayOpacity) || 0,
      );
      setEl(
        'cai-overlay-val',
        'text',
        (parseFloat(s.bgOverlayOpacity) || 0) + '',
      );
      setEl('cai-overlay-color', 'value', s.bgOverlayColor);
      updateColorBtnBorder('cai-overlay-color-btn', s.bgOverlayColor);
      document
        .getElementById('cai-url-input')
        ?.classList.toggle('cai-hidden', s.bgType !== 'url');
      document
        .getElementById('cai-file-input')
        ?.classList.toggle('cai-hidden', s.bgType !== 'file');

      // Initialize URL input and clear button visibility
      const bgUrlInput = document.getElementById('cai-bg-url');
      const clearUrlBtn = document.getElementById('cai-clear-url');
      if (bgUrlInput && clearUrlBtn) {
        const savedUrl = s.bgUrl || '';
        bgUrlInput.value = savedUrl;
        clearUrlBtn.style.display = savedUrl.trim().length > 0 ? 'flex' : 'none';
      }

      updateFileStatusDisplay();

      // Misc
      setEl(
        'cai-disclaimer-toggle',
        'checked',
        GM_getValue('cai_disclaimers_hidden', false),
      );
      setEl('cai-session-timer-toggle', 'checked', s.sessionTimerEnabled);
      setEl('cai-session-timer-prefix', 'value', s.sessionTimerPrefix);
      const prefixPreview = document.getElementById('cai-timer-preview');
      if (prefixPreview) {
        const isActive = s.sessionTimerPrefix === 'Active';
        if (isActive) {
          prefixPreview.innerHTML = `Preview: "🟢 Active now" → "Active 12 mins ago. This is a fake offline mode to simulate the look for FB/Instagram."`;
        } else {
          prefixPreview.textContent = `Preview: "${s.sessionTimerPrefix} 12 minutes"`;
        }
      }
      updateCornerPreviews();
    }

    // ============================================
    // RESET FUNCTIONS — fully restore defaults incl. toggled features
    // ============================================
    function resetCategory(category) {
      const keys = {
        background: [
          'bgType',
          'bgBlur',
          'bgBrightness',
          'bgOverlayOpacity',
          'bgOverlayColor',
        ],
        bubbles: [
          'bubbleMode',
          'bubbleGlobal',
          'bubbleAi',
          'bubbleUser',
          'bubbleSpacing',
          'shadowEnabled',
          'shadowBlur',
          'shadowSpread',
          'shadowOffsetX',
          'shadowOffsetY',
          'shadowOpacity',
          'shadowColor',
          'glassEnabled',
          'glassBlur',
          'glassOpacity',
          'borderEnabled',
          'borderWidth',
          'borderColor',
        ],
        corners: [
          'cornerMode',
          'cornerTopLeft',
          'cornerTopRight',
          'cornerBottomRight',
          'cornerBottomLeft',
        ],
        typography: [
          'fontFamily',
          'fontCustomUrl',
          'fontSize',
          'fontWeight',
          'lineHeight',
          'textColorMode',
          'textColorGlobal',
          'textColorAi',
          'textColorUser',
          'textItalicColor',
          'textBoldColor',
          'textItalicEnabled',
          'textBoldEnabled',
        ],
      };
      const toReset = keys[category] || [];
      toReset.forEach((key) => {
        if (STORAGE[key] !== undefined && DEFAULTS[key] !== undefined) {
          GM_setValue(STORAGE[key], DEFAULTS[key]);
          STATE[key] = DEFAULTS[key];
        }
      });
      if (category === 'typography') cleanupOldFonts();
      applyAll();
      const labels = {
        background: 'Background',
        bubbles: 'Bubble',
        corners: 'Corners',
        typography: 'Typography',
      };
      showNotification(
        `${labels[category] || category} reset to default`,
        'info',
      );
    }

    function resetAllToDefault() {
      if (
        confirm('Reset ALL settings to default? This will reload the page.')
      ) {
        for (const [key, storageKey] of Object.entries(STORAGE)) {
          if (DEFAULTS[key] !== undefined)
            GM_setValue(storageKey, DEFAULTS[key]);
        }
        showNotification('All settings reset. Reloading...', 'success');
        setTimeout(() => location.reload(), 1000);
      }
    }

    // ============================================
    // CORNER PREVIEWS
    // ============================================
    function updateCornerPreviews() {
      const mode = STATE.cornerMode || 'uniform';
      if (mode === 'uniform') {
        const r = STATE.cornerTopLeft || '18';
        const p = document.getElementById('cai-uniform-preview');
        if (p) p.style.borderRadius = `${r}px`;
      } else {
        const tl = STATE.cornerTopLeft || '18',
          tr = STATE.cornerTopRight || '18';
        const br = STATE.cornerBottomRight || '18',
          bl = STATE.cornerBottomLeft || '18';
        const p = document.getElementById('cai-custom-preview');
        if (p) p.style.borderRadius = `${tl}px ${tr}px ${br}px ${bl}px`;
      }
    }

    // ============================================
    // SETTINGS DEFINITIONS
    // ============================================
    const ALL_SETTINGS = [
      // Background
      {
        id: 'setting-bg-type',
        label: 'Background Type Image URL Local File',
        category: 'Background',
        html: `
    <div class="cai-section-title">Background Type</div>
    <div class="cai-chip-group">
        <button class="cai-chip" data-bg-type="none">None</button>
        <button class="cai-chip" data-bg-type="url">Image URL</button>
        <button class="cai-chip" data-bg-type="file">Local File</button>
    </div>
        <div id="cai-url-input" class="cai-hidden" style="margin-top:10px;">
        <div style="position:relative;display:flex;align-items:center;">
            <input type="text" id="cai-bg-url" placeholder="https://..." style="flex:1;padding-right:36px;">
            <button id="cai-clear-url" style="position:absolute;right:8px;width:24px;height:24px;background:none;border:none;cursor:pointer;border-radius:50%;display:none;align-items:center;justify-content:center;transition:background 0.15s;padding:0;" title="Clear URL">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#8888a0" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
            </button>
        </div>
        <div id="cai-url-status" style="display:none;margin-top:6px;padding:5px 8px;background:rgba(255,255,255,0.04);border-radius:6px;font-size:10px;color:#8888a0;word-break:break-all;"></div>
        <button id="cai-apply-bg" class="cai-btn" style="margin-top:8px;width:100%;">Apply</button>
    </div>
    <div id="cai-file-input" class="cai-hidden" style="margin-top:10px;">
        <label class="cai-file-label">Choose Image <input type="file" id="cai-bg-file" accept="image/*"></label>
                <div id="cai-file-status" style="display:none;margin-top:8px;">
            <div style="display:inline-flex;align-items:center;gap:8px;padding:6px 12px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-radius:100px;">
                <span id="cai-file-name" style="color:#8888a0;font-size:11px;">No file selected</span>
                <button id="cai-clear-file" style="width:18px;height:18px;background:none;border:none;cursor:pointer;border-radius:50%;display:flex;align-items:center;justify-content:center;padding:0;transition:background 0.15s;" title="Clear file">
                    <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#8888a0" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
                </button>
            </div>
        </div>
    </div>
`,
      },

      {
        id: 'setting-per-char-bg',
        label: 'Per-Character Backgrounds',
        category: 'Background',
        html: `
    <div style="display:flex;justify-content:space-between;align-items:center;">
        <div>
            <div class="cai-section-title" style="margin:0;">Per-Character Backgrounds</div>
            <div class="cai-info-small" style="margin-top:3px;">Save different backgrounds for each character</div>
        </div>
        <label class="cai-switch-label">
            <input type="checkbox" id="cai-per-char-bg-toggle">
            <span class="cai-switch-slider"></span>
        </label>
    </div>
    <div id="cai-storage-warning" class="cai-storage-warning" style="display:none; margin-top:10px; padding:10px; background:rgba(245,158,11,0.1); border-left:3px solid #f59e0b; border-radius:6px; font-size:11px;">
        <strong>⚠️ Storage Warning</strong><br>
        Using local files for character backgrounds consumes script storage quickly.<br>
        <strong>Recommended:</strong> Use image URLs instead of uploading files.<br>
        <span id="cai-storage-usage" style="font-size:10px; opacity:0.8;"></span>
    </div>
    <div id="cai-per-char-bg-actions" style="margin-top:12px;display:none;">
        <button id="cai-save-char-bg" class="cai-btn" style="width:100%;">Save current background for this character</button>
        <button id="cai-clear-char-bg" class="cai-reset-btn" style="margin-top:8px;">Clear character background</button>
    </div>
    <div id="cai-char-bg-status" class="cai-info-small" style="margin-top:8px;color:#6655bb;display:none;"></div>
`,
      },
      {
        id: 'setting-bg-blur',
        label: 'Background Blur',
        category: 'Background',
        html: `
                <div class="cai-section-title">Blur <span class="cai-slider-val" id="cai-blur-val">0</span>px</div>
                <input type="range" id="cai-bg-blur" min="0" max="20" value="0">
            `,
      },
      {
        id: 'setting-bg-brightness',
        label: 'Background Brightness',
        category: 'Background',
        html: `
                <div class="cai-section-title">Brightness <span class="cai-slider-val" id="cai-bright-val">100</span>%</div>
                <input type="range" id="cai-bg-brightness" min="30" max="150" value="100">
            `,
      },
      {
        id: 'setting-overlay',
        label: 'Overlay Opacity Color',
        category: 'Background',
        html: `
                <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
                    <span class="cai-section-title" style="margin:0;">Overlay Color</span>
                    <div style="position:relative;">
                        <button class="cai-color-btn" id="cai-overlay-color-btn" onclick="document.getElementById('cai-overlay-color').click()">Select to change</button>
                        <input type="color" id="cai-overlay-color" value="#000000" style="position:absolute;opacity:0;width:0;height:0;">
                    </div>
                </div>
                <div class="cai-section-title">Overlay Opacity <span class="cai-slider-val" id="cai-overlay-val">0</span>%</div>
                <input type="range" id="cai-overlay-opacity" min="0" max="80" value="0">
            `,
      },
      {
        id: 'setting-bg-reset',
        label: 'Reset Background',
        category: 'Background',
        html: `<button class="cai-reset-btn" data-category="background">Reset to Default</button>`,
      },

      // Bubble Chat
            {
        id: 'setting-bubble-combined',
        label: 'Bubble Colors',
        category: 'Bubble Chat',
        html: `
    <div class="cai-section-title">Bubble Mode</div>
    <div class="cai-chip-group" style="margin-bottom:14px;">
        <button class="cai-chip" data-bubble-mode="global">Same Color for All</button>
        <button class="cai-chip" data-bubble-mode="separate">Separate Colors</button>
    </div>
    <div id="cai-global-color-group">
        <div style="display:flex;justify-content:space-between;align-items:center;">
            <span class="cai-section-title" style="margin:0;">Global Bubble Color</span>
            <div style="position:relative;">
                <button class="cai-color-btn" id="cai-global-color-btn" onclick="document.getElementById('cai-global-color').click()">Select to change</button>
                <input type="color" id="cai-global-color" value="#2d2d3d" style="position:absolute;opacity:0;width:0;height:0;">
            </div>
        </div>
    </div>
    <div id="cai-separate-color-group" class="cai-hidden">
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
            <span class="cai-section-title" style="margin:0;">AI Bubble Color</span>
            <div style="position:relative;">
                <button class="cai-color-btn" id="cai-ai-color-btn" onclick="document.getElementById('cai-ai-color').click()">Select to change</button>
                <input type="color" id="cai-ai-color" value="#2d2d3d" style="position:absolute;opacity:0;width:0;height:0;">
            </div>
        </div>
        <div style="display:flex;justify-content:space-between;align-items:center;">
            <span class="cai-section-title" style="margin:0;">User Bubble Color</span>
            <div style="position:relative;">
                <button class="cai-color-btn" id="cai-user-color-btn" onclick="document.getElementById('cai-user-color').click()">Select to change</button>
                <input type="color" id="cai-user-color" value="#1a1a2e" style="position:absolute;opacity:0;width:0;height:0;">
            </div>
        </div>
    </div>
`,
      },
      {
        id: 'setting-message-spacing',
        label: 'Message Spacing',
        category: 'Bubble Chat',
        html: `
    <div class="cai-section-title">Space Between Messages <span class="cai-slider-val" id="cai-spacing-val">8</span>px</div>
    <input type="range" id="cai-message-spacing" min="0" max="32" step="1" value="8">
    <div class="cai-info-small">Adds vertical space between each chat bubble</div>
`,
      },
      {
        id: 'setting-glass',
        label: 'Glassmorphism Effect Blur Intensity Opacity',
        category: 'Bubble Chat',
        html: `
                <div style="display:flex;justify-content:space-between;align-items:center;">
                    <span class="cai-section-title" style="margin:0;">Glassmorphism Effect</span>
                    <label class="cai-switch-label"><input type="checkbox" id="cai-glass-enabled"><span class="cai-switch-slider"></span></label>
                </div>
                <div class="cai-info-small">May affect performance in long conversations</div>
                <div style="margin-top:10px;">
                    <div class="cai-section-title">Blur Intensity <span class="cai-slider-val" id="cai-glass-blur-val">10</span>px</div>
                    <input type="range" id="cai-glass-blur" min="0" max="30" value="10">
                    <div class="cai-section-title" style="margin-top:8px;">Opacity <span class="cai-slider-val" id="cai-glass-opacity-val">70%</span></div>
                    <input type="range" id="cai-glass-opacity" min="0" max="1" step="0.05" value="0.7">
                </div>
            `,
      },
      {
        id: 'setting-border',
        label: 'Chat Bubble Border Width Color',
        category: 'Bubble Chat',
        html: `
                <div style="display:flex;justify-content:space-between;align-items:center;">
                    <span class="cai-section-title" style="margin:0;">Chat Bubble Border</span>
                    <label class="cai-switch-label"><input type="checkbox" id="cai-border-enabled"><span class="cai-switch-slider"></span></label>
                </div>
                <div style="margin-top:10px;">
                    <div class="cai-section-title">Border Width <span class="cai-slider-val" id="cai-border-width-val">2</span>px</div>
                    <input type="range" id="cai-border-width" min="1" max="8" step="1" value="2">
                    <div style="display:flex;justify-content:space-between;align-items:center;margin-top:10px;">
                        <span class="cai-section-title" style="margin:0;">Border Color</span>
                        <div style="position:relative;">
                            <button class="cai-color-btn" id="cai-border-color-btn" onclick="document.getElementById('cai-border-color').click()">Select to change</button>
                            <input type="color" id="cai-border-color" value="#ffffff" style="position:absolute;opacity:0;width:0;height:0;">
                        </div>
                    </div>
                </div>
            `,
      },
      {
        id: 'setting-shadow',
        label: 'Shadow Effects Blur Offset Opacity Color',
        category: 'Bubble Chat',
        html: `
                <div style="display:flex;justify-content:space-between;align-items:center;">
                    <span class="cai-section-title" style="margin:0;">Shadow Effects</span>
                    <label class="cai-switch-label"><input type="checkbox" id="cai-shadow-enabled"><span class="cai-switch-slider"></span></label>
                </div>
                <div style="margin-top:10px;">
                    <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
                        <span class="cai-section-title" style="margin:0;">Shadow Color</span>
                        <div style="position:relative;">
                            <button class="cai-color-btn" id="cai-shadow-color-btn" onclick="document.getElementById('cai-shadow-color').click()">Select to change</button>
                            <input type="color" id="cai-shadow-color" value="#000000" style="position:absolute;opacity:0;width:0;height:0;">
                        </div>
                    </div>
                    <div class="cai-two-col">
                        <div>
                            <div class="cai-section-title">Blur <span class="cai-slider-val" id="cai-shadow-blur-val">12</span>px</div>
                            <input type="range" id="cai-shadow-blur" min="0" max="40" value="12">
                        </div>
                        <div>
                            <div class="cai-section-title">Offset Y <span class="cai-slider-val" id="cai-shadow-offset-y-val">4</span>px</div>
                            <input type="range" id="cai-shadow-offset-y" min="-20" max="20" value="4">
                        </div>
                    </div>
                    <div class="cai-section-title" style="margin-top:8px;">Opacity <span class="cai-slider-val" id="cai-shadow-opacity-val">30%</span></div>
                    <input type="range" id="cai-shadow-opacity" min="0" max="1" step="0.01" value="0.3">
                </div>
            `,
      },
      {
        id: 'setting-bubbles-reset',
        label: 'Reset Bubble Settings',
        category: 'Bubble Chat',
        html: `<button class="cai-reset-btn" data-category="bubbles">Reset to Default</button>`,
      },

      // Corners
      {
        id: 'setting-corner-mode',
        label: 'Corner Mode Uniform Custom',
        category: 'Corners',
        html: `
                <div class="cai-section-title">Corner Mode</div>
                <div class="cai-two-mode-btns">
                    <button id="cai-corner-uniform" class="cai-mode-btn2 active">Uniform</button>
                    <button id="cai-corner-custom" class="cai-mode-btn2">Custom</button>
                </div>
            `,
      },
      {
        id: 'setting-corner-radius',
        label: 'Corner Radius Top Left Right Bottom',
        category: 'Corners',
        html: `
                <div id="cai-uniform-group">
                    <div class="cai-two-col">
                        <div>
                            <div class="cai-section-title">Corner Radius <span class="cai-slider-val" id="cai-uniform-val">18</span>px</div>
                            <input type="range" id="cai-uniform-radius" min="0" max="60" step="2" value="18">
                        </div>
                        <div class="cai-corner-preview-box"><div id="cai-uniform-preview" class="cai-preview-shape" style="border-radius:18px;"></div></div>
                    </div>
                </div>
                <div id="cai-custom-group" class="cai-hidden">
                    <div class="cai-corner-grid">
                        <div class="cai-corner-item">
                            <div class="cai-section-title">Top-Left <span class="cai-slider-val" id="cai-tl-val">18</span>px</div>
                            <input type="range" id="cai-corner-tl" min="0" max="60" step="2" value="18">
                        </div>
                        <div class="cai-corner-item">
                            <div class="cai-section-title">Top-Right <span class="cai-slider-val" id="cai-tr-val">18</span>px</div>
                            <input type="range" id="cai-corner-tr" min="0" max="60" step="2" value="18">
                        </div>
                        <div class="cai-corner-item">
                            <div class="cai-section-title">Bottom-Right <span class="cai-slider-val" id="cai-br-val">18</span>px</div>
                            <input type="range" id="cai-corner-br" min="0" max="60" step="2" value="18">
                        </div>
                        <div class="cai-corner-item">
                            <div class="cai-section-title">Bottom-Left <span class="cai-slider-val" id="cai-bl-val">18</span>px</div>
                            <input type="range" id="cai-corner-bl" min="0" max="60" step="2" value="18">
                        </div>
                    </div>
                    <div class="cai-corner-preview-box"><div id="cai-custom-preview" class="cai-preview-shape"></div></div>
                </div>
            `,
      },
      {
        id: 'setting-corners-reset',
        label: 'Reset Corners',
        category: 'Corners',
        html: `<button class="cai-reset-btn" data-category="corners">Reset to Default</button>`,
      },

      // Typography
      {
        id: 'setting-typography-combined',
        label: 'Font Settings',
        category: 'Typography',
        html: `
    <div class="cai-two-col">
        <div>
            <div class="cai-section-title">Font Family</div>
            <div class="cai-select-wrapper">
                <select id="cai-font-family">
                    <option value="custom">Custom Font URL</option>
                    ${Object.keys(GOOGLE_FONTS)
                      .map((f) => `<option value="${f}">${f}</option>`)
                      .join('')}
                </select>
            </div>
        </div>
        <div>
            <div class="cai-section-title">Font Weight</div>
            <div class="cai-select-wrapper">
                <select id="cai-font-weight">
                    <option value="300">Light</option>
                    <option value="400" selected>Regular</option>
                    <option value="500">Medium</option>
                    <option value="600">Semi-Bold</option>
                    <option value="700">Bold</option>
                </select>
            </div>
        </div>
    </div>
    <div id="cai-custom-font-group" class="cai-hidden" style="margin-top:10px;">
        <div class="cai-section-title">Custom Google Font URL</div>
        <input type="text" id="cai-custom-font-url" placeholder="https://fonts.googleapis.com/css2?family=...">
    </div>
    <div class="cai-two-col" style="margin-top:12px;">
        <div>
            <div class="cai-section-title">Font Size <span class="cai-slider-val" id="cai-fontsize-val">14</span>px</div>
            <input type="range" id="cai-font-size" min="10" max="24" step="1" value="14">
        </div>
        <div>
            <div class="cai-section-title">Line Height <span class="cai-slider-val" id="cai-lineheight-val">1.5</span></div>
            <input type="range" id="cai-line-height" min="1.2" max="2.0" step="0.05" value="1.5">
        </div>
    </div>
`,
      },
      {
        id: 'setting-text-formats',
        label: 'Text Formats Main Italic Bold Quote Color',
        category: 'Typography',
        html: `
          <div class="cai-section-title" style="margin-bottom:12px;">Text Formats</div>

          <!-- ITALIC TEXT -->
          <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
            <div style="display:flex;align-items:center;gap:8px;">
              <span style="font-size:12px;color:#d0d0d8;">Italic Text</span>
              <label class="cai-switch-label"><input type="checkbox" id="cai-italic-enabled"><span class="cai-switch-slider"></span></label>
            </div>
            <div style="position:relative;">
              <button class="cai-color-btn" id="cai-italic-color-btn" onclick="document.getElementById('cai-italic-color').click()">Select to change</button>
              <input type="color" id="cai-italic-color" value="#a855f7" style="position:absolute;opacity:0;width:0;height:0;">
            </div>
          </div>

          <hr style="border:none;border-top:1px solid rgba(255,255,255,0.06);margin:10px 0;">

          <!-- BOLD TEXT -->
          <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
            <div style="display:flex;align-items:center;gap:8px;">
              <span style="font-size:12px;color:#d0d0d8;">Bold Text</span>
              <label class="cai-switch-label"><input type="checkbox" id="cai-bold-enabled"><span class="cai-switch-slider"></span></label>
            </div>
            <div style="position:relative;">
              <button class="cai-color-btn" id="cai-bold-color-btn" onclick="document.getElementById('cai-bold-color').click()">Select to change</button>
              <input type="color" id="cai-bold-color" value="#f59e0b" style="position:absolute;opacity:0;width:0;height:0;">
            </div>
          </div>

          <hr style="border:none;border-top:1px solid rgba(255,255,255,0.06);margin:10px 0;">

          <!-- QUOTE TEXT -->
          <div style="display:flex;justify-content:space-between;align-items:center;">
            <div style="display:flex;align-items:center;gap:8px;">
              <span style="font-size:12px;color:#d0d0d8;">Quote Text</span>
              <label class="cai-switch-label"><input type="checkbox" id="cai-quote-enabled"><span class="cai-switch-slider"></span></label>
            </div>
            <div style="position:relative;">
              <button class="cai-color-btn" id="cai-quote-color-btn" onclick="document.getElementById('cai-quote-color').click()">Select to change</button>
              <input type="color" id="cai-quote-color" value="#e0df7f" style="position:absolute;opacity:0;width:0;height:0;">
            </div>
          </div>
        `,
      },
      {
        id: 'setting-text-colors',
        label: 'Text Color Mode Global Separate AI User',
        category: 'Typography',
        html: `
    <div class="cai-section-title">Text Color Mode</div>
    <div class="cai-chip-group" style="margin-bottom:10px;">
        <button class="cai-chip" data-text-mode="global">Global</button>
        <button class="cai-chip" data-text-mode="separate">Separate</button>
    </div>
    <div id="cai-text-global-group">
        <div style="display:flex;justify-content:space-between;align-items:center;">
            <span class="cai-section-title" style="margin:0;">Global Text Color</span>
            <div style="position:relative;">
                <button class="cai-color-btn" id="cai-text-global-color-btn" onclick="document.getElementById('cai-text-global-color').click()">Select to change</button>
                <input type="color" id="cai-text-global-color" value="#e0e0e0" style="position:absolute;opacity:0;width:0;height:0;">
            </div>
        </div>
    </div>
    <div id="cai-text-separate-group" class="cai-hidden">
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
            <span class="cai-section-title" style="margin:0;">AI Text Color</span>
            <div style="position:relative;">
                <button class="cai-color-btn" id="cai-text-ai-color-btn" onclick="document.getElementById('cai-text-ai-color').click()">Select to change</button>
                <input type="color" id="cai-text-ai-color" value="#e0e0e0" style="position:absolute;opacity:0;width:0;height:0;">
            </div>
        </div>
        <div style="display:flex;justify-content:space-between;align-items:center;">
            <span class="cai-section-title" style="margin:0;">User Text Color</span>
            <div style="position:relative;">
                <button class="cai-color-btn" id="cai-text-user-color-btn" onclick="document.getElementById('cai-text-user-color').click()">Select to change</button>
                <input type="color" id="cai-text-user-color" value="#e0e0e0" style="position:absolute;opacity:0;width:0;height:0;">
            </div>
        </div>
    </div>
`,
      },
      {
        id: 'setting-typography-reset',
        label: 'Reset Typography',
        category: 'Typography',
        html: `<button class="cai-reset-btn" data-category="typography">Reset to Default</button>`,
      },

      // Presets
      {
        id: 'setting-presets',
        label: 'Save Load Delete Export Import Preset',
        category: 'Presets',
        html: `
                <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
                    <span class="cai-section-title" style="margin:0;">Save Current Presets</span>
                    <button class="cai-import-btn" id="cai-import-trigger">Import Preset</button>
                    <input type="file" id="cai-import-file" accept=".json" style="display:none;">
                </div>
                <div style="display:flex;gap:8px;margin-bottom:16px;">
                    <input type="text" id="cai-preset-name" placeholder="Preset name" style="flex:1;">
                    <button id="cai-save-preset" class="cai-btn-accent">Save</button>
                </div>
                <div class="cai-section-title">Saved Presets</div>
                <div id="cai-preset-list" class="cai-preset-list"></div>
            `,
      },

      // Miscellaneous
      {
        id: 'setting-session-timer',
        label: 'Session Timer Creator Replace Duration',
        category: 'Miscellaneous',
        html: `
    <div style="display:flex;justify-content:space-between;align-items:center;">
        <div>
            <div class="cai-section-title" style="margin:0;font-size:12px;color:#d0d0d8;">Session Timer</div>
            <div class="cai-info-small" style="margin-top:3px;">Replace creator name with how long you've been chatting.</div>
        </div>
        <label class="cai-switch-label">
            <input type="checkbox" id="cai-session-timer-toggle">
            <span class="cai-switch-slider"></span>
        </label>
    </div>
    <div id="cai-session-timer-settings" style="margin-top:10px;display:none;">
        <div class="cai-section-title" style="margin-bottom:4px;">Prefix</div>
        <div class="cai-select-wrapper">
            <select id="cai-session-timer-prefix">
                <option value="Talking for">Talking for</option>
                <option value="Chatting for">Chatting for</option>
                <option value="Conversation started">Conversation started</option>
                <option value="Live for">Live for</option>
                <option value="Connected">Connected</option>
                <option value="Active">Active</option>
            </select>
        </div>
        <div id="cai-reset-timer-group" style="margin-top:12px;display:none;">
            <button id="cai-reset-conversation-timer" class="cai-reset-btn" style="width:100%;">
                Reset conversation timer for this character
            </button>
            <div class="cai-info-small" style="margin-top:6px;">
                Conversation started: <span id="cai-timer-start-display">--</span>
            </div>
        </div>
        <div class="cai-info-small" style="margin-top:8px;font-style:italic;" id="cai-timer-preview">Preview: "Talking for 12 minutes"</div>
    </div>
`,
      },
      {
        id: 'setting-misc-export',
        label: 'Export Chat Conversation Download HTML',
        category: 'Miscellaneous',
        html: `
                <div style="display:flex;justify-content:space-between;align-items:center;">
                    <div>
                        <div class="cai-section-title" style="margin:0;font-size:12px;color:#d0d0d8;">Export this current chatbot conversation</div>
                        <div class="cai-info-small" style="margin-top:3px;">Download your current chatbot in a clean HTML format.</div>
                    </div>
                    <button id="cai-export-chat-btn" style="
                        display:flex;align-items:center;gap:6px;
                        padding:7px 13px;
                        background:#1e1e2e;border:1px solid rgba(255,255,255,0.1);
                        border-radius:8px;color:#d0d0d8;font-size:12px;
                        font-family:inherit;cursor:pointer;white-space:nowrap;
                        transition:background 0.15s;flex-shrink:0;
                    ">Export Chat <span style="opacity:0.5;font-size:11px;">›</span></button>
                </div>
            `,
      },
      {
        id: 'setting-misc-disclaimer',
        label: 'Remove Disclaimer Text AI Chatbot Warning',
        category: 'Miscellaneous',
        html: `
                <div style="display:flex;justify-content:space-between;align-items:center;">
                    <div>
                        <div class="cai-section-title" style="margin:0;font-size:12px;color:#d0d0d8;">Remove disclaimer text</div>
                        <div class="cai-info-small" style="margin-top:3px;">Remove disclaimer text on a chatbot. Just remember you're still talking to a bot.</div>
                    </div>
                    <label class="cai-switch-label">
                        <input type="checkbox" id="cai-disclaimer-toggle">
                        <span class="cai-switch-slider"></span>
                    </label>
                </div>
            `,
      },
      // NEW: Character Backgrounds Manager
      {
        id: 'setting-char-bg-manager',
        label: 'Manage Saved Character Backgrounds',
        category: 'Character Backgrounds',
        html: `
                <div id="cai-char-bg-manager">
                        <div id="cai-manager-storage-info" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding:0px 0px 12px 0px;border-bottom:1px solid rgba(255,255,255,0.06);">                        <span style="font-size:12px;">Storage: <span id="cai-manager-storage-used">0</span> MB / <span id="cai-manager-storage-limit">10</span> MB</span>
                        <div style="display:flex;gap:8px;">
<button id="cai-manager-export-all" style="background:none;border:none;color:#8888a0;font-size:11px;font-family:inherit;cursor:pointer;padding:4px 8px;border-radius:4px;transition:all 0.15s;">Export All</button>
<button id="cai-manager-import" style="background:none;border:none;color:#8888a0;font-size:11px;font-family:inherit;cursor:pointer;padding:4px 8px;border-radius:4px;transition:all 0.15s;">Import</button>                            <input type="file" id="cai-manager-import-file" accept=".json" style="display:none;">
                        </div>
                    </div>
                    <div id="cai-char-bg-list" style="max-height:400px;overflow-y:auto;">
                        <div class="cai-empty-state" style="padding:30px 20px;">
                            <div class="cai-empty-icon">📭</div>
                            <div>No saved character backgrounds</div>
                            <div class="cai-empty-sub">Save a background for a character to see it here.</div>
                        </div>
                    </div>
                    <div style="margin-top:12px;">
<button id="cai-manager-delete-all" style="width:100%;background:none;border:none;color:#8888a0;font-size:12px;font-family:inherit;cursor:pointer;padding:12px;border-radius:8px;transition:all 0.2s;">Delete All Character Backgrounds</button>
                    </div>
                </div>
            `,
      },
    ];

    const CATEGORIES = [
      'Background',
      'Bubble Chat',
      'Corners',
      'Typography',
      'Presets',
      'Character Backgrounds',
      'Miscellaneous',
    ];

    // ============================================
    // CREATE UI
    // ============================================
    const edgeBump = document.createElement('div');
    edgeBump.id = 'cai-edge-bump';
    edgeBump.innerHTML = '<div class="cai-edge-grip"></div>';
    document.body.appendChild(edgeBump);

    const edgeDrawer = document.createElement('div');
    edgeDrawer.id = 'cai-edge-drawer';
    edgeDrawer.innerHTML = `
        <div class="cai-drawer-content">
            <button id="cai-open-settings-btn" class="cai-drawer-btn">⚙️</button>
        </div>
    `;
    document.body.appendChild(edgeDrawer);

        const settingsBtn = document.createElement('div');
        settingsBtn.id = 'cai-settings-btn';
        document.body.appendChild(settingsBtn);
        settingsBtn.innerHTML = '⚙️';

    const settingsPanel = document.createElement('div');
    settingsPanel.id = 'cai-settings-panel';
    settingsPanel.innerHTML = `
    <div class="cai-panel-header" id="cai-drag-handle">
        <span class="cai-panel-title">CharFlow - Character AI Customization</span>
        <button id="cai-close-panel" class="cai-close-btn">✕</button>
    </div>
    <div class="cai-controls-row">
        <div class="cai-select-wrapper cai-category-select">
            <select id="cai-category-select">
                ${!GM_getValue('cai_onboarded', false) ? `<option value="">Select Category</option>` : ''}
                ${CATEGORIES.map((c) => `<option value="${c}">${c}</option>`).join('')}
            </select>
        </div>
        <div class="cai-search-wrapper">
            <span class="cai-search-icon">🔍</span>
            <input type="text" id="cai-search-input" placeholder="Search settings..." class="cai-search-input">
        </div>
    </div>
    <div class="cai-panel-content" id="cai-panel-content">
        ${
          !GM_getValue('cai_onboarded', false)
            ? `
            <div class="cai-empty-state" id="cai-select-hint">
                <div class="cai-empty-icon">👆</div>
                <div>Select a category above to get started.</div>
                <div class="cai-empty-sub">This won't show again.</div>
            </div>
        `
            : `
            <div class="cai-empty-state">
                <div class="cai-empty-icon">🙁</div>
                <div>Nothing to see here...</div>
                <div class="cai-empty-sub">Select a category above.</div>
            </div>
        `
        }
    </div>
    <div class="cai-panel-footer">
        <button id="cai-reset-all" class="cai-reset-all-btn">Reset All to Default</button>
    </div>
`;
    document.body.appendChild(settingsPanel);

    // ============================================
    // RENDER SETTINGS
    // ============================================
    let currentCategory = GM_getValue('cai_last_category', '');

    function renderSettings(category, searchQuery = '') {
      const content = document.getElementById('cai-panel-content');
      if (!content) return;

      if (searchQuery.trim()) {
        const q = searchQuery.toLowerCase();
        const matches = ALL_SETTINGS.filter(
          (s) =>
            s.label.toLowerCase().includes(q) ||
            s.category.toLowerCase().includes(q),
        );
        if (matches.length === 0) {
          content.innerHTML = `<div class="cai-empty-state"><div class="cai-empty-icon">🔍</div><div>No results for "${searchQuery}"</div></div>`;
        } else {
          content.innerHTML = matches
            .map(
              (s) =>
                `<div class="cai-settings-card"><div class="cai-card-category-badge">${s.category}</div>${s.html}</div>`,
            )
            .join('');
        }
      } else if (category) {
        const filtered = ALL_SETTINGS.filter((s) => s.category === category);
        if (filtered.length === 0) {
          content.innerHTML = `<div class="cai-empty-state"><div>Nothing here.</div></div>`;
        } else {
          content.innerHTML = `<div class="cai-category-heading">${category}</div>${filtered.map((s) => `<div class="cai-settings-card">${s.html}</div>`).join('')}`;
        }
      } else {
        content.innerHTML = `<div class="cai-empty-state"><div class="cai-empty-icon">🙁</div><div>Nothing to see here...</div><div class="cai-empty-sub">Select a category above.</div></div>`;
      }

      setupListeners();
      requestAnimationFrame(() => {
        syncUIWithSettings();
        updateCornerPreviews();
        setupCharBackgroundListeners();

        // Restore per-character background UI state after re-render
        const charId = getCurrentCharacterId();
        const savedBg = charId ? loadCharBackground() : null;
        const hasSavedBg = savedBg && savedBg.bgType && savedBg.bgType !== 'none';

        // Handle clear button visibility
        const clearBtn = document.getElementById('cai-clear-char-bg');
        if (clearBtn) {
          clearBtn.style.display = hasSavedBg ? 'block' : 'none';
        }

        // Handle status message visibility
        const statusEl = document.getElementById('cai-char-bg-status');
        const toggle = document.getElementById('cai-per-char-bg-toggle');

        if (statusEl && toggle && toggle.checked) {
          if (hasSavedBg) {
            statusEl.style.display = 'block';
            const savedDate = new Date(savedBg.savedAt).toLocaleDateString();
            statusEl.innerHTML = `🎨 Using custom background for this character (saved ${savedDate})`;
            statusEl.style.color = '#6655bb';
          } else {
            statusEl.style.display = 'none';
          }
        }

        if (
          category === 'Presets' ||
          searchQuery.toLowerCase().includes('preset')
        )
          loadPresetList();
        if (category === 'Character Backgrounds') {
          setupCharacterBackgroundManager();
        }
      });
    }

    // ============================================
    // DRAGGABLE
    // ============================================

        const savedLeft = GM_getValue('cai_btn_left', '20px');
        const savedTop = GM_getValue('cai_btn_top', '100px');
        settingsBtn.style.left = savedLeft;
        settingsBtn.style.top = savedTop;
        settingsBtn.style.transform = 'none';

    let isDraggingBtn = false, hasDragged = false, startX, startY, initialLeft, initialTop;

    function onDragStart(e) {
      e.preventDefault();
      isDraggingBtn = true;
      hasDragged = false;
      const point = e.touches ? e.touches[0] : e;
      startX = point.clientX;
      startY = point.clientY;
      const r = settingsBtn.getBoundingClientRect();
      initialLeft = r.left;
      initialTop = r.top;
    }

    function onDragMove(e) {
      if (!isDraggingBtn) return;
      const point = e.touches ? e.touches[0] : e;
      let l = initialLeft + point.clientX - startX;
      let top = initialTop + point.clientY - startY;
      if (Math.abs(point.clientX - startX) > 3 || Math.abs(point.clientY - startY) > 3) hasDragged = true;
      l = Math.min(window.innerWidth - settingsBtn.offsetWidth, Math.max(0, l));
      top = Math.min(window.innerHeight - settingsBtn.offsetHeight, Math.max(0, top));
      settingsBtn.style.left = l + 'px';
      settingsBtn.style.top = top + 'px';
      settingsBtn.style.transform = 'none';
    }

    function onDragEnd() {
      if (isDraggingBtn) {
        isDraggingBtn = false;
        GM_setValue('cai_btn_left', settingsBtn.style.left);
        GM_setValue('cai_btn_top', settingsBtn.style.top);
      }
    }

    settingsBtn.addEventListener('mousedown', onDragStart);
    window.addEventListener('mousemove', onDragMove);
    window.addEventListener('mouseup', onDragEnd);
    settingsBtn.addEventListener('touchstart', onDragStart, {passive: false});
    window.addEventListener('touchmove', onDragMove, {passive: false});
    window.addEventListener('touchend', onDragEnd);
    let isDraggingPanel = false,
      panelStartX,
      panelStartY,
      panelInitLeft,
      panelInitTop;
    const panelPosLeft = GM_getValue('cai_panel_left', null),
      panelPosTop = GM_getValue('cai_panel_top', null);
    const dragHandle = document.getElementById('cai-drag-handle');
    if (panelPosLeft && panelPosTop) {
      settingsPanel.style.left = panelPosLeft;
      settingsPanel.style.top = panelPosTop;
      settingsPanel.style.transform = 'none';
    }

    dragHandle.addEventListener('mousedown', (e) => {
      e.preventDefault();
      isDraggingPanel = true;
      panelStartX = e.clientX;
      panelStartY = e.clientY;
      const r = settingsPanel.getBoundingClientRect();
      panelInitLeft = r.left;
      panelInitTop = r.top;
      dragHandle.style.cursor = 'grabbing';
      settingsPanel.style.transform = 'none';
    });
    window.addEventListener('mousemove', (e) => {
      if (!isDraggingPanel) return;
      let l = panelInitLeft + e.clientX - panelStartX,
        t = panelInitTop + e.clientY - panelStartY;
      l = Math.min(
        window.innerWidth - settingsPanel.offsetWidth - 10,
        Math.max(10, l),
      );
      t = Math.min(
        window.innerHeight - settingsPanel.offsetHeight - 10,
        Math.max(10, t),
      );
      settingsPanel.style.left = l + 'px';
      settingsPanel.style.top = t + 'px';
    });
    window.addEventListener('mouseup', () => {
      if (isDraggingPanel) {
        isDraggingPanel = false;
        dragHandle.style.cursor = 'grab';
        GM_setValue('cai_panel_left', settingsPanel.style.left);
        GM_setValue('cai_panel_top', settingsPanel.style.top);
      }
    });
    dragHandle.style.cursor = 'grab';

    // Panel toggle
    let panelOpen = false;
    // Handle edge bump click - slide drawer in/out
    let drawerOpen = false;
        edgeBump.addEventListener('click', () => {
      drawerOpen = !drawerOpen;
      if (drawerOpen) {
        edgeDrawer.classList.add('cai-drawer-open');
        edgeBump.classList.add('cai-bump-pushed');
      } else {
        edgeDrawer.classList.remove('cai-drawer-open');
        edgeBump.classList.remove('cai-bump-pushed');
      }
    });

    // Desktop floating button opens panel directly
        settingsBtn.addEventListener('click', () => {
      if (hasDragged) return;
      panelOpen = !panelOpen;
      if (panelOpen) {
        if (!GM_getValue('cai_panel_left', null)) {
          settingsPanel.style.left = '';
          settingsPanel.style.top = '';
          settingsPanel.style.transform = 'translate(-50%, -50%)';
        } else {
          settingsPanel.style.transform = 'none';
        }
        const lastCat = GM_getValue('cai_last_category', '');
        if (lastCat) {
          document.getElementById('cai-category-select').value = lastCat;
          currentCategory = lastCat;
          renderSettings(lastCat);
        }
        updateStorageWarning();
      }
      settingsPanel.classList.toggle('open', panelOpen);
    });

    // Settings button inside drawer opens the actual settings panel
    document.getElementById('cai-open-settings-btn').addEventListener('click', () => {
      // Close the drawer first
      drawerOpen = false;
      edgeDrawer.classList.remove('cai-drawer-open');
      edgeBump.classList.remove('cai-bump-pushed');

      // Then open settings panel
      panelOpen = !panelOpen;
      if (panelOpen) {
        if (!GM_getValue('cai_panel_left', null)) {
          settingsPanel.style.left = '';
          settingsPanel.style.top = '';
          settingsPanel.style.transform = 'translate(-50%, -50%)';
        } else {
          settingsPanel.style.transform = 'none';
        }
        const lastCat = GM_getValue('cai_last_category', '');
        if (lastCat) {
          document.getElementById('cai-category-select').value = lastCat;
          currentCategory = lastCat;
          renderSettings(lastCat);
        }
        updateStorageWarning();
      }
      settingsPanel.classList.toggle('open', panelOpen);
    });

    // Click outside closes drawer
    document.addEventListener('click', (e) => {
      if (drawerOpen && !edgeDrawer.contains(e.target) && !edgeBump.contains(e.target)) {
        drawerOpen = false;
        edgeDrawer.classList.remove('cai-drawer-open');
        edgeBump.classList.remove('cai-bump-pushed');
      }
    });

    document.getElementById('cai-close-panel').addEventListener('click', () => {
      panelOpen = false;
      settingsPanel.classList.remove('open');
    });

    document
      .getElementById('cai-category-select')
      .addEventListener('change', (e) => {
        currentCategory = e.target.value;
        GM_setValue('cai_last_category', currentCategory);
        GM_setValue('cai_onboarded', true); // mark as seen
        document.getElementById('cai-search-input').value = '';

        const placeholder = document.querySelector(
          '#cai-category-select option[value=""]',
        );
        if (placeholder) placeholder.remove();

        document.getElementById('cai-search-input').value = '';
        renderSettings(currentCategory);
      });

    let searchDebounce = null;
    document
      .getElementById('cai-search-input')
      .addEventListener('input', (e) => {
        clearTimeout(searchDebounce);
        searchDebounce = setTimeout(
          () => renderSettings(currentCategory, e.target.value),
          200,
        );
      });

    document
      .getElementById('cai-reset-all')
      .addEventListener('click', resetAllToDefault);

    // ============================================
    // EVENT LISTENERS (re-run after each render)
    // ============================================
    function setupListeners() {
      // Reset buttons
      document
        .querySelectorAll('.cai-reset-btn')
        .forEach((btn) =>
          btn.addEventListener('click', () =>
            resetCategory(btn.dataset.category),
          ),
        );

      // Background type
        document.querySelectorAll('.cai-chip[data-bg-type]').forEach((chip) => {
        chip.addEventListener('click', () => {
          const newType = chip.dataset.bgType;
          saveState('bgType', newType);

          // Update active state
          document
            .querySelectorAll('.cai-chip[data-bg-type]')
            .forEach((c) => c.classList.remove('active'));
          chip.classList.add('active');

          // Auto-clear file when switching away from file mode
          if (newType !== 'file') {
            if (STATE.bgFile && STATE.bgFile !== '') {
              saveState('bgFile', '');
              showNotification(
                'File cleared (switched background type)',
                'info',
              );
            }
          }

          // Auto-clear URL when switching away from URL mode (silent)
          if (newType !== 'url') {
            if (STATE.bgUrl && STATE.bgUrl !== '') {
              saveState('bgUrl', '');
              const urlInput = document.getElementById('cai-bg-url');
              if (urlInput) urlInput.value = '';
            }
          }

          document
            .getElementById('cai-url-input')
            ?.classList.toggle('cai-hidden', newType !== 'url');
          document
            .getElementById('cai-file-input')
            ?.classList.toggle('cai-hidden', newType !== 'file');

          updateFileStatusDisplay();
          applyBackground();

          // Update save button state when background type changes
          const saveBtn = document.getElementById('cai-save-char-bg');
          if (saveBtn) {
            if (newType === 'none') {
              saveBtn.disabled = true;
              saveBtn.style.opacity = '0.5';
              saveBtn.style.cursor = 'not-allowed';
            } else {
              saveBtn.disabled = false;
              saveBtn.style.opacity = '1';
              saveBtn.style.cursor = 'pointer';
            }
          }
        });
      });
              // URL input - show/hide clear button based on content
      const bgUrlInput = document.getElementById('cai-bg-url');
      const clearUrlBtn = document.getElementById('cai-clear-url');

      if (bgUrlInput && clearUrlBtn) {
        // Show/hide clear button as user types
        bgUrlInput.addEventListener('input', () => {
          const hasValue = bgUrlInput.value.trim().length > 0;
          clearUrlBtn.style.display = hasValue ? 'flex' : 'none';
          // Save URL on input for live preview feel
          saveState('bgUrl', bgUrlInput.value.trim());
        });

        // Initialize visibility based on current state
        const initUrl = STATE.bgUrl || '';
        if (initUrl) {
          bgUrlInput.value = initUrl;
          clearUrlBtn.style.display = 'flex';
        }
      }

        document.getElementById('cai-apply-bg')?.addEventListener('click', () => {
            if (!window.location.pathname.includes('/chat/')) {
                showNotification('This setting only works inside a character chat', 'warning');
                return;
            }
            const url = bgUrlInput?.value?.trim();
            if (url) {
                saveState('bgUrl', url);
                applyBackground();
                const status = document.getElementById('cai-url-status');
                if (status) {
                    status.textContent = '✅ Applied: ' + url;
                    status.style.display = 'block';
                }
            }
        });

      clearUrlBtn?.addEventListener('click', () => {
        if (bgUrlInput) {
          bgUrlInput.value = '';
          clearUrlBtn.style.display = 'none';
        }
        saveState('bgUrl', '');
        applyBackground();
        const status = document.getElementById('cai-url-status');
        if (status) {
          status.textContent = '';
          status.style.display = 'none';
        }
      });

      // Hover effect for the clear button
      clearUrlBtn?.addEventListener('mouseenter', () => {
        clearUrlBtn.style.background = 'rgba(255,255,255,0.08)';
        const svgPath = clearUrlBtn.querySelector('path');
        if (svgPath) svgPath.setAttribute('stroke', '#d0d0d8');
      });

      clearUrlBtn?.addEventListener('mouseleave', () => {
        clearUrlBtn.style.background = 'transparent';
        const svgPath = clearUrlBtn.querySelector('path');
        if (svgPath) svgPath.setAttribute('stroke', '#8888a0');
      });

      document.getElementById('cai-clear-url')?.addEventListener('click', () => {
        const input = document.getElementById('cai-bg-url');
        if (input) input.value = '';
        saveState('bgUrl', '');
        applyBackground();
        const status = document.getElementById('cai-url-status');
        if (status) {
          status.textContent = '';
          status.style.display = 'none';
        }
      });

        document.getElementById('cai-bg-file')?.addEventListener('change', e => {
            if (!window.location.pathname.includes('/chat/')) {
                showNotification('Local file background only works inside a character chat', 'warning');
                e.target.value = '';
                return;
            }
            const file = e.target.files[0];
            if (file) {
                const r = new FileReader();
                r.onload = ev => {
                    saveState('bgFile', ev.target.result);
                    saveState('bgType', 'file');
                    applyBackground();
                    updateFileStatusDisplay();
                    if (file.size > 500 * 1024) {
                        showNotification(`⚠️ Large file...`, 'warning');
                    } else if (file.size > 300 * 1024) {
                        showNotification(`Note: This file...`, 'info');
                    } else {
                        showNotification('Background loaded for preview', 'success');
                    }
                    updateStorageWarning();
                };
                r.readAsDataURL(file);
            }
        });

        // Also handle case when user cancels file selection or selects the same file
        document.getElementById('cai-bg-file')?.addEventListener('click', function(e) {
            this.value = null; // Allow re-selecting the same file
        });

      // Clear file button (chip-style)
      const clearFileBtn = document.getElementById('cai-clear-file');
      if (clearFileBtn) {
        clearFileBtn.addEventListener('click', () => {
          if (STATE.bgType === 'file') {
            clearLoadedFile();
          } else {
            // Even if not in file mode, just clear the stored file
            saveState('bgFile', '');
            updateFileStatusDisplay();
            showNotification('File cleared from storage', 'info');
          }
        });

        // Hover effects for the circular clear button
        clearFileBtn.addEventListener('mouseenter', () => {
          clearFileBtn.style.background = 'rgba(239,68,68,0.15)';
          const svgPath = clearFileBtn.querySelector('path');
          if (svgPath) svgPath.setAttribute('stroke', '#ff9090');
        });

        clearFileBtn.addEventListener('mouseleave', () => {
          clearFileBtn.style.background = 'transparent';
          const svgPath = clearFileBtn.querySelector('path');
          if (svgPath) svgPath.setAttribute('stroke', '#8888a0');
        });
      }

      // Sliders
      const sliders = [
        [
          'cai-bg-blur',
          'cai-blur-val',
          'px',
          (v) => {
            saveState('bgBlur', v + 'px');
            applyBackground();
          },
          false,
        ],
        [
          'cai-bg-brightness',
          'cai-bright-val',
          '%',
          (v) => {
            saveState('bgBrightness', v + '%');
            applyBackground();
          },
          false,
        ],
        [
          'cai-overlay-opacity',
          'cai-overlay-val',
          '%',
          (v) => {
            saveState('bgOverlayOpacity', v);
            applyBackground();
          },
          false,
        ],
        [
          'cai-message-spacing',
          'cai-spacing-val',
          'px',
          (v) => {
            saveState('bubbleSpacing', v + 'px');
            applyStyles();
          },
          false,
        ],
        [
          'cai-glass-blur',
          'cai-glass-blur-val',
          'px',
          (v) => {
            saveState('glassBlur', v);
            applyStyles();
          },
          false,
        ],
        [
          'cai-glass-opacity',
          'cai-glass-opacity-val',
          '%',
          (v) => {
            saveState('glassOpacity', v);
            applyStyles();
          },
          true,
        ],
        [
          'cai-border-width',
          'cai-border-width-val',
          'px',
          (v) => {
            saveState('borderWidth', v);
            applyStyles();
          },
          false,
        ],
        [
          'cai-shadow-blur',
          'cai-shadow-blur-val',
          'px',
          (v) => {
            saveState('shadowBlur', v);
            applyStyles();
          },
          false,
        ],
        [
          'cai-shadow-offset-y',
          'cai-shadow-offset-y-val',
          'px',
          (v) => {
            saveState('shadowOffsetY', v);
            applyStyles();
          },
          false,
        ],
        [
          'cai-shadow-opacity',
          'cai-shadow-opacity-val',
          '%',
          (v) => {
            saveState('shadowOpacity', v);
            applyStyles();
          },
          true,
        ],
        [
          'cai-font-size',
          'cai-fontsize-val',
          'px',
          (v) => {
            saveState('fontSize', v + 'px');
            applyStyles();
          },
          false,
        ],
        [
          'cai-line-height',
          'cai-lineheight-val',
          '',
          (v) => {
            saveState('lineHeight', v);
            applyStyles();
          },
          false,
        ],
        [
          'cai-uniform-radius',
          'cai-uniform-val',
          'px',
          (v) => {
            [
              'cornerTopLeft',
              'cornerTopRight',
              'cornerBottomRight',
              'cornerBottomLeft',
            ].forEach((k) => saveState(k, v));
            updateCornerPreviews();
            applyStyles();
          },
          false,
        ],
        [
          'cai-corner-tl',
          'cai-tl-val',
          'px',
          (v) => {
            saveState('cornerTopLeft', v);
            updateCornerPreviews();
            applyStyles();
          },
          false,
        ],
        [
          'cai-corner-tr',
          'cai-tr-val',
          'px',
          (v) => {
            saveState('cornerTopRight', v);
            updateCornerPreviews();
            applyStyles();
          },
          false,
        ],
        [
          'cai-corner-br',
          'cai-br-val',
          'px',
          (v) => {
            saveState('cornerBottomRight', v);
            updateCornerPreviews();
            applyStyles();
          },
          false,
        ],
        [
          'cai-corner-bl',
          'cai-bl-val',
          'px',
          (v) => {
            saveState('cornerBottomLeft', v);
            updateCornerPreviews();
            applyStyles();
          },
          false,
        ],
      ];

        sliders.forEach(([sid, vid, suffix, fn, isPercent]) => {
            const slider = document.getElementById(sid),
                  valEl = document.getElementById(vid);
            if (slider && valEl)
                slider.addEventListener('input', (e) => {
                    // For suffix-based sliders, just show the number without unit in the display
                    // The unit is already shown separately in the label next to the value
                    if (isPercent) {
                        valEl.textContent = Math.round(parseFloat(e.target.value) * 100) + '%';
                    } else if (suffix === 'px') {
                        valEl.textContent = e.target.value; // Just the number, no "px" added here
                    } else if (suffix === '%') {
                        valEl.textContent = e.target.value;
                    } else {
                        valEl.textContent = e.target.value + suffix;
                    }
                    fn(e.target.value);
                });
        });

      // Color pickers
      const colors = [
        [
          'cai-overlay-color',
          'cai-overlay-color-btn',
          'bgOverlayColor',
          () => applyBackground(),
        ],
        [
          'cai-global-color',
          'cai-global-color-btn',
          'bubbleGlobal',
          () => applyStyles(),
        ],
        ['cai-ai-color', 'cai-ai-color-btn', 'bubbleAi', () => applyStyles()],
        [
          'cai-user-color',
          'cai-user-color-btn',
          'bubbleUser',
          () => applyStyles(),
        ],
        [
          'cai-border-color',
          'cai-border-color-btn',
          'borderColor',
          () => applyStyles(),
        ],
        [
          'cai-shadow-color',
          'cai-shadow-color-btn',
          'shadowColor',
          () => applyStyles(),
        ],
        [
          'cai-text-global-color',
          'cai-text-global-color-btn',
          'textColorGlobal',
          () => applyStyles(),
        ],
        [
          'cai-text-ai-color',
          'cai-text-ai-color-btn',
          'textColorAi',
          () => applyStyles(),
        ],
        [
          'cai-text-user-color',
          'cai-text-user-color-btn',
          'textColorUser',
          () => applyStyles(),
        ],
        [
          'cai-italic-color',
          'cai-italic-color-btn',
          'textItalicColor',
          () => applyStyles(),
        ],
        [
          'cai-bold-color',
          'cai-bold-color-btn',
          'textBoldColor',
          () => applyStyles(),
        ],
        [
          'cai-quote-color',
          'cai-quote-color-btn',
          'textQuoteColor',
          () => applyStyles(),
        ],
      ];
      colors.forEach(([inputId, btnId, stateKey, fn]) => {
        const input = document.getElementById(inputId),
          btn = document.getElementById(btnId);
        if (input)
          input.addEventListener('input', (e) => {
            saveState(stateKey, e.target.value);
            if (btn) btn.style.borderColor = e.target.value;
            fn();
          });
      });

      // Bubble mode
      document
        .querySelectorAll('.cai-chip[data-bubble-mode]')
        .forEach((chip) => {
          chip.addEventListener('click', () => {
            const mode = chip.dataset.bubbleMode;
            saveState('bubbleMode', mode);

            // Update active state
            document
              .querySelectorAll('.cai-chip[data-bubble-mode]')
              .forEach((c) => c.classList.remove('active'));
            chip.classList.add('active');

            document
              .getElementById('cai-global-color-group')
              ?.classList.toggle('cai-hidden', mode !== 'global');
            document
              .getElementById('cai-separate-color-group')
              ?.classList.toggle('cai-hidden', mode !== 'separate');
            applyStyles();
          });
        });

      // Toggles
      document
        .getElementById('cai-glass-enabled')
        ?.addEventListener('change', (e) => {
          saveState('glassEnabled', e.target.checked);
          applyStyles();
        });
      document
        .getElementById('cai-border-enabled')
        ?.addEventListener('change', (e) => {
          saveState('borderEnabled', e.target.checked);
          applyStyles();
        });
      document
        .getElementById('cai-shadow-enabled')
        ?.addEventListener('change', (e) => {
          saveState('shadowEnabled', e.target.checked);
          applyStyles();
        });
      document
        .getElementById('cai-italic-enabled')
        ?.addEventListener('change', (e) => {
          saveState('textItalicEnabled', e.target.checked);
          applyStyles();
        });
      document
        .getElementById('cai-bold-enabled')
        ?.addEventListener('change', (e) => {
          saveState('textBoldEnabled', e.target.checked);
          applyStyles();
        });
      document
        .getElementById('cai-quote-enabled')
        ?.addEventListener('change', (e) => {
          saveState('textQuoteEnabled', e.target.checked);
          applyStyles();
        });

      // Font family — show/hide custom URL field immediately on change
      document
        .getElementById('cai-font-family')
        ?.addEventListener('change', (e) => {
          saveState('fontFamily', e.target.value);
          const isCustom = e.target.value === 'custom';
          document
            .getElementById('cai-custom-font-group')
            ?.classList.toggle('cai-hidden', !isCustom);
          if (!isCustom) applyStyles();
        });
      document
        .getElementById('cai-custom-font-url')
        ?.addEventListener('input', (e) =>
          saveState('fontCustomUrl', e.target.value),
        );
      document
        .getElementById('cai-custom-font-url')
        ?.addEventListener('change', () => applyStyles());
      document
        .getElementById('cai-font-weight')
        ?.addEventListener('change', (e) => {
          saveState('fontWeight', e.target.value);
          applyStyles();
        });

      // Text Color Mode Chips
      document.querySelectorAll('.cai-chip[data-text-mode]').forEach((chip) => {
        chip.addEventListener('click', () => {
          const mode = chip.dataset.textMode;
          saveState('textColorMode', mode);

          // Update active state
          document
            .querySelectorAll('.cai-chip[data-text-mode]')
            .forEach((c) => c.classList.remove('active'));
          chip.classList.add('active');

          document
            .getElementById('cai-text-global-group')
            ?.classList.toggle('cai-hidden', mode !== 'global');
          document
            .getElementById('cai-text-separate-group')
            ?.classList.toggle('cai-hidden', mode !== 'separate');
          applyStyles();
        });
      });

      // Corner mode buttons
      document
        .getElementById('cai-corner-uniform')
        ?.addEventListener('click', () => {
          saveState('cornerMode', 'uniform');
          document
            .getElementById('cai-corner-uniform')
            ?.classList.add('active');
          document
            .getElementById('cai-corner-custom')
            ?.classList.remove('active');
          document
            .getElementById('cai-uniform-group')
            ?.classList.remove('cai-hidden');
          document
            .getElementById('cai-custom-group')
            ?.classList.add('cai-hidden');
          updateCornerPreviews();
          applyStyles();
        });
      document
        .getElementById('cai-corner-custom')
        ?.addEventListener('click', () => {
          saveState('cornerMode', 'custom');
          document.getElementById('cai-corner-custom')?.classList.add('active');
          document
            .getElementById('cai-corner-uniform')
            ?.classList.remove('active');
          document
            .getElementById('cai-custom-group')
            ?.classList.remove('cai-hidden');
          document
            .getElementById('cai-uniform-group')
            ?.classList.add('cai-hidden');
          updateCornerPreviews();
          applyStyles();
        });

      // Presets
      document
        .getElementById('cai-save-preset')
        ?.addEventListener('click', () => {
          const name = document
            .getElementById('cai-preset-name')
            ?.value?.trim();
          if (name) {
            savePreset(name);
            document.getElementById('cai-preset-name').value = '';
          } else showNotification('Enter a preset name', 'error');
        });
      document
        .getElementById('cai-import-trigger')
        ?.addEventListener('click', () =>
          document.getElementById('cai-import-file')?.click(),
        );
      document
        .getElementById('cai-import-file')
        ?.addEventListener('change', (e) => {
          if (e.target.files?.[0]) {
            importPreset(e.target.files[0]);
            e.target.value = '';
          }
        });

      // Miscellaneous — Export Chat
      document
        .getElementById('cai-export-chat-btn')
        ?.addEventListener('click', () => runExportChat());

      // Miscellaneous — Session Timer
      const sessionTimerToggle = document.getElementById(
        'cai-session-timer-toggle',
      );
      const sessionTimerSettings = document.getElementById(
        'cai-session-timer-settings',
      );

      if (sessionTimerToggle) {
        sessionTimerToggle.checked = STATE.sessionTimerEnabled;
        if (sessionTimerSettings) {
          sessionTimerSettings.style.display = STATE.sessionTimerEnabled
            ? 'block'
            : 'none';
        }

        sessionTimerToggle.addEventListener('change', (e) => {
          saveState('sessionTimerEnabled', e.target.checked);
          STATE.sessionTimerEnabled = e.target.checked;
          if (sessionTimerSettings) {
            sessionTimerSettings.style.display = e.target.checked
              ? 'block'
              : 'none';
          }
          if (e.target.checked) {
            // Reset last activity time
            lastActivityTime = Date.now();
            startSessionTimer();
            // Initialize message observer
            if (!messageObserver) {
              messageObserver = watchForNewMessages();
            }
          } else {
            stopSessionTimer();
            if (messageObserver) {
              messageObserver.disconnect();
              messageObserver = null;
            }
            applySessionTimer();
          }
          updateResetButtonDisplay();
        });
      }

      const sessionTimerPrefix = document.getElementById(
        'cai-session-timer-prefix',
      );
      if (sessionTimerPrefix) {
        sessionTimerPrefix.value = STATE.sessionTimerPrefix;
        sessionTimerPrefix.addEventListener('change', (e) => {
          saveState('sessionTimerPrefix', e.target.value);
          STATE.sessionTimerPrefix = e.target.value;
          const preview = document.getElementById('cai-timer-preview');
          if (preview) {
            const isActive = e.target.value === 'Active';
            if (isActive) {
              preview.innerHTML = `Preview:🟢 Active now" → "Active 12 mins ago. This is a fake offline mode to simulate the look for FB/Instagram.`;
            } else {
              preview.textContent = `Preview: "${e.target.value} 12 minutes"`;
            }
          }
          updateResetButtonDisplay();
          applySessionTimer();
        });
      }

      // Reset conversation timer button
      const resetTimerBtn = document.getElementById(
        'cai-reset-conversation-timer',
      );
      if (resetTimerBtn) {
        resetTimerBtn.addEventListener('click', () => {
          const charId = getCurrentCharacterId();
          if (!charId) {
            showNotification('Not in a character chat', 'warning');
            return;
          }

          const currentStart = getConversationStartTime(charId);
          const formattedCurrent = currentStart
            ? formatTimestamp(currentStart)
            : 'not set';

          if (
            confirm(
              `Reset conversation timer for this character?\n\nCurrent start time: ${formattedCurrent}\n\nThis will reset the timer to start counting from now.`,
            )
          ) {
            resetConversationStartTime(charId);
            showNotification(
              'Conversation timer reset for this character',
              'success',
            );
            applySessionTimer();
            updateResetButtonDisplay();
          }
        });
      }

      // Miscellaneous — Disclaimer toggle
      const disclaimerToggle = document.getElementById('cai-disclaimer-toggle');
      if (disclaimerToggle) {
        disclaimerToggle.checked = disclaimerHidden;
        disclaimerToggle.addEventListener('change', (e) => {
          disclaimerHidden = e.target.checked;
          GM_setValue('cai_disclaimers_hidden', disclaimerHidden);
          applyDisclaimerState();
        });
      }
    }

    // ============================================
    // MISCELLANEOUS — EXPORT CHAT
    // ============================================
    function escapeHTMLExport(str) {
      return String(str || '').replace(
        /[&<>"]/g,
        (m) => ({'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;'})[m],
      );
    }

    async function imageToDataURL(url) {
      try {
        const blob = await fetch(url).then((r) => r.blob());
        return await new Promise((resolve) => {
          const reader = new FileReader();
          reader.onload = () => resolve(reader.result);
          reader.readAsDataURL(blob);
        });
      } catch {
        return '';
      }
    }

    function askExportNames() {
      return new Promise((resolve) => {
        const overlay = document.createElement('div');
        overlay.style.cssText = `position:fixed;inset:0;background:rgba(0,0,0,.65);display:flex;align-items:center;justify-content:center;z-index:1000000;font-family:'Inter',system-ui;`;
        const box = document.createElement('div');
        box.style.cssText = `width:340px;background:#16161e;border:1px solid rgba(255,255,255,0.1);border-radius:16px;padding:22px;color:#d0d0d8;`;
        box.innerHTML = `
                    <h3 style="margin:0 0 10px;font-size:15px;color:#e0e0f0;">Export Chat</h3>
                    <p style="font-size:12px;color:#8888a0;line-height:1.5;margin:0 0 14px;">Enter your name and the character's name to label the exported chat.</p>
                    <div id="cai-export-error" style="display:none;color:#f87171;font-size:12px;margin-bottom:10px;"></div>
                    <input id="cai-export-user" placeholder="Your name" style="width:100%;padding:9px 12px;margin-bottom:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.1);background:#1e1e2e;color:#d0d0d8;font-size:12px;font-family:inherit;box-sizing:border-box;outline:none;">
                    <input id="cai-export-bot" placeholder="Character name" style="width:100%;padding:9px 12px;margin-bottom:14px;border-radius:8px;border:1px solid rgba(255,255,255,0.1);background:#1e1e2e;color:#d0d0d8;font-size:12px;font-family:inherit;box-sizing:border-box;outline:none;">
                    <div style="display:flex;gap:8px;">
                        <button id="cai-export-cancel" style="flex:1;padding:10px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);border-radius:8px;color:#8888a0;font-size:12px;font-family:inherit;cursor:pointer;">Cancel</button>
                        <button id="cai-export-ok" style="flex:1;padding:10px;background:#3b2f88;border:none;border-radius:8px;color:#c0b0ff;font-size:12px;font-family:inherit;font-weight:700;cursor:pointer;">Export</button>
                    </div>
                `;
        overlay.appendChild(box);
        document.body.appendChild(overlay);
        const errBox = box.querySelector('#cai-export-error');
        const close = (res) => {
          overlay.remove();
          resolve(res);
        };
        box.querySelector('#cai-export-cancel').onclick = () => close(null);
        box.querySelector('#cai-export-ok').onclick = () => {
          const user = box.querySelector('#cai-export-user').value.trim();
          const bot = box.querySelector('#cai-export-bot').value.trim();
          if (!user || !bot) {
            errBox.style.display = 'block';
            errBox.textContent = 'Both fields are required.';
            return;
          }
          close({user, bot});
        };
      });
    }

    async function loadAllChatMessages() {
      const container = document.querySelector('#chat-messages');

      if (!container) {
        showNotification(
          'Chat container not found — are you inside a chat?',
          'error',
        );
        return false;
      }

      let last = 0,
        stable = 0;
      const MAX_ATTEMPTS = 20;
      let attempts = 0;

      try {
        while (attempts < MAX_ATTEMPTS) {
          attempts++;
          container.scrollTop = -container.scrollHeight;
          await new Promise((r) => setTimeout(r, 1200));

          const count = container.querySelectorAll('.group').length;

          if (count === last) {
            stable++;
            if (stable > 3) break;
          } else {
            stable = 0;
            last = count;
          }
        }

        if (attempts >= MAX_ATTEMPTS) {
          showNotification(
            'Export loaded partially — some older messages may be missing',
            'warning',
          );
        }
      } catch (e) {
        console.error('[CharFlow] Failed to load messages:', e);
        showNotification(
          'Failed to load messages — export may be incomplete',
          'error',
        );
        return false;
      }

      return true;
    }

    async function extractChatAvatars() {
      let botAvatar = '',
        userAvatar = '';
      const groups = [...document.querySelectorAll('#chat-messages > .group')];
      for (const g of groups) {
        const img = g.querySelector('img');
        if (!img) continue;
        const isUser = g.querySelector('.flex-row-reverse') !== null;
        try {
          if (isUser && !userAvatar) userAvatar = await imageToDataURL(img.src);
          if (!isUser && !botAvatar) botAvatar = await imageToDataURL(img.src);
        } catch (e) {
          console.warn('[CharFlow] Could not load one of the avatars:', e);
          // silently continue — avatars are optional
        }
        if (userAvatar && botAvatar) break;
      }
      return {userAvatar, botAvatar};
    }

    function extractChatMessages(userName, botName, avatars) {
      const blocks = [...document.querySelectorAll('#chat-messages > .group')];
      const messages = [];
      for (let i = blocks.length - 1; i >= 0; i--) {
        const block = blocks[i];
        const isUser = block.querySelector('.flex-row-reverse') !== null;
        const node = block.querySelector(
          '[data-testid="completed-message"] .prose',
        );
        if (!node) continue;
        const text = node.innerText.trim();
        if (!text) continue;
        messages.push({
          role: isUser ? userName : botName,
          type: isUser ? 'user' : 'bot',
          avatar: isUser ? avatars.userAvatar : avatars.botAvatar,
          html: node.innerHTML.trim(),
          text,
        });
      }
      return messages;
    }

    function groupChatMessages(messages) {
      const groups = [];
      for (const m of messages) {
        const prev = groups[groups.length - 1];
        if (prev && prev.type === m.type) {
          prev.messages.push(m);
        } else {
          groups.push({
            role: m.role,
            type: m.type,
            avatar: m.avatar,
            messages: [m],
          });
        }
      }
      return groups;
    }

    function buildExportHTML(messages, botName) {
      const groups = groupChatMessages(messages);
      const userCount = messages.filter((m) => m.type === 'user').length;
      const totalWords = messages.reduce(
        (a, m) => a + m.text.split(/\s+/).length,
        0,
      );
      const body = groups
        .map(
          (g) => `
                <div class="row ${g.type}">
                    <div class="avatarWrap">${g.avatar ? `<img class="avatar" src="${g.avatar}">` : `<div class="avatar fallback">${escapeHTMLExport(g.role[0].toUpperCase())}</div>`}</div>
                    <div class="stack">
                        <div class="name">${escapeHTMLExport(g.role)}</div>
                        ${g.messages.map((m) => `<div class="bubble">${m.html}</div>`).join('')}
                    </div>
                </div>
            `,
        )
        .join('');
      return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHTMLExport(botName)} Conversation</title>
<style>

:root{
  --bg:#0f1218;
  --surface:#151a22;
  --surface2:#1c2330;
  --text:#e7edf8;
  --muted:#9aa4b2;

  --primary:#4f8cff;

  --radius:14px;

  --s1:4px;
  --s2:8px;
  --s3:12px;
  --s4:16px;
  --s5:24px;
}

body.light{
  --bg:#f6f7fb;
  --surface:#ffffff;
  --surface2:#f1f5f9;
  --text:#111827;
  --muted:#6b7280;
}

body{
  margin:0;
  padding:28px;
  background:var(--bg);
  color:var(--text);
  font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
}

.wrap{
  max-width:900px;
  margin:auto;
}

/* HEADER */
header{
  background:var(--surface);
  border:1px solid rgba(148,163,184,.15);
  border-radius:var(--radius);
  padding:var(--s5);
  margin-bottom:var(--s4);
}

h1{
  margin:0 0 var(--s2);
  font-size:24px;
  font-weight:700;
}

.meta{
  font-size:13px;
  color:var(--muted);
}

/* TOOLBAR */
.toolbar{
  position:sticky;
  top:10px;
  display:flex;
  gap:var(--s2);
  padding:var(--s2);
  margin-bottom:var(--s4);
  background:rgba(21,26,34,.9);
  backdrop-filter:blur(10px);
  border-radius:var(--radius);
  border:1px solid rgba(148,163,184,.15);
}

input{
  flex:1;
  padding:10px 12px;
  border-radius:10px;
  border:1px solid rgba(148,163,184,.2);
  background:var(--surface);
  color:var(--text);
  outline:none;
}

input:focus{
  border-color:var(--primary);
}

/* BUTTONS */
button{
  padding:10px 12px;
  border-radius:10px;
  border:none;
  background:var(--primary);
  color:white;
  font-weight:600;
  cursor:pointer;
  transition:.15s ease;
}

button:hover{
  transform:translateY(-1px);
  opacity:.95;
}

/* CHAT LAYOUT */
.chat{
  display:flex;
  flex-direction:column;
  gap:var(--s4);
}

/* MESSAGE ROW */
.row{
  display:flex;
  gap:var(--s3);
  max-width:85%;
  align-items:flex-end;
}

.row.user{
  margin-left:auto;
  flex-direction:row-reverse;
}

/* AVATARS */
.avatar, .fallback{
  width:34px;
  height:34px;
  border-radius:50%;
  display:flex;
  align-items:center;
  justify-content:center;
  font-size:13px;
  font-weight:600;
  background:var(--surface);
  border:1px solid rgba(148,163,184,.15);
  object-fit:cover;
}

/* STACK */
.stack{
  display:flex;
  flex-direction:column;
  gap:var(--s1);
}

/* NAME (subtle hierarchy) */
.name{
  font-size:12px;
  color:var(--muted);
  margin-left:6px;
}

/* MESSAGE SURFACE */
.bubble{
  padding:12px 14px;
  border-radius:var(--radius);
  line-height:1.5;
  font-size:14px;
  background:var(--surface);
  border:1px solid rgba(148,163,184,.12);
  box-shadow:0 6px 18px rgba(0,0,0,.12);
  transition:.15s ease;
}

.bubble:hover{
  transform:translateY(-1px);
}

/* USER VS BOT DIFFERENCE */
.bot .bubble{
  background:var(--surface2);
}

.user .bubble{
  background:rgba(79,140,255,.12);
  border-color:rgba(79,140,255,.25);
}

/* LIGHT MODE SHADOW SOFTER */
body.light .bubble{
  box-shadow:0 4px 14px rgba(0,0,0,.06);
}

</style></head><body>
<div class="wrap">
<header><h1>${escapeHTMLExport(botName)} Conversation</h1>
<div class="meta">${messages.length} messages • ${userCount} user • ${messages.length - userCount} bot • ${totalWords} words • ${new Date().toLocaleString()}</div></header>
<div class="toolbar"><input id="search" placeholder="Filter messages..."><button onclick="document.body.classList.toggle('light')">Theme</button></div>
<div class="chat" id="chat">${body}</div></div>
<script>document.getElementById("search").addEventListener("input",function(){const q=this.value.toLowerCase();document.querySelectorAll(".row").forEach(r=>{r.style.display=!q||r.textContent.toLowerCase().includes(q)?"":"none";});});<\/script>
</body></html>`;
    }

    async function runExportChat() {
      try {
        const res = await askExportNames();
        if (!res) return;

        const {user, bot} = res;

        showNotification('Loading messages...', 'info');
        const loaded = await loadAllChatMessages();
        if (!loaded) return;

        showNotification('Extracting avatars...', 'info');
        const avatars = await extractChatAvatars().catch((e) => {
          console.error('[CharFlow] Avatar extraction failed:', e);
          showNotification(
            'Could not load avatars — exporting without them',
            'warning',
          );
          return {userAvatar: '', botAvatar: ''};
        });

        showNotification('Building export...', 'info');
        const messages = extractChatMessages(user, bot, avatars);

        if (messages.length === 0) {
          showNotification('No messages found to export', 'error');
          return;
        }

        const html = buildExportHTML(messages, bot);
        const blob = new Blob([html], {type: 'text/html'});
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `cai_chat_${bot}.html`;
        a.click();
        URL.revokeObjectURL(url);

        showNotification(
          `Exported ${messages.length} messages successfully!`,
          'success',
        );
      } catch (e) {
        console.error('[CharFlow] Export failed:', e);
        showNotification(
          'Export failed unexpectedly — check the console for details',
          'error',
        );
      }
    }

    // ============================================
    // MISCELLANEOUS — SESSION TIMER
    // ============================================
    let sessionStartTime = Date.now();
    let sessionTimerInterval = null;

    function formatDuration(ms) {
      const seconds = Math.floor(ms / 1000);
      const minutes = Math.floor(seconds / 60);
      const hours = Math.floor(minutes / 60);
      const days = Math.floor(hours / 24);

      if (seconds < 60) return 'just now';
      if (minutes < 60)
        return `${minutes} ${minutes === 1 ? 'minute' : 'minutes'}`;
      if (hours < 24) return `${hours} ${hours === 1 ? 'hour' : 'hours'}`;
      return `${days} ${days === 1 ? 'day' : 'days'}`;
    }

    function formatActiveDuration(ms) {
      const seconds = Math.floor(ms / 1000);
      const minutes = Math.floor(seconds / 60);
      const hours = Math.floor(minutes / 60);
      const days = Math.floor(hours / 24);

      if (seconds < 60) return 'now';
      if (minutes < 60)
        return `${minutes} ${minutes === 1 ? 'min' : 'mins'} ago`;
      if (hours < 24) return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;
      return `${days} ${days === 1 ? 'day' : 'days'} ago`;
    }

    function applySessionTimer() {
      try {
        if (!window.location.pathname.includes('/chat/')) return;
        const creatorLink = document.querySelector('a[href^="/profile/"]');

        if (!creatorLink) {
          if (STATE.sessionTimerEnabled) {
            console.debug(
              '[CharFlow] Session Timer: Creator link not found — will retry',
            );
          }
          return;
        }

        if (STATE.sessionTimerEnabled) {
          if (!creatorLink.dataset.caiOriginalText) {
            creatorLink.dataset.caiOriginalText = creatorLink.textContent;
          }
          if (!creatorLink.dataset.caiOriginalHref) {
            creatorLink.dataset.caiOriginalHref =
              creatorLink.getAttribute('href');
          }

          const isActiveMode = STATE.sessionTimerPrefix === 'Active';
          let elapsed;

          if (isActiveMode) {
            // Active mode: use last activity time
            elapsed = Date.now() - lastActivityTime;
          } else {
            // Regular mode: use permanent conversation start time
            const charId = getCurrentCharacterId();
            let startTime = getConversationStartTime(charId);

            if (!startTime && charId) {
              // First time talking to this character - set start time now
              startTime = Date.now();
              setConversationStartTime(charId, startTime);
            }

            if (startTime) {
              elapsed = Date.now() - startTime;
            } else {
              elapsed = 0;
            }
          }

          // Remove previous dot if exists
          const existingDot = creatorLink.querySelector('.cai-green-dot');
          if (existingDot) existingDot.remove();

          if (isActiveMode) {
            const seconds = Math.floor(elapsed / 1000);
            if (seconds < 60) {
              const dot = document.createElement('span');
              dot.className = 'cai-green-dot';
              dot.style.cssText =
                'display:inline-block;width:8px;height:8px;background:#22c55e;border-radius:50%;margin-right:6px;vertical-align:middle;';
              creatorLink.textContent = '';
              creatorLink.appendChild(dot);
              creatorLink.appendChild(document.createTextNode('Active now'));
            } else {
              creatorLink.textContent = `Active ${formatActiveDuration(elapsed)}`;
            }
          } else {
            creatorLink.textContent = `${STATE.sessionTimerPrefix} ${formatDuration(elapsed)}`;
          }
          creatorLink.style.textDecoration = 'none';
        } else {
          const existingDot = creatorLink.querySelector('.cai-green-dot');
          if (existingDot) existingDot.remove();
          if (creatorLink.dataset.caiOriginalText) {
            creatorLink.textContent = creatorLink.dataset.caiOriginalText;
          }
          if (creatorLink.dataset.caiOriginalHref) {
            creatorLink.setAttribute(
              'href',
              creatorLink.dataset.caiOriginalHref,
            );
          }
          creatorLink.style.textDecoration = '';
        }

        // Update the reset button display if it exists
        updateResetButtonDisplay();
      } catch (err) {
        console.error('[CharFlow] Session Timer error:', err.message);
      }
    }

    function updateResetButtonDisplay() {
      const resetGroup = document.getElementById('cai-reset-timer-group');
      const displaySpan = document.getElementById('cai-timer-start-display');

      if (resetGroup && displaySpan) {
        const isTimerEnabled = STATE.sessionTimerEnabled;
        const isActiveMode = STATE.sessionTimerPrefix === 'Active';
        const shouldShow = isTimerEnabled && !isActiveMode;

        resetGroup.style.display = shouldShow ? 'block' : 'none';

        if (shouldShow) {
          const charId = getCurrentCharacterId();
          const startTime = getConversationStartTime(charId);
          if (startTime) {
            displaySpan.textContent = formatTimestamp(startTime);
          } else {
            displaySpan.textContent =
              'Not set yet (will start on first message)';
          }
        }
      }
    }

    function startSessionTimer() {
      if (sessionTimerInterval) clearInterval(sessionTimerInterval);
      if (STATE.sessionTimerEnabled) {
        // Reset last activity time when starting
        lastActivityTime = Date.now();
        applySessionTimer();
        sessionTimerInterval = setInterval(applySessionTimer, 5000); // Update every 5 seconds for better accuracy
      }
    }

    function stopSessionTimer() {
      if (sessionTimerInterval) {
        clearInterval(sessionTimerInterval);
        sessionTimerInterval = null;
      }
    }

    // ============================================
    // MISCELLANEOUS — REMOVE DISCLAIMER
    // ============================================
    let disclaimerHidden = GM_getValue('cai_disclaimers_hidden', false);
    let disclaimerDebounce = null;
    let disclaimerObserver = null;

    const disclaimerStyle = document.createElement('style');
    disclaimerStyle.textContent = `.cai-disc-anim{transition:opacity 180ms ease,transform 180ms ease;will-change:opacity,transform;}.cai-disc-hide{opacity:0!important;transform:translateX(14px);pointer-events:none;}`;
    document.head.appendChild(disclaimerStyle);

    function hideDisclaimer(el) {
      if (el.dataset.caiHidden === '1') return;
      el.dataset.caiHidden = '1';
      el.classList.add('cai-disc-anim');
      void el.offsetWidth;
      el.classList.add('cai-disc-hide');
      el.addEventListener('transitionend', function h() {
        el.style.display = 'none';
        el.removeEventListener('transitionend', h);
      });
    }

    function showDisclaimer(el, displayType) {
      if (el.dataset.caiHidden !== '1') return;
      el.dataset.caiHidden = '0';
      el.style.display = displayType;
      el.classList.add('cai-disc-anim');
      el.classList.add('cai-disc-hide');
      void el.offsetWidth;
      el.classList.remove('cai-disc-hide');
    }

    function applyDisclaimerState() {
      const bigWarnings = document.querySelectorAll(
        'div.flex.flex-row.space-x-4.rounded-xl.bg-warning\\/20, ' +
          'div.flex.flex-row.space-x-4.rounded-xl.max-w-\\[340px\\].bg-warning\\/20',
      );
      bigWarnings.forEach((el) => {
        disclaimerHidden ? hideDisclaimer(el) : showDisclaimer(el, 'flex');
      });

      const smallText = document.querySelectorAll(
        'p.text-muted-foreground.text-\\[0\\.70rem\\].select-none',
      );
      smallText.forEach((el) => {
        if (
          el.textContent.includes('A.I. chatbot') ||
          el.textContent.includes('not a real person') ||
          el.textContent.includes('not a licensed professional')
        ) {
          disclaimerHidden ? hideDisclaimer(el) : showDisclaimer(el, 'block');
        }
      });

      // Target the chevron by walking up from the SVG path itself
      document
        .querySelectorAll('svg path[d="m6 9 6 6 6-6"]')
        .forEach((path) => {
          const btn = path.closest('button');
          if (!btn) return;
          disclaimerHidden ? hideDisclaimer(btn) : showDisclaimer(btn, 'flex');
        });
    }

    function startDisclaimerObserver() {
      if (disclaimerObserver) return;
      disclaimerObserver = new MutationObserver(() => {
        clearTimeout(disclaimerDebounce);
        disclaimerDebounce = setTimeout(() => {
          if (disclaimerHidden) applyDisclaimerState();
        }, 100);
      });
      disclaimerObserver.observe(document.body, {
        childList: true,
        subtree: true,
      });
    }

    // Apply disclaimer state on load
    applyDisclaimerState();

    // ============================================
    // INIT
    // ============================================
    function cleanup() {
      if (observer) observer.disconnect();
      cleanupOldFonts();
      cleanupBackground();
      if (globalStyleElement?.parentNode) globalStyleElement.remove();
      if (notificationElement) notificationElement.remove();
      stopSessionTimer();
      disableScript();
    }
    window.addEventListener('beforeunload', cleanup);

    function processQuotes() {
      const MSG_SEL = '[data-testid="completed-message"] p, [data-testid="active-message"] p';
      document.querySelectorAll(MSG_SEL).forEach(p => {
        if (p.closest('[contenteditable="true"], textarea, input')) return;
        if (p.dataset.caiQuoteProcessed === 'true') return;

        const walker = document.createTreeWalker(p, NodeFilter.SHOW_TEXT, null, false);
        const nodes = [];
        let node;
        while ((node = walker.nextNode())) {
          if (node.nodeValue.match(/["\u201C\u201D\u00AB\u00BB]/)) {
            nodes.push(node);
          }
        }
        nodes.forEach(textNode => {
          const replaced = textNode.nodeValue.replace(
            /(["\u201C\u00AB][^"\u201D\u00BB]*["\u201D\u00BB])/g,
            `<span class="cai-quote-colored">$1</span>`
          );
          const wrapper = document.createElement('span');
          wrapper.innerHTML = replaced;
          textNode.parentNode.replaceChild(wrapper, textNode);
        });
        p.dataset.caiQuoteProcessed = 'true';
      });
    }

    let debounceTimer = null;
    observer = new MutationObserver(() => {
      clearTimeout(debounceTimer);
      debounceTimer = setTimeout(() => {
        applyStyles();
        if (STATE.textQuoteEnabled) processQuotes();
        if (disclaimerHidden) applyDisclaimerState();
      }, 300);
    });

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

    function isInChat() {
      return window.location.pathname.includes('/chat/');
    }

      function disableScript() {
      try {
        cleanupBackground();

        // Store the original background color BEFORE we change it (if not already stored)
        if (!document.body.hasAttribute('data-cai-original-bg')) {
          const computedBg = window.getComputedStyle(document.body).backgroundColor;
          document.body.setAttribute('data-cai-original-bg', computedBg);
        }

        // Restore original background color
        const originalBg = document.body.getAttribute('data-cai-original-bg');
        if (originalBg && originalBg !== 'rgba(0, 0, 0, 0)') {
          document.body.style.backgroundColor = originalBg;
        } else {
          // Fallback to what Character.AI actually uses
          document.body.style.backgroundColor = '#1a1a24';
        }

        document.body.style.position = '';
        document.body.style.zIndex = '';

        if (globalStyleElement && globalStyleElement.parentNode) {
          globalStyleElement.remove();
          globalStyleElement = null;
          lastStyleOutput = '';
        }
        if (observer) {
          observer.disconnect();
          observer = null;
        }

        if (sessionTimerInterval) {
          clearInterval(sessionTimerInterval);
          sessionTimerInterval = null;
        }
      } catch (e) {
        console.error('[CharFlow] Failed to disable script:', e);
        showNotification(
          'CharFlow failed to clean up properly — try refreshing',
          'error',
        );
      }
    }

    function enableScript() {
      try {
        if (observer) {
          observer.disconnect();
          observer = null;
        }
        observer = new MutationObserver(() => {
          clearTimeout(debounceTimer);
          debounceTimer = setTimeout(() => {
            applyStyles();
            if (disclaimerHidden) applyDisclaimerState();
            if (
              STATE.sessionTimerEnabled &&
              window.location.pathname.includes('/chat/')
            )
              applySessionTimer();
          }, 300);
        });
        observer.observe(document.body, {childList: true, subtree: true});
        applyBackground();
        applyStyles();
      } catch (e) {
        console.error('[CharFlow] Failed to re-enable script:', e);
        showNotification(
          'CharFlow failed to reactivate — try refreshing the page',
          'error',
        );
      }
    }

    const _pushState = history.pushState.bind(history);
    history.pushState = function (...args) {
      _pushState(...args);
      setTimeout(() => {
        if (isInChat()) {
          enableScript();
          // Reset session timer when navigating to a new chat page
          if (STATE.sessionTimerEnabled) {
            startSessionTimer(true);
          }
          if (STATE.perCharBgEnabled) {
            const currentCharId = getCurrentCharacterId();
            if (currentCharId !== lastCharId) {
              lastCharId = currentCharId;
              applyBackground();
            }
          }
        } else {
          disableScript();
        }
      }, 200);
    };

    // Also catch browser back/forward
    window.addEventListener('popstate', () => {
      setTimeout(() => {
        if (isInChat()) {
          enableScript();
          // Reset session timer when navigating to a chat page (new character)
          if (STATE.sessionTimerEnabled) {
            startSessionTimer(true);
          }
        } else {
          disableScript();
        }
      }, 200);
    });

    loadState();
    applyBackground();
    applyStyles();
    ensureBackgroundLayer();

    if (STATE.sessionTimerEnabled) {
      lastActivityTime = Date.now();
      startSessionTimer();
      setTimeout(applySessionTimer, 1000);
      // Start watching for new messages
      messageObserver = watchForNewMessages();
    }

    // Initial update of reset button display
    setTimeout(updateResetButtonDisplay, 500);
  }

  // ============================================
  // CSS STYLES
  // ============================================
  GM_addStyle(`
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');

        #cai-settings-btn {
            position: fixed; z-index: 9998;
            width: 44px; height: 44px;
            background: #1a1a2e; color: white;
            border-radius: 12px;
            display: flex; align-items: center; justify-content: center;
            font-size: 20px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.4);
            cursor: pointer; user-select: none;
            transition: transform 0.2s, box-shadow 0.2s;
            border: 1px solid rgba(255,255,255,0.1);
        }
        #cai-settings-btn:hover { transform: scale(1.06); box-shadow: 0 6px 24px rgba(0,0,0,0.5); }
        #cai-settings-btn:active { transform: scale(0.95); }

        #cai-settings-panel {
            position: fixed; top: 50%; left: 50%;
            transform: translate(-50%, -50%) scale(0.97);
            width: 360px; max-width: 95vw; max-height: 88vh;
            background: #16161e;
            border-radius: 16px;
            box-shadow: 0 24px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.06);
            z-index: 10000;
            opacity: 0; visibility: hidden;
            transition: opacity 0.2s ease, visibility 0.2s ease, transform 0.2s ease;
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
            color: #d0d0d8;
            display: flex; flex-direction: column;
        }
        #cai-settings-panel.open { opacity: 1; visibility: visible; transform: translate(-50%, -50%) scale(1); }

        .cai-panel-header {
            display: flex; justify-content: space-between; align-items: center;
            padding: 14px 16px;
            border-bottom: 1px solid rgba(255,255,255,0.07);
            flex-shrink: 0; user-select: none;
        }
        .cai-panel-title { font-size: 12px; font-weight: 500; color: #8888a0; letter-spacing: 0.02em; }
        .cai-close-btn {
            background: none; border: none; color: #8888a0; font-size: 16px; cursor: pointer;
            width: 28px; height: 28px; border-radius: 8px;
            display: flex; align-items: center; justify-content: center;
            transition: background 0.15s, color 0.15s;
        }
        .cai-close-btn:hover { background: rgba(255,255,255,0.08); color: #d0d0d8; }

        .cai-controls-row {
            display: flex; gap: 8px; padding: 12px 16px;
            border-bottom: 1px solid rgba(255,255,255,0.07); flex-shrink: 0;
        }
        .cai-category-select { flex: 0 0 160px; }

        .cai-select-wrapper { position: relative; }
        .cai-select-wrapper::after { content:'▾'; position:absolute; right:10px; top:50%; transform:translateY(-50%); color:#5555aa; pointer-events:none; font-size:12px; }
        .cai-select-wrapper select {
            width: 100%; padding: 8px 28px 8px 12px;
            background: #1e1e2e; border: 1px solid rgba(255,255,255,0.08);
            border-radius: 8px; color: #d0d0d8; font-size: 12px; font-family: inherit;
            appearance: none; cursor: pointer; outline: none; transition: border-color 0.15s;
        }
        .cai-select-wrapper select:focus { border-color: rgba(100,80,180,0.5); }

        .cai-search-wrapper { flex: 1; position: relative; display: flex; align-items: center; }
        .cai-search-icon { position: absolute; left: 10px; font-size: 12px; pointer-events: none; }
        .cai-search-input {
            width: 100%; padding: 8px 12px 8px 30px;
            background: #1e1e2e; border: 1px solid rgba(255,255,255,0.08);
            border-radius: 8px; color: #d0d0d8; font-size: 12px; font-family: inherit;
            outline: none; transition: border-color 0.15s; box-sizing: border-box;
        }
        .cai-search-input:focus { border-color: rgba(100,80,180,0.5); }
        .cai-search-input::placeholder { color: #555568; }

        .cai-panel-content {
            flex: 1; overflow-y: auto; padding: 12px 16px;
            scrollbar-width: thin; scrollbar-color: #333350 transparent;
        }
        .cai-panel-content::-webkit-scrollbar { width: 4px; }
        .cai-panel-content::-webkit-scrollbar-track { background: transparent; }
        .cai-panel-content::-webkit-scrollbar-thumb { background: #333350; border-radius: 2px; }

        .cai-empty-state { text-align: center; padding: 40px 20px; color: #555568; font-size: 13px; }
        .cai-empty-icon { font-size: 36px; margin-bottom: 12px; opacity: 0.4; }
        .cai-empty-sub { margin-top: 6px; font-size: 12px; color: #404058; }

        .cai-category-heading { font-size: 16px; font-weight: 600; color: #e0e0f0; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid rgba(255,255,255,0.07); }
        .cai-card-category-badge { font-size: 10px; color: #6655bb; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; margin-bottom: 8px; }

        .cai-settings-card {
            background: #1c1c28; border: 1px solid rgba(255,255,255,0.06);
            border-radius: 12px; padding: 14px; margin-bottom: 10px;
        }
        .cai-section-title { font-size: 11px; font-weight: 500; color: #8888a0; margin-bottom: 8px; letter-spacing: 0.02em; }

        .cai-settings-card input[type="text"] {
            width: 100%; padding: 8px 12px;
            background: #12121c; border: 1px solid rgba(255,255,255,0.08);
            border-radius: 8px; color: #d0d0d8; font-size: 12px; font-family: inherit;
            outline: none; box-sizing: border-box; transition: border-color 0.15s;
        }
        .cai-settings-card input[type="text"]:focus { border-color: rgba(100,80,180,0.5); }

        .cai-settings-card input[type="range"] {
            width: 100%; height: 4px; -webkit-appearance: none; appearance: none;
            background: #2a2a3e; border-radius: 2px; outline: none; cursor: pointer;
        }
        .cai-settings-card input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none; width: 14px; height: 14px; border-radius: 50%;
            background: #6655bb; cursor: pointer; box-shadow: 0 0 0 2px rgba(102,85,187,0.3);
        }
        .cai-settings-card input[type="range"]::-moz-range-thumb {
            width: 14px; height: 14px; border-radius: 50%;
            background: #6655bb; cursor: pointer; border: none;
        }
        .cai-slider-val { color: #d0d0d8; font-weight: 500; }

        .cai-color-btn {
            padding: 6px 14px;
            background: #1e1e2e; border: 2px solid #444466;
            border-radius: 8px; color: #d0d0d8; font-size: 11px;
            cursor: pointer; font-family: inherit;
            transition: background 0.15s; white-space: nowrap;
        }
        .cai-color-btn:hover { background: #28283c; }

        .cai-switch-label { position: relative; display: inline-block; width: 40px; height: 22px; flex-shrink: 0; }
        .cai-switch-label input { opacity: 0; width: 0; height: 0; }
        .cai-switch-slider { position: absolute; cursor: pointer; top:0; left:0; right:0; bottom:0; background:#2a2a3e; border-radius:22px; transition:0.2s; }
        .cai-switch-slider:before { position:absolute; content:""; height:16px; width:16px; left:3px; bottom:3px; background:#8888a0; border-radius:50%; transition:0.2s; }
        .cai-switch-label input:checked + .cai-switch-slider { background: #3b2f88; }
        .cai-switch-label input:checked + .cai-switch-slider:before { transform: translateX(18px); background: #a090ff; }

        .cai-two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

        .cai-two-mode-btns { display: flex; gap: 8px; margin-top: 4px; }
        .cai-mode-btn2 {
            flex: 1; padding: 8px; background: #1e1e2e;
            border: 1px solid rgba(255,255,255,0.08); border-radius: 8px;
            color: #8888a0; font-size: 12px; font-family: inherit; cursor: pointer; transition: all 0.15s;
        }
        .cai-mode-btn2.active { background: #2a2040; border-color: #6655bb; color: #c0b0ff; }
        .cai-mode-btn2:hover:not(.active) { background: #22223a; color: #d0d0d8; }

        .cai-corner-preview-box { display: flex; align-items: center; justify-content: center; }
        .cai-preview-shape { width: 52px; height: 52px; background: #6655bb; transition: border-radius 0.1s; border-radius: 18px; }
        .cai-corner-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 12px; }
        .cai-corner-item { background: #12121c; border-radius: 8px; padding: 10px; }

        .cai-info-small { font-size: 10px; color: #555568; margin-top: 4px; }
        .cai-text-preview { padding: 6px 14px; background: #12121c; border-radius: 8px; font-size: 13px; color: #d0d0d8; min-width: 80px; text-align: center; }

        .cai-preset-list { margin-top: 8px; }
        .cai-preset-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; background: #12121c; border-radius: 8px; margin-bottom: 6px; }
        .cai-preset-name { font-size: 12px; color: #d0d0d8; word-break: break-word; }
        .cai-preset-dot-menu { position: relative; flex-shrink: 0; }
        .cai-preset-dot-btn { background: none; border: none; color: #8888a0; font-size: 14px; cursor: pointer; padding: 4px 8px; border-radius: 6px; transition: background 0.15s; letter-spacing: 2px; }
        .cai-preset-dot-btn:hover { background: rgba(255,255,255,0.08); color: #d0d0d8; }
        .cai-preset-dropdown { position: absolute; right: 0; top: 100%; background: #1e1e2e; border: 1px solid rgba(255,255,255,0.1); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); z-index: 100; min-width: 110px; display: none; overflow: hidden; }
        .cai-preset-dropdown.open { display: block; }
        .cai-preset-dropdown button { display: block; width: 100%; text-align: left; padding: 9px 14px; background: none; border: none; color: #d0d0d8; font-size: 12px; font-family: inherit; cursor: pointer; transition: background 0.12s; }
        .cai-preset-dropdown button:hover { background: rgba(255,255,255,0.07); }
        .cai-preset-load:hover { color: #a090ff !important; }
        .cai-preset-delete:hover { color: #ff7070 !important; }
        .cai-preset-export:hover { color: #60d499 !important; }
        .cai-empty-presets { text-align: center; color: #555568; font-size: 12px; padding: 20px 0; }

        .cai-btn { padding: 8px 14px; background: #3b2f88; border: none; border-radius: 8px; color: #c0b0ff; font-size: 12px; font-family: inherit; cursor: pointer; transition: background 0.15s; }
        .cai-btn:hover { background: #4a3a9c; }
        .cai-btn-accent { padding: 8px 16px; background: #3b2f88; border: none; border-radius: 8px; color: #c0b0ff; font-size: 12px; font-family: inherit; cursor: pointer; white-space: nowrap; transition: background 0.15s; }
        .cai-btn-accent:hover { background: #4a3a9c; }
        .cai-import-btn { padding: 6px 12px; background: transparent; border: 1px solid rgba(255,255,255,0.12); border-radius: 8px; color: #8888a0; font-size: 11px; font-family: inherit; cursor: pointer; white-space: nowrap; transition: all 0.15s; }
        .cai-import-btn:hover { background: rgba(255,255,255,0.06); color: #d0d0d8; }
        .cai-reset-btn { width: 100%; padding: 9px; background: rgba(239,68,68,0.08); border: 1px solid rgba(239,68,68,0.2); border-radius: 8px; color: #cc7070; font-size: 12px; font-family: inherit; cursor: pointer; transition: all 0.15s; }
        .cai-reset-btn:hover { background: rgba(239,68,68,0.15); }
        .cai-file-label { display: block; padding: 8px 12px; background: #12121c; border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; color: #8888a0; font-size: 12px; cursor: pointer; text-align: center; }
        .cai-file-label input { display: none; }
        .cai-file-label:hover { background: #1a1a2a; color: #d0d0d8; }

        .cai-panel-footer { padding: 10px 16px; border-top: 1px solid rgba(255,255,255,0.07); flex-shrink: 0; }
        .cai-reset-all-btn { width: 100%; padding: 9px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; color: #8888a0; font-size: 12px; font-family: inherit; cursor: pointer; transition: all 0.15s; }
        .cai-reset-all-btn:hover { background: rgba(255,255,255,0.08); color: #d0d0d8; }

        .cai-hidden { display: none !important; }

/* Pill Chip Styles */
.cai-chip-group {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}
.cai-chip {
    padding: 6px 14px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 100px;
    color: #8888a0;
    font-size: 12px;
    font-family: inherit;
    cursor: pointer;
    transition: all 0.15s ease;
    white-space: nowrap;
}
.cai-chip:hover {
    background: rgba(255, 255, 255, 0.08);
    border-color: rgba(255, 255, 255, 0.2);
    color: #d0d0d8;
}
.cai-chip.active {
    background: #3b2f88;
    border-color: #3b2f88;
    color: white;
}

        /* Notifications */
        .cai-notification { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 10001; display: flex; align-items: center; gap: 10px; padding: 10px 18px; background: #1c1c28; border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.4); border-left: 3px solid; animation: caiSlideDown 0.3s ease forwards; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; font-size: 13px; max-width: 90vw; min-width: 200px; }
        .cai-notification-success { border-left-color: #10b981; }
        .cai-notification-success .cai-notification-icon { color: #10b981; }
        .cai-notification-error { border-left-color: #ef4444; }
        .cai-notification-error .cai-notification-icon { color: #ef4444; }
        .cai-notification-warning { border-left-color: #f59e0b; }
        .cai-notification-warning .cai-notification-icon { color: #f59e0b; }
        .cai-notification-info { border-left-color: #6655bb; }
        .cai-notification-info .cai-notification-icon { color: #6655bb; }
        .cai-notification-icon { font-size: 16px; font-weight: bold; }
        .cai-notification-message { flex: 1; color: #d0d0d8; }
        .cai-notification-close { background: none; border: none; color: #8888a0; cursor: pointer; font-size: 16px; padding: 0; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; border-radius: 6px; transition: background 0.15s; }
        .cai-notification-close:hover { background: rgba(255,255,255,0.1); color: #d0d0d8; }
        .cai-notification-hide { animation: caiSlideUp 0.3s ease forwards; }
        @keyframes caiSlideDown { from { opacity:0; transform:translateX(-50%) translateY(-16px); } to { opacity:1; transform:translateX(-50%) translateY(0); } }
        @keyframes caiSlideUp { from { opacity:1; transform:translateX(-50%) translateY(0); } to { opacity:0; transform:translateX(-50%) translateY(-16px); } }

        /* Samsung-style Edge Panel - Gray version */
        #cai-edge-bump {
            position: fixed;
            right: 0;
            top: 50%;
            transform: translateY(-50%);
            width: 6px;
            height: 80px;
            background: linear-gradient(90deg, rgba(120,120,140,0.4) 0%, rgba(100,100,120,0.8) 100%);
            border-radius: 6px 0 0 6px;
            cursor: pointer;
            z-index: 9998;
            transition: all 0.2s ease;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        #cai-edge-bump:hover {
            width: 8px;
            background: linear-gradient(90deg, rgba(130,130,150,0.6) 0%, rgba(110,110,130,1) 100%);
        }

        .cai-edge-grip {
            width: 2px;
            height: 30px;
            background: rgba(255,255,255,0.3);
            border-radius: 2px;
        }

            #cai-edge-drawer {
            position: fixed;
            right: -80px;
            top: 50%;
            transform: translateY(-50%);
            width: 80px;
            background: transparent;
            backdrop-filter: none;
            border-radius: 0;
            padding: 0;
            z-index: 9997;
            transition: right 0.3s cubic-bezier(0.2, 0.9, 0.4, 1.1);
            box-shadow: none;
            border-left: none;
        }

        #cai-edge-drawer.cai-drawer-open {
            right: 0;
        }

        .cai-drawer-content {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            gap: 0;
        }

            .cai-drawer-btn {
            background: rgba(100, 100, 120, 0.9);
            border: none;
            border-radius: 50%;
            padding: 0;
            color: #e0e0e0;
            font-size: 22px;
            width: 48px;
            height: 48px;
            text-align: center;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.2s ease;
            backdrop-filter: blur(4px);
            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
        }

        .cai-drawer-btn:hover {
            background: rgba(120, 120, 140, 1);
            transform: scale(1.05);
        }

        .cai-bump-pushed {
            opacity: 0.5;
            width: 4px !important;
        }

        /* Show floating button on desktop, hide on mobile (shown via edge panel) */
        @media (min-width: 769px) {
            #cai-settings-btn {
                display: flex !important;
            }
            #cai-edge-bump,
            #cai-edge-drawer {
                display: none !important;
            }
        }

        @media (max-width: 768px) {
            #cai-settings-btn {
                display: none !important;
            }
            #cai-edge-bump,
            #cai-edge-drawer {
                display: flex !important;
            }
        }

        #cai-custom-overlay { pointer-events: none; }

        /* Storage Warning Styles */
        .cai-storage-warning {
            font-size: 11px;
            line-height: 1.4;
            margin-top: 10px;
            padding: 10px;
            background: rgba(245, 158, 11, 0.1);
            border-left: 3px solid #f59e0b;
            border-radius: 6px;
        }
        .cai-storage-warning strong {
            color: #f59e0b;
        }

        /* Character Backgrounds Manager Styles */
        .cai-manager-item {
            transition: all 0.15s ease;
        }
        .cai-manager-item:hover {
            background: #1a1a2a !important;
        }
        .cai-manager-preview:hover {
            background: #2a2a40 !important;
            color: #d0d0d8 !important;
        }
.cai-manager-delete:hover {
    background: rgba(239, 68, 68, 0.2) !important;
    color: #ff9090 !important;
}

/* Compact button hover effects */
#cai-manager-export-all:hover,
#cai-manager-import:hover {
    background: rgba(100, 80, 180, 0.15) !important;
    color: #c0b0ff !important;
}

#cai-manager-delete-all:hover {
    background: rgba(239, 68, 68, 0.12) !important;
    color: #ff9090 !important;
}

                .cai-clear-file-btn {
            background: none;
            border: none;
            color: #cc7070;
            cursor: pointer;
            font-size: 14px;
            padding: 0 4px;
            border-radius: 4px;
            transition: all 0.15s;
        }
        .cai-clear-file-btn:hover {
            background: rgba(239, 68, 68, 0.1);
            color: #ff9090;
        }
        #cai-file-status {
            transition: all 0.15s;
        }
    `);
})();