Character.AI Customization Full

Full: customization with advanced options (font, immersion, wallpaper, themes, video).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  // ==UserScript==
// @name         Character.AI Customization Full
// @namespace    http://tampermonkey.net/
// @version      2026.07.24
// @description  Full: customization with advanced options (font, immersion, wallpaper, themes, video).
// @match        https://character.ai/*
// @match        https://www.character.ai/*
// @author       Arachne Project - discord.gg/yEwpaUTEhT
// @icon         https://cdn.discordapp.com/attachments/1485377643233808505/1525689073371709590/nwq6htznkbss2z659sqwr2b00nbvph9bp756vn40nr6m2r4nwj.png?ex=6a544c2d&is=6a52faad&hm=fcfc1bc965c252452087006881ce278a18d0e47375373312b9c35cca1b9b960a&animated=true
// @grant        window.onurlchange
// ==/UserScript==

(function() {
    'use strict';

    const STORAGE_KEY = 'cai_custom_data';

    const themes = [
        { name: 'Default', userColor: 'rgba(38,39,43,0.85)', charColor: 'rgba(48,49,54,0.9)' },
        { name: 'Charcoal', userColor: 'rgb(30,30,30)', charColor: 'rgb(51,51,51)' },
        { name: 'Cobalt', userColor: 'rgb(30,30,30)', charColor: 'rgb(26,94,255)' },
        { name: 'Red', userColor: 'rgb(41,33,34)', charColor: 'rgb(159,45,60)' },
        { name: 'Peach', userColor: 'rgb(51,43,41)', charColor: 'rgb(249,106,93)' },
        { name: 'Clay', userColor: 'rgb(31,28,24)', charColor: 'rgb(79,45,0)' },
        { name: 'Honey', userColor: 'rgb(41,37,33)', charColor: 'rgb(153,89,0)' },
        { name: 'Yellow', userColor: 'rgb(56,47,0)', charColor: 'rgb(255,213,5)' },
        { name: 'Green', userColor: 'rgb(42,61,42)', charColor: 'rgb(10,104,8)' },
        { name: 'Pine', userColor: 'rgb(42,61,55)', charColor: 'rgb(0,82,57)' },
        { name: 'Aqua', userColor: 'rgb(42,61,61)', charColor: 'rgb(0,127,127)' },
        { name: 'Blue', userColor: 'rgb(42,50,61)', charColor: 'rgb(20,100,204)' },
        { name: 'Indigo', userColor: 'rgb(49,49,61)', charColor: 'rgb(77,77,191)' },
        { name: 'Purple', userColor: 'rgb(55,42,61)', charColor: 'rgb(137,41,172)' },
        { name: 'Silver', userColor: 'rgb(61,61,61)', charColor: 'rgb(227,226,230)' },
        { name: 'Grey', userColor: 'rgb(77,77,77)', charColor: 'rgb(42,44,46)' },
        { name: 'Black', userColor: 'rgb(61,61,61)', charColor: 'rgb(0,0,0)' }
    ];

    // ============================================
    // BUBBLE STYLE PACKS (imported from third-party bubbles set, adapted to native DOM)
    // ============================================
    const BUBBLES = [
        // ── CAT 1: CLASSICS
        { id:1, name:'iOS Light', cat:'1. The Classics', font:'Poppins', aiBg:'#e5e5ea', aiTx:'#000000', uBg:'#0b93f6', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); } .cai-ai { border-radius: 18px 18px 18px 4px; } .cai-user { border-radius: 18px 18px 4px 18px; }' },
        { id:2, name:'iOS Dark Blue', cat:'1. The Classics', font:'Poppins', aiBg:'#0b93f6', aiTx:'#ffffff', uBg:'#e5e5ea', uTx:'#000000', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); } .cai-ai { border-radius: 18px 18px 18px 4px; } .cai-user { border-radius: 18px 18px 4px 18px; }' },
        { id:3, name:'WhatsApp', cat:'1. The Classics', font:'Poppins', aiBg:'#ffffff', aiTx:'#000000', uBg:'#dcf8c6', uTx:'#000000', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); box-shadow: 0 1px 1px rgba(0,0,0,0.1); position: relative; overflow: visible !important; } .cai-ai { border-radius: 0 8px 8px 8px; } .cai-ai::before { content:""; position:absolute; top:0; right:100%; width:0; height:0; border-top: 10px solid var(--cai-bg); border-left: 10px solid transparent; } .cai-user { border-radius: 8px 0 8px 8px; } .cai-user::before { content:""; position:absolute; top:0; left:100%; width:0; height:0; border-top: 10px solid var(--cai-bg); border-right: 10px solid transparent; }' },
        { id:4, name:'Android/SMS', cat:'1. The Classics', font:'Poppins', aiBg:'#ffffff', aiTx:'#000000', uBg:'#0084ff', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); box-shadow: 0 1px 2px rgba(0,0,0,0.1); } .cai-ai { border-radius: 12px 12px 12px 0; } .cai-user { border-radius: 18px; }' },
        { id:5, name:'Messenger', cat:'1. The Classics', font:'Poppins', aiBg:'#f1f0f0', aiTx:'#000000', uBg:'#0084ff', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); } .cai-ai { border-radius: 20px; border: 1px solid #ddd; } .cai-user { border-radius: 18px; }' },
        { id:6, name:'Dark Mode', cat:'1. The Classics', font:'Poppins', aiBg:'#333333', aiTx:'#ffffff', uBg:'#555555', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); } .cai-ai { border-radius: 18px 18px 18px 4px; } .cai-user { border-radius: 18px 18px 4px 18px; }' },
        { id:7, name:'Wireframe', cat:'1. The Classics', font:'Poppins', aiBg:'#f1f0f0', aiTx:'#000000', uBg:'#ffffff', uTx:'#000000', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 20px; border: 1px solid #ddd; }' },
        { id:8, name:'WeChat Green', cat:'1. The Classics', font:'Poppins', aiBg:'#ffffff', aiTx:'#000000', uBg:'#95ec69', uTx:'#000000', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 4px; position: relative; overflow: visible !important; } .cai-ai::before { content:""; position:absolute; top:12px; right:100%; width:0; height:0; border-style: solid; border-width: 5px 6px 5px 0; border-color: transparent var(--cai-bg) transparent transparent; } .cai-user::before { content:""; position:absolute; top:12px; left:100%; width:0; height:0; border-style: solid; border-width: 5px 0 5px 6px; border-color: transparent transparent transparent var(--cai-bg); }' },
        { id:9, name:'Discord Blurple', cat:'1. The Classics', font:'Poppins', aiBg:'#5865F2', aiTx:'#ffffff', uBg:'#5865F2', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 8px; }' },
        { id:10, name:'Dating App Pink', cat:'1. The Classics', font:'Poppins', aiBg:'#FF3366', aiTx:'#ffffff', uBg:'#FF3366', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); } .cai-ai { border-radius: 20px 20px 0 20px; } .cai-user { border-radius: 20px 20px 20px 0; }' },

        // ── CAT 2: GRADIENTS
        { id:11, name:'Sunset', cat:'2. Gorgeous Gradients', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { color: var(--cai-tx) !important; } .cai-msg * { color: var(--cai-tx) !important; } .cai-ai { background: linear-gradient(135deg, #ff9a9e, #fecfef) !important; border-radius: 18px 18px 18px 4px; } .cai-user { background: linear-gradient(135deg, #fecfef, #ff9a9e) !important; border-radius: 18px 18px 4px 18px; }' },
        { id:12, name:'Ocean', cat:'2. Gorgeous Gradients', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { color: var(--cai-tx) !important; } .cai-msg * { color: var(--cai-tx) !important; } .cai-ai { background: linear-gradient(135deg, #a1c4fd, #c2e9fb) !important; border-radius: 18px 18px 18px 4px; } .cai-user { background: linear-gradient(135deg, #c2e9fb, #a1c4fd) !important; border-radius: 18px 18px 4px 18px; }' },
        { id:13, name:'Mango', cat:'2. Gorgeous Gradients', colorLock:true, font:'Poppins', aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { color: var(--cai-tx) !important; } .cai-msg * { color: var(--cai-tx) !important; } .cai-ai { background: linear-gradient(135deg, #f6d365, #fda085) !important; border-radius: 18px 18px 18px 4px; } .cai-user { background: linear-gradient(135deg, #fda085, #f6d365) !important; border-radius: 18px 18px 4px 18px; }' },
        { id:14, name:'Mint', cat:'2. Gorgeous Gradients', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { color: var(--cai-tx) !important; } .cai-msg * { color: var(--cai-tx) !important; } .cai-ai { background: linear-gradient(135deg, #84fab0, #8fd3f4) !important; border-radius: 18px 18px 18px 4px; } .cai-user { background: linear-gradient(135deg, #8fd3f4, #84fab0) !important; border-radius: 18px 18px 4px 18px; }' },
        { id:15, name:'Lavender', cat:'2. Gorgeous Gradients', colorLock:true, font:'Poppins', aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { color: var(--cai-tx) !important; } .cai-msg * { color: var(--cai-tx) !important; } .cai-ai { background: linear-gradient(135deg, #a18cd1, #fbc2eb) !important; border-radius: 18px 18px 18px 4px; } .cai-user { background: linear-gradient(135deg, #fbc2eb, #a18cd1) !important; border-radius: 18px 18px 4px 18px; }' },
        { id:16, name:'Cyber', cat:'2. Gorgeous Gradients', colorLock:true, font:'Poppins', aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { color: var(--cai-tx) !important; } .cai-msg * { color: var(--cai-tx) !important; } .cai-ai { background: linear-gradient(135deg, #ff0844, #ffb199) !important; border-radius: 18px 18px 18px 4px; } .cai-user { background: linear-gradient(135deg, #ffb199, #ff0844) !important; border-radius: 18px 18px 4px 18px; }' },
        { id:17, name:'Twilight', cat:'2. Gorgeous Gradients', colorLock:true, font:'Poppins', aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { color: var(--cai-tx) !important; } .cai-msg * { color: var(--cai-tx) !important; } .cai-ai { background: linear-gradient(135deg, #30cfd0, #330867) !important; border-radius: 18px 18px 18px 4px; } .cai-user { background: linear-gradient(135deg, #330867, #30cfd0) !important; border-radius: 18px 18px 4px 18px; }' },
        { id:18, name:'Breeze', cat:'2. Gorgeous Gradients', colorLock:true, font:'Poppins', aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { color: var(--cai-tx) !important; } .cai-msg * { color: var(--cai-tx) !important; } .cai-ai { background: linear-gradient(135deg, #4facfe, #00f2fe) !important; border-radius: 18px 18px 18px 4px; } .cai-user { background: linear-gradient(135deg, #00f2fe, #4facfe) !important; border-radius: 18px 18px 4px 18px; }' },
        { id:19, name:'Forest', cat:'2. Gorgeous Gradients', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { color: var(--cai-tx) !important; } .cai-msg * { color: var(--cai-tx) !important; } .cai-ai { background: linear-gradient(135deg, #43e97b, #38f9d7) !important; border-radius: 18px 18px 18px 4px; } .cai-user { background: linear-gradient(135deg, #38f9d7, #43e97b) !important; border-radius: 18px 18px 4px 18px; }' },
        { id:20, name:'Lemonade', cat:'2. Gorgeous Gradients', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { color: var(--cai-tx) !important; } .cai-msg * { color: var(--cai-tx) !important; } .cai-ai { background: linear-gradient(135deg, #fa709a, #fee140) !important; border-radius: 18px 18px 18px 4px; } .cai-user { background: linear-gradient(135deg, #fee140, #fa709a) !important; border-radius: 18px 18px 4px 18px; }' },

        // ── CAT 3: SHADOWS & NEUMORPHISM
        { id:21, name:'Neumorphic Light', cat:'3. Shadows & Neumorphism', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { background: #e0e5ec !important; color: var(--cai-tx) !important; border-radius: 16px; box-shadow: 5px 5px 10px #b8bcc2, -5px -5px 10px #ffffff !important; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:22, name:'Neumorphic Dark', cat:'3. Shadows & Neumorphism', colorLock:true, font:'Poppins', aiTx:'#dddddd', uTx:'#dddddd', css:'.cai-msg { background: #2b2b2b !important; color: var(--cai-tx) !important; border-radius: 16px; box-shadow: 4px 4px 8px #1a1a1a, -4px -4px 8px #3c3c3c !important; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:23, name:'Neumorphic Inset', cat:'3. Shadows & Neumorphism', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { background: #e0e5ec !important; color: var(--cai-tx) !important; border-radius: 16px; box-shadow: inset 4px 4px 8px #b8bcc2, inset -4px -4px 8px #ffffff !important; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:24, name:'Floating White', cat:'3. Shadows & Neumorphism', colorLock:false, font:'Poppins', aiBg:'#ffffff', aiTx:'#333333', uBg:'#ffffff', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; box-shadow: 0 10px 20px rgba(0,0,0,0.15) !important; transform: translateY(-2px); }' },
        { id:25, name:'Sharp Shadow Light', cat:'3. Shadows & Neumorphism', colorLock:false, font:'Poppins', aiBg:'#ffffff', aiTx:'#333333', uBg:'#ffffff', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border: 2px solid #000; border-radius: 12px; box-shadow: 4px 4px 0 #000 !important; }' },
        { id:26, name:'Sharp Shadow Gold', cat:'3. Shadows & Neumorphism', colorLock:false, font:'Poppins', aiBg:'#ffd700', aiTx:'#000000', uBg:'#ffd700', uTx:'#000000', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border: 2px solid #000; border-radius: 12px; box-shadow: 4px 4px 0 #000 !important; }' },
        { id:27, name:'Clay Pink', cat:'3. Shadows & Neumorphism', colorLock:true, font:'Poppins', aiTx:'#000000', uTx:'#000000', css:'.cai-msg { background: #ffb6c1 !important; color: var(--cai-tx) !important; border-radius: 24px; box-shadow: inset -4px -4px 8px rgba(0,0,0,0.1), inset 4px 4px 8px rgba(255,255,255,0.8) !important; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:28, name:'Clay Blue', cat:'3. Shadows & Neumorphism', colorLock:true, font:'Poppins', aiTx:'#000000', uTx:'#000000', css:'.cai-msg { background: #87ceeb !important; color: var(--cai-tx) !important; border-radius: 24px; box-shadow: inset -4px -4px 8px rgba(0,0,0,0.1), inset 4px 4px 8px rgba(255,255,255,0.8) !important; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:29, name:'Left Connected', cat:'3. Shadows & Neumorphism', colorLock:false, font:'Poppins', aiBg:'#f3f4f6', aiTx:'#333333', uBg:'#f3f4f6', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); } .cai-ai { border-radius: 0 16px 16px 16px; border-left: 4px solid #3b82f6; box-shadow: 2px 2px 10px rgba(0,0,0,0.05); } .cai-user { border-radius: 16px 0 16px 16px; border-right: 4px solid #ef4444; box-shadow: -2px 2px 10px rgba(0,0,0,0.05); }' },
        { id:30, name:'Right Connected', cat:'3. Shadows & Neumorphism', colorLock:false, font:'Poppins', aiBg:'#f3f4f6', aiTx:'#333333', uBg:'#f3f4f6', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); } .cai-ai { border-radius: 16px 0 16px 16px; border-right: 4px solid #ef4444; box-shadow: -2px 2px 10px rgba(0,0,0,0.05); } .cai-user { border-radius: 0 16px 16px 16px; border-left: 4px solid #3b82f6; box-shadow: 2px 2px 10px rgba(0,0,0,0.05); }' },

        // ── CAT 4: KAWAII SOFT & SWEET
        { id:31, name:'Cotton Candy', cat:'4. Kawaii Soft & Sweet', colorLock:true, font:'Poppins', aiTx:'#4c1d95', uTx:'#4c1d95', css:'.cai-msg { background: linear-gradient(135deg, #fbcfe8 0%, #bfdbfe 100%) !important; color: var(--cai-tx) !important; border-radius: 24px; box-shadow: 0 4px 15px rgba(191,219,254,0.5) !important; font-weight: 500; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:32, name:'Strawberry Milk', cat:'4. Kawaii Soft & Sweet', colorLock:false, font:'Poppins', aiBg:'#fdf2f8', aiTx:'#831843', uBg:'#fdf2f8', uTx:'#831843', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border: 3px solid #f9a8d4; font-weight: 500; } .cai-ai { border-radius: 24px 24px 24px 8px; } .cai-user { border-radius: 24px 24px 8px 24px; }' },
        { id:33, name:'Blushing Cheeks', cat:'4. Kawaii Soft & Sweet', colorLock:false, font:'Poppins', aiBg:'#ffffff', aiTx:'#333333', uBg:'#ffffff', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 24px; position: relative; overflow: hidden; box-shadow: 0 4px 10px rgba(0,0,0,0.05); } .cai-msg::before, .cai-msg::after { content: ""; position: absolute; bottom: -15px; width: 50px; height: 50px; background: #f9a8d4; border-radius: 50%; filter: blur(12px); opacity: 0.6; z-index: 0; pointer-events: none; } .cai-msg::before { left: -15px; } .cai-msg::after { right: -15px; }' },
        { id:34, name:'Squishy Mochi', cat:'4. Kawaii Soft & Sweet', colorLock:false, font:'Poppins', aiBg:'#dcfce7', aiTx:'#14532d', uBg:'#dcfce7', uTx:'#14532d', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 30px; box-shadow: inset -4px -4px 10px rgba(20,83,45,0.1), inset 4px 4px 10px rgba(255,255,255,0.9) !important; font-weight: 500; }' },
        { id:35, name:'Lemon Dashed', cat:'4. Kawaii Soft & Sweet', colorLock:false, font:'Poppins', aiBg:'#fef9c3', aiTx:'#854d0e', uBg:'#fef9c3', uTx:'#854d0e', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 20px; border: 2px dashed #fde047; }' },
        { id:36, name:'Scalloped Lace', cat:'4. Kawaii Soft & Sweet', colorLock:false, font:'Poppins', aiBg:'#e0e7ff', aiTx:'#312e81', uBg:'#e0e7ff', uTx:'#312e81', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 0 0 16px 16px; margin-top: 12px; position: relative; overflow: visible !important; } .cai-msg::before { content:""; position:absolute; top:-10px; left:0; right:0; height:10px; background-image: radial-gradient(circle at 50% 100%, var(--cai-bg) 6px, transparent 6px); background-size: 12px 10px; background-repeat: repeat-x; }' },
        { id:37, name:'Bubblegum Gloss', cat:'4. Kawaii Soft & Sweet', colorLock:true, font:'Poppins', aiTx:'#831843', uTx:'#831843', css:'.cai-msg { background: #fbcfe8 !important; color: var(--cai-tx) !important; border-radius: 30px; box-shadow: inset 0 8px 10px rgba(255,255,255,0.8), 0 4px 10px rgba(249,168,212,0.4) !important; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:38, name:'Caramel Pudding', cat:'4. Kawaii Soft & Sweet', colorLock:false, font:'Poppins', aiBg:'#fef08a', aiTx:'#713f12', uBg:'#fef08a', uTx:'#713f12', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 12px 12px 24px 24px; border-top: 14px solid #78350f; position: relative; overflow: visible !important; } .cai-msg::before { content:""; position:absolute; top:-14px; left:16px; width:12px; height:22px; background:#78350f; border-radius:10px; } .cai-msg::after { content:""; position:absolute; top:-14px; right:24px; width:14px; height:28px; background:#78350f; border-radius:10px; }' },
        { id:39, name:'Fluffy Cloud', cat:'4. Kawaii Soft & Sweet', colorLock:false, font:'Poppins', aiBg:'#ffffff', aiTx:'#333333', uBg:'#ffffff', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 24px; margin-top: 16px; box-shadow: 0 4px 10px rgba(0,0,0,0.05); position: relative; overflow: visible !important; } .cai-msg::before, .cai-msg::after { content: ""; position: absolute; background: var(--cai-bg); border-radius: 50%; z-index: -1; } .cai-msg::before { top: -14px; left: 24px; width: 32px; height: 32px; } .cai-msg::after { top: -18px; left: 50px; width: 44px; height: 44px; }' },
        { id:40, name:'Sweet Confetti', cat:'4. Kawaii Soft & Sweet', colorLock:false, font:'Poppins', aiBg:'#ffffff', aiTx:'#333333', uBg:'#ffffff', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 20px; background-image: radial-gradient(circle at 15px 15px, #f9a8d4 3px, transparent 4px), radial-gradient(circle at 45px 30px, #818cf8 3px, transparent 4px), radial-gradient(circle at 75px 12px, #fde047 3px, transparent 4px), radial-gradient(circle at 105px 25px, #6ee7b7 3px, transparent 4px), radial-gradient(circle at 135px 15px, #fca5a5 3px, transparent 4px); background-size: 150px 45px; border: 2px solid #f3f4f6; }' },

        // ── CAT 5: BADGES & ADD-ONS
        { id:41, name:'Royal Crown', cat:'5. Badges & Add-ons', colorLock:false, font:'Poppins', aiBg:'#ffffff', aiTx:'#333333', uBg:'#ffffff', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; border: 1px solid #ffd700; margin-top:15px; position:relative; overflow: visible !important; } .cai-msg::after { content:"👑"; position:absolute; top:-18px; right:-10px; font-size:24px; }' },
        { id:42, name:'Heart Ribbon', cat:'5. Badges & Add-ons', colorLock:false, font:'Poppins', aiBg:'#ffffff', aiTx:'#333333', uBg:'#ffffff', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; border: 1px solid #ff69b4; position:relative; overflow: visible !important; } .cai-msg::after { content:"💖"; position:absolute; bottom:-10px; right:-10px; font-size:20px; }' },
        { id:43, name:'Unread Dot', cat:'5. Badges & Add-ons', colorLock:false, font:'Poppins', aiBg:'#e5e5ea', aiTx:'#333333', uBg:'#e5e5ea', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; position:relative; overflow: visible !important; } .cai-msg::after { content:""; position:absolute; top:-5px; right:-5px; width:12px; height:12px; background:red; border-radius:50%; border:2px solid #f3f4f6; }' },
        { id:44, name:'Quoted Message', cat:'5. Badges & Add-ons', colorLock:false, font:'Poppins', aiBg:'#ffffff', aiTx:'#333333', uBg:'#ffffff', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; overflow:hidden; position:relative; z-index:1; } .cai-msg::before { content:"\\""; position:absolute; top:-10px; left:5px; font-size:60px; color:rgba(0,0,0,0.05); font-family:serif; z-index:-1; }' },
        { id:45, name:'Pinned Note', cat:'5. Badges & Add-ons', colorLock:false, font:'Poppins', aiBg:'#fffbd5', aiTx:'#333333', uBg:'#fffbd5', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 4px; box-shadow: 2px 2px 5px rgba(0,0,0,0.1); position:relative; margin-top:15px; overflow: visible !important; } .cai-msg::before { content:"📌"; position:absolute; top:-12px; left:50%; transform:translateX(-50%); font-size:20px; }' },
        { id:46, name:'VIP Star', cat:'5. Badges & Add-ons', colorLock:false, font:'Poppins', aiBg:'#1a1a1a', aiTx:'#ffffff', uBg:'#1a1a1a', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; position:relative; overflow: visible !important; } .cai-msg::after { content:"✨"; position:absolute; top:-10px; left:-10px; font-size:20px; }' },
        { id:47, name:'Spring Flower', cat:'5. Badges & Add-ons', colorLock:false, font:'Poppins', aiBg:'#e0f7fa', aiTx:'#006064', uBg:'#e0f7fa', uTx:'#006064', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; position:relative; overflow: visible !important; } .cai-msg::after { content:"🌸"; position:absolute; bottom:-10px; left:-10px; font-size:24px; }' },
        { id:48, name:'Sparkles', cat:'5. Badges & Add-ons', colorLock:false, font:'Poppins', aiBg:'#ffffff', aiTx:'#333333', uBg:'#ffffff', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; border: 2px dashed #a855f7; position:relative; overflow: visible !important; } .cai-msg::before { content:"✦"; position:absolute; top:-8px; right:-5px; color:#a855f7; font-size:18px;} .cai-msg::after { content:"✦"; position:absolute; bottom:-8px; left:-5px; color:#a855f7; font-size:18px;}' },
        { id:49, name:'Music Note', cat:'5. Badges & Add-ons', colorLock:false, font:'Poppins', aiBg:'#fdf2f8', aiTx:'#831843', uBg:'#fdf2f8', uTx:'#831843', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; position:relative; overflow: visible !important; } .cai-msg::after { content:"🎵"; position:absolute; top:-12px; right:10px; font-size:18px; animation: caiBounce 2s infinite; } @keyframes caiBounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }' },
        { id:50, name:'Sleeping Zzz', cat:'5. Badges & Add-ons', colorLock:false, font:'Poppins', aiBg:'#1e1b4b', aiTx:'#c7d2fe', uBg:'#1e1b4b', uTx:'#c7d2fe', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; position:relative; overflow: visible !important; } .cai-msg::after { content:"💤"; position:absolute; top:-15px; right:-5px; font-size:16px; color:#fff;}' },

        // ── CAT 6: PATTERNS
        { id:51, name:'Polka Dots', cat:'6. Fun Patterns', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { background: radial-gradient(#cbd5e1 15%, transparent 16%) 0 0, radial-gradient(#cbd5e1 15%, transparent 16%) 8px 8px !important; background-size: 16px 16px !important; background-color: #f8fafc !important; color: var(--cai-tx) !important; border-radius: 16px; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:52, name:'Candy Stripes', cat:'6. Fun Patterns', colorLock:true, font:'Poppins', aiTx:'#991b1b', uTx:'#991b1b', css:'.cai-msg { background: repeating-linear-gradient(45deg, #fecaca, #fecaca 10px, #fff 10px, #fff 20px) !important; color: var(--cai-tx) !important; border-radius: 16px; font-weight: 500;} .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:53, name:'Graph Paper', cat:'6. Fun Patterns', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { background: linear-gradient(90deg, rgba(0,0,0,0.05) 1px, transparent 1px), linear-gradient(rgba(0,0,0,0.05) 1px, transparent 1px) !important; background-size: 10px 10px !important; background-color: #fff !important; color: var(--cai-tx) !important; border-radius: 16px; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:54, name:'Sunburst', cat:'6. Fun Patterns', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { background: repeating-conic-gradient(#e2e8f0 0% 25%, #fff 0% 50%) !important; background-size: 20px 20px !important; color: var(--cai-tx) !important; border-radius: 16px; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:55, name:'Blue Chevron', cat:'6. Fun Patterns', colorLock:true, font:'Poppins', aiTx:'#1e3a8a', uTx:'#1e3a8a', css:'.cai-msg { background: repeating-linear-gradient(-45deg, #bfdbfe, #bfdbfe 5px, #fff 5px, #fff 10px) !important; color: var(--cai-tx) !important; border-radius: 16px; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:56, name:'Yellow Zigzag', cat:'6. Fun Patterns', colorLock:true, font:'Poppins', aiTx:'#854d0e', uTx:'#854d0e', css:'.cai-msg { background: repeating-linear-gradient(0deg, #fef08a, #fef08a 2px, #fff 2px, #fff 6px) !important; color: var(--cai-tx) !important; border-radius: 16px; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:57, name:'Purple Waves', cat:'6. Fun Patterns', colorLock:true, font:'Poppins', aiTx:'#4c1d95', uTx:'#4c1d95', css:'.cai-msg { background: radial-gradient(circle at 100% 50%, transparent 20%, #e9d5ff 21%, #e9d5ff 34%, transparent 35%, transparent), radial-gradient(circle at 0% 50%, transparent 20%, #e9d5ff 21%, #e9d5ff 34%, transparent 35%, transparent) 0 -25px !important; background-size: 30px 40px !important; background-color: #fff !important; color: var(--cai-tx) !important; border-radius: 16px; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:58, name:'TV Static Noise', cat:'6. Fun Patterns', colorLock:true, font:'VT323', aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { background: url("data:image/svg+xml,%3Csvg viewBox=\'0 0 200 200\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cfilter id=\'noiseFilter\'%3E%3CfeTurbulence type=\'fractalNoise\' baseFrequency=\'0.65\' numOctaves=\'3\' stitchTiles=\'stitch\'/%3E%3C/filter%3E%3Crect width=\'100%25\' height=\'100%25\' filter=\'url(%23noiseFilter)\'/%3E%3C/svg%3E") !important; color: var(--cai-tx) !important; border-radius: 16px; text-shadow: 1px 1px 2px #000; background-color: #444 !important;} .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:59, name:'Tartan Plaid', cat:'6. Fun Patterns', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { background: repeating-linear-gradient(90deg, transparent, transparent 20px, rgba(255,0,0,0.1) 20px, rgba(255,0,0,0.1) 22px), repeating-linear-gradient(0deg, transparent, transparent 20px, rgba(0,0,255,0.1) 20px, rgba(0,0,255,0.1) 22px) !important; background-color: #fff !important; color: var(--cai-tx) !important; border-radius: 16px; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:60, name:'Honey Dots', cat:'6. Fun Patterns', colorLock:true, font:'Poppins', aiTx:'#b45309', uTx:'#b45309', css:'.cai-msg { background: radial-gradient(circle, transparent 20%, #fff 20%, #fff 80%, transparent 80%, transparent), radial-gradient(circle, transparent 20%, #fff 20%, #fff 80%, transparent 80%, transparent) 25px 25px, linear-gradient(#fcd34d 4px, transparent 4px) 0 -2px, linear-gradient(90deg, #fcd34d 4px, #fff 4px) !important; background-size: 50px 50px, 50px 50px, 25px 25px, 25px 25px !important; color: var(--cai-tx) !important; border-radius: 16px; } .cai-msg * { color: var(--cai-tx) !important; }' },

        // ── CAT 7: SHAPES & OUTLINES
        { id:61, name:'Comic Book', cat:'7. Shapes & Outlines', font:'Comic Neue', aiBg:'#ffffff', aiTx:'#000000', uBg:'#ffffff', uTx:'#000000', spDef:'#ff3366', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border: 3px solid #000; border-radius: 16px; box-shadow: 3px 3px 0 var(--cai-sp) !important; }' },
        { id:62, name:'Dashed Border', cat:'7. Shapes & Outlines', font:'Poppins', aiBg:'transparent', aiTx:'#333333', uBg:'transparent', uTx:'#333333', spDef:'#888888', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border: 2px dashed var(--cai-sp); border-radius: 16px; }' },
        { id:63, name:'Dotted Border', cat:'7. Shapes & Outlines', font:'Poppins', aiBg:'#ffffff', aiTx:'#333333', uBg:'#ffffff', uTx:'#333333', spDef:'#3b82f6', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border: 3px dotted var(--cai-sp); border-radius: 16px; }' },
        { id:64, name:'Double Line', cat:'7. Shapes & Outlines', font:'Poppins', aiBg:'#ffffff', aiTx:'#333333', uBg:'#ffffff', uTx:'#333333', spDef:'#10b981', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border: 4px double var(--cai-sp); border-radius: 16px; }' },
        { id:65, name:'Leaf Cut', cat:'7. Shapes & Outlines', font:'Poppins', aiBg:'#34d399', aiTx:'#ffffff', uBg:'#34d399', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); } .cai-ai { border-radius: 0 24px 0 24px; } .cai-user { border-radius: 24px 0 24px 0; }' },
        { id:66, name:'Pill Shape', cat:'7. Shapes & Outlines', font:'Poppins', aiBg:'#f472b6', aiTx:'#ffffff', uBg:'#f472b6', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 50px; text-align: center; }' },
        { id:67, name:'Sharp Box', cat:'7. Shapes & Outlines', font:'Poppins', aiBg:'#333333', aiTx:'#ffffff', uBg:'#333333', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 0; }' },
        { id:68, name:'Movie Ticket', cat:'7. Shapes & Outlines', font:'Poppins', aiBg:'#ffffff', aiTx:'#333333', uBg:'#ffffff', uTx:'#333333', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 8px; mask-image: radial-gradient(circle at 100% 50%, transparent 6px, black 7px), radial-gradient(circle at 0% 50%, transparent 6px, black 7px); mask-size: 51% 100%; mask-position: right, left; mask-repeat: no-repeat; -webkit-mask-image: radial-gradient(circle at 100% 50%, transparent 6px, black 7px), radial-gradient(circle at 0% 50%, transparent 6px, black 7px); -webkit-mask-size: 51% 100%; -webkit-mask-position: right, left; -webkit-mask-repeat: no-repeat; border-left: 2px dashed #ccc; border-right: 2px dashed #ccc; }' },
        { id:69, name:'Folded Paper Corner', cat:'7. Shapes & Outlines', font:'Poppins', aiBg:'#fbbf24', aiTx:'#000000', uBg:'#fbbf24', uTx:'#000000', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 8px; position: relative; } .cai-ai { border-top-left-radius: 0; } .cai-ai::before { content:""; position:absolute; top:0; left:0; width:0; height:0; border-width: 0 0 20px 20px; border-style: solid; border-color: transparent transparent var(--cai-bg) rgba(0,0,0,0.15); box-shadow: 2px 2px 2px rgba(0,0,0,0.1); border-bottom-right-radius: 4px; } .cai-user { border-top-right-radius: 0; } .cai-user::before { content:""; position:absolute; top:0; right:0; width:0; height:0; border-width: 0 20px 20px 0; border-style: solid; border-color: transparent transparent var(--cai-bg) rgba(0,0,0,0.15); box-shadow: -2px 2px 2px rgba(0,0,0,0.1); border-bottom-left-radius: 4px; }' },
        { id:70, name:'Hexagon Cut', cat:'7. Shapes & Outlines', font:'Poppins', aiBg:'#8b5cf6', aiTx:'#ffffff', uBg:'#8b5cf6', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); clip-path: polygon(10% 0, 100% 0, 100% 80%, 90% 100%, 0 100%, 0 20%); border-radius: 4px; }' },

        // ── CAT 8: MATERIALS & TEXTURES
        { id:71, name:'Frosted Glass', cat:'8. Materials & Textures', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { background: rgba(255,255,255,0.4) !important; backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important; border: 1px solid rgba(255,255,255,0.5); color: var(--cai-tx) !important; border-radius: 16px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:72, name:'Tinted Dark Glass', cat:'8. Materials & Textures', colorLock:true, font:'Poppins', aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { background: rgba(0,0,0,0.6) !important; backdrop-filter: blur(8px) !important; -webkit-backdrop-filter: blur(8px) !important; color: var(--cai-tx) !important; border-radius: 16px; border: 1px solid rgba(255,255,255,0.1); } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:73, name:'Metallic Silver', cat:'8. Materials & Textures', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { background: linear-gradient(to bottom, #f5f5f5, #e0e0e0) !important; color: var(--cai-tx) !important; border-radius: 16px; border: 1px solid #ccc; box-shadow: inset 0 1px 0 #fff; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:74, name:'Gold Foil', cat:'8. Materials & Textures', colorLock:true, font:'Poppins', aiTx:'#4a3300', uTx:'#4a3300', css:'.cai-msg { background: linear-gradient(135deg, #bf953f, #fcf6ba, #b38728, #fbf5b7, #aa771c) !important; color: var(--cai-tx) !important; border-radius: 16px; font-weight: 600; box-shadow: 0 4px 10px rgba(170,119,28,0.3) !important; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:75, name:'Wooden Board', cat:'8. Materials & Textures', font:'Comic Neue', colorLock:true, aiTx:'#5c3a21', uTx:'#5c3a21', css:'.cai-msg { background: #deb887 !important; color: var(--cai-tx) !important; border-radius: 16px; background-image: repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(0,0,0,0.05) 10px, rgba(0,0,0,0.05) 20px) !important; border: 2px solid #8b4513; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:76, name:'Chalkboard', cat:'8. Materials & Textures', font:'Comic Neue', colorLock:true, aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { background: #2f4f4f !important; color: var(--cai-tx) !important; border-radius: 8px; border: 4px solid #8b4513; box-shadow: inset 0 0 10px rgba(0,0,0,0.5); } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:77, name:'Notepad Paper', cat:'8. Materials & Textures', font:'Poppins', colorLock:true, aiTx:'#000000', uTx:'#000000', css:'.cai-msg { background: #fffacd !important; color: var(--cai-tx) !important; border-radius: 4px; background-image: linear-gradient(#87ceeb 1px, transparent 1px) !important; background-size: 100% 20px !important; line-height: 20px; padding-top: 14px; box-shadow: 2px 2px 5px rgba(0,0,0,0.1); border-left: 2px solid #ff7f50; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:78, name:'Brown Leather', cat:'8. Materials & Textures', font:'Poppins', aiBg:'#8b4513', aiTx:'#ffffff', uBg:'#8b4513', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 12px; border: 2px dashed #deb887; box-shadow: 0 0 0 4px var(--cai-bg); margin: 4px; }' },
        { id:79, name:'Blue Denim', cat:'8. Materials & Textures', font:'Poppins', aiBg:'#1e3a8a', aiTx:'#ffffff', uBg:'#1e3a8a', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 12px; border: 2px dashed #f59e0b; box-shadow: 0 0 0 4px var(--cai-bg); margin: 4px; }' },
        { id:80, name:'Holographic Foil', cat:'8. Materials & Textures', font:'Poppins', colorLock:true, aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { background: linear-gradient(124deg, #ff2400, #e81d1d, #e8b71d, #e3e81d, #1de840, #1ddde8, #2b1de8, #dd00f3, #dd00f3) !important; background-size: 400% 400% !important; animation: caiHolo 5s ease infinite; color: var(--cai-tx) !important; border-radius: 16px; font-weight: bold; text-shadow: 1px 1px 2px #000; } .cai-msg * { color: var(--cai-tx) !important; }' },

        // ── CAT 9: SCI-FI & RETRO
        { id:81, name:'8-Bit Box', cat:'9. Sci-Fi & Retro', font:'VT323', aiBg:'#000000', aiTx:'#ffffff', uBg:'#000000', uTx:'#ffffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); font-size: 18px; border: 4px solid #fff; box-shadow: inset -4px -4px 0 #888, inset 4px 4px 0 #ccc; border-radius: 0; }' },
        { id:82, name:'Cyberpunk Polygon', cat:'9. Sci-Fi & Retro', font:'VT323', aiBg:'#fdf100', aiTx:'#000000', uBg:'#fdf100', uTx:'#000000', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); font-weight: bold; clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%); border-left: 4px solid #00f0ff; }' },
        { id:83, name:'Vaporwave', cat:'9. Sci-Fi & Retro', colorLock:true, font:'Poppins', aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { background: linear-gradient(180deg, #ff71ce 0%, #01cdfe 100%) !important; color: var(--cai-tx) !important; border-radius: 0; border: 2px solid #05ffa1; text-shadow: 2px 2px #b967ff; font-family: "Times New Roman", serif !important; } .cai-msg * { color: var(--cai-tx) !important; font-family: "Times New Roman", serif !important; }' },
        { id:84, name:'Terminal Green', cat:'9. Sci-Fi & Retro', font:'VT323', aiBg:'#000000', aiTx:'#00ff00', uBg:'#000000', uTx:'#00ff00', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); font-size: 18px; border-radius: 4px; border: 1px solid #0f0; }' },
        { id:85, name:'Retro Arcade', cat:'9. Sci-Fi & Retro', font:'VT323', aiBg:'#111827', aiTx:'#fde047', uBg:'#111827', uTx:'#fde047', spDef:'#ec4899', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); font-size: 18px; border: 2px solid var(--cai-sp); box-shadow: 4px 4px 0 var(--cai-sp) !important; border-radius: 8px; text-transform: uppercase; }' },
        { id:86, name:'Matrix Code', cat:'9. Sci-Fi & Retro', font:'VT323', aiBg:'#000000', aiTx:'#00ff00', uBg:'#000000', uTx:'#00ff00', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); overflow: hidden; position: relative; border-radius: 8px; } .cai-msg::before { content:"1010101010"; position:absolute; top:0; left:0; color:rgba(0,255,0,0.1); font-size:40px; word-wrap:break-word; line-height:0.8; z-index:0; } .cai-msg * { position:relative; z-index:1; }' },
        { id:87, name:'CRT Scanlines', cat:'9. Sci-Fi & Retro', font:'VT323', aiBg:'#222222', aiTx:'#88ff88', uBg:'#222222', uTx:'#88ff88', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); font-size:18px; border-radius: 10px; background-image: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.2) 2px, rgba(0,0,0,0.2) 4px); box-shadow: inset 0 0 10px #000; }' },
        { id:88, name:'Neon Magenta', cat:'9. Sci-Fi & Retro', font:'Poppins', aiBg:'#111111', aiTx:'#ffffff', uBg:'#111111', uTx:'#ffffff', spDef:'#ff00ff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; border: 2px solid var(--cai-sp); box-shadow: 0 0 10px var(--cai-sp), inset 0 0 10px var(--cai-sp) !important; text-shadow: 0 0 5px var(--cai-sp); }' },
        { id:89, name:'Neon Cyan', cat:'9. Sci-Fi & Retro', font:'Poppins', aiBg:'#111111', aiTx:'#ffffff', uBg:'#111111', uTx:'#ffffff', spDef:'#00ffff', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; border: 2px solid var(--cai-sp); box-shadow: 0 0 10px var(--cai-sp), inset 0 0 10px var(--cai-sp) !important; text-shadow: 0 0 5px var(--cai-sp); }' },
        { id:90, name:'Windows 95 Classic', cat:'9. Sci-Fi & Retro', font:'VT323', aiBg:'#c0c0c0', aiTx:'#000000', uBg:'#c0c0c0', uTx:'#000000', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 0; border-top: 2px solid #fff; border-left: 2px solid #fff; border-right: 2px solid #888; border-bottom: 2px solid #888; font-size: 18px; }' },

        // ── CAT 10: VIP & ANIMATIONS
        { id:91, name:'Cosmic Space', cat:'10. VIP & Animations', colorLock:true, font:'Poppins', aiTx:'#e2e8f0', uTx:'#e2e8f0', css:'.cai-msg { background: #0f172a !important; color: var(--cai-tx) !important; border-radius: 16px; background-image: radial-gradient(white, rgba(255,255,255,.2) 2px, transparent 4px), radial-gradient(white, rgba(255,255,255,.15) 1px, transparent 3px) !important; background-size: 30px 30px, 20px 20px !important; background-position: 0 0, 15px 15px !important; border: 1px solid #334155; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:92, name:'Aurora Borealis', cat:'10. VIP & Animations', colorLock:true, font:'Poppins', aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { background: linear-gradient(270deg, #10b981, #3b82f6, #8b5cf6) !important; background-size: 600% 600% !important; animation: caiAurora 6s ease infinite; color: var(--cai-tx) !important; border-radius: 16px; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:93, name:'Pulse Glow', cat:'10. VIP & Animations', font:'Poppins', aiBg:'#c084fc', aiTx:'#ffffff', uBg:'#c084fc', uTx:'#ffffff', spDef:'#c084fc', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; animation: caiPulse 2s infinite; } @keyframes caiPulse { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--cai-sp) 70%, transparent); } 70% { box-shadow: 0 0 0 10px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }' },
        { id:94, name:'Shake on Hover', cat:'10. VIP & Animations', font:'Poppins', aiBg:'#fcd34d', aiTx:'#78350f', uBg:'#fcd34d', uTx:'#78350f', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; transition: transform 0.2s; } .cai-msg:hover { animation: caiShake 0.5s cubic-bezier(.36,.07,.19,.97) both; } @keyframes caiShake { 10%, 90% { transform: translate3d(-1px, 0, 0); } 20%, 80% { transform: translate3d(2px, 0, 0); } 30%, 50%, 70% { transform: translate3d(-4px, 0, 0); } 40%, 60% { transform: translate3d(4px, 0, 0); } }' },
        { id:95, name:'Rainbow Text', cat:'10. VIP & Animations', colorLock:true, font:'Poppins', aiTx:'transparent', uTx:'transparent', css:'.cai-msg { background: #111 !important; border-radius: 16px; font-weight: bold; border: 1px solid #333; } .cai-msg p, .cai-msg span, .cai-msg div, .cai-msg textarea { background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet) !important; -webkit-background-clip: text !important; background-clip: text !important; -webkit-text-fill-color: transparent !important; color: transparent !important; }' },
        { id:96, name:'Color Shifting', cat:'10. VIP & Animations', colorLock:true, font:'Poppins', aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { background: #3b82f6 !important; color: var(--cai-tx) !important; border-radius: 16px; animation: caiColorShift 5s infinite alternate; } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:97, name:'Neon Pulse Border', cat:'10. VIP & Animations', font:'Poppins', aiBg:'#000000', aiTx:'#ffffff', uBg:'#000000', uTx:'#ffffff', spDef:'#a855f7', css:'.cai-msg { background: var(--cai-bg); color: var(--cai-tx); border-radius: 16px; border: 2px solid var(--cai-sp); box-shadow: 0 0 10px var(--cai-sp), inset 0 0 10px var(--cai-sp) !important; animation: caiSmoothNeon 2s ease-in-out infinite alternate; text-shadow: 0 0 5px #fff, 0 0 10px var(--cai-sp); } @keyframes caiSmoothNeon { 0% { box-shadow: 0 0 5px var(--cai-sp), inset 0 0 5px var(--cai-sp); border-color: var(--cai-sp); } 100% { box-shadow: 0 0 20px color-mix(in srgb, var(--cai-sp) 80%, white), 0 0 10px color-mix(in srgb, var(--cai-sp) 80%, white), inset 0 0 10px color-mix(in srgb, var(--cai-sp) 80%, white); border-color: color-mix(in srgb, var(--cai-sp) 80%, white); color: #fff; text-shadow: 0 0 8px #fff, 0 0 15px color-mix(in srgb, var(--cai-sp) 80%, white); } }' },
        { id:98, name:'Liquid Gold', cat:'10. VIP & Animations', colorLock:true, font:'Poppins', aiTx:'#ffffff', uTx:'#ffffff', css:'.cai-msg { background: linear-gradient(135deg, #f6d365 0%, #fda085 100%) !important; color: var(--cai-tx) !important; border-radius: 16px; position: relative; overflow: hidden; border: none; } .cai-msg::after { content:""; position:absolute; top:0; left:-100%; width:50%; height:100%; background: linear-gradient(to right, transparent, rgba(255,255,255,0.4), transparent); transform: skewX(-20deg); animation: caiShine 3s infinite; } @keyframes caiShine { 0% { left: -100%; } 20% { left: 200%; } 100% { left: 200%; } } .cai-msg * { color: var(--cai-tx) !important; }' },
        { id:99, name:'Party Confetti', cat:'10. VIP & Animations', colorLock:true, font:'Poppins', aiTx:'#333333', uTx:'#333333', css:'.cai-msg { background: #fff !important; color: var(--cai-tx) !important; border-radius: 16px; position: relative; overflow: hidden; } .cai-msg::before { content:""; position:absolute; top:0; left:0; right:0; bottom:0; background-image: radial-gradient(circle, #ff0000 2px, transparent 2px), radial-gradient(circle, #00ff00 2px, transparent 2px), radial-gradient(circle, #0000ff 2px, transparent 2px) !important; background-size: 20px 20px !important; background-position: 0 0, 10px 10px, 5px 15px !important; opacity: 0.3; z-index: 0; } .cai-msg * { color: var(--cai-tx) !important; position: relative; z-index: 1; }' },
        { id:100, name:'Ultimate Diamond VIP', cat:'10. VIP & Animations', colorLock:true, font:'Poppins', aiTx:'#ffd700', uTx:'#ffd700', css:'.cai-msg { background: linear-gradient(45deg, #111, #333) !important; color: var(--cai-tx) !important; border-radius: 20px; border: 2px solid #ffd700; box-shadow: 0 0 15px #ffd700 !important; text-transform: uppercase; font-weight: 900; letter-spacing: 1px; position: relative; overflow: visible !important; } .cai-msg::before { content:"💎"; position:absolute; top:-15px; left:-10px; font-size:24px; animation: caiFloat 2s infinite; } .cai-msg::after { content:"👑"; position:absolute; bottom:-15px; right:-10px; font-size:24px; animation: caiFloat 2s infinite reverse; } @keyframes caiFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } } .cai-msg * { color: var(--cai-tx) !important; }' },

        // ── CAT 11: EMOJI BG (iOS Style)
        { id:101, name:'Peaches', cat:'11. Emoji Backgrounds (iOS Style)', emojiCat:true, defaultEmoji:'🍑', font:'Poppins', aiBg:'#fff5f5', aiTx:'#9a3412', uBg:'#fff5f5', uTx:'#9a3412', css:'.cai-msg { color: var(--cai-tx); border-radius: 18px; border: 1px solid #ffedd5; box-shadow: 0 4px 10px rgba(0,0,0,0.05); }' },
        { id:102, name:'Cherry Blossoms', cat:'11. Emoji Backgrounds (iOS Style)', emojiCat:true, defaultEmoji:'🌸', font:'Poppins', aiBg:'#fdf2f8', aiTx:'#831843', uBg:'#fdf2f8', uTx:'#831843', css:'.cai-msg { color: var(--cai-tx); border-radius: 18px; border: 1px solid #fce7f3; box-shadow: 0 4px 10px rgba(0,0,0,0.05); }' },
        { id:103, name:'Strawberries', cat:'11. Emoji Backgrounds (iOS Style)', emojiCat:true, defaultEmoji:'🍓', font:'Poppins', aiBg:'#fef2f2', aiTx:'#7f1d1d', uBg:'#fef2f2', uTx:'#7f1d1d', css:'.cai-msg { color: var(--cai-tx); border-radius: 18px; border: 1px solid #fee2e2; box-shadow: 0 4px 10px rgba(0,0,0,0.05); }' },
        { id:104, name:'Puppies', cat:'11. Emoji Backgrounds (iOS Style)', emojiCat:true, defaultEmoji:'🐶', font:'Poppins', aiBg:'#fffbeb', aiTx:'#78350f', uBg:'#fffbeb', uTx:'#78350f', css:'.cai-msg { color: var(--cai-tx); border-radius: 18px; border: 1px solid #fef3c7; box-shadow: 0 4px 10px rgba(0,0,0,0.05); }' },
        { id:105, name:'Sparkle Stars', cat:'11. Emoji Backgrounds (iOS Style)', emojiCat:true, defaultEmoji:'✨', font:'Poppins', aiBg:'#fafafa', aiTx:'#111111', uBg:'#fafafa', uTx:'#111111', css:'.cai-msg { color: var(--cai-tx); border-radius: 18px; border: 1px solid #f3f4f6; box-shadow: 0 4px 10px rgba(0,0,0,0.05); }' },
        { id:106, name:'Avocados', cat:'11. Emoji Backgrounds (iOS Style)', emojiCat:true, defaultEmoji:'🥑', font:'Poppins', aiBg:'#f0fdf4', aiTx:'#14532d', uBg:'#f0fdf4', uTx:'#14532d', css:'.cai-msg { color: var(--cai-tx); border-radius: 18px; border: 1px solid #dcfce7; box-shadow: 0 4px 10px rgba(0,0,0,0.05); }' },
        { id:107, name:'Ghosts', cat:'11. Emoji Backgrounds (iOS Style)', emojiCat:true, defaultEmoji:'👻', font:'Poppins', aiBg:'#f8fafc', aiTx:'#334155', uBg:'#f8fafc', uTx:'#334155', css:'.cai-msg { color: var(--cai-tx); border-radius: 18px; border: 1px solid #e2e8f0; box-shadow: 0 4px 10px rgba(0,0,0,0.05); }' },
        { id:108, name:'Clouds', cat:'11. Emoji Backgrounds (iOS Style)', emojiCat:true, defaultEmoji:'☁️', font:'Poppins', aiBg:'#eff6ff', aiTx:'#1e3a8a', uBg:'#eff6ff', uTx:'#1e3a8a', css:'.cai-msg { color: var(--cai-tx); border-radius: 18px; border: 1px solid #dbeafe; box-shadow: 0 4px 10px rgba(0,0,0,0.05); }' },
        { id:109, name:'Money Stack', cat:'11. Emoji Backgrounds (iOS Style)', emojiCat:true, defaultEmoji:'💸', font:'Poppins', aiBg:'#ecfdf5', aiTx:'#064e3b', uBg:'#ecfdf5', uTx:'#064e3b', css:'.cai-msg { color: var(--cai-tx); border-radius: 18px; border: 1px solid #d1fae5; box-shadow: 0 4px 10px rgba(0,0,0,0.05); }' },
        { id:110, name:'Dark Skulls', cat:'11. Emoji Backgrounds (iOS Style)', emojiCat:true, defaultEmoji:'💀', font:'Poppins', aiBg:'#0f172a', aiTx:'#f8fafc', uBg:'#0f172a', uTx:'#f8fafc', css:'.cai-msg { color: var(--cai-tx); border-radius: 18px; border: 1px solid #334155; box-shadow: 0 4px 10px rgba(0,0,0,0.05); }' },
    ];

    // Native DOM targets: char bubble = elevation-3, user bubble = elevation-2 (per FINDINGS)
    const PACK_CHAR = '#chat-messages .bg-surface-elevation-3';
    const PACK_USER = '#chat-messages .bg-surface-elevation-2';
    const PACK_MSG = PACK_CHAR + ', ' + PACK_USER;

    function packBuildEmojiCSS(cfg, def) {
        const emojis = (cfg.customEmoji && cfg.customEmoji.length) ? cfg.customEmoji : (def.defaultEmoji ? [def.defaultEmoji] : ['⭐']);
        const angle = cfg.emojiAngle || 0;
        const alpha = cfg.bgAlpha !== undefined ? cfg.bgAlpha : 0.85;
        function hexToRgba(hex, al) {
            if (!hex || !hex.startsWith('#')) return `rgba(255,255,255,${al})`;
            const r = parseInt(hex.slice(1,3)||'ff',16), g = parseInt(hex.slice(3,5)||'ff',16), b = parseInt(hex.slice(5,7)||'ff',16);
            return `rgba(${r},${g},${b},${al})`;
        }
        const n = emojis.length, w = 50, h = 46, fontSize = 22;
        const pw = Math.max(w * n, w), ph = h * 2;
        let texts = '';
        for (let i = -2; i <= n + 1; i++) {
            const idx = ((i % n) + n) % n, emoji = emojis[idx];
            texts += `<text x="${i * w + w/2}" y="${h/2}" font-size="${fontSize}" text-anchor="middle" dominant-baseline="central">${emoji}</text>`;
            texts += `<text x="${i * w + w/2 + w/2}" y="${h + h/2}" font-size="${fontSize}" text-anchor="middle" dominant-baseline="central">${emoji}</text>`;
        }
        const svgStr = `<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%'><defs><pattern id='p' width='${pw}' height='${ph}' patternUnits='userSpaceOnUse' patternTransform='rotate(${angle})'>${texts}</pattern></defs><rect width='100%' height='100%' fill='url(#p)' /></svg>`;
        const enc = encodeURIComponent(svgStr).replace(/'/g, "%27").replace(/"/g, "%22");
        const aiRgba = hexToRgba(cfg.aiColor || def.aiBg, alpha);
        const uRgba  = hexToRgba(cfg.userColor || def.uBg, alpha);
        return `.cai-ai { background: linear-gradient(${aiRgba}, ${aiRgba}), url("data:image/svg+xml;charset=utf-8,${enc}") !important; } .cai-user { background: linear-gradient(${uRgba}, ${uRgba}), url("data:image/svg+xml;charset=utf-8,${enc}") !important; }`;
    }

    let bubblePackStyleEl = null;
    function applyBubbleStyle(id) {
        if (bubblePackStyleEl) { bubblePackStyleEl.remove(); bubblePackStyleEl = null; }
        if (!id) {
            // Reassert the user's font — pack CSS forced its own family, so restore on clear.
            try { applyFont({ fontName: getFontSetting() }); } catch (e) {}
            updatePackPreview(null);
            return;
        }
        const def = BUBBLES.find(b => b.id === id);
        if (!def) return;
        const font = def.font || 'Poppins';
        // Load pack font under its own link so it doesn't evict the user's chat font.
        try {
            const packFontUrl = getGoogleFontUrl(font);
            let pl = document.getElementById('cai-pack-font-link');
            if (!pl) {
                pl = document.createElement('link');
                pl.id = 'cai-pack-font-link';
                pl.rel = 'stylesheet';
                pl.crossOrigin = 'anonymous';
                document.head.appendChild(pl);
            }
            pl.href = packFontUrl;
        } catch (e) {}
        const aiBg = def.aiBg || '#ffffff', aiTx = def.aiTx || '#000000';
        const uBg = def.uBg || '#ffffff', uTx = def.uTx || '#000000';
        const sp = def.spDef || '#000000';
        let css = `
            ${PACK_CHAR} { --cai-bg:${aiBg}; --cai-tx:${aiTx}; --cai-sp:${sp}; background:var(--cai-bg) !important; font-family:'${font}',sans-serif !important; }
            ${PACK_USER} { --cai-bg:${uBg}; --cai-tx:${uTx}; --cai-sp:${sp}; background:var(--cai-bg) !important; font-family:'${font}',sans-serif !important; }
            ${PACK_CHAR} .prose, ${PACK_CHAR} .prose *, ${PACK_CHAR} p, ${PACK_CHAR} span { color:var(--cai-tx) !important; font-family:'${font}',sans-serif !important; }
            ${PACK_USER} .prose, ${PACK_USER} .prose *, ${PACK_USER} p, ${PACK_USER} span { color:var(--cai-tx) !important; font-family:'${font}',sans-serif !important; }
        `;
        let shared = (def.css || '')
            .replace(/\.cai-msg/g, ':is(' + PACK_MSG + ')')
            .replace(/\.cai-ai/g, PACK_CHAR)
            .replace(/\.cai-user/g, PACK_USER);
        css += shared;
        if (def.emojiCat) {
            let eCSS = packBuildEmojiCSS({ customEmoji: [def.defaultEmoji], emojiAngle: 0, bgAlpha: 0.85, aiColor: aiBg, userColor: uBg }, def)
                .replace(/\.cai-ai/g, PACK_CHAR).replace(/\.cai-user/g, PACK_USER);
            css += eCSS;
        }
        css += `
            @keyframes caiHolo{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
            @keyframes caiAurora{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
            @keyframes caiColorShift{0%{filter:hue-rotate(0deg)}100%{filter:hue-rotate(360deg)}}
        `;
        bubblePackStyleEl = document.createElement('style');
        bubblePackStyleEl.id = 'cai-bubble-pack';
        bubblePackStyleEl.textContent = css;
        document.head.appendChild(bubblePackStyleEl);
        updatePackPreview(id);
    }

    // Paint the modal's little preview bubbles with the selected pack's colors/font/shape.
    function updatePackPreview(id) {
        const charBubble = document.querySelector('#preview-char');
        const userBubble = document.querySelector('#preview-user');
        if (!charBubble || !userBubble) return;
        [charBubble, userBubble].forEach(b => {
            b.style.backgroundImage = '';
            b.style.border = '';
            b.style.fontFamily = '';
        });
        if (!id) {
            // Revert to whatever the active color theme wants.
            const s = getCurrentSettings();
            updatePreview(s.theme || 'Default');
            return;
        }
        const def = BUBBLES.find(b => b.id === id);
        if (!def) return;
        const aiBg = def.aiBg || '#ffffff', uBg = def.uBg || '#ffffff';
        // Gradient packs: pull the first gradient from the css blob if present.
        const grabBg = (side) => {
            const m = new RegExp('\\.cai-' + side + '\\s*\\{[^}]*background:\\s*(linear-gradient\\([^;!]*\\))', 'i').exec(def.css || '');
            return m ? m[1] : null;
        };
        const charGrad = grabBg('ai'), userGrad = grabBg('user');
        // PACK_CHAR targets elevation-3 (user bubble), PACK_USER targets elevation-2 (bot bubble).
        // Actual chat: bot gets uBg, user gets aiBg — preview must match this swap.
        charBubble.style.background = userGrad || uBg;
        userBubble.style.background = charGrad || aiBg;
    }

    // Curated subset shown in the picker. c1 = user-side swatch, c2 = char-side swatch
    // (mirrors native chat-color circle: user color left, char color right).
    const CURATED_PACKS = [
        { id:1,  c1:'#0b93f6', c2:'#e5e5ea' }, // iOS Light
        { id:9,  c1:'#5865F2', c2:'#5865F2' }, // Discord Blurple
        { id:11, c1:'#ff9a9e', c2:'#fecfef' }, // Sunset
        { id:12, c1:'#a1c4fd', c2:'#c2e9fb' }, // Ocean
        { id:15, c1:'#a18cd1', c2:'#fbc2eb' }, // Lavender
        { id:17, c1:'#30cfd0', c2:'#330867' }, // Twilight
        { id:22, c1:'#2b2b2b', c2:'#2b2b2b' }, // Neumorphic Dark
        { id:28, c1:'#87ceeb', c2:'#87ceeb' }, // Clay Blue
        { id:31, c1:'#bfdbfe', c2:'#fbcfe8' }, // Cotton Candy
        { id:32, c1:'#fdf2f8', c2:'#fdf2f8' }, // Strawberry Milk
        { id:37, c1:'#fbcfe8', c2:'#fbcfe8' }, // Bubblegum Gloss
        { id:65, c1:'#34d399', c2:'#34d399' }, // Leaf Cut
        { id:66, c1:'#f472b6', c2:'#f472b6' }, // Pill Shape
        { id:71, c1:'#e2e8f0', c2:'#e2e8f0' }, // Frosted Glass
        { id:74, c1:'#fcf6ba', c2:'#bf953f' }, // Gold Foil
        { id:83, c1:'#ff71ce', c2:'#01cdfe' }, // Vaporwave
        { id:84, c1:'#000000', c2:'#000000' }, // Terminal Green
        { id:89, c1:'#111111', c2:'#00ffff' }, // Neon Cyan
        { id:91, c1:'#0f172a', c2:'#0f172a' }, // Cosmic Space
        { id:92, c1:'#3b82f6', c2:'#8b5cf6' }, // Aurora Borealis
    ];

    function buildBubblePackListHTML(activeId) {
        let html = `<div class="flex flex-row flex-wrap w-full gap-4" style="justify-content:flex-start;">`;
        // None swatch
        html += `<div class="relative w-10 h-10 rounded-full cursor-pointer flex items-center justify-center cai-pack-swatch ${!activeId ? 'ring-1 ring-white ring-offset-1' : ''}" data-id="" title="None">
            <div class="w-9 h-9 rounded-full overflow-hidden flex items-center justify-center" style="background: var(--surface-elevation-2); color: var(--muted-foreground); font-size: 0.8rem;">✕</div>
        </div>`;
        CURATED_PACKS.forEach(p => {
            const def = BUBBLES.find(b => b.id === p.id);
            if (!def) return;
            const sel = activeId === p.id;
            html += `<div class="relative w-10 h-10 rounded-full cursor-pointer flex items-center justify-center cai-pack-swatch ${sel ? 'ring-1 ring-white ring-offset-1' : ''}" data-id="${p.id}" title="${def.name}">
                <div class="w-9 h-9 rounded-full overflow-hidden" style="background-image: linear-gradient(135deg, ${p.c1} 50%, ${p.c2} 50%);"></div>
            </div>`;
        });
        html += `</div>`;
        return html;
    }

    function getDefaultSettings() {
        return {
            theme: 'Default',
            bgType: 'file',
            wallpaper: null,
            backgroundColor: '#00000000',
            wallpaperOpacity: 100,
            opacity: 100,
            hideInputButtons: true,
            hideWarning: true,
            hideCharacterInfo: true,
            hideBadgeAndVoice: true,
            fontName: '',
            immersionMode: false,
            videoSpeed: 1,
            videoSound: false,
            inputWidth: 150,
            bubbleOutlines: false,
            textSize: 100,
            bubbleRadius: 16,
            bubbleStyle: null,
            customCss: '',
            audioWallpaperUrl: null,
            audioWallpaperVolume: 100,
            audioWallpaperLoop: true,
            particlesEnabled: false,
            particlesEffect: 'rain',
            particlesDensity: 50,
            particlesSpeed: 50,
            particlesOpacity: 100
        };
    }

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

    function getStorageData() {
        try {
            const raw = localStorage.getItem(STORAGE_KEY);
            if (raw) {
                const data = JSON.parse(raw);
                if (!data.global) data.global = getDefaultSettings();
                if (!data.chats) data.chats = {};
                const defaults = getDefaultSettings();
                for (const key in defaults) {
                    if (!(key in data.global)) data.global[key] = defaults[key];
                }
                for (const chatId in data.chats) {
                    for (const key in defaults) {
                        if (!(key in data.chats[chatId])) data.chats[chatId][key] = defaults[key];
                    }
                }
                function migrateFont(s) {
                    if (s.fontFamily && s.fontFamily !== 'default' && !s.fontName) {
                        s.fontName = s.fontFamily === 'custom' && s.customFontUrl ? s.customFontUrl : s.fontFamily;
                    }
                    if (!s.fontName) s.fontName = '';
                    delete s.fontFamily;
                    delete s.customFontUrl;
                }
                migrateFont(data.global);
                for (const chatId in data.chats) migrateFont(data.chats[chatId]);
                return data;
            }
        } catch (e) {}
        return { global: getDefaultSettings(), chats: {} };
    }

    function saveStorageData(data) {
        try {
            localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
            return true;
        } catch (e) {
            if (e.name === 'QuotaExceededError' || e.code === 22) {
                showAlertDialog('File is too large! The data exceeds browser localStorage limits. Use a smaller file or a URL link instead (under 2MB recommended).');
            } else {
                throw e;
            }
            return false;
        }
    }

    function getCurrentSettings() {
        const data = getStorageData();
        const chatId = getChatIdFromUrl();
        if (chatId && data.chats[chatId]) {
            return data.chats[chatId];
        }
        return data.global;
    }

    function getFontSetting(chatData) {
        if (chatData) {
            return (chatData.perChatFont && chatData.fontName) || '';
        }
        const data = getStorageData();
        const chatId = getChatIdFromUrl();
        const chat = chatId && data.chats[chatId];
        if (chat && chat.perChatFont) {
            return chat.fontName || '';
        }
        return data.global.fontName || '';
    }

    function saveCurrentSettings(settings) {
        const data = getStorageData();
        const chatId = getChatIdFromUrl();
        if (chatId) {
            data.chats[chatId] = settings;
        } else {
            data.global = settings;
        }
        return saveStorageData(data);
    }

    function closeDialog(modal, overlay) {
        modal.style.opacity = '0';
        modal.style.transform = 'translate(-50%, -50%) scale(0.95)';
        overlay.style.opacity = '0';
        setTimeout(() => { modal.remove(); overlay.remove(); }, 200);
    }

    function escapeHtml(str) {
        const div = document.createElement('div');
        div.textContent = str;
        return div.innerHTML;
    }

    function showAlertDialog(message) {
        document.querySelector('.cai-alert-dialog')?.remove();
        document.querySelector('.cai-alert-overlay')?.remove();
        const overlay = document.createElement('div');
        overlay.className = 'cai-alert-overlay fixed inset-0 bg-black/50 z-[60]';
        overlay.style.transition = 'opacity 0.2s ease';
        document.body.appendChild(overlay);
        const dialog = document.createElement('div');
        dialog.className = 'cai-alert-dialog fixed z-[60] p-6 shadow-lg border-none left-[50%] top-[50%] translate-y-[-50%] translate-x-[-50%] sm:rounded-spacing-l max-w-sm w-11/12 flex flex-col gap-4 bg-surface-elevation-1 text-foreground';
        dialog.style.transition = 'opacity 0.2s ease, transform 0.2s ease';
        dialog.innerHTML = `<p class="text-sm text-muted-foreground">${escapeHtml(message)}</p><div class="flex justify-end gap-2 mt-1"><button class="ok-btn px-4 py-2 text-sm rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors">OK</button></div>`;
        document.body.appendChild(dialog);
        const close = () => closeDialog(dialog, overlay);
        dialog.querySelector('.ok-btn').addEventListener('click', close);
        overlay.addEventListener('click', close);
    }

    function showConfirmDialog(message) {
        return new Promise(resolve => {
            document.querySelector('.cai-confirm-dialog')?.remove();
            document.querySelector('.cai-confirm-overlay')?.remove();
            const overlay = document.createElement('div');
            overlay.className = 'cai-confirm-overlay fixed inset-0 bg-black/50 z-[60]';
            overlay.style.transition = 'opacity 0.2s ease';
            document.body.appendChild(overlay);
            const dialog = document.createElement('div');
            dialog.className = 'cai-confirm-dialog fixed z-[60] p-6 shadow-lg border-none left-[50%] top-[50%] translate-y-[-50%] translate-x-[-50%] sm:rounded-spacing-l max-w-sm w-11/12 flex flex-col gap-4 bg-surface-elevation-1 text-foreground';
            dialog.style.transition = 'opacity 0.2s ease, transform 0.2s ease';
            dialog.innerHTML = `<p class="text-sm text-muted-foreground">${escapeHtml(message)}</p><div class="flex justify-end gap-2 mt-1"><button class="cancel-btn px-4 py-2 text-sm rounded-md border border-input text-muted-foreground hover:text-foreground transition-colors">Cancel</button><button class="confirm-btn px-4 py-2 text-sm rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors">Continue</button></div>`;
            document.body.appendChild(dialog);
            function close(val) {
                closeDialog(dialog, overlay);
                resolve(val);
            }
            dialog.querySelector('.cancel-btn').addEventListener('click', () => close(false));
            dialog.querySelector('.confirm-btn').addEventListener('click', () => close(true));
            overlay.addEventListener('click', () => close(false));
        });
    }

    function getWallpaperSettings() {
        const data = getStorageData();
        const chatId = getChatIdFromUrl();
        if (chatId) {
            if (!data.chats[chatId]) {
                data.chats[chatId] = Object.assign({}, getDefaultSettings());
                saveStorageData(data);
            }
            return data.chats[chatId];
        }
        return data.global;
    }

    function saveGlobalSetting(key, value) {
        const data = getStorageData();
        data.global[key] = value;
        saveStorageData(data);
    }

    function parseRGB(rgbStr) {
        const match = rgbStr.match(/\d+/g);
        if (!match) return null;
        const [r, g, b] = match.map(Number);
        return { r, g, b };
    }

    function getLuminance(r, g, b) {
        const srgb = [r, g, b].map(c => c / 255);
        const gamma = srgb.map(c => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
        return 0.2126 * gamma[0] + 0.7152 * gamma[1] + 0.0722 * gamma[2];
    }

    function getContrastColor(rgbStr) {
        const parsed = parseRGB(rgbStr);
        if (!parsed) return 'var(--foreground)';
        const lum = getLuminance(parsed.r, parsed.g, parsed.b);
        return lum > 0.5 ? '#000000' : '#ffffff';
    }

    function isVideoUrl(url) {
        if (!url) return false;
        if (url.startsWith('data:video/')) return true;
        return /\.(mp4|webm|mov|ogg)(\?|#|$)/i.test(url);
    }

    function isAudioUrl(url) {
        if (!url) return false;
        if (url.startsWith('data:audio/')) return true;
        return /\.(mp3|wav|ogg|m4a|flac|aac|wma|webm)(\?|#|$)/i.test(url);
    }

    let injectedFontLinkId = 'cai-google-font-link';

    function loadGoogleFont(url) {
        const existing = document.getElementById(injectedFontLinkId);
        if (existing) existing.remove();
        if (!url || !url.includes('fonts.googleapis.com')) return;
        const link = document.createElement('link');
        link.id = injectedFontLinkId;
        link.rel = 'stylesheet';
        link.href = url;
        link.crossOrigin = 'anonymous';
        document.head.appendChild(link);
    }

    function getGoogleFontUrl(fontName) {
        return `https://fonts.googleapis.com/css2?family=${fontName.replace(/ /g, '+')}:wght@400;500;600;700&display=swap`;
    }

    function applyFont(settings) {
        const containers = [
            document.querySelector('#chat-body'),
            document.querySelector('#chat-messages')
        ].filter(Boolean);
        if (!containers.length) return;
        const fontName = (settings.fontName || '').trim();

        let fontValue = '';
        if (fontName) {
            if (fontName.includes('fonts.googleapis.com') || fontName.startsWith('http')) {
                if (fontName.includes('fonts.googleapis.com')) loadGoogleFont(fontName);
                const m = fontName.match(/family=([^:&]+)/);
                fontValue = m ? `'${m[1].replace(/\+/g, ' ')}', sans-serif` : fontName;
            } else {
                loadGoogleFont(getGoogleFontUrl(fontName));
                fontValue = `'${fontName}', sans-serif`;
            }
        }

        document.documentElement.style.setProperty('--cai-chat-font', fontValue || 'inherit');
        containers.forEach(el => {
            el.style.setProperty('--cai-chat-font', fontValue || 'inherit');
        });

        const chatMessages = document.querySelector('#chat-messages');
        if (chatMessages) {
            const bubbles = chatMessages.querySelectorAll('.prose, .prose *');
            bubbles.forEach(el => {
                el.style.fontFamily = fontValue || '';
            });
        }
    }

    let immersionExitButton = null;

    function applyImmersion(settings) {
        const enabled = settings.immersionMode || false;
        const leftSidebar = document.querySelector('aside.fixed.top-0.left-0.z-50.h-full');
        const spacer = document.querySelector('div.transition-width.w-64');
        const rightSidebar = document.querySelector('#chat-details');
        const mainContent = document.querySelector('#main-content');
        const mainContainer = document.querySelector('main.flex-1');

        if (enabled) {
            if (leftSidebar) leftSidebar.style.display = 'none';
            if (spacer) spacer.style.display = 'none';
            if (rightSidebar) {
                rightSidebar.style.display = 'none';
                rightSidebar.classList.add('cai-immersion-hidden');
            }
            if (mainContent) {
                mainContent.style.maxWidth = '100%';
                mainContent.style.width = '100%';
                mainContent.style.flex = '1';
            }
            if (mainContainer) {
                mainContainer.style.maxWidth = '100%';
                mainContainer.style.width = '100%';
            }
            const parent = mainContent?.parentElement;
            if (parent) {
                parent.style.maxWidth = '100%';
                parent.style.width = '100%';
            }
            showImmersionExitButton();
        } else {
            if (leftSidebar) leftSidebar.style.display = '';
            if (spacer) spacer.style.display = '';
            if (rightSidebar) {
                rightSidebar.style.display = '';
                rightSidebar.classList.remove('cai-immersion-hidden');
            }
            if (mainContent) {
                mainContent.style.maxWidth = '';
                mainContent.style.width = '';
                mainContent.style.flex = '';
            }
            if (mainContainer) {
                mainContainer.style.maxWidth = '';
                mainContainer.style.width = '';
            }
            const parent = mainContent?.parentElement;
            if (parent) {
                parent.style.maxWidth = '';
                parent.style.width = '';
            }
            hideImmersionExitButton();
        }
    }

    function showImmersionExitButton() {
        if (immersionExitButton) {
            immersionExitButton.style.display = 'flex';
            return;
        }
        const btn = document.createElement('div');
        btn.id = 'cai-immersion-exit-btn';
        btn.style.cssText = `
            position: fixed;
            bottom: 24px;
            right: 24px;
            z-index: 99999;
            background: rgba(0,0,0,0.7);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            color: #fff;
            border: 1px solid rgba(255,255,255,0.15);
            border-radius: 50%;
            width: 48px;
            height: 48px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-size: 20px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.4);
            transition: transform 0.2s, opacity 0.2s;
            font-family: system-ui, -apple-system, sans-serif;
            user-select: none;
        `;
        btn.innerHTML = `
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 24px; height: 24px;">
                <path d="M18 6L6 18M6 6l12 12"/>
            </svg>
        `;
        btn.title = 'Exit immersion mode (Esc)';
        btn.addEventListener('mouseenter', () => {
            btn.style.transform = 'scale(1.1)';
            btn.style.opacity = '1';
        });
        btn.addEventListener('mouseleave', () => {
            btn.style.transform = 'scale(1)';
            btn.style.opacity = '0.85';
        });
        btn.style.opacity = '0.85';
        btn.addEventListener('click', () => {
            saveGlobalSetting('immersionMode', false);
            applyImmersion(getStorageData().global);
            updateImmersionToggleInModal(false);
        });
        document.body.appendChild(btn);
        immersionExitButton = btn;
    }

    function hideImmersionExitButton() {
        if (immersionExitButton) {
            immersionExitButton.style.display = 'none';
        }
    }

    function updateImmersionToggleInModal(state) {
        const toggle = document.querySelector('#toggle-immersion');
        if (toggle) {
            const isChecked = state;
            toggle.setAttribute('aria-checked', isChecked);
            toggle.classList.toggle('bg-blue', isChecked);
            toggle.classList.toggle('bg-muted', !isChecked);
            const thumb = toggle.querySelector('span');
            if (thumb) {
                thumb.classList.toggle('translate-x-5', isChecked);
                thumb.classList.toggle('translate-x-0', !isChecked);
            }
        }
    }

    document.addEventListener('keydown', function(e) {
        if (e.key === 'Escape') {
            const data = getStorageData();
            if (data.global.immersionMode) {
                data.global.immersionMode = false;
                saveStorageData(data);
                applyImmersion(data.global);
                updateImmersionToggleInModal(false);
            }
        }
    });

    function injectAdaptiveStyles() {
        const style = document.createElement('style');
        style.id = 'cai-adaptive-text-styles';
        style.textContent = `
            #chat-messages .bg-surface-elevation-2 .prose,
            #chat-messages .bg-surface-elevation-2 .prose *,
            #chat-messages .bg-surface-elevation-3 .prose,
            #chat-messages .bg-surface-elevation-3 .prose * {
                color: var(--bubble-text-char, var(--foreground)) !important;
                font-family: var(--cai-chat-font, inherit) !important;
            }
            #chat-messages .bg-surface-elevation-3 .prose,
            #chat-messages .bg-surface-elevation-3 .prose * {
                color: var(--bubble-text-user, var(--foreground)) !important;
                font-family: var(--cai-chat-font, inherit) !important;
            }
            #chat-messages .bg-surface-elevation-2,
            #chat-messages .bg-surface-elevation-3 {
                opacity: var(--bubble-opacity, 1) !important;
                background-color: var(--bubble-bg-user, var(--surface-elevation-2)) !important;
            }
            #chat-messages .bg-surface-elevation-3 {
                background-color: var(--bubble-bg-char, var(--surface-elevation-3)) !important;
            }
            #chat-body {
                position: relative;
                background-color: var(--cai-bg-color, transparent);
            }
            #chat-body::before {
                content: '';
                position: absolute;
                inset: 0;
                background-image: var(--cai-wallpaper-image, none);
                background-size: cover;
                background-position: center;
                background-repeat: no-repeat;
                opacity: var(--cai-wallpaper-opacity, 1);
                pointer-events: none;
                z-index: 0;
            }
            #chat-body > * {
                position: relative;
                z-index: 1;
            }
            .cai-preview-bg::before {
                content: '';
                position: absolute;
                inset: 0;
                background-image: var(--cai-preview-wallpaper, none);
                background-size: cover;
                background-position: center;
                background-repeat: no-repeat;
                opacity: var(--cai-preview-opacity, 1);
                pointer-events: none;
                z-index: 0;
                border-radius: inherit;
            }
            .cai-preview-bg > * {
                position: relative;
                z-index: 1;
            }
            #cai-bg-video {
                position: absolute;
                inset: 0;
                width: 100%;
                height: 100%;
                object-fit: cover;
                pointer-events: none;
                z-index: 0;
                opacity: var(--cai-wallpaper-opacity, 1);
            }
            .cai-slider {
                -webkit-appearance: none;
                appearance: none;
                width: 100%;
                height: 4px;
                border-radius: 2px;
                background: var(--surface-elevation-2);
                outline: none;
                transition: background 0.2s ease;
                cursor: pointer;
            }
            .cai-slider::-webkit-slider-thumb {
                -webkit-appearance: none;
                appearance: none;
                width: 16px;
                height: 16px;
                border-radius: 50%;
                background: var(--background);
                box-shadow: 0 1px 3px rgba(0,0,0,0.15), 0 1px 2px rgba(0,0,0,0.08);
                border: 1px solid var(--border-outline);
                transition: transform 0.15s ease, box-shadow 0.15s ease;
                cursor: pointer;
            }
            .cai-slider::-webkit-slider-thumb:hover {
                transform: scale(1.1);
                box-shadow: 0 2px 6px rgba(0,0,0,0.2);
            }
            .cai-slider::-moz-range-thumb {
                width: 16px;
                height: 16px;
                border-radius: 50%;
                background: var(--background);
                box-shadow: 0 1px 3px rgba(0,0,0,0.15), 0 1px 2px rgba(0,0,0,0.08);
                border: 1px solid var(--border-outline);
                transition: transform 0.15s ease, box-shadow 0.15s ease;
                cursor: pointer;
            }
            .cai-slider::-moz-range-thumb:hover {
                transform: scale(1.1);
                box-shadow: 0 2px 6px rgba(0,0,0,0.2);
            }
            .cai-slider:focus-visible::-webkit-slider-thumb {
                outline: 2px solid var(--focus);
                outline-offset: 2px;
            }
            .cai-slider:focus-visible::-moz-range-thumb {
                outline: 2px solid var(--focus);
                outline-offset: 2px;
            }
            .cai-custom-modal {
                transition: opacity 0.2s ease, transform 0.2s ease;
                will-change: opacity, transform;
                max-height: 90vh;
                overflow-y: auto;
            }
            .cai-custom-modal.closing {
                opacity: 0;
                transform: translate(-50%, -50%) scale(0.95);
            }
            .cai-custom-overlay {
                transition: opacity 0.2s ease;
                will-change: opacity;
            }
            .cai-custom-overlay.closing {
                opacity: 0;
            }
            .cai-wallpaper-dialog.closing {
                opacity: 0;
                transform: translate(-50%, -50%) scale(0.95);
                transition: opacity 0.2s ease, transform 0.2s ease;
            }
            .cai-toggle-track {
                display: inline-flex; flex-shrink: 0; cursor: pointer;
                align-items: center; width: 36px; height: 20px;
                border-radius: 9999px; border: 2px solid transparent;
                transition: background 0.15s ease;
            }
            .cai-toggle-thumb {
                pointer-events: none; display: block;
                width: 16px; height: 16px; border-radius: 9999px;
                background: var(--background);
                box-shadow: 0 1px 3px rgba(0,0,0,0.15);
                transition: transform 0.15s ease;
            }
            .cai-bg-type-group {
                display: flex;
                gap: 2px;
                background: var(--surface-elevation-2);
                border-radius: 8px;
                padding: 2px;
                margin-top: 8px;
            }
            .cai-bg-type-group button {
                flex: 1;
                padding: 4px 12px;
                border-radius: 6px;
                border: none;
                background: transparent;
                color: var(--muted-foreground);
                font-size: 0.75rem;
                cursor: pointer;
                transition: background 0.15s, color 0.15s;
                font-weight: 500;
            }
            .cai-bg-type-group button.active {
                background: var(--background);
                color: var(--foreground);
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            }
            .cai-bg-type-group button:hover:not(.active) {
                background: var(--surface-elevation-1);
            }
            .cai-bg-input-area {
                margin-top: 8px;
                width: 100%;
            }
            .cai-bg-input-area input[type="text"] {
                width: 100%;
                padding: 6px 10px;
                border-radius: 6px;
                border: 1px solid var(--border-outline);
                background: var(--background);
                color: var(--foreground);
                font-size: 0.875rem;
            }
            .cai-bg-upload-btn {
                width: 100%;
                padding: 8px;
                border-radius: 6px;
                border: 1px dashed var(--border-outline);
                background: var(--surface-elevation-1);
                color: var(--muted-foreground);
                font-size: 0.875rem;
                cursor: pointer;
                transition: background 0.15s;
                text-align: center;
            }
            .cai-bg-upload-btn:hover {
                background: var(--surface-elevation-2);
            }
            .cai-bg-upload-btn.has-image {
                border-color: var(--primary);
                color: var(--primary);
            }
            .cai-bg-remove-btn {
                margin-top: 4px;
                padding: 4px 12px;
                border-radius: 6px;
                border: 1px solid var(--error);
                background: transparent;
                color: var(--error);
                font-size: 0.75rem;
                cursor: pointer;
                width: 100%;
            }
            .cai-bg-remove-btn:hover {
                background: var(--error-container);
            }
            #chat-messages .absolute.-bottom-0 {
                background: rgba(0, 0, 0, 0.5) !important;
                backdrop-filter: blur(12px) !important;
                -webkit-backdrop-filter: blur(12px) !important;
                border-radius: 9999px !important;
                padding: 2px 4px !important;
                gap: 2px !important;
                box-shadow: 0 2px 12px rgba(0,0,0,0.3) !important;
                margin-bottom: 2px !important;
                transform: translateY(4px) !important;
            }
            #chat-messages .absolute.-bottom-0 button {
                color: #fff !important;
                min-width: 24px !important;
                width: 24px !important;
                height: 24px !important;
                min-height: 24px !important;
            }
            #chat-messages .absolute.-bottom-0 button svg {
                stroke: #fff !important;
                fill: none !important;
                width: 12px !important;
                height: 12px !important;
            }
            #chat-messages .absolute.-bottom-0 button:hover {
                background: rgba(255,255,255,0.15) !important;
            }
            #chat-messages .absolute.-bottom-0 .flex.h-6.items-center {
                height: 24px !important;
            }
            #chat-messages .absolute.-bottom-0 .text-sm {
                font-size: 10px !important;
            }
            .cai-custom-color-picker {
                display: flex;
                flex-wrap: wrap;
                gap: 6px;
                margin-top: 4px;
                align-items: center;
            }
            .cai-custom-color-picker .color-swatch {
                width: 28px;
                height: 28px;
                border-radius: 50%;
                border: 2px solid var(--border-outline);
                cursor: pointer;
                transition: transform 0.15s, border-color 0.15s;
                flex-shrink: 0;
            }
            .cai-custom-color-picker .color-swatch:hover {
                transform: scale(1.1);
            }
            .cai-custom-color-picker .color-swatch.active {
                border-color: var(--primary);
                box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--primary);
            }
            .cai-custom-color-picker .color-hex-input {
                width: 70px;
                padding: 4px 6px;
                border-radius: 4px;
                border: 1px solid var(--border-outline);
                background: var(--background);
                color: var(--foreground);
                font-size: 12px;
                font-family: monospace;
                text-transform: uppercase;
                outline: none;
            }
            .cai-custom-color-picker .color-hex-input:focus {
                border-color: var(--primary);
            }
            [role="tab"][data-state="active"].bg-accent {
                background: #18181b !important;
            }
            .bg-primary-foreground {
                background: #18181b !important;
            }
            .cai-immersion-hidden {
                display: none !important;
            }
            #chat-messages span.rounded-full {
                filter: drop-shadow(0 0 2px rgba(255,255,255,0.5));
            }
            #chat-messages .flex.flex-row.items-center.gap-2.font-light > div.text-sm {
                -webkit-text-stroke: 1.5px rgba(0,0,0,0.85);
                paint-order: stroke fill;
                font-weight: 600;
            }
            .cai-font-selector {
                width: 100%;
                padding: 6px 10px;
                border-radius: 6px;
                border: 1px solid var(--border-outline);
                background: var(--background);
                color: var(--foreground);
                font-size: 0.875rem;
                outline: none;
                cursor: pointer;
            }
            .cai-font-selector:focus {
                border-color: var(--primary);
            }
            .cai-font-input {
                width: 100%;
                padding: 10px 12px;
                border-radius: var(--spacing-s, 8px);
                border: 1px solid var(--border-input, var(--border-outline));
                background: var(--surface-elevation-3, var(--surface-elevation-1));
                color: var(--foreground);
                font-size: 0.875rem;
                outline: none;
                margin-top: 4px;
                transition: border-color 0.15s ease;
            }
            .cai-font-input:focus {
                border-color: var(--primary);
            }
            .cai-font-input::placeholder {
                color: var(--muted-foreground);
            }
            .cai-custom-css-input {
                width: 100%;
                padding: 10px 12px;
                border-radius: var(--spacing-s, 8px);
                border: 1px solid var(--border-input, var(--border-outline));
                background: var(--surface-elevation-3, var(--surface-elevation-1));
                color: var(--foreground);
                font-size: 0.875rem;
                font-family: monospace;
                outline: none;
                resize: none;
                transition: border-color 0.15s ease;
            }
            .cai-custom-css-input:focus {
                border-color: var(--primary);
            }
            .cai-custom-css-input::placeholder {
                color: var(--muted-foreground);
            }
            .cai-font-hint {
                font-size: 0.65rem;
                color: var(--muted-foreground);
                margin-top: 2px;
            }
            .cai-pack-swatch {
                transition: transform 0.12s ease;
            }
            .cai-pack-swatch:hover {
                transform: scale(1.08);
            }
            body[data-cai-hide-input="true"] button:has(svg path[d*="M6.754 3"]),
            body[data-cai-hide-input="true"] button:has(svg path[d*="M5.22287 3"]) {
                display: none !important;
            }
            body[data-cai-hide-warning="true"] button[aria-label="Common.expand"] {
                display: none !important;
            }
            body[data-cai-hide-charinfo="true"] .group.relative.max-w-3xl.m-auto.w-full .flex.flex-col.items-center.justify-center.text-center.gap-1.px-16.pb-6.pt-12 {
                display: none !important;
            }
            body[data-cai-hide-badge="true"] div.flex.items-center:has(> div.rounded-2xl.text-sm.bg-secondary.px-2.font-light.h-fit) {
                display: none !important;
            }
            div.bg-surface-elevation-1:has(textarea#chat-input-textarea) {
                background: rgba(0, 0, 0, 0.3) !important;
                backdrop-filter: blur(16px) !important;
                -webkit-backdrop-filter: blur(16px) !important;
                border-radius: 16px !important;
                border-color: rgba(255, 255, 255, 0.08) !important;
                box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), 0 4px 24px rgba(0,0,0,0.4) !important;
            }
            div.bg-surface-elevation-1:has(textarea#chat-input-textarea) textarea {
                background: transparent !important;
                color: #fff !important;
            }
            div.bg-surface-elevation-1:has(textarea#chat-input-textarea) textarea::placeholder {
                color: rgba(255,255,255,0.35) !important;
            }
            div.bg-surface-elevation-1:has(textarea#chat-input-textarea) button[aria-label="Send a message..."] {
                background: #3a3a40 !important;
                border-radius: 10px !important;
            }
            div.bg-surface-elevation-1:has(textarea#chat-input-textarea) button[aria-label="Send a message..."]:hover {
                background: #4a4a52 !important;
            }
            div.bg-surface-elevation-1:has(textarea#chat-input-textarea) button[aria-label="Send a message..."] svg {
                color: #fff !important;
            }
            #chat-messages {
                padding-bottom: 40px !important;
            }
            div.flex.md\\:ml-14.absolute.\\-bottom-0.items-center {
                bottom: 4px !important;
            }

            /* ---- BUBBLE OUTLINES ---- */
            #chat-messages .bg-surface-elevation-2 {
                box-shadow: var(--outline-user, none) !important;
            }
            #chat-messages .bg-surface-elevation-3 {
                box-shadow: var(--outline-bot, none) !important;
            }

            /* ---- TEXT SIZE ---- */
            #chat-messages .prose {
                font-size: var(--text-scale, 1rem) !important;
            }

            /* ---- BUBBLE ROUNDEDNESS ---- */
            #chat-messages .bg-surface-elevation-2,
            #chat-messages .bg-surface-elevation-3 {
                border-radius: var(--bubble-radius, 16px) !important;
            }

            /* ---- CUSTOM CSS placeholder (injected dynamically) ---- */

            /* ---- AUDIO WALLPAPER ---- */
            .cai-audio-dialog.closing {
                opacity: 0;
                transform: translate(-50%, -50%) scale(0.95);
                transition: opacity 0.2s ease, transform 0.2s ease;
            }
            .cai-audio-upload-row {
                display: flex;
                flex-direction: row;
                gap: 10px;
                align-items: stretch;
            }
            .cai-audio-visualizer {
                display: flex;
                align-items: flex-end;
                justify-content: center;
                gap: 3px;
                width: 52px;
                padding: 10px 0;
                border-radius: 8px;
                flex-shrink: 0;
                background: linear-gradient(135deg, rgba(83,109,198,0.08), rgba(120,80,200,0.05));
            }
            .cai-audio-visualizer.active .cai-wave-bar {
                animation: caiWave 1.2s ease-in-out infinite;
            }
            .cai-wave-bar {
                width: 5px;
                height: 12px;
                border-radius: 2px;
                background: linear-gradient(to top, var(--blue,#536dc6), #7860c8);
                animation: none;
                transform-origin: bottom;
            }
            @keyframes caiWave {
                0%, 100% { height: 8px; }
                25% { height: 28px; }
                50% { height: 16px; }
                75% { height: 36px; }
            }
            .cai-audio-upload-area {
                display: flex;
                flex-direction: column;
                gap: 6px;
                flex: 1;
                min-width: 0;
            }
            .cai-audio-upload-btn {
                width: 100%;
                padding: 8px 10px;
                border-radius: 8px;
                border: 1px dashed var(--border-outline);
                background: linear-gradient(135deg, rgba(83,109,198,0.06), rgba(120,80,200,0.04));
                color: var(--muted-foreground);
                font-size: 0.8rem;
                cursor: pointer;
                transition: background 0.15s, border-color 0.15s;
                display: flex;
                align-items: center;
                justify-content: center;
                gap: 6px;
            }
            .cai-audio-upload-btn:hover {
                background: linear-gradient(135deg, rgba(83,109,198,0.12), rgba(120,80,200,0.08));
                border-color: var(--blue,#536dc6);
                color: var(--foreground);
            }
            .cai-audio-link-area {
                display: flex;
                flex-direction: column;
                gap: 4px;
            }
            .cai-audio-link-label {
                font-size: 0.7rem;
                color: var(--muted-foreground);
            }
            .cai-audio-link-input {
                width: 100%;
                padding: 8px 10px;
                border-radius: 6px;
                border: 1px solid var(--border-outline);
                background: var(--background);
                color: var(--foreground);
                font-size: 0.8rem;
                outline: none;
                transition: border-color 0.15s;
            }
            .cai-audio-link-input:focus {
                border-color: var(--blue,#536dc6);
            }
            .cai-audio-controls {
                display: flex;
                flex-direction: column;
                gap: 12px;
            }
            .cai-audio-controls.muted {
                opacity: 0.4;
                pointer-events: none;
            }
            .cai-audio-player-bar {
                display: flex;
                align-items: center;
                gap: 8px;
                padding: 6px 8px;
                border-radius: 8px;
                background: var(--surface-elevation-2);
            }
            .cai-audio-play-btn {
                width: 28px;
                height: 28px;
                border-radius: 50%;
                border: none;
                background: var(--blue,#536dc6);
                color: #fff;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                flex-shrink: 0;
                transition: background 0.15s;
            }
            .cai-audio-play-btn:hover {
                background: #6577d6;
            }
            .cai-audio-play-btn svg {
                display: block;
            }
            .cai-audio-seek-track {
                flex: 1;
                position: relative;
                height: 4px;
                border-radius: 2px;
                background: var(--surface-elevation-3);
                min-width: 0;
            }
            .cai-audio-seek-fill {
                position: absolute;
                left: 0;
                top: 0;
                height: 100%;
                border-radius: 2px;
                background: linear-gradient(to right, var(--blue,#536dc6), #7860c8);
                pointer-events: none;
            }
            .cai-audio-seek-slider {
                position: absolute;
                inset: -6px 0;
                width: 100%;
                -webkit-appearance: none;
                appearance: none;
                background: transparent;
                cursor: pointer;
                z-index: 1;
                margin: 0;
                opacity: 0;
            }
            .cai-audio-seek-slider::-webkit-slider-thumb {
                -webkit-appearance: none;
                width: 14px;
                height: 14px;
                border-radius: 50%;
                background: var(--background);
                border: 2px solid var(--blue,#536dc6);
                box-shadow: 0 1px 3px rgba(0,0,0,0.2);
                cursor: pointer;
            }
            .cai-audio-time {
                font-size: 0.7rem;
                color: var(--muted-foreground);
                font-variant-numeric: tabular-nums;
                white-space: nowrap;
                flex-shrink: 0;
                min-width: 70px;
                text-align: right;
            }
            .cai-audio-vol-row {
                display: flex;
                align-items: center;
                gap: 8px;
            }
            .cai-audio-vol-label {
                display: flex;
                align-items: center;
                gap: 4px;
                font-size: 0.8rem;
                color: var(--muted-foreground);
                min-width: 70px;
                flex-shrink: 0;
            }
            .cai-audio-vol-track {
                flex: 1;
                position: relative;
                height: 4px;
                border-radius: 2px;
                background: var(--surface-elevation-2);
            }
            .cai-audio-vol-fill {
                position: absolute;
                left: 0;
                top: 0;
                height: 100%;
                border-radius: 2px;
                background: linear-gradient(to right, var(--blue,#536dc6), #7860c8);
                pointer-events: none;
            }
            .cai-audio-vol-slider {
                position: absolute;
                inset: -6px 0;
                width: 100%;
                -webkit-appearance: none;
                appearance: none;
                background: transparent;
                cursor: pointer;
                z-index: 1;
                margin: 0;
                opacity: 0;
            }
            .cai-audio-vol-slider::-webkit-slider-thumb {
                -webkit-appearance: none;
                width: 16px;
                height: 16px;
                border-radius: 50%;
                background: var(--background);
                border: 2px solid var(--blue,#536dc6);
                box-shadow: 0 1px 4px rgba(0,0,0,0.2);
                cursor: pointer;
            }
            .cai-audio-vol-pct {
                font-size: 0.75rem;
                color: var(--muted-foreground);
                min-width: 32px;
                text-align: right;
                flex-shrink: 0;
            }
            .cai-audio-loop-row {
                display: flex;
                align-items: center;
                justify-content: space-between;
            }
            .cai-audio-loop-label {
                font-size: 0.8rem;
                color: var(--muted-foreground);
            }
            .cai-audio-loop-toggle {
                width: 36px;
                height: 20px;
                border-radius: 9999px;
                background: var(--surface-elevation-2);
                cursor: pointer;
                position: relative;
                transition: background 0.15s;
                flex-shrink: 0;
            }
            .cai-audio-loop-toggle.on {
                background: linear-gradient(to right, var(--blue,#536dc6), #7860c8);
            }
            .cai-audio-loop-thumb {
                position: absolute;
                top: 2px;
                left: 2px;
                width: 16px;
                height: 16px;
                border-radius: 50%;
                background: var(--background);
                box-shadow: 0 1px 3px rgba(0,0,0,0.15);
                transition: transform 0.15s;
            }
            .cai-audio-loop-toggle.on .cai-audio-loop-thumb {
                transform: translateX(16px);
            }
            .cai-audio-actions {
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding-top: 4px;
                border-top: 1px solid var(--border-outline);
            }
            .cai-audio-play-status {
                display: flex;
                align-items: center;
                gap: 6px;
                font-size: 0.8rem;
                color: var(--muted-foreground);
            }
            .cai-audio-playing-dot {
                width: 8px;
                height: 8px;
                border-radius: 50%;
                background: #22c55e;
                animation: caiPulse 1.2s ease-in-out infinite;
            }
            @keyframes caiPulse {
                0%, 100% { opacity: 1; }
                50% { opacity: 0.4; }
            }
            .cai-audio-paused-label {
                color: var(--muted-foreground);
            }
            .cai-audio-idle-label {
                color: var(--muted-foreground);
                opacity: 0.6;
            }
            .cai-audio-stop-btn {
                padding: 4px 12px;
                border-radius: 6px;
                border: 1px solid var(--error,#ef4444);
                background: transparent;
                color: var(--error,#ef4444);
                font-size: 0.75rem;
                cursor: pointer;
                transition: background 0.15s;
            }
            .cai-audio-stop-btn:hover {
                background: rgba(239,68,68,0.1);
            }
            #cai-particles-canvas {
                position: absolute;
                inset: 0;
                width: 100%;
                height: 100%;
                pointer-events: none;
                z-index: 0;
            }
            .cai-particle-select {
                width: 100%;
                padding: 8px 10px;
                border-radius: 6px;
                border: 1px solid var(--border-outline);
                background: var(--background);
                color: var(--foreground);
                font-size: 0.85rem;
                outline: none;
                cursor: pointer;
                transition: border-color 0.15s;
            }
            .cai-particle-select:focus {
                border-color: var(--blue,#536dc6);
            }
        `;
        document.head.appendChild(style);
    }

    function applyTheme(themeName) {
        const containers = [
            document.querySelector('#chat-body'),
            document.querySelector('#chat-messages')
        ].filter(Boolean);
        if (!containers.length) return;
        const theme = themes.find(t => t.name === themeName);
        if (!theme) return;
        if (themeName === 'Default') {
            containers.forEach(el => {
                el.style.removeProperty('--surface-elevation-2');
                el.style.removeProperty('--surface-elevation-3');
                el.style.removeProperty('--bubble-bg-char');
                el.style.removeProperty('--bubble-bg-user');
                el.style.removeProperty('--bubble-text-char');
                el.style.removeProperty('--bubble-text-user');
            });
        } else {
            const textChar = getContrastColor(theme.charColor);
            const textUser = getContrastColor(theme.userColor);
            containers.forEach(el => {
                el.style.setProperty('--bubble-bg-char', theme.charColor);
                el.style.setProperty('--bubble-bg-user', theme.userColor);
                el.style.setProperty('--bubble-text-char', textChar);
                el.style.setProperty('--bubble-text-user', textUser);
            });
        }
        updatePreview(themeName);
    }

    function updatePreview(themeName) {
        const charBubble = document.querySelector('#preview-char');
        const userBubble = document.querySelector('#preview-user');
        if (!charBubble || !userBubble) return;
        const theme = themes.find(t => t.name === themeName);
        if (!theme) return;
        if (themeName === 'Default') {
            charBubble.style.background = 'var(--surface-elevation-2)';
            userBubble.style.background = 'var(--surface-elevation-3)';
        } else {
            charBubble.style.background = theme.userColor;
            userBubble.style.background = theme.charColor;
        }
        const s = getCurrentSettings();
        if (s.bubbleOutlines) {
            updatePreviewOutlines(true, s);
        }
    }

    function updatePreviewOutlines(enabled, settings) {
        const charBubble = document.querySelector('#preview-char');
        const userBubble = document.querySelector('#preview-user');
        if (!charBubble || !userBubble) return;
        if (!enabled) {
            charBubble.style.boxShadow = '';
            userBubble.style.boxShadow = '';
            return;
        }
        const theme = themes.find(t => t.name === (settings.theme || 'Default'));
        let charColor, userColor;
        if (theme && theme.name !== 'Default') {
            charColor = theme.charColor;
            userColor = theme.userColor;
        } else {
            charColor = 'var(--surface-elevation-3)';
            userColor = 'var(--surface-elevation-2)';
        }
        charBubble.style.boxShadow = `0 0 0 2px ${charColor}`;
        userBubble.style.boxShadow = `0 0 0 2px ${userColor}`;
    }

    function updatePreviewBackground(settings) {
        const previewContainer = document.querySelector('.cai-custom-modal .w-full.h-full.rounded-\\[13\\.5px\\].relative.border.border-foreground-300.bg-surface.overflow-hidden');
        if (!previewContainer) return;
        const existingPreviewVideo = previewContainer.querySelector('.cai-preview-video');
        if (existingPreviewVideo) existingPreviewVideo.remove();
        const bgType = settings.bgType || 'file';
        const wallpaper = settings.wallpaper;
        const backgroundColor = settings.backgroundColor || 'transparent';
        const opacity = (settings.wallpaperOpacity !== undefined ? settings.wallpaperOpacity : 100) / 100;
        previewContainer.style.setProperty('--cai-preview-wallpaper', 'none');
        previewContainer.style.setProperty('--cai-preview-opacity', '1');
        previewContainer.style.backgroundImage = '';
        previewContainer.style.backgroundSize = '';
        previewContainer.style.backgroundPosition = '';
        previewContainer.style.backgroundRepeat = '';
        previewContainer.style.backgroundColor = '';
        if (bgType === 'color') {
            previewContainer.style.backgroundColor = backgroundColor;
            previewContainer.style.setProperty('--cai-preview-wallpaper', 'none');
        } else if (bgType === 'file' || bgType === 'link') {
            if (wallpaper && isVideoUrl(wallpaper)) {
                const video = document.createElement('video');
                video.className = 'cai-preview-video';
                video.autoplay = true;
                video.loop = true;
                video.muted = true;
                video.playsInline = true;
                video.src = wallpaper;
                video.playbackRate = settings.videoSpeed || 1;
                video.style.cssText = `position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:-1;opacity:${opacity};`;
                previewContainer.style.backgroundColor = 'transparent';
                previewContainer.style.setProperty('--cai-preview-wallpaper', 'none');
                previewContainer.appendChild(video);
            } else if (wallpaper) {
                previewContainer.style.setProperty('--cai-preview-wallpaper', `url("${wallpaper}")`);
                previewContainer.style.setProperty('--cai-preview-opacity', String(opacity));
                previewContainer.style.backgroundColor = 'transparent';
            }
        }
    }

    function applyBackground(settings) {
        const chatBody = document.querySelector('#chat-body');
        if (!chatBody) return;
        const bgType = settings.bgType || 'file';
        const wallpaper = settings.wallpaper;
        const backgroundColor = settings.backgroundColor || 'transparent';
        const opacityVal = (settings.wallpaperOpacity !== undefined ? settings.wallpaperOpacity : 100) / 100;
        chatBody.style.removeProperty('--cai-wallpaper-image');
        chatBody.style.removeProperty('--cai-wallpaper-opacity');
        chatBody.style.removeProperty('--cai-bg-color');
        const existingVideo = chatBody.querySelector('#cai-bg-video');
        if (existingVideo) existingVideo.remove();
        if (bgType === 'color') {
            chatBody.style.setProperty('--cai-bg-color', backgroundColor);
            chatBody.style.setProperty('--cai-wallpaper-image', 'none');
        } else if (bgType === 'file' || bgType === 'link') {
            if (wallpaper && isVideoUrl(wallpaper)) {
                chatBody.style.setProperty('--cai-wallpaper-image', 'none');
                chatBody.style.setProperty('--cai-bg-color', 'transparent');
                chatBody.style.setProperty('--cai-wallpaper-opacity', opacityVal);
                const speed = settings.videoSpeed || 1;
                const sound = settings.videoSound || false;
                const video = document.createElement('video');
                video.id = 'cai-bg-video';
                video.autoplay = true;
                video.loop = true;
                video.muted = !sound;
                video.playsInline = true;
                video.src = wallpaper;
                video.playbackRate = speed;
                chatBody.prepend(video);
            } else if (wallpaper) {
                chatBody.style.setProperty('--cai-wallpaper-image', `url("${wallpaper}")`);
                chatBody.style.setProperty('--cai-wallpaper-opacity', opacityVal);
                chatBody.style.setProperty('--cai-bg-color', 'transparent');
            } else {
                chatBody.style.setProperty('--cai-wallpaper-image', 'none');
                chatBody.style.setProperty('--cai-bg-color', 'transparent');
            }
        }
        updatePreviewBackground(settings);
    }

    function applyAudioWallpaper(settings) {
        const url = settings.audioWallpaperUrl;
        const volume = (settings.audioWallpaperVolume !== undefined ? settings.audioWallpaperVolume : 100) / 100;
        const loop = settings.audioWallpaperLoop !== undefined ? settings.audioWallpaperLoop : true;
        const existing = document.getElementById('cai-bg-audio');
        if (!url) {
            if (existing) {
                existing.pause();
                existing.remove();
            }
            return;
        }
        if (existing) {
            if (existing.src !== url) {
                existing.src = url;
                existing.load();
                existing.play().catch(() => {});
            }
            existing.volume = volume;
            existing.loop = loop;
        } else {
            const audio = document.createElement('audio');
            audio.id = 'cai-bg-audio';
            audio.src = url;
            audio.loop = loop;
            audio.volume = volume;
            audio.style.display = 'none';
            document.body.appendChild(audio);
            audio.play().catch(() => {});
        }
    }

    let _particleEngine = null;
    function applyParticles(settings) {
        const enabled = settings.particlesEnabled;
        if (!enabled) {
            if (_particleEngine) { _particleEngine.stop(); _particleEngine = null; }
            const existing = document.getElementById('cai-particles-canvas');
            if (existing) existing.remove();
            return;
        }
        const chatBody = document.querySelector('#chat-body');
        if (!chatBody) return;
        chatBody.style.position = 'relative';
        let canvas = document.getElementById('cai-particles-canvas');
        if (!canvas) {
            canvas = document.createElement('canvas');
            canvas.id = 'cai-particles-canvas';
            const rect = chatBody.getBoundingClientRect();
            canvas.width = rect.width;
            canvas.height = rect.height;
            chatBody.appendChild(canvas);
        }
        if (!_particleEngine) {
            const ParticleEngine = (() => {
                const EFFECTS = {
                    rain: { countRange: [20,200], create(rng) { return { x: Math.random() * rng.w, y: Math.random() * rng.h * -1 - 20, length: 8 + Math.random() * 16, speed: 6 + Math.random() * 8, opacity: 0.3 + Math.random() * 0.5 }; }, update(p, dt, rng) { p.y += p.speed * dt * 60; p.x += 1.5 * dt * 60; if (p.y > rng.h + 20) { p.y = -20; p.x = Math.random() * rng.w; } }, draw(p, ctx) { ctx.strokeStyle = `rgba(180,210,255,${p.opacity})`; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(p.x + 2, p.y + p.length); ctx.stroke(); } },
                    snow: { countRange: [15,120], create(rng) { return { x: Math.random() * rng.w, y: Math.random() * rng.h * -1 - 10, radius: 1.5 + Math.random() * 3, speed: 1 + Math.random() * 3, drift: 0.3 + Math.random() * 0.8, opacity: 0.4 + Math.random() * 0.6, phase: Math.random() * Math.PI * 2 }; }, update(p, dt, rng) { p.y += p.speed * dt * 60; p.x += Math.sin(p.phase + Date.now() * 0.001 * p.drift) * 0.3 * dt * 60; if (p.y > rng.h + 10) { p.y = -10; p.x = Math.random() * rng.w; p.phase = Math.random() * Math.PI * 2; } }, draw(p, ctx) { ctx.fillStyle = `rgba(255,255,255,${p.opacity})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); ctx.fill(); } },
                    leaves: { countRange: [8,60], create(rng) { const colors = ['#5a8f4c','#7ab363','#c4a34a','#bf7f3f','#8b5e3c','#4a7c59']; return { x: Math.random() * rng.w, y: Math.random() * rng.h * -1 - 30, size: 6 + Math.random() * 10, speed: 1 + Math.random() * 2.5, swayAmp: 20 + Math.random() * 40, swayFreq: 0.3 + Math.random() * 0.4, rotation: Math.random() * Math.PI * 2, rotSpeed: (Math.random() - 0.5) * 0.1, phase: Math.random() * Math.PI * 2, color: colors[Math.floor(Math.random() * colors.length)] }; }, update(p, dt, rng) { p.y += p.speed * dt * 60; p.x += Math.sin(p.phase + Date.now() * 0.001 * p.swayFreq) * 0.5 * dt * 60; p.rotation += p.rotSpeed * dt * 60; if (p.y > rng.h + 30) { p.y = -30; p.x = Math.random() * rng.w; p.phase = Math.random() * Math.PI * 2; } }, draw(p, ctx) { ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(p.rotation); ctx.fillStyle = p.color; ctx.globalAlpha = 0.85; ctx.beginPath(); ctx.ellipse(0, 0, p.size * 0.5, p.size * 0.25, 0, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } },
                    cherry_blossom: { countRange: [8,50], create(rng) { const colors = ['#f8d7e0','#f2b5c4','#e891a8','#f0c0d0','#fce4ec']; return { x: Math.random() * rng.w, y: Math.random() * rng.h * -1 - 30, size: 5 + Math.random() * 8, speed: 0.8 + Math.random() * 2, swayAmp: 30 + Math.random() * 50, swayFreq: 0.2 + Math.random() * 0.3, rotation: Math.random() * Math.PI * 2, rotSpeed: (Math.random() - 0.5) * 0.08, phase: Math.random() * Math.PI * 2, color: colors[Math.floor(Math.random() * colors.length)] }; }, update(p, dt, rng) { p.y += p.speed * dt * 60; p.x += Math.sin(p.phase + Date.now() * 0.001 * p.swayFreq) * 0.4 * dt * 60; p.rotation += p.rotSpeed * dt * 60; if (p.y > rng.h + 30) { p.y = -30; p.x = Math.random() * rng.w; p.phase = Math.random() * Math.PI * 2; } }, draw(p, ctx) { ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(p.rotation); ctx.fillStyle = p.color; ctx.globalAlpha = 0.8; ctx.beginPath(); for (let i = 0; i < 5; i++) { const a = (i / 5) * Math.PI * 2 - Math.PI / 2; const r = p.size * 0.4; const x = Math.cos(a) * r; const y = Math.sin(a) * r; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.closePath(); ctx.fill(); ctx.restore(); } },
                    bubbles: { countRange: [10,80], create(rng) { return { x: Math.random() * rng.w, y: rng.h + 10 + Math.random() * 50, radius: 3 + Math.random() * 8, speed: 1 + Math.random() * 3, wobble: 0.5 + Math.random() * 1.5, opacity: 0.15 + Math.random() * 0.35, phase: Math.random() * Math.PI * 2 }; }, update(p, dt, rng) { p.y -= p.speed * dt * 60; p.x += Math.sin(p.phase + Date.now() * 0.001 * p.wobble) * 0.2 * dt * 60; if (p.y < -20) { p.y = rng.h + 10; p.x = Math.random() * rng.w; p.phase = Math.random() * Math.PI * 2; } }, draw(p, ctx) { ctx.save(); ctx.globalAlpha = p.opacity; ctx.strokeStyle = 'rgba(255,255,255,0.7)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); ctx.stroke(); ctx.fillStyle = 'rgba(255,255,255,0.08)'; ctx.fill(); ctx.restore(); } },
                    stars: { countRange: [10,60], create(rng) { return { x: Math.random() * rng.w, y: Math.random() * rng.h, size: 1 + Math.random() * 2.5, baseOpacity: 0.2 + Math.random() * 0.5, twinkleSpeed: 0.5 + Math.random() * 2, phase: Math.random() * Math.PI * 2 }; }, update(p, dt) { }, draw(p, ctx) { const alpha = p.baseOpacity + Math.sin(Date.now() * 0.001 * p.twinkleSpeed + p.phase) * 0.3; ctx.fillStyle = `rgba(255,255,255,${Math.max(0, alpha)})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill(); } }
                };
                return class {
                    constructor(canvas) {
                        this.canvas = canvas;
                        this.ctx = canvas.getContext('2d');
                        this.particles = [];
                        this.running = false;
                        this.rafId = null;
                        this.config = { effect: 'rain', density: 50, speed: 50, opacity: 100 };
                        this._onResize = () => { const p = this.canvas.parentElement; if (p) { const r = p.getBoundingClientRect(); this.canvas.width = r.width; this.canvas.height = r.height; } };
                        window.addEventListener('resize', this._onResize);
                    }
                    setConfig(config) {
                        const effectChanged = config.effect && config.effect !== this.config.effect;
                        Object.assign(this.config, config);
                        const effect = EFFECTS[this.config.effect];
                        if (!effect) return;
                        const [min, max] = effect.countRange;
                        const targetCount = min + Math.floor((max - min) * (this.config.density / 100));
                        const speedMul = 0.5 + (this.config.speed / 100) * 1.5;
                        const rng = { w: this.canvas.width, h: this.canvas.height };
                        if (effectChanged) {
                            this.particles = [];
                            for (let i = 0; i < targetCount; i++) this.particles.push(effect.create(rng));
                        } else if (this.config.effect === 'stars') {
                            this.particles = [];
                            for (let i = 0; i < targetCount; i++) this.particles.push(effect.create(rng));
                        } else {
                            while (this.particles.length < targetCount) this.particles.push(effect.create(rng));
                            if (this.particles.length > targetCount) this.particles.length = targetCount;
                            this.particles.forEach(p => { if (!('_baseSpeed' in p)) p._baseSpeed = p.speed; p.speed = p._baseSpeed * speedMul; });
                        }
                    }
                    start() {
                        if (this.running) return;
                        this.running = true;
                        const ctx = this.ctx;
                        const canvas = this.canvas;
                        const effect = EFFECTS[this.config.effect];
                        if (!effect) return;
                        let lastTime = performance.now();
                        const rng = { w: canvas.width, h: canvas.height };
                        const loop = (now) => {
                            if (!this.running) return;
                            const pEl = canvas.parentElement;
                            if (pEl) { const pr = pEl.getBoundingClientRect(); if (canvas.width !== pr.width || canvas.height !== pr.height) { canvas.width = pr.width; canvas.height = pr.height; } }
                            const dt = Math.min((now - lastTime) / 1000, 0.05);
                            lastTime = now;
                            ctx.clearRect(0, 0, canvas.width, canvas.height);
                            ctx.globalAlpha = this.config.opacity / 100;
                            const e = EFFECTS[this.config.effect];
                            rng.w = canvas.width;
                            rng.h = canvas.height;
                            for (const p of this.particles) {
                                e.update(p, dt, rng);
                                e.draw(p, ctx);
                            }
                            ctx.globalAlpha = 1;
                            this.rafId = requestAnimationFrame(loop);
                        };
                        this.rafId = requestAnimationFrame(loop);
                    }
                    stop() {
                        this.running = false;
                        if (this.rafId) { cancelAnimationFrame(this.rafId); this.rafId = null; }
                        this.particles = [];
                        if (this.ctx) this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                    }
                    destroy() {
                        this.stop();
                        window.removeEventListener('resize', this._onResize);
                    }
                };
            })();
            _particleEngine = new ParticleEngine(canvas);
        }
        _particleEngine.setConfig({
            effect: settings.particlesEffect || 'rain',
            density: settings.particlesDensity !== undefined ? settings.particlesDensity : 50,
            speed: settings.particlesSpeed !== undefined ? settings.particlesSpeed : 50,
            opacity: settings.particlesOpacity !== undefined ? settings.particlesOpacity : 100
        });
        if (!_particleEngine.running) _particleEngine.start();
    }

    function applyOpacity(value) {
        const containers = [
            document.querySelector('#chat-body'),
            document.querySelector('#chat-messages')
        ].filter(Boolean);
        containers.forEach(el => {
            el.style.setProperty('--bubble-opacity', (value / 100).toString());
        });
        const previewBubbles = document.querySelectorAll('#preview-char, #preview-user');
        previewBubbles.forEach(b => {
            b.style.opacity = (value / 100).toString();
        });
    }

    function applyToggles() {
        document.body.dataset.caiHideInput = 'true';
        document.body.dataset.caiHideWarning = 'true';
        document.body.dataset.caiHideBadge = 'true';
    }

    function applyCharacterInfoVisibility() {
        document.body.dataset.caiHideCharinfo = 'true';
    }

    function applyInputWidth(value) {
        const chatBody = document.querySelector('#chat-body');
        if (!chatBody) return;
        const outer = chatBody.querySelector('.max-w-2xl');
        const inner = outer ? outer.querySelector('.max-w-3xl') : null;
        if (outer) {
            outer.style.maxWidth = 'none';
            outer.style.width = '100%';
        }
        if (inner) {
            const rem = (value / 100) * 42;
            inner.style.maxWidth = rem + 'rem';
        }
    }

    function applyOutlines(enabled, settings) {
        const containers = [
            document.querySelector('#chat-body'),
            document.querySelector('#chat-messages')
        ].filter(Boolean);
        if (!enabled) {
            containers.forEach(el => {
                el.style.removeProperty('--outline-bot');
                el.style.removeProperty('--outline-user');
            });
            return;
        }
        const theme = themes.find(t => t.name === (settings.theme || 'Default'));
        let charColor, userColor;
        if (theme && theme.name !== 'Default') {
            charColor = theme.charColor;
            userColor = theme.userColor;
        } else {
            charColor = 'var(--surface-elevation-3)';
            userColor = 'var(--surface-elevation-2)';
        }
        containers.forEach(el => {
            el.style.setProperty('--outline-bot', `0 0 0 2px ${userColor}`);
            el.style.setProperty('--outline-user', `0 0 0 2px ${charColor}`);
        });
    }

    function applyTextSize(value) {
        document.documentElement.style.setProperty('--text-scale', (value / 100) + 'rem');
        const target = document.querySelector('#chat-messages');
        if (target) {
            target.style.setProperty('--text-scale', (value / 100) + 'rem');
        }
    }

    function applyBubbleRadius(value) {
        document.documentElement.style.setProperty('--bubble-radius', value + 'px');
        const chatMessages = document.querySelector('#chat-messages');
        if (chatMessages) {
            chatMessages.style.setProperty('--bubble-radius', value + 'px');
        }
        const charBubble = document.querySelector('#preview-char');
        const userBubble = document.querySelector('#preview-user');
        if (charBubble && userBubble) {
            const r = value + 'px';
            charBubble.style.borderRadius = r;
            userBubble.style.borderRadius = r;
        }
    }

    let customCssStyleEl = null;
    function applyCustomCss(css) {
        if (customCssStyleEl) customCssStyleEl.remove();
        if (!css || !css.trim()) return;
        customCssStyleEl = document.createElement('style');
        customCssStyleEl.id = 'cai-custom-css';
        customCssStyleEl.textContent = css;
        document.head.appendChild(customCssStyleEl);
    }

    function loadSavedSettings(retries = 20) {
        const settings = getCurrentSettings();
        _chatMessagesRetries = 40;
        applyChatIndependentSettings(settings);
        const body = document.querySelector('#chat-body');
        if (!body) {
            if (retries > 0) {
                setTimeout(() => loadSavedSettings(retries - 1), 500);
            }
            return;
        }
        applyTheme(settings.theme || 'Default');
        applyOpacity(settings.opacity !== undefined ? settings.opacity : 100);
        applyBackground(settings);
        applyToggles();
        applyCharacterInfoVisibility();
        applyFont({fontName: getFontSetting()});
        applyInputWidth(settings.inputWidth !== undefined ? settings.inputWidth : 150);
        applyOutlines(settings.bubbleOutlines || false, settings);
        applyTextSize(settings.textSize !== undefined ? settings.textSize : 100);
        applyBubbleRadius(settings.bubbleRadius !== undefined ? settings.bubbleRadius : 16);
        applyBubbleStyle(settings.bubbleStyle || null);
        applyCustomCss(settings.customCss || '');
        if (settings.audioWallpaperUrl && settings.audioWallpaperUrl.startsWith('blob:')) {
            const data = getStorageData();
            const chatId = getChatIdFromUrl();
            const scope = chatId && data.chats[chatId] ? data.chats[chatId] : data.global;
            delete scope.audioWallpaperUrl;
            delete scope.audioWallpaperVolume;
            delete scope.audioWallpaperLoop;
            saveStorageData(data);
            settings.audioWallpaperUrl = undefined;
        }
        applyAudioWallpaper(settings);
        applyParticles(settings);
        const g = getStorageData().global;
        applyImmersion(g);
    }

    let _chatMessagesTimer = null;
    let _chatMessagesRetries = 40;
    function applyChatIndependentSettings(settings) {
        clearTimeout(_chatMessagesTimer);
        const chatMessages = document.querySelector('#chat-messages');
        if (!chatMessages) {
            if (_chatMessagesRetries > 0) {
                _chatMessagesRetries--;
                _chatMessagesTimer = setTimeout(() => applyChatIndependentSettings(settings), 300);
            }
            return;
        }
        applyTextSize(settings.textSize !== undefined ? settings.textSize : 100);
        applyBubbleRadius(settings.bubbleRadius !== undefined ? settings.bubbleRadius : 16);
        applyFont({fontName: getFontSetting()});
    }

    function removeOriginalCustomize() {
        const rows = document.querySelectorAll('#chat-details .flex.justify-between.items-center.w-full');
        for (const row of rows) {
            const btn = row.querySelector('button');
            if (!btn) continue;
            const svg = btn.querySelector('svg[viewBox="0 0 512 512"]');
            if (svg) {
                row.remove();
            }
        }
    }

    function injectCustomButton() {
        const detailsPanel = document.querySelector('#chat-details');
        if (!detailsPanel) return;
        const targetContainer = detailsPanel.querySelector('.flex.flex-col.gap-3.h-fit');
        if (!targetContainer) return;

        const existing = targetContainer.querySelector('.cai-custom-button');
        if (existing) {
            const wrapper = existing.closest('.flex.justify-between.items-center.w-full');
            if (wrapper) wrapper.remove();
            else existing.remove();
        }

        const wrapper = document.createElement('div');
        wrapper.className = 'flex justify-between items-center w-full';

        const button = document.createElement('button');
        button.className = 'cai-custom-button z-0 group relative items-center box-border appearance-none select-none whitespace-nowrap font-normal subpixel-antialiased overflow-hidden tap-highlight-transparent outline-none data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 hover:bg-surface-elevation-1 px-unit-4 min-w-unit-20 h-unit-10 text-md gap-unit-2 rounded-md [&>svg]:max-w-[theme(spacing.unit-8)] data-[pressed=true]:scale-[0.97] transition-transform-colors-opacity motion-reduce:transition-none bg-ghost text-primary data-[hover=true]:opacity-hover w-full flex justify-between rounded-spacing-xs';
        button.type = 'button';
        button.innerHTML = `
            <div class="flex items-center w-fit gap-3">
                <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" class="size-5 p-1" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
                    <path d="M167.02 309.34c-40.12 2.58-76.53 17.86-97.19 72.3-2.35 6.21-8 9.98-14.59 9.98-11.11 0-45.46-27.67-55.25-34.35C0 439.62 37.93 512 128 512c75.86 0 128-43.77 128-120.19 0-3.11-.65-6.08-.97-9.13l-88.01-73.34zM457.89 0c-15.16 0-29.37 6.71-40.21 16.45C213.27 199.05 192 203.34 192 257.09c0 13.7 3.25 26.76 8.73 38.7l63.82 53.18c7.21 1.8 14.64 3.03 22.39 3.03 62.11 0 98.11-45.47 211.16-256.46 7.38-14.35 13.9-29.85 13.9-45.99C512 20.64 486 0 457.89 0z"></path>
                </svg>
                <span>Customize</span>
            </div>
            <div class="flex gap-1 items-center">
                <svg viewBox="0 0 24 24" fill="none" class="text-muted-foreground size-3">
                    <path d="m9 4 8 8-8 8" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path>
                </svg>
            </div>
        `;
        wrapper.appendChild(button);
        button.addEventListener('click', openCustomModal);

        let historyBtn = null;
        for (const child of targetContainer.children) {
            if (child.tagName === 'BUTTON') {
                const text = child.textContent.trim();
                if (text === 'History' || text.includes('History')) {
                    historyBtn = child;
                    break;
                }
            }
        }
        if (!historyBtn) {
            const buttons = targetContainer.querySelectorAll('button');
            for (const btn of buttons) {
                const svg = btn.querySelector('svg');
                if (svg && svg.innerHTML && svg.innerHTML.includes('M15 4.582a8 8 0 0 0-6.5 14.614')) {
                    historyBtn = btn;
                    break;
                }
            }
        }

        if (historyBtn && historyBtn.parentNode === targetContainer) {
            targetContainer.insertBefore(wrapper, historyBtn.nextSibling);
        } else {
            let pinnedWrapper = null;
            for (const child of targetContainer.children) {
                if (child.tagName === 'DIV' && child.classList.contains('flex') && child.classList.contains('justify-between')) {
                    const btn = child.querySelector('button');
                    if (btn && btn.textContent && btn.textContent.trim() === 'Pinned') {
                        pinnedWrapper = child;
                        break;
                    }
                }
            }
            if (pinnedWrapper) {
                targetContainer.insertBefore(wrapper, pinnedWrapper);
            } else {
                targetContainer.appendChild(wrapper);
            }
        }
    }

    function createCustomColorPicker(initialColor, onChange) {
        const container = document.createElement('div');
        container.className = 'cai-custom-color-picker';
        const palette = [
            '#000000', '#1a1a1a', '#333333', '#4d4d4d', '#666666', '#808080', '#999999', '#b3b3b3', '#cccccc', '#e6e6e6', '#ffffff',
            '#ff0000', '#cc0000', '#990000', '#660000', '#330000',
            '#ff6600', '#cc4400', '#993300', '#662200', '#331100',
            '#ffcc00', '#cc9900', '#997700', '#664400', '#332200',
            '#00ff00', '#00cc00', '#009900', '#006600', '#003300',
            '#00ccff', '#0099cc', '#006699', '#003366', '#001133',
            '#0000ff', '#0000cc', '#000099', '#000066', '#000033',
            '#cc00ff', '#9900cc', '#660099', '#330066', '#1a0033',
            '#ff00cc', '#cc0099', '#990066', '#660033', '#33001a',
            '#ff0066', '#cc0052', '#99003d', '#660029', '#330014'
        ];
        palette.forEach(color => {
            const swatch = document.createElement('div');
            swatch.className = 'color-swatch' + (color === initialColor ? ' active' : '');
            swatch.style.backgroundColor = color;
            swatch.dataset.color = color;
            swatch.addEventListener('click', function() {
                container.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('active'));
                this.classList.add('active');
                const hex = this.dataset.color;
                hexInput.value = hex.toUpperCase();
                onChange(hex);
            });
            container.appendChild(swatch);
        });
        const hexInput = document.createElement('input');
        hexInput.type = 'text';
        hexInput.className = 'color-hex-input';
        hexInput.maxLength = 7;
        hexInput.value = initialColor.toUpperCase();
        hexInput.addEventListener('input', function() {
            let val = this.value.trim();
            if (val.startsWith('#')) {
                val = val.substring(1);
            }
            if (/^[0-9a-fA-F]{6}$/.test(val)) {
                const full = '#' + val.toUpperCase();
                container.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('active'));
                let found = false;
                container.querySelectorAll('.color-swatch').forEach(s => {
                    if (s.dataset.color.toUpperCase() === full.toUpperCase()) {
                        s.classList.add('active');
                        found = true;
                    }
                });
                if (!found) {
                    // custom color
                }
                onChange(full);
            }
        });
        hexInput.addEventListener('blur', function() {
            let val = this.value.trim();
            if (!val.startsWith('#')) val = '#' + val;
            if (/^#[0-9a-fA-F]{6}$/.test(val)) {
                this.value = val.toUpperCase();
            } else {
                const current = container.querySelector('.color-swatch.active')?.dataset.color || '#000000';
                this.value = current.toUpperCase();
            }
        });
        container.appendChild(hexInput);
        const label = document.createElement('span');
        label.textContent = 'hex';
        label.style.fontSize = '10px';
        label.style.color = 'var(--muted-foreground)';
        label.style.marginLeft = '4px';
        container.appendChild(label);
        const initialSwatch = container.querySelector(`.color-swatch[data-color="${initialColor}"]`);
        if (initialSwatch) initialSwatch.classList.add('active');
        return {
            container: container,
            setColor: function(color) {
                container.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('active'));
                const swatch = container.querySelector(`.color-swatch[data-color="${color}"]`);
                if (swatch) swatch.classList.add('active');
                hexInput.value = color.toUpperCase();
            }
        };
    }

    function openAudioWallpaperDialog() {
        const settings = getCurrentSettings();
        const url = settings.audioWallpaperUrl || '';
        const volume = settings.audioWallpaperVolume !== undefined ? settings.audioWallpaperVolume : 100;
        const loop = settings.audioWallpaperLoop !== undefined ? settings.audioWallpaperLoop : true;

        document.querySelector('.cai-audio-dialog')?.remove();

        const dialog = document.createElement('div');
        dialog.className = 'cai-audio-dialog fixed max-w-full z-[60] shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 border-none left-[50%] top-[50%] translate-y-[-50%] translate-x-[-50%] data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-spacing-l w-11/12 sm:max-w-md p-0 flex flex-col gap-0 bg-surface-elevation-1 text-foreground';
        dialog.setAttribute('role', 'dialog');
        dialog.setAttribute('data-state', 'open');
        dialog.style.pointerEvents = 'auto';
        dialog.tabIndex = -1;

        const isActive = !!url;
        const audioEl = document.getElementById('cai-bg-audio');
        const isPlaying = isActive && audioEl && !audioEl.paused;

        dialog.innerHTML = `
            <div class="w-full flex flex-row justify-between items-center px-4 py-3 sm:px-6 sm:py-4 border-b border-border-outline">
                <h2 class="text-base sm:text-lg font-medium">Audio</h2>
                <button type="button" class="close-dialog-btn rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
                </button>
            </div>
            <div class="cai-audio-body flex flex-col px-4 pb-4 pt-4 sm:px-6 sm:pb-6 sm:pt-4 gap-4">
                <div class="cai-audio-upload-row">
                    <div class="cai-audio-visualizer ${isActive ? 'active' : ''}">
                        <div class="cai-wave-bar" style="animation-delay:0s"></div>
                        <div class="cai-wave-bar" style="animation-delay:0.1s"></div>
                        <div class="cai-wave-bar" style="animation-delay:0.2s"></div>
                        <div class="cai-wave-bar" style="animation-delay:0.3s"></div>
                        <div class="cai-wave-bar" style="animation-delay:0.4s"></div>
                    </div>
                    <div class="cai-audio-upload-area">
                        <button class="cai-audio-upload-btn" id="cai-audio-upload-btn">
                            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                                <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
                                <polyline points="17 8 12 3 7 8"/>
                                <line x1="12" y1="3" x2="12" y2="15"/>
                            </svg>
                            <span>${url ? 'Change' : 'Upload'}</span>
                        </button>
                        <div class="cai-audio-link-area">
                            <div class="cai-audio-link-label">or paste a link</div>
                            <input type="text" class="cai-audio-link-input" id="cai-audio-link-input" placeholder="https://example.com/audio.mp3" value="${escapeHtml(url)}" />
                        </div>
                    </div>
                </div>

                <div class="cai-audio-player-bar" id="cai-audio-player-bar" style="display: ${isActive ? 'flex' : 'none'};">
                    <button type="button" class="cai-audio-play-btn" id="cai-audio-play-btn">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
                            ${isPlaying ? '<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>' : '<path d="M8 5v14l11-7z"/>'}
                        </svg>
                    </button>
                    <div class="cai-audio-seek-track">
                        <div class="cai-audio-seek-fill" id="cai-audio-seek-fill" style="width:0%"></div>
                        <input type="range" min="0" max="100" value="0" class="cai-audio-seek-slider" id="cai-audio-seek-slider" />
                    </div>
                    <span class="cai-audio-time" id="cai-audio-time">0:00 / 0:00</span>
                </div>

                <div class="cai-audio-controls ${isActive ? '' : 'muted'}">
                    <div class="cai-audio-vol-row">
                        <label class="cai-audio-vol-label">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                                <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
                                <path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/>
                            </svg>
                            Volume
                        </label>
                        <div class="cai-audio-vol-track">
                            <div class="cai-audio-vol-fill" style="width:${volume}%"></div>
                            <input type="range" min="0" max="100" value="${volume}" class="cai-audio-vol-slider" id="cai-audio-vol-slider" />
                        </div>
                        <span class="cai-audio-vol-pct" id="cai-audio-vol-label">${volume}%</span>
                    </div>

                    <div class="cai-audio-loop-row">
                        <label class="cai-audio-loop-label">Loop</label>
                        <div class="cai-audio-loop-toggle ${loop ? 'on' : ''}" id="cai-audio-loop-toggle" role="switch" aria-checked="${loop}" tabindex="0">
                            <div class="cai-audio-loop-thumb"></div>
                        </div>
                    </div>

                    <div class="cai-audio-actions">
                        ${isActive ? '<button class="cai-audio-stop-btn" id="cai-audio-stop-btn">Stop & Remove</button>' : ''}
                    </div>
                </div>
            </div>
        `;

        document.body.appendChild(dialog);

        function closeDialog() {
            updateAudioModalStatus(!!getCurrentSettings().audioWallpaperUrl);
            dialog.classList.add('closing');
            const onEnd = () => {
                dialog.removeEventListener('transitionend', onEnd);
                dialog.remove();
            };
            dialog.addEventListener('transitionend', onEnd);
            setTimeout(() => {
                if (document.body.contains(dialog)) {
                    dialog.removeEventListener('transitionend', onEnd);
                    dialog.remove();
                }
            }, 300);
        }

        dialog.querySelector('.close-dialog-btn').addEventListener('click', closeDialog);
        document.addEventListener('click', function outsideHandler(e) {
            if (!dialog.contains(e.target) && dialog.parentNode) {
                closeDialog();
                document.removeEventListener('click', outsideHandler);
            }
        });
        document.addEventListener('keydown', function escHandler(e) {
            if (e.key === 'Escape') {
                closeDialog();
                document.removeEventListener('keydown', escHandler);
            }
        });

        const uploadBtn = dialog.querySelector('#cai-audio-upload-btn');
        if (uploadBtn) {
            uploadBtn.addEventListener('click', function() {
                const input = document.createElement('input');
                input.type = 'file';
                input.accept = 'audio/*';
                    input.onchange = function(e) {
                        const file = e.target.files[0];
                        if (!file) return;
                        const maxPermSize = 1024 * 1024;
                        if (file.size <= maxPermSize) {
                            const reader = new FileReader();
                            reader.onload = function(ev) {
                                const s = getCurrentSettings();
                                s.audioWallpaperUrl = ev.target.result;
                                saveCurrentSettings(s);
                                applyAudioWallpaper(s);
                                updateAudioModalStatus(true);
                                closeDialog();
                            };
                            reader.readAsDataURL(file);
                        } else {
                            const objectUrl = URL.createObjectURL(file);
                            const s = getCurrentSettings();
                            s.audioWallpaperUrl = objectUrl;
                            saveCurrentSettings(s);
                            applyAudioWallpaper(s);
                            updateAudioModalStatus(true);
                            closeDialog();
                            setTimeout(() => {
                                showAlertDialog('Audio file exceeds 1MB and cannot be saved permanently. It will play for this session only. Use a URL link for persistent playback.');
                            }, 100);
                        }
                    };
                input.click();
            });
        }

        const linkInput = dialog.querySelector('#cai-audio-link-input');
        if (linkInput) {
            let linkTimer;
            linkInput.addEventListener('input', function() {
                clearTimeout(linkTimer);
                linkTimer = setTimeout(() => {
                    const val = this.value.trim();
                    if (val && isAudioUrl(val)) {
                        const s = getCurrentSettings();
                        s.audioWallpaperUrl = val;
                        saveCurrentSettings(s);
                        applyAudioWallpaper(s);
                    } else if (!val) {
                        const s = getCurrentSettings();
                        s.audioWallpaperUrl = null;
                        saveCurrentSettings(s);
                        applyAudioWallpaper(s);
                    }
                }, 600);
            });
        }

        function formatTime(s) {
            s = Math.floor(s) || 0;
            const m = Math.floor(s / 60);
            const sec = s % 60;
            return m + ':' + (sec < 10 ? '0' : '') + sec;
        }

        const playBtn = dialog.querySelector('#cai-audio-play-btn');
        const seekSlider = dialog.querySelector('#cai-audio-seek-slider');
        const seekFill = dialog.querySelector('#cai-audio-seek-fill');
        const timeLabel = dialog.querySelector('#cai-audio-time');
        const playerAudio = document.getElementById('cai-bg-audio');

        function updatePlayerUI() {
            if (!playerAudio || !playBtn || !timeLabel) return;
            const paused = playerAudio.paused;
            const svg = playBtn.querySelector('svg');
            if (svg) {
                svg.innerHTML = paused
                    ? '<path d="M8 5v14l11-7z"/>'
                    : '<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>';
            }
            const cur = playerAudio.currentTime || 0;
            const dur = playerAudio.duration || 0;
            timeLabel.textContent = formatTime(cur) + ' / ' + formatTime(dur);
            if (seekSlider && !seekSlider.dataset.dragging) {
                const pct = dur > 0 ? (cur / dur) * 100 : 0;
                seekSlider.value = pct;
                if (seekFill) seekFill.style.width = pct + '%';
            }
        }

        if (playBtn && playerAudio) {
            playBtn.addEventListener('click', function() {
                if (playerAudio.paused) {
                    playerAudio.play().catch(() => {});
                } else {
                    playerAudio.pause();
                }
            });
            playerAudio.addEventListener('play', updatePlayerUI);
            playerAudio.addEventListener('pause', updatePlayerUI);
            playerAudio.addEventListener('timeupdate', updatePlayerUI);
            playerAudio.addEventListener('loadedmetadata', updatePlayerUI);
            playerAudio.addEventListener('ended', updatePlayerUI);
        }

        if (seekSlider && playerAudio) {
            seekSlider.addEventListener('input', function() {
                this.dataset.dragging = '1';
                const dur = playerAudio.duration || 0;
                const pct = parseFloat(this.value);
                if (seekFill) seekFill.style.width = pct + '%';
                if (dur > 0) {
                    const t = (pct / 100) * dur;
                    playerAudio.currentTime = t;
                    if (timeLabel) timeLabel.textContent = formatTime(t) + ' / ' + formatTime(dur);
                }
            });
            seekSlider.addEventListener('change', function() {
                delete this.dataset.dragging;
            });
        }

        const volSlider = dialog.querySelector('#cai-audio-vol-slider');
        const volFill = dialog.querySelector('.cai-audio-vol-fill');
        const volLabel = dialog.querySelector('#cai-audio-vol-label');
        if (volSlider) {
            volSlider.addEventListener('input', function() {
                const val = parseInt(this.value);
                if (volFill) volFill.style.width = val + '%';
                if (volLabel) volLabel.textContent = val + '%';
                const s = getCurrentSettings();
                s.audioWallpaperVolume = val;
                saveCurrentSettings(s);
                applyAudioWallpaper(s);
            });
        }

        const loopToggle = dialog.querySelector('#cai-audio-loop-toggle');
        if (loopToggle) {
            loopToggle.addEventListener('click', function() {
                const now = this.classList.toggle('on');
                this.setAttribute('aria-checked', now);
                const s = getCurrentSettings();
                s.audioWallpaperLoop = now;
                saveCurrentSettings(s);
                applyAudioWallpaper(s);
            });
        }

        const stopBtn = dialog.querySelector('#cai-audio-stop-btn');
        if (stopBtn) {
            stopBtn.addEventListener('click', function() {
                const s = getCurrentSettings();
                s.audioWallpaperUrl = null;
                saveCurrentSettings(s);
                applyAudioWallpaper(s);
                updateAudioModalStatus(false);
                closeDialog();
            });
        }

        function updateAudioModalStatus(hasAudio) {
            const mainModal = document.querySelector('.cai-custom-modal');
            if (!mainModal) return;
            const statusEl = mainModal.querySelector('.cai-audio-status');
            const btnEl = mainModal.querySelector('#cai-audio-add-btn');
            if (statusEl) {
                if (!hasAudio) {
                    statusEl.textContent = 'No audio set';
                } else {
                    const audio = document.getElementById('cai-bg-audio');
                    statusEl.textContent = audio && !audio.paused ? 'Now playing' : 'Paused';
                }
            }
            if (btnEl) btnEl.textContent = hasAudio ? 'Edit' : 'Add';
        }
    }

    function openWallpaperDialog() {
        const settings = getWallpaperSettings();
        const bgType = settings.bgType || 'file';
        const wallpaper = settings.wallpaper;
        const backgroundColor = settings.backgroundColor || '#00000000';
        const wallpaperOpacity = settings.wallpaperOpacity !== undefined ? settings.wallpaperOpacity : 100;

        document.querySelector('.cai-wallpaper-dialog')?.remove();

        const dialog = document.createElement('div');
        dialog.className = 'cai-wallpaper-dialog fixed max-w-full z-[60] shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 border-none left-[50%] top-[50%] translate-y-[-50%] translate-x-[-50%] data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-spacing-l w-11/12 sm:max-w-md p-0 flex flex-col gap-0 bg-surface-elevation-1 text-foreground';
        dialog.setAttribute('role', 'dialog');
        dialog.setAttribute('data-state', 'open');
        dialog.style.pointerEvents = 'auto';
        dialog.tabIndex = -1;

        dialog.innerHTML = `
            <div class="w-full flex flex-row justify-between items-center px-4 py-3 sm:px-6 sm:py-4 border-b border-border-outline">
                <h2 class="text-base sm:text-lg font-medium">Wallpaper</h2>
                <button type="button" class="close-dialog-btn rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
                    <span class="sr-only">Close</span>
                </button>
            </div>
            <div class="flex flex-col px-4 pb-4 pt-4 sm:px-6 sm:pb-6 sm:pt-4 gap-4">
                <div class="w-full flex flex-col gap-2">
                    <p class="text-sm text-muted-foreground">Choose an image or solid color for your chat background.</p>
                    <div class="cai-bg-type-group" role="group" aria-label="Background type">
                        <button class="bg-type-btn ${bgType === 'file' ? 'active' : ''}" data-type="file">File</button>
                        <button class="bg-type-btn ${bgType === 'link' ? 'active' : ''}" data-type="link">Link</button>
                        <button class="bg-type-btn ${bgType === 'color' ? 'active' : ''}" data-type="color">Color</button>
                    </div>
                    <div class="cai-bg-input-area" id="cai-bg-input-area">
                        <div class="cai-bg-file-input" style="display: ${bgType === 'file' ? 'block' : 'none'};">
                            <button class="cai-bg-upload-btn ${wallpaper ? 'has-image' : ''}" id="cai-wallpaper-upload-btn">
                                ${wallpaper ? '✓ Wallpaper uploaded' : 'Click to upload an image or video'}
                            </button>
                            ${wallpaper && bgType === 'file' ? `<button class="cai-bg-remove-btn" id="cai-wallpaper-remove-btn">Remove</button>` : ''}
                        </div>
                        <div class="cai-bg-link-input" style="display: ${bgType === 'link' ? 'block' : 'none'};">
                            <input type="text" id="cai-bg-link-input" placeholder="Enter image URL..." value="${bgType === 'link' && wallpaper ? escapeHtml(wallpaper) : ''}" />
                        </div>
                        <div class="cai-bg-color-input" style="display: ${bgType === 'color' ? 'block' : 'none'};">
                            <div id="cai-bg-color-picker-container"></div>
                        </div>
                    </div>
                    <div id="cai-bg-advanced" style="display: ${bgType !== 'color' ? 'block' : 'none'}; margin-top: 8px;">
                        <div class="flex items-center gap-2 text-sm mt-2">
                            <label class="text-xs text-muted-foreground">Opacity</label>
                            <div class="flex items-center gap-1 flex-1">
                                <input type="range" min="10" max="100" value="${wallpaperOpacity}" class="cai-wallpaper-opacity-slider flex-1 cai-slider" />
                                <span class="cai-wallpaper-opacity-label text-xs w-8">${wallpaperOpacity}%</span>
                            </div>
                        </div>
                        <div id="cai-bg-video-options" style="display: ${wallpaper && isVideoUrl(wallpaper) ? 'block' : 'none'}; margin-top: 8px;">
                            <div class="flex items-center gap-2 text-sm mt-2">
                                <label class="text-xs text-muted-foreground">Speed</label>
                                <div class="flex items-center gap-1 flex-1">
                                    <input type="range" min="0.25" max="2" step="0.25" value="${settings.videoSpeed || 1}" class="cai-video-speed-slider flex-1 cai-slider" />
                                    <span class="cai-video-speed-label text-xs w-8">${(settings.videoSpeed || 1).toFixed(2).replace(/\.?0+$/, '')}x</span>
                                </div>
                            </div>
                            <div class="flex items-center justify-between text-sm mt-2">
                                <label class="text-xs text-muted-foreground">Sound</label>
                                <button type="button" role="switch" aria-checked="${settings.videoSound || false}" id="cai-video-sound-toggle" class="cai-toggle-track ${settings.videoSound ? 'bg-blue' : 'bg-muted'}">
                                    <span class="cai-toggle-thumb" style="transform: ${settings.videoSound ? 'translateX(16px)' : 'translateX(0)'}"></span>
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        `;

        document.body.appendChild(dialog);

        const bgPickerContainer = document.getElementById('cai-bg-color-picker-container');
        if (bgPickerContainer) {
            const bgPicker = createCustomColorPicker(backgroundColor, function(color) {
                const settingsNow = getWallpaperSettings();
                settingsNow.bgType = 'color';
                settingsNow.backgroundColor = color;
                saveCurrentSettings(settingsNow);
                applyBackground(settingsNow);
            });
            bgPickerContainer.appendChild(bgPicker.container);
        }

        function closeDialog() {
            dialog.classList.add('closing');
            const onTransitionEnd = () => {
                dialog.removeEventListener('transitionend', onTransitionEnd);
                dialog.remove();
            };
            dialog.addEventListener('transitionend', onTransitionEnd);
            setTimeout(() => {
                if (document.body.contains(dialog)) {
                    dialog.removeEventListener('transitionend', onTransitionEnd);
                    dialog.remove();
                }
            }, 300);
        }

        dialog.querySelector('.close-dialog-btn').addEventListener('click', closeDialog);
        document.addEventListener('click', function outsideClickHandler(e) {
            if (!dialog.contains(e.target) && dialog.parentNode) {
                closeDialog();
                document.removeEventListener('click', outsideClickHandler);
            }
        });
        document.addEventListener('keydown', function escHandler(e) {
            if (e.key === 'Escape') {
                closeDialog();
                document.removeEventListener('keydown', escHandler);
            }
        });

        const typeBtns = dialog.querySelectorAll('.bg-type-btn');
        const fileInputArea = dialog.querySelector('.cai-bg-file-input');
        const linkInputArea = dialog.querySelector('.cai-bg-link-input');
        const colorInputArea = dialog.querySelector('.cai-bg-color-input');
        const advancedSection = dialog.querySelector('#cai-bg-advanced');

        typeBtns.forEach(btn => {
            btn.addEventListener('click', function() {
                const type = this.dataset.type;
                typeBtns.forEach(b => b.classList.remove('active'));
                this.classList.add('active');
                const settingsNow = getWallpaperSettings();
                settingsNow.bgType = type;
                fileInputArea.style.display = type === 'file' ? 'block' : 'none';
                linkInputArea.style.display = type === 'link' ? 'block' : 'none';
                colorInputArea.style.display = type === 'color' ? 'block' : 'none';
                if (advancedSection) advancedSection.style.display = type !== 'color' ? 'block' : 'none';
                const videoOpts = dialog.querySelector('#cai-bg-video-options');
                if (videoOpts) videoOpts.style.display = (type !== 'color' && settingsNow.wallpaper && isVideoUrl(settingsNow.wallpaper)) ? 'block' : 'none';

                if (type === 'file') {
                    const hasImg = !!settingsNow.wallpaper;
                    uploadBtn.textContent = hasImg ? '✓ Wallpaper uploaded' : 'Click to upload an image or video';
                    uploadBtn.classList.toggle('has-image', hasImg);
                    let removeBtn = dialog.querySelector('#cai-wallpaper-remove-btn');
                    if (hasImg && !removeBtn) {
                        removeBtn = document.createElement('button');
                        removeBtn.id = 'cai-wallpaper-remove-btn';
                        removeBtn.className = 'cai-bg-remove-btn';
                        removeBtn.textContent = 'Remove';
                        removeBtn.addEventListener('click', function() {
                            const s = getWallpaperSettings();
                            s.wallpaper = null;
                            saveCurrentSettings(s);
                            applyBackground(s);
                            uploadBtn.textContent = 'Click to upload an image or video';
                            uploadBtn.classList.remove('has-image');
                            this.remove();
                        });
                        fileInputArea.appendChild(removeBtn);
                    } else if (!hasImg && removeBtn) {
                        removeBtn.remove();
                    }
                } else if (type === 'color') {
                    settingsNow.wallpaper = null;
                } else if (type === 'link') {
                    const linkInput = dialog.querySelector('#cai-bg-link-input');
                    if (settingsNow.wallpaper) {
                        linkInput.value = settingsNow.wallpaper;
                        settingsNow.wallpaper = linkInput.value;
                    } else {
                        linkInput.value = '';
                        settingsNow.wallpaper = null;
                    }
                }
                saveCurrentSettings(settingsNow);
                applyBackground(settingsNow);
            });
        });

        const uploadBtn = dialog.querySelector('#cai-wallpaper-upload-btn');
        if (uploadBtn) {
            uploadBtn.addEventListener('click', function() {
                const input = document.createElement('input');
                input.type = 'file';
                input.accept = 'image/*,video/mp4,video/webm,video/ogg';
                input.onchange = async function(e) {
                    const file = e.target.files[0];
                    if (!file) return;
                    const maxSize = file.type.startsWith('video/') ? 10 : file.type === 'image/gif' ? 5 : 3;
                    if (file.size > maxSize * 1024 * 1024) {
                        const proceed = await showConfirmDialog(`File is larger than ${maxSize}MB. Large files may fail to save due to browser storage limits. Continue anyway?`);
                        if (!proceed) return;
                    }
                    const reader = new FileReader();
                    reader.onload = function(ev) {
                        const dataUrl = ev.target.result;
                        const settingsNow = getWallpaperSettings();
                        settingsNow.bgType = 'file';
                        settingsNow.wallpaper = dataUrl;
                        const saved = saveCurrentSettings(settingsNow);
                        applyBackground(settingsNow);
                        const isVideo = isVideoUrl(dataUrl);
                        if (!saved && isVideo) {
                            showAlertDialog('Video wallpaper is too large for permanent storage and will only last until the page is refreshed.');
                        }
                        uploadBtn.textContent = '✓ Wallpaper uploaded';
                        uploadBtn.classList.add('has-image');
                        typeBtns.forEach(b => b.classList.remove('active'));
                        dialog.querySelector('.bg-type-btn[data-type="file"]').classList.add('active');
                        fileInputArea.style.display = 'block';
                        linkInputArea.style.display = 'none';
                        colorInputArea.style.display = 'none';
                        const videoOpts = dialog.querySelector('#cai-bg-video-options');
                        if (videoOpts) videoOpts.style.display = isVideo ? 'block' : 'none';
                        const existingRemove = dialog.querySelector('#cai-wallpaper-remove-btn');
                        if (!existingRemove) {
                            const removeBtn = document.createElement('button');
                            removeBtn.id = 'cai-wallpaper-remove-btn';
                            removeBtn.className = 'cai-bg-remove-btn';
                            removeBtn.textContent = 'Remove';
                            fileInputArea.appendChild(removeBtn);
                            removeBtn.addEventListener('click', function() {
                                const settingsNow2 = getWallpaperSettings();
                                settingsNow2.wallpaper = null;
                                saveCurrentSettings(settingsNow2);
                                applyBackground(settingsNow2);
                                uploadBtn.textContent = 'Click to upload an image or video';
                                uploadBtn.classList.remove('has-image');
                                if (videoOpts) videoOpts.style.display = 'none';
                                removeBtn.remove();
                            });
                        }
                    };
                    reader.onerror = function() {
                        showAlertDialog('Failed to read the file. Please try again.');
                        uploadBtn.textContent = 'Click to upload an image or video';
                    };
                    reader.readAsDataURL(file);
                };
                input.click();
            });
        }

        const removeBtn = dialog.querySelector('#cai-wallpaper-remove-btn');
        if (removeBtn) {
            removeBtn.addEventListener('click', function() {
                const settingsNow = getWallpaperSettings();
                settingsNow.wallpaper = null;
                saveCurrentSettings(settingsNow);
                applyBackground(settingsNow);
                const uploadBtn2 = dialog.querySelector('#cai-wallpaper-upload-btn');
                if (uploadBtn2) {
                    uploadBtn2.textContent = 'Click to upload an image or video';
                    uploadBtn2.classList.remove('has-image');
                }
                const videoOpts = dialog.querySelector('#cai-bg-video-options');
                if (videoOpts) videoOpts.style.display = 'none';
                this.remove();
            });
        }

        const linkInput = dialog.querySelector('#cai-bg-link-input');
        if (linkInput) {
            let linkTimeout;
            linkInput.addEventListener('input', function() {
                clearTimeout(linkTimeout);
                linkTimeout = setTimeout(() => {
                    const url = this.value.trim();
                    const settingsNow = getWallpaperSettings();
                    if (url) {
                        settingsNow.bgType = 'link';
                        settingsNow.wallpaper = url;
                    } else {
                        settingsNow.wallpaper = null;
                    }
                    saveCurrentSettings(settingsNow);
                    applyBackground(settingsNow);
                    const videoOpts = dialog.querySelector('#cai-bg-video-options');
                    if (videoOpts) videoOpts.style.display = (url && isVideoUrl(url)) ? 'block' : 'none';
                }, 500);
            });
        }

        const wOpacitySlider = dialog.querySelector('.cai-wallpaper-opacity-slider');
        const wOpacityLabel = dialog.querySelector('.cai-wallpaper-opacity-label');
        if (wOpacitySlider && wOpacityLabel) {
            wOpacitySlider.addEventListener('input', function() {
                const val = parseInt(this.value);
                wOpacityLabel.textContent = val + '%';
                const settingsNow = getWallpaperSettings();
                settingsNow.wallpaperOpacity = val;
                saveCurrentSettings(settingsNow);
                const opacity = val / 100;
                document.querySelector('#chat-body')?.style.setProperty('--cai-wallpaper-opacity', opacity);
                const chatVid = document.querySelector('#cai-bg-video');
                if (chatVid) chatVid.style.opacity = opacity;
                const previewVid = document.querySelector('.cai-preview-video');
                if (previewVid) previewVid.style.opacity = opacity;
                const previewBg = document.querySelector('.cai-preview-bg');
                if (previewBg) previewBg.style.setProperty('--cai-preview-opacity', opacity);
            });
        }

        const speedSlider = dialog.querySelector('.cai-video-speed-slider');
        const speedLabel = dialog.querySelector('.cai-video-speed-label');
        if (speedSlider && speedLabel) {
            speedSlider.addEventListener('input', function() {
                const val = parseFloat(this.value);
                speedLabel.textContent = val.toFixed(2).replace(/\.?0+$/, '') + 'x';
                const settingsNow = getWallpaperSettings();
                settingsNow.videoSpeed = val;
                saveCurrentSettings(settingsNow);
                const chatVid = document.querySelector('#cai-bg-video');
                if (chatVid) chatVid.playbackRate = val;
                const previewVid = document.querySelector('.cai-preview-video');
                if (previewVid) previewVid.playbackRate = val;
            });
        }

        const soundToggle = dialog.querySelector('#cai-video-sound-toggle');
        if (soundToggle) {
            soundToggle.addEventListener('click', function() {
                const isChecked = this.getAttribute('aria-checked') === 'true';
                const newState = !isChecked;
                this.setAttribute('aria-checked', newState);
                this.classList.toggle('bg-blue', newState);
                this.classList.toggle('bg-muted', !newState);
                const thumb = this.querySelector('.cai-toggle-thumb');
                if (thumb) thumb.style.transform = newState ? 'translateX(16px)' : 'translateX(0)';
                const settingsNow = getWallpaperSettings();
                settingsNow.videoSound = newState;
                saveCurrentSettings(settingsNow);
                const chatVid = document.querySelector('#cai-bg-video');
                if (chatVid) chatVid.muted = !newState;
                const previewVid = document.querySelector('.cai-preview-video');
                if (previewVid) previewVid.muted = !newState;
            });
        }
    }

    function openCustomModal() {
        document.querySelector('.cai-custom-modal')?.remove();
        document.querySelector('.cai-custom-overlay')?.remove();

        const data = getStorageData();
        const globalData = data.global;
        const chatId = getChatIdFromUrl();
        const settings = chatId && data.chats[chatId] ? data.chats[chatId] : data.global;
        const currentTheme = settings.theme || 'Default';
        const currentOpacity = settings.opacity !== undefined ? settings.opacity : 100;
        const hasWallpaper = !!(chatId && data.chats[chatId] && data.chats[chatId].wallpaper);
        const hasAudioWallpaper = !!(settings.audioWallpaperUrl) && !settings.audioWallpaperUrl.startsWith('blob:');
        const isAudioPlaying = hasAudioWallpaper && document.getElementById('cai-bg-audio') && !document.getElementById('cai-bg-audio').paused;
        const fontName = getFontSetting();
        const isPerChatFont = chatId && data.chats[chatId] && data.chats[chatId].perChatFont === true;
        const immersionMode = globalData.immersionMode || false;

        const modal = document.createElement('div');
        modal.className = 'cai-custom-modal fixed p-6 shadow-lg data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] border-none left-[50%] top-[50%] translate-y-[-50%] translate-x-[-50%] sm:rounded-spacing-l pt-[14px] max-w-lg overflow-y-auto flex flex-col gap-4 bg-surface-elevation-1 text-foreground px-2';
        modal.style.width = '410px';
        modal.style.zIndex = '52';
        modal.setAttribute('role', 'dialog');
        modal.setAttribute('data-state', 'open');
        modal.style.pointerEvents = 'auto';
        modal.style.scrollbarGutter = 'stable';
        modal.tabIndex = -1;

        modal.innerHTML = `
            <div class="space-y-1.5 text-center sm:text-left text-display min-h-14 h-fit w-full flex flex-row justify-between items-center px-5 pt-1 -mb-4">
                <div class="flex flex-row gap-4 text-md items-center">
                    <button type="button" class="close-modal-btn">
                        <svg viewBox="0 0 24 24" fill="none" height="1.25em" color="var(--icon-secondary)">
                            <path d="m5 5 14 14m0-14L5 19" stroke="currentColor" stroke-linecap="round" stroke-width="2"></path>
                        </svg>
                        <span class="sr-only">Close</span>
                    </button>
                    <h2 class="text-lg leading-none">Customize</h2>
                </div>
                <button type="button" id="cai-reset-btn" style="color:#ef4444;">
                    <svg viewBox="0 0 24 24" fill="none" height="1.25em" stroke="currentColor" stroke-linecap="round" stroke-width="2">
                        <path d="M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2M19 6v14a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V6"/>
                    </svg>
                </button>
            </div>

            <div class="w-full max-w-sm mx-auto">
                <div class="overflow-hidden flex flex-col justify-center items-center h-48">
                    <div class="w-[180px] h-[120px] flex justify-center">
                        <div class="w-full h-full rounded-[13.5px] relative border border-foreground-300 bg-surface overflow-hidden cai-preview-bg">
                            <div class="relative flex flex-col justify-center items-center w-full h-full gap-3">
                                <div class="w-[65%] flex flex-row">
                                    <div class="w-[5.6px] h-[5.6px] rounded-full" style="background: rgb(217, 217, 217);"></div>
                                    <div id="preview-char" class="w-[67.5px] h-[21.5px] mx-1 self-start" style="border-radius: 4.5px; background: var(--surface-elevation-2);"></div>
                                </div>
                                <div class="w-[65%] flex flex-row-reverse">
                                    <div class="w-[5.6px] h-[5.6px] rounded-full" style="background: rgb(217, 217, 217);"></div>
                                    <div id="preview-user" class="w-[67.5px] h-[21.5px] mx-1 self-end" style="border-radius: 4.5px; background: var(--surface-elevation-3);"></div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <p class="text-sm text-muted-foreground p-3">Preview</p>
                </div>
            </div>

            <div class="w-full flex flex-col items-center px-3">
                <div class="w-full px-4 py-2 rounded-spacing-s flex justify-between items-center border-1 border-foreground-200">
                    <div>
                        <p class="m-0 font-md-semibold text-foreground">Wallpaper</p>
                        <p class="m-0 text-muted-foreground text-md">Customize your chat with a uploaded wallpaper of your own.</p>
                    </div>
                    <div class="flex flex-col items-center gap-2 ml-2">
                        <button class="z-0 group relative inline-flex items-center justify-center box-border appearance-none select-none whitespace-nowrap font-normal subpixel-antialiased overflow-hidden tap-highlight-transparent outline-none data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 hover:bg-primary/90 px-unit-4 h-unit-10 text-md gap-unit-2 rounded-md [&>&svg]:max-w-[theme(spacing.unit-8)] data-[pressed=true]:scale-[0.97] transition-transform-colors-opacity motion-reduce:transition-none bg-primary text-primary-foreground data-[hover=true]:opacity-hover border-1 border-foreground-200 mb-2 min-w-[100px]" type="button" id="cai-wallpaper-add-btn">${hasWallpaper ? 'Edit' : 'Add'}</button>
                    </div>
                </div>
            </div>

            <div class="w-full flex flex-col items-center px-3">
                <div class="w-full px-4 py-2 rounded-spacing-s flex justify-between items-center border-1 border-foreground-200">
                    <div>
                        <p class="m-0 font-md-semibold text-foreground">Audio</p>
                        <p class="cai-audio-status m-0 text-muted-foreground text-md">${hasAudioWallpaper ? (isAudioPlaying ? 'Now playing' : 'Paused') : 'No audio set'}</p>
                    </div>
                    <div class="flex flex-col items-center gap-2 ml-2">
                        <button class="z-0 group relative inline-flex items-center justify-center box-border appearance-none select-none whitespace-nowrap font-normal subpixel-antialiased overflow-hidden tap-highlight-transparent outline-none data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 hover:bg-primary/90 px-unit-4 h-unit-10 text-md gap-unit-2 rounded-md data-[pressed=true]:scale-[0.97] transition-transform-colors-opacity motion-reduce:transition-none bg-primary text-primary-foreground data-[hover=true]:opacity-hover border-1 border-foreground-200 mb-2 min-w-[100px]" type="button" id="cai-audio-add-btn">${hasAudioWallpaper ? 'Edit' : 'Add'}</button>
                    </div>
                </div>
            </div>

            <div class="w-full flex flex-col items-center px-3">
                <div class="w-full px-4 py-2 rounded-spacing-s border-1 border-foreground-200">
                    <div class="w-full flex items-center justify-between">
                        <div class="flex flex-col">
                            <label class="text-sm">Particles</label>
                            <p class="text-xs text-muted-foreground">Ambient particle effects behind messages.</p>
                        </div>
                        <div class="flex items-start shrink-0">
                            <button type="button" role="switch" aria-checked="${settings.particlesEnabled ? 'true' : 'false'}" id="toggle-particles" class="peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 ${settings.particlesEnabled ? 'bg-blue' : 'bg-muted'}">
                                <span class="pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform ${settings.particlesEnabled ? 'translate-x-5' : 'translate-x-0'}"></span>
                            </button>
                        </div>
                    </div>
                    <div id="cai-particles-controls" class="w-full flex-col gap-3 pt-2" style="display: ${settings.particlesEnabled ? 'flex' : 'none'};">
                        <div class="flex flex-col gap-1">
                            <label class="text-xs text-muted-foreground">Effect</label>
                            <select id="cai-particle-effect" class="cai-particle-select">
                                <option value="rain" ${settings.particlesEffect === 'rain' ? 'selected' : ''}>Rain</option>
                                <option value="snow" ${settings.particlesEffect === 'snow' ? 'selected' : ''}>Snow</option>
                                <option value="leaves" ${settings.particlesEffect === 'leaves' ? 'selected' : ''}>Leaves</option>
                                <option value="cherry_blossom" ${settings.particlesEffect === 'cherry_blossom' ? 'selected' : ''}>Cherry Blossom</option>
                                <option value="bubbles" ${settings.particlesEffect === 'bubbles' ? 'selected' : ''}>Bubbles</option>
                                <option value="stars" ${settings.particlesEffect === 'stars' ? 'selected' : ''}>Stars</option>
                            </select>
                        </div>
                        <div class="flex justify-between items-center">
                            <label for="particle-density-slider" class="text-sm">Density</label>
                            <span class="text-sm" id="particle-density-value" style="min-width: 44px; text-align: right; flex-shrink: 0;">${settings.particlesDensity !== undefined ? settings.particlesDensity : 50}%</span>
                        </div>
                        <input type="range" min="0" max="100" value="${settings.particlesDensity !== undefined ? settings.particlesDensity : 50}" id="particle-density-slider" class="cai-slider" />
                        <div class="flex justify-between items-center">
                            <label for="particle-speed-slider" class="text-sm">Speed</label>
                            <span class="text-sm" id="particle-speed-value" style="min-width: 44px; text-align: right; flex-shrink: 0;">${settings.particlesSpeed !== undefined ? settings.particlesSpeed : 50}%</span>
                        </div>
                        <input type="range" min="0" max="100" value="${settings.particlesSpeed !== undefined ? settings.particlesSpeed : 50}" id="particle-speed-slider" class="cai-slider" />
                        <div class="flex justify-between items-center">
                            <label for="particle-opacity-slider" class="text-sm">Opacity</label>
                            <span class="text-sm" id="particle-opacity-value" style="min-width: 44px; text-align: right; flex-shrink: 0;">${settings.particlesOpacity !== undefined ? settings.particlesOpacity : 100}%</span>
                        </div>
                        <input type="range" min="0" max="100" value="${settings.particlesOpacity !== undefined ? settings.particlesOpacity : 100}" id="particle-opacity-slider" class="cai-slider" />
                    </div>
                </div>
            </div>

            <div class="w-full px-3 rounded-sm shadow-sm py-2">
                <div class="w-full flex flex-col items-center">
                    <div class="w-full flex justify-between items-center pb-2 mx-auto">
                        <h4 class="flex items-center">Chat color</h4>
                        <span class="text-sm flex items-center" id="current-theme-label">${currentTheme}</span>
                    </div>
                    <div class="w-full px-2 py-2 rounded-spacing-s border-1 flex justify-center border-input">
                        <div class="flex flex-row flex-wrap w-full gap-5" id="color-theme-picker">
                            ${themes.map(theme => {
                                const isSelected = theme.name === currentTheme;
                                return `
                                    <div class="relative w-10 h-10 rounded-full cursor-pointer flex items-center justify-center theme-circle ${isSelected ? 'ring-1 ring-white ring-offset-1' : ''}" data-theme="${theme.name}" style="background: transparent !important; border: none !important;">
                                        <div class="w-9 h-9 rounded-full overflow-hidden" style="background-image: linear-gradient(135deg, ${theme.userColor} 50%, ${theme.charColor} 50%);"></div>
                                    </div>
                                `;
                            }).join('')}
                        </div>
                    </div>
                </div>
            </div>

            <div class="w-full px-3 rounded-sm shadow-sm py-2">
                <div class="w-full flex flex-col items-center">
                    <div class="w-full flex justify-between items-center pb-2 mx-auto cursor-pointer select-none" id="cai-advanced-toggle">
                        <h4 class="flex items-center">Custom</h4>
                        <svg class="cai-chevron transition-transform duration-200" viewBox="0 0 24 24" width="16" height="16" style="transform: rotate(0deg);">
                            <path d="m9 4 8 8-8 8" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" fill="none"/>
                        </svg>
                    </div>
                    <div id="cai-advanced-content" class="w-full flex flex-col gap-3 overflow-hidden transition-all duration-300 ease-in-out" style="max-height: 0;">
                        <p class="text-xs font-semibold text-muted-foreground uppercase tracking-wider mt-1">Bubbles</p>
                        <div class="w-full px-2 py-2 rounded-spacing-s border-1 flex justify-center border-input">
                            <div class="w-full flex flex-col">
                                <div class="flex justify-between items-center" style="gap: 16px;">
                                    <label for="opacity-slider" class="text-sm">Opacity</label>
                                    <span class="text-sm" id="opacity-value" style="min-width: 44px; text-align: right; flex-shrink: 0;">${currentOpacity}%</span>
                                </div>
                                <input type="range" min="10" max="100" value="${currentOpacity}" id="opacity-slider" class="cai-slider" style="margin-top: 6px;" />
                            </div>
                        </div>
                        <div class="w-full px-2 py-2 rounded-spacing-s border-1 flex justify-center border-input">
                            <div class="w-full flex flex-col">
                                <div class="flex justify-between items-center" style="gap: 16px;">
                                    <label for="bubble-radius-slider" class="text-sm">Roundness</label>
                                    <span class="text-sm" id="bubble-radius-value" style="min-width: 44px; text-align: right; flex-shrink: 0;">${settings.bubbleRadius !== undefined ? settings.bubbleRadius : 16}px</span>
                                </div>
                                <input type="range" min="0" max="24" value="${settings.bubbleRadius !== undefined ? settings.bubbleRadius : 16}" id="bubble-radius-slider" class="cai-slider" style="margin-top: 6px;" />
                            </div>
                        </div>
                        <div class="w-full px-2 py-2 rounded-spacing-s border-1 flex justify-center border-input">
                            <div class="w-full flex items-center justify-between">
                                <div class="flex flex-col">
                                    <label for="toggle-outlines" class="text-sm">Outlines</label>
                                    <p class="text-xs text-muted-foreground">Swap-color outlines on chat bubbles.</p>
                                </div>
                                <div class="flex items-start shrink-0">
                                    <button type="button" role="switch" aria-checked="${settings.bubbleOutlines || false}" id="toggle-outlines" class="peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 ${settings.bubbleOutlines ? 'bg-blue' : 'bg-muted'}">
                                        <span class="pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform ${settings.bubbleOutlines ? 'translate-x-5' : 'translate-x-0'}"></span>
                                    </button>
                                </div>
                            </div>
                        </div>
                        <div class="w-full px-2 py-2 rounded-spacing-s border-1 flex flex-col border-input gap-2">
                            <div class="w-full flex items-center justify-between">
                                <div class="flex flex-col">
                                    <label class="text-sm">Bubble styles</label>
                                    <p class="text-xs text-muted-foreground">Prebuilt bubble looks. Overrides chat color.</p>
                                </div>
                                <div class="flex items-start shrink-0">
                                    <button type="button" role="switch" aria-checked="${settings.bubbleStyle ? 'true' : 'false'}" id="toggle-bubble-styles" class="peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 ${settings.bubbleStyle ? 'bg-blue' : 'bg-muted'}">
                                        <span class="pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform ${settings.bubbleStyle ? 'translate-x-5' : 'translate-x-0'}"></span>
                                    </button>
                                </div>
                            </div>
                            <div id="cai-bubble-styles-content" class="w-full flex-col gap-1 pt-1" style="display: ${settings.bubbleStyle ? 'flex' : 'none'};">
                                ${buildBubblePackListHTML(settings.bubbleStyle || null)}
                            </div>
                        </div>
                        <p class="text-xs font-semibold text-muted-foreground uppercase tracking-wider mt-1">Text</p>
                        <div class="w-full px-2 py-2 rounded-spacing-s border-1 flex justify-center border-input">
                            <div class="w-full flex flex-col gap-2">
                                <div class="flex justify-between items-center">
                                    <label for="cai-font-name" class="text-sm">Chat font</label>
                                    <span class="text-xs text-muted-foreground" id="current-font-label">${fontName || 'Default'}</span>
                                </div>
                                <input type="text" id="cai-font-name" class="cai-font-input" placeholder="e.g. Roboto, Open Sans, Lora..." value="${fontName}" />
                                <p class="cai-font-hint">Type any Google Font name. The link loads automatically.</p>
                                <div class="flex items-center justify-between mt-1">
                                    <span class="text-xs text-muted-foreground">Override for this chat only</span>
                                    <button type="button" role="switch" aria-checked="${isPerChatFont}" id="cai-font-perchat-toggle" class="peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 ${isPerChatFont ? 'bg-blue' : 'bg-muted'}">
                                        <span class="pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform ${isPerChatFont ? 'translate-x-5' : 'translate-x-0'}"></span>
                                    </button>
                                </div>
                            </div>
                        </div>
                        <div class="w-full px-2 py-2 rounded-spacing-s border-1 flex justify-center border-input">
                            <div class="w-full flex flex-col">
                                <div class="flex justify-between items-center" style="gap: 16px;">
                                    <label for="text-size-slider" class="text-sm">Text size</label>
                                    <span class="text-sm" id="text-size-value" style="min-width: 44px; text-align: right; flex-shrink: 0;">${settings.textSize !== undefined ? settings.textSize : 100}%</span>
                                </div>
                                <input type="range" min="75" max="150" value="${settings.textSize !== undefined ? settings.textSize : 100}" id="text-size-slider" class="cai-slider" style="margin-top: 6px;" />
                            </div>
                        </div>
                        <p class="text-xs font-semibold text-muted-foreground uppercase tracking-wider mt-1">Layout</p>
                        <div class="w-full px-2 py-2 rounded-spacing-s border-1 flex justify-center border-input">
                            <div class="w-full flex flex-col">
                                <div class="flex justify-between items-center" style="gap: 16px;">
                                    <label for="input-width-slider" class="text-sm">Input box width</label>
                                    <span class="text-sm" id="input-width-value" style="min-width: 44px; text-align: right; flex-shrink: 0;">${settings.inputWidth !== undefined ? settings.inputWidth : 150}%</span>
                                </div>
                                <input type="range" min="100" max="200" value="${settings.inputWidth !== undefined ? settings.inputWidth : 150}" id="input-width-slider" class="cai-slider" style="margin-top: 6px;" />
                            </div>
                        </div>
                        <div class="w-full px-2 py-2 rounded-spacing-s border-1 flex justify-center border-input">
                            <div class="w-full flex items-center justify-between">
                                <div class="flex flex-col">
                                    <label for="toggle-immersion" class="text-sm">Full immersion mode</label>
                                    <p class="text-xs text-muted-foreground">Hide both sidebars for full-screen chat. Press Esc to exit.</p>
                                </div>
                                <div class="flex items-start shrink-0">
                                    <button type="button" role="switch" aria-checked="${immersionMode}" id="toggle-immersion" class="peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 ${immersionMode ? 'bg-blue' : 'bg-muted'}">
                                        <span class="pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform ${immersionMode ? 'translate-x-5' : 'translate-x-0'}"></span>
                                    </button>
                                </div>
                            </div>
                        </div>
                        <p class="text-xs font-semibold text-muted-foreground uppercase tracking-wider mt-1">Power User</p>
                        <div class="w-full px-2 py-2 rounded-spacing-s border-1 flex justify-center border-input">
                            <div class="w-full flex flex-col gap-2">
                                <label for="custom-css-input" class="text-sm">Custom CSS</label>
                                <textarea id="custom-css-input" class="cai-custom-css-input" rows="4" placeholder="/* your CSS here */">${settings.customCss || ''}</textarea>
                                <p class="text-xs text-muted-foreground">Inject your own styles. Be careful.</p>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

        `;

        document.body.appendChild(modal);

        function closeModal() {
            modal.classList.add('closing');
            overlay.classList.add('closing');
            const onTransitionEnd = () => {
                modal.removeEventListener('transitionend', onTransitionEnd);
                modal.remove();
                overlay.remove();
            };
            modal.addEventListener('transitionend', onTransitionEnd);
            setTimeout(() => {
                if (document.body.contains(modal)) {
                    modal.removeEventListener('transitionend', onTransitionEnd);
                    modal.remove();
                    overlay.remove();
                }
            }, 300);
        }

        const overlay = document.createElement('div');
        overlay.className = 'cai-custom-overlay fixed inset-0 bg-black/50';
        overlay.style.zIndex = '51';
        overlay.style.pointerEvents = 'auto';
        document.body.appendChild(overlay);

        modal.querySelector('.close-modal-btn').addEventListener('click', closeModal);
        overlay.addEventListener('click', closeModal);
        document.addEventListener('keydown', function escHandler(e) {
            if (e.key === 'Escape') {
                closeModal();
                document.removeEventListener('keydown', escHandler);
            }
        });

        modal.querySelectorAll('.theme-circle').forEach(circle => {
            circle.addEventListener('click', function() {
                const themeName = this.dataset.theme;
                applyTheme(themeName);
                const s = getCurrentSettings();
                s.theme = themeName;
                saveCurrentSettings(s);
                modal.querySelectorAll('.theme-circle').forEach(c => {
                    c.classList.remove('ring-1', 'ring-white', 'ring-offset-1');
                });
                this.classList.add('ring-1', 'ring-white', 'ring-offset-1');
                const label = modal.querySelector('#current-theme-label');
                if (label) label.textContent = themeName;
                if (s.bubbleStyle) updatePackPreview(s.bubbleStyle);
            });
        });

        const advToggle = modal.querySelector('#cai-advanced-toggle');
        const advContent = modal.querySelector('#cai-advanced-content');
        const advChevron = modal.querySelector('.cai-chevron');
        if (advToggle && advContent) {
            advContent.addEventListener('transitionend', function(e) {
                if (e.propertyName !== 'max-height') return;
                // Once open, drop the fixed cap so nested collapsibles can grow freely.
                if (advContent.style.maxHeight !== '0px' && advContent.style.maxHeight !== '0') {
                    advContent.style.maxHeight = 'none';
                }
            });
            advToggle.addEventListener('click', function() {
                const isOpen = advContent.style.maxHeight !== '0px' && advContent.style.maxHeight !== '0';
                if (isOpen) {
                    // Collapse: pin to current height first so the transition has a start value.
                    advContent.style.maxHeight = advContent.scrollHeight + 'px';
                    void advContent.offsetHeight;
                    advContent.style.maxHeight = '0';
                } else {
                    advContent.style.maxHeight = advContent.scrollHeight + 'px';
                }
                if (advChevron) {
                    advChevron.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(90deg)';
                }
            });
        }

        const slider = modal.querySelector('#opacity-slider');
        const opacityLabel = modal.querySelector('#opacity-value');
        if (slider) {
            slider.addEventListener('input', function() {
                const val = parseInt(this.value);
                opacityLabel.textContent = val + '%';
                applyOpacity(val);
                const s = getCurrentSettings();
                s.opacity = val;
                saveCurrentSettings(s);
            });
        }

        const fontInput = modal.querySelector('#cai-font-name');
        const currentFontLabel = modal.querySelector('#current-font-label');
        const fontPerchatToggle = modal.querySelector('#cai-font-perchat-toggle');

        function saveFontValue(val) {
            const d = getStorageData();
            const cid = getChatIdFromUrl();
            const isPerChat = fontPerchatToggle && fontPerchatToggle.getAttribute('aria-checked') === 'true';
            if (cid && isPerChat) {
                if (!d.chats[cid]) d.chats[cid] = {};
                d.chats[cid].fontName = val;
                d.chats[cid].perChatFont = true;
            } else {
                d.global.fontName = val;
                if (cid && d.chats[cid]) {
                    delete d.chats[cid].fontName;
                    delete d.chats[cid].perChatFont;
                    if (!Object.keys(d.chats[cid]).length) delete d.chats[cid];
                }
            }
            if (!saveStorageData(d)) {
                console.error('Failed to save font setting');
            }
        }

        if (fontInput) {
            fontInput.addEventListener('input', function() {
                const val = this.value.trim();
                if (currentFontLabel) currentFontLabel.textContent = val || 'Default';
                saveFontValue(val);
                applyFont({fontName: val});
            });
        }

        if (fontPerchatToggle) {
            fontPerchatToggle.addEventListener('click', function() {
                const isChecked = this.getAttribute('aria-checked') === 'true';
                const newState = !isChecked;
                this.setAttribute('aria-checked', newState);
                this.classList.toggle('bg-blue', newState);
                this.classList.toggle('bg-muted', !newState);
                const knob = this.querySelector('span:last-child');
                if (knob) {
                    knob.classList.toggle('translate-x-5', newState);
                    knob.classList.toggle('translate-x-0', !newState);
                }
                if (fontInput) {
                    saveFontValue(fontInput.value.trim());
                }
            });
        }

        const immersionToggle = modal.querySelector('#toggle-immersion');
        if (immersionToggle) {
            immersionToggle.addEventListener('click', function() {
                const isChecked = this.getAttribute('aria-checked') === 'true';
                const newState = !isChecked;
                this.setAttribute('aria-checked', newState);
                this.classList.toggle('bg-blue', newState);
                this.classList.toggle('bg-muted', !newState);
                const thumb = this.querySelector('span');
                if (thumb) {
                    thumb.classList.toggle('translate-x-5', newState);
                    thumb.classList.toggle('translate-x-0', !newState);
                }
                saveGlobalSetting('immersionMode', newState);
                applyImmersion(getStorageData().global);
                if (newState) {
                    setTimeout(() => {
                        closeModal();
                    }, 300);
                }
            });
        }

        const inputWidthSlider = modal.querySelector('#input-width-slider');
        const inputWidthLabel = modal.querySelector('#input-width-value');
        if (inputWidthSlider) {
            inputWidthSlider.addEventListener('input', function() {
                const val = parseInt(this.value);
                inputWidthLabel.textContent = val + '%';
                applyInputWidth(val);
                const s = getCurrentSettings();
                s.inputWidth = val;
                saveCurrentSettings(s);
            });
        }

        const outlinesToggle = modal.querySelector('#toggle-outlines');
        if (outlinesToggle) {
            outlinesToggle.addEventListener('click', function() {
                const isChecked = this.getAttribute('aria-checked') === 'true';
                const newState = !isChecked;
                this.setAttribute('aria-checked', newState);
                this.classList.toggle('bg-blue', newState);
                this.classList.toggle('bg-muted', !newState);
                const thumb = this.querySelector('span');
                if (thumb) {
                    thumb.classList.toggle('translate-x-5', newState);
                    thumb.classList.toggle('translate-x-0', !newState);
                }
                const s = getCurrentSettings();
                s.bubbleOutlines = newState;
                saveCurrentSettings(s);
                applyOutlines(newState, s);
                updatePreviewOutlines(newState, s);
            });
        }

        const bubbleStylesToggle = modal.querySelector('#toggle-bubble-styles');
        const bubbleStylesContent = modal.querySelector('#cai-bubble-styles-content');

        // Keep the Advanced panel tall enough to show newly revealed content (no clipping).
        function reflowAdvanced() {
            if (!advContent) return;
            const isOpen = advContent.style.maxHeight !== '0px' && advContent.style.maxHeight !== '0';
            if (isOpen) advContent.style.maxHeight = 'none';
        }

        function setBubbleStylesOpen(open) {
            if (!bubbleStylesContent) return;
            bubbleStylesContent.style.display = open ? 'flex' : 'none';
            reflowAdvanced();
        }
        if (settings.bubbleStyle) setBubbleStylesOpen(true);

        function setBubbleToggleUI(on) {
            if (!bubbleStylesToggle) return;
            bubbleStylesToggle.setAttribute('aria-checked', on);
            bubbleStylesToggle.classList.toggle('bg-blue', on);
            bubbleStylesToggle.classList.toggle('bg-muted', !on);
            const thumb = bubbleStylesToggle.querySelector('span');
            if (thumb) {
                thumb.classList.toggle('translate-x-5', on);
                thumb.classList.toggle('translate-x-0', !on);
            }
        }

        function markSwatch(id) {
            if (!bubbleStylesContent) return;
            bubbleStylesContent.querySelectorAll('.cai-pack-swatch').forEach(el => {
                const elId = el.dataset.id ? parseInt(el.dataset.id) : null;
                const on = elId === (id || null);
                el.classList.toggle('ring-1', on);
                el.classList.toggle('ring-white', on);
                el.classList.toggle('ring-offset-1', on);
            });
        }

        function selectBubblePack(id) {
            applyBubbleStyle(id);
            const s = getCurrentSettings();
            s.bubbleStyle = id || null;
            saveCurrentSettings(s);
            markSwatch(id);
            setBubbleToggleUI(!!id);
        }

        if (bubbleStylesToggle) {
            bubbleStylesToggle.addEventListener('click', function(e) {
                e.stopPropagation();
                const isChecked = this.getAttribute('aria-checked') === 'true';
                const newState = !isChecked;
                setBubbleToggleUI(newState);
                if (newState) {
                    setBubbleStylesOpen(true);
                    const s = getCurrentSettings();
                    selectBubblePack(s.bubbleStyle || CURATED_PACKS[0].id);
                } else {
                    selectBubblePack(null);
                    setBubbleStylesOpen(false);
                }
            });
        }
        if (bubbleStylesContent) {
            bubbleStylesContent.querySelectorAll('.cai-pack-swatch').forEach(item => {
                item.addEventListener('click', function() {
                    const id = this.dataset.id ? parseInt(this.dataset.id) : null;
                    selectBubblePack(id);
                });
            });
        }

        const textSizeSlider = modal.querySelector('#text-size-slider');
        const textSizeLabel = modal.querySelector('#text-size-value');
        if (textSizeSlider) {
            textSizeSlider.addEventListener('input', function() {
                const val = parseInt(this.value);
                textSizeLabel.textContent = val + '%';
                applyTextSize(val);
                const s = getCurrentSettings();
                s.textSize = val;
                saveCurrentSettings(s);
            });
        }

        const bubbleRadiusSlider = modal.querySelector('#bubble-radius-slider');
        const bubbleRadiusLabel = modal.querySelector('#bubble-radius-value');
        if (bubbleRadiusSlider) {
            bubbleRadiusSlider.addEventListener('input', function() {
                const val = parseInt(this.value);
                bubbleRadiusLabel.textContent = val + 'px';
                applyBubbleRadius(val);
                const s = getCurrentSettings();
                s.bubbleRadius = val;
                saveCurrentSettings(s);
            });
        }

        const customCssInput = modal.querySelector('#custom-css-input');
        if (customCssInput) {
            let cssTimeout;
            customCssInput.addEventListener('input', function() {
                clearTimeout(cssTimeout);
                cssTimeout = setTimeout(() => {
                    const val = this.value;
                    applyCustomCss(val);
                    const s = getCurrentSettings();
                    s.customCss = val;
                    saveCurrentSettings(s);
                }, 500);
            });
        }

        const particlesToggle = modal.querySelector('#toggle-particles');
        const particlesControls = modal.querySelector('#cai-particles-controls');
        if (particlesToggle) {
            particlesToggle.addEventListener('click', function() {
                const on = this.getAttribute('aria-checked') !== 'true';
                this.setAttribute('aria-checked', on ? 'true' : 'false');
                this.classList.toggle('bg-blue', on);
                this.classList.toggle('bg-muted', !on);
                const thumb = this.querySelector('span');
                if (thumb) { thumb.classList.toggle('translate-x-5', on); thumb.classList.toggle('translate-x-0', !on); }
                if (particlesControls) particlesControls.style.display = on ? 'flex' : 'none';
                const s = getCurrentSettings();
                s.particlesEnabled = on;
                saveCurrentSettings(s);
                applyParticles(s);
            });
        }
        const particleEffect = modal.querySelector('#cai-particle-effect');
        if (particleEffect) {
            particleEffect.addEventListener('change', function() {
                const s = getCurrentSettings();
                s.particlesEffect = this.value;
                saveCurrentSettings(s);
                applyParticles(s);
            });
        }
        function makeParticleSlider(slider, label, key) {
            if (!slider) return;
            let _timer;
            slider.addEventListener('input', function() {
                const val = parseInt(this.value);
                if (label) label.textContent = val + '%';
                if (_particleEngine) _particleEngine.setConfig({ [key]: val });
                clearTimeout(_timer);
                _timer = setTimeout(() => {
                    const s = getCurrentSettings();
                    s['particles' + key.charAt(0).toUpperCase() + key.slice(1)] = val;
                    saveCurrentSettings(s);
                }, 200);
            });
        }
        makeParticleSlider(modal.querySelector('#particle-density-slider'), modal.querySelector('#particle-density-value'), 'density');
        makeParticleSlider(modal.querySelector('#particle-speed-slider'), modal.querySelector('#particle-speed-value'), 'speed');
        makeParticleSlider(modal.querySelector('#particle-opacity-slider'), modal.querySelector('#particle-opacity-value'), 'opacity');

        const addBtn = modal.querySelector('#cai-wallpaper-add-btn');
        if (addBtn) {
            addBtn.addEventListener('click', function(e) {
                e.stopPropagation();
                openWallpaperDialog();
            });
        }

        const audioBtn = modal.querySelector('#cai-audio-add-btn');
        if (audioBtn) {
            audioBtn.addEventListener('click', function(e) {
                e.stopPropagation();
                openAudioWallpaperDialog();
            });
        }

        const resetBtn = modal.querySelector('#cai-reset-btn');
        if (resetBtn) {
            resetBtn.addEventListener('click', function() {
                showResetConfirmModal(closeModal);
            });
        }

        updatePreview(currentTheme);
        updatePreviewBackground(settings);
        if (settings.bubbleStyle) updatePackPreview(settings.bubbleStyle);
    }

    function showResetConfirmModal(onReset) {
        document.querySelector('.cai-reset-confirm-modal')?.remove();
        document.querySelector('.cai-reset-confirm-overlay')?.remove();

        const overlay = document.createElement('div');
        overlay.className = 'cai-reset-confirm-overlay fixed inset-0 bg-black/50';
        overlay.style.zIndex = '53';
        overlay.style.transition = 'opacity 0.2s ease';
        overlay.style.willChange = 'opacity';
        document.body.appendChild(overlay);

        const modal = document.createElement('div');
        modal.className = 'cai-reset-confirm-modal fixed p-6 shadow-lg border-none left-[50%] top-[50%] translate-y-[-50%] translate-x-[-50%] sm:rounded-spacing-l max-w-sm w-11/12 flex flex-col gap-4 bg-surface-elevation-1 text-foreground';
        modal.style.zIndex = '54';
        modal.style.transition = 'opacity 0.2s ease, transform 0.2s ease';
        modal.style.willChange = 'opacity, transform';
        modal.innerHTML = `
            <h2 class="text-base sm:text-lg font-semibold">Reset settings</h2>
            <p class="text-sm text-muted-foreground">Are you sure you want to reset all settings to default? This cannot be undone.</p>
            <div class="flex justify-end gap-2 mt-2">
                <button class="cancel-btn px-4 py-2 text-sm rounded-md border border-input text-muted-foreground hover:text-foreground hover:border-foreground-300 transition-colors">Cancel</button>
                <button class="confirm-btn px-4 py-2 text-sm rounded-md" disabled style="background:#dc2626;color:#fff;border:1px solid #b91c1c;opacity:0.5;cursor:not-allowed;">Confirm (5s)</button>
            </div>
        `;
        document.body.appendChild(modal);

        function animateClose(callback) {
            clearInterval(ci);
            modal.style.opacity = '0';
            modal.style.transform = 'translate(-50%, -50%) scale(0.95)';
            overlay.style.opacity = '0';
            const onEnd = () => {
                modal.removeEventListener('transitionend', onEnd);
                modal.remove();
                overlay.remove();
                if (callback) callback();
            };
            modal.addEventListener('transitionend', onEnd);
            setTimeout(() => {
                if (document.body.contains(modal)) {
                    modal.removeEventListener('transitionend', onEnd);
                    modal.remove();
                    overlay.remove();
                    if (callback) callback();
                }
            }, 300);
        }

        modal.querySelector('.cancel-btn').addEventListener('click', () => animateClose());
        overlay.addEventListener('click', () => animateClose());

        const confirmBtn = modal.querySelector('.confirm-btn');
        let countdown = 5;
        confirmBtn.textContent = 'Confirm (5s)';
        const ci = setInterval(() => {
            countdown--;
            if (countdown > 0) {
                confirmBtn.textContent = `Confirm (${countdown}s)`;
            } else {
                clearInterval(ci);
                confirmBtn.disabled = false;
                confirmBtn.style.opacity = '1';
                confirmBtn.style.cursor = 'pointer';
                confirmBtn.textContent = 'Confirm';
            }
        }, 1000);

        confirmBtn.addEventListener('click', function() {
            if (this.disabled) return;
            localStorage.removeItem(STORAGE_KEY);
            animateClose(() => {
                onReset();
                setTimeout(() => location.reload(), 300);
            });
        });
    }

    // Debounced init to avoid multiple rapid calls
    let initTimer = null;
    function initChat() {
        clearTimeout(initTimer);
        initTimer = setTimeout(() => {
            if (!getChatIdFromUrl()) return;
            removeOriginalCustomize();
            injectCustomButton();
            loadSavedSettings();
        }, 300);
    }

    injectAdaptiveStyles();

    // Watch for #chat-messages being added/replaced (React swaps it)
    let _chatMsgTimer = null;
    const chatMsgObserver = new MutationObserver(() => {
        if (document.querySelector('#chat-messages')) {
            clearTimeout(_chatMsgTimer);
            _chatMsgTimer = setTimeout(() => {
                const settings = getCurrentSettings();
                applyTextSize(settings.textSize !== undefined ? settings.textSize : 100);
                applyBubbleRadius(settings.bubbleRadius !== undefined ? settings.bubbleRadius : 16);
                applyFont({fontName: getFontSetting()});
            }, 300);
        }
    });
    chatMsgObserver.observe(document.body || document.documentElement, { childList: true, subtree: true });

    // Initial load
    setTimeout(initChat, 1000);

    // SPA navigation handling
    if (window.onurlchange === null) {
        window.addEventListener('urlchange', () => initChat());
    } else {
        let lastUrl = window.location.href;
        const observer = new MutationObserver(() => {
            const currentUrl = window.location.href;
            if (currentUrl !== lastUrl) {
                lastUrl = currentUrl;
                initChat();
            }
        });
        observer.observe(document, { subtree: true, childList: true });
    }
})();