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 });
    }
})();