Perplexity Widescreen & Compact & Code Collapser

V1.7.10: 用戶氣泡全寬 | Toolbar 滾動跟隨浮動(sticky) | 繼承v1.7.9所有功能

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Perplexity Widescreen & Compact & Code Collapser
// @namespace    http://tampermonkey.net/
// @version      1.7.10
// @description  V1.7.10: 用戶氣泡全寬 | Toolbar 滾動跟隨浮動(sticky) | 繼承v1.7.9所有功能
// @author       Custom
// @match        https://www.perplexity.ai/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    const MIN_W = 140, MAX_W = 380;
    let savedWidth = 185;
    try { savedWidth = Math.min(Math.max(GM_getValue('pplx_sidebar_width', 185), MIN_W), MAX_W); } catch(e){}

    const css = `
        /* SCROLLBAR */
        *::-webkit-scrollbar{display:none!important;width:0!important;height:0!important}
        *{scrollbar-width:none!important;-ms-overflow-style:none!important}
        .scrollable-container::-webkit-scrollbar{display:block!important;width:4px!important}
        .scrollable-container::-webkit-scrollbar-thumb{background:rgba(128,128,128,.25)!important;border-radius:2px!important}
        .scrollable-container{scrollbar-width:thin!important;-ms-overflow-style:auto!important}

        /* WIDESCREEN */
        html,body{max-width:100%!important;overflow-x:hidden!important}
        .scrollable-container{max-width:100%!important;width:100%!important;padding-left:16px!important;padding-right:12px!important}
        .scrollable-container>div{max-width:100%!important;width:100%!important}
        .scrollable-container div[class*="max-w-"],
        .scrollable-container section[class*="max-w-"],
        .scrollable-container article[class*="max-w-"],
        .scrollable-container main[class*="max-w-"]{max-width:100%!important}
        .scrollable-container .container,.scrollable-container .container.isolate{max-width:100%!important;width:100%!important;padding-left:0!important;padding-right:0!important}
        .scrollable-container .erp-sidecar,.scrollable-container [class*="erp-sidecar"]{max-width:100%!important;width:100%!important}
        .prose,.prose>*{max-width:100%!important;width:100%!important}

        /* HEADER */
        [class*="h-headerHeight"]{height:auto!important;min-height:0!important}
        [class*="containerheader"],[class*="container"][class*="header"]{height:auto!important;min-height:0!important;max-height:none!important}
        [role="tablist"] button[role="tab"]{padding-top:2px!important;padding-bottom:2px!important}
        [role="tablist"]{height:auto!important;gap:12px!important}

        /* PILL V0 */
        div[data-ask-input-container="true"]{max-width:520px!important;margin-left:auto!important;margin-right:auto!important}
        #ask-input{max-height:30vh!important;overflow-y:auto!important}
        div:has(>div[data-ask-input-container="true"]){padding-top:2px!important;padding-bottom:2px!important;margin-top:0!important;margin-bottom:0!important}

        /* ============================================================
           v1.7.9 CHAT SPACING
           ============================================================ */
        .scrollable-container .gap-y-lg{row-gap:4px!important}
        .scrollable-container .gap-y-md{row-gap:2px!important}
        .scrollable-container .gap-y-sm{row-gap:1px!important}
        .scrollable-container .gap-5{gap:4px!important}
        .scrollable-container .gap-4{gap:2px!important}
        .scrollable-container .gap-md{gap:2px!important}
        .scrollable-container .gap-sm{gap:1px!important}
        .scrollable-container .mt-md:not(nav .mt-md):not(.prose .mt-md){margin-top:2px!important}
        .scrollable-container .mt-xs:not(nav .mt-xs):not(.prose .mt-xs){margin-top:0!important}
        .scrollable-container [class*="pt-md"]:not(nav [class*="pt-md"]):not(.prose [class*="pt-md"]){padding-top:2px!important}
        .scrollable-container [class*="pt-lg"]:not(nav [class*="pt-lg"]):not(.prose [class*="pt-lg"]){padding-top:2px!important}

        /* action bar */
        .scrollable-container .flex.items-center.justify-between{padding-top:0!important;margin-top:0!important}
        .scrollable-container button.flex.items-center[class*="gap-sm"][class*="text-quiet"]{margin-top:0!important;margin-bottom:0!important;padding-top:0!important;padding-bottom:0!important}
        .scrollable-container .text-sm.text-quiet[class*="font-sans"]{margin-top:0!important;margin-bottom:0!important;line-height:1.2!important}

        /* PROSE */
        .prose p,.prose li,.prose blockquote{line-height:1.35!important;margin-top:0!important;margin-bottom:2px!important;margin-block-start:0!important;margin-block-end:2px!important;padding-top:0!important;padding-bottom:0!important}
        .prose{margin-top:2px!important}
        .prose h1,.prose h2,.prose h3,.prose h4{margin-top:4px!important;margin-bottom:2px!important;line-height:1.25!important}
        .prose ul,.prose ol{margin-top:0!important;margin-bottom:2px!important;padding-left:1.2em!important}
        .prose li{margin:0!important}
        .prose hr{margin-top:4px!important;margin-bottom:4px!important}

        /* ============================================================
           v1.7.9 TABLE — 比較表單元格內距精簡
           ============================================================ */
        .scrollable-container table{border-collapse:separate!important;border-spacing:0!important}
        .scrollable-container table th{padding:3px 6px!important;min-width:40px!important;vertical-align:bottom!important;white-space:normal!important;word-break:break-word!important}
        .scrollable-container table td{padding:2px 6px!important;min-width:40px!important;vertical-align:top!important;white-space:normal!important;word-break:break-word!important}
        .scrollable-container .overflow-auto[class*="rounded"]{overflow-x:auto!important;max-width:100%!important;display:block!important}

        /* ============================================================
           v1.7.10 USER BUBBLE — 全寬展開
           ============================================================ */
        main [class*="justify-end"]{position:relative!important}
        main [class*="justify-end"]>[class*="invisible"]{position:absolute!important;right:4px!important;top:4px!important;left:auto!important;z-index:20!important}
        main [class*="justify-end"]>div:last-child,
        main [class*="justify-end"]>*:last-child:not(button):not(svg){width:100%!important;max-width:100%!important;text-align:left!important}
        main [class*="ml-auto"]:not(button):not(svg):not(input){max-width:100%!important}
        main [class*="ms-auto"]:not(button):not(svg):not(input){max-width:100%!important}
        [class*="UserQuery"],[class*="user-query"],[class*="human"]{max-width:100%!important}
        /* grouptitle: inline-flex → flex so it fills row width */
        .scrollable-container [class*="grouptitle"]{display:flex!important;width:100%!important;max-width:100%!important;flex-direction:column!important}
        /* groupquery wrapper full width */
        .scrollable-container [class*="groupquery"]{width:100%!important;max-width:100%!important}
        /* bubble pill itself: remove 600px cap */
        .scrollable-container [class*="rounded-tl-2xl"][class*="bg-subtle"]{max-width:100%!important;width:100%!important}

        /* SIDEBAR */
        div.w-sideBarWidth{flex-shrink:0!important;transition:width .05s!important}
        nav.groupsidebar,nav[class*="groupsidebar"]{overflow-x:hidden!important}
        nav span.w-full.overflow-hidden{display:block!important;white-space:nowrap!important;overflow:hidden!important;-webkit-mask-image:none!important;mask-image:none!important;line-height:1.3!important;max-height:1.3em!important}
        nav [class*="-ml-md"][class*="px-12px"]{width:9999px!important;min-width:unset!important;max-width:unset!important}
        nav .flex.flex-none.flex-col{height:auto!important;min-height:0!important;gap:0!important;padding:0!important}
        nav a,nav button{padding-top:0!important;padding-bottom:0!important;margin-top:0!important;margin-bottom:0!important;min-height:0!important}
        nav a[href],nav button{height:auto!important;max-height:22px!important;line-height:1.2!important}
        nav a svg,nav button svg{width:14px!important;height:14px!important}
        nav [class*="gap-"]{gap:0!important}
        nav [class*="py-"]{padding-top:0!important;padding-bottom:0!important}
        nav [class*="my-"]{margin-top:0!important;margin-bottom:0!important}
        nav [class*="mt-"]{margin-top:0!important}
        nav [class*="mb-"]{margin-bottom:0!important}
        nav [class*="pt-"]{padding-top:0!important}
        nav [class*="pb-"]{padding-bottom:0!important}
        nav [class*="space-y"]>*{margin-top:1px!important}
        nav [class*="px-md"]{padding-left:4px!important;padding-right:4px!important}
        nav [class*="px-12px"]{padding-left:4px!important;padding-right:4px!important}
        nav [class*="pl-14px"]{padding-left:6px!important}
        nav [class*="ml-26px"]{margin-left:10px!important}
        nav [class*="-ml-md"]{margin-left:-4px!important}
        nav [class*="border-t"]{margin-top:0!important;margin-bottom:0!important}

        /* CODE */
        pre,code{max-width:100%!important;word-break:break-all!important;white-space:pre-wrap!important}

        /* ============================================================
           v1.7.10 CODE TOOLBAR — sticky 浮動(隨代碼捲動保持可見)
           ============================================================ */
        .pplx-pre-container{
            position:relative!important;
            display:block!important;
            overflow:visible!important;
            margin-top:2px!important;
            margin-bottom:2px!important
        }
        /* height:0 讓 pre 從同一行起頭;sticky 讓 toolbar 捲動時浮在頂部 */
        .pplx-code-wrapper{
            position:sticky!important;
            top:0!important;
            height:0!important;
            width:100%!important;
            display:flex!important;
            justify-content:flex-end!important;
            align-items:flex-start!important;
            z-index:200!important;
            pointer-events:none!important;
            overflow:visible!important
        }
        /* pre 頂端留白 = toolbar 高度,避免第一行被蓋住 */
        .pplx-pre-container>pre{padding-top:38px!important;margin-top:0!important}
        .pplx-code-toolbar{
            pointer-events:auto!important;
            margin-right:6px!important;
            margin-top:3px!important;
            display:flex!important;
            flex-direction:row!important;
            align-items:center!important;
            gap:5px!important;
            background:var(--background-base-color,#ffffff)!important;
            border:1px solid rgba(14,165,233,.22)!important;
            padding:3px 6px 3px 8px!important;
            border-radius:20px!important;
            box-shadow:0 2px 10px rgba(14,165,233,.18)!important;
            max-width:calc(100% - 40px)!important;
            min-width:100px!important
        }
        .pplx-tb-left{display:flex!important;align-items:center!important;gap:4px!important;overflow:hidden!important;flex:1!important;min-width:0!important}
        .pplx-code-name{font-size:11px!important;font-weight:700!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;color:#0ea5e9!important;user-select:none!important;background:rgba(14,165,233,.10)!important;padding:1px 7px 1px 5px!important;border-radius:10px!important;line-height:1.6!important;cursor:default!important}
        .pplx-code-name::before{content:'●';margin-right:4px;font-size:7px;vertical-align:middle;opacity:.7}
        .pplx-code-ver{font-size:10px!important;font-weight:500!important;white-space:nowrap!important;flex-shrink:0!important;color:#64748b!important;user-select:none!important;background:rgba(100,116,139,.10)!important;padding:1px 6px!important;border-radius:8px!important;line-height:1.6!important;cursor:default!important}
        .pplx-code-arrow{flex-shrink:0!important;cursor:pointer!important;color:#fff!important;font-size:10px!important;padding:2px 8px!important;border-radius:10px!important;background:#0ea5e9!important;transition:background .15s!important;user-select:none!important;line-height:1.5!important;font-weight:bold!important}
        .pplx-code-arrow:hover{background:#0284c7!important}
        .pplx-code-arrow.pplx-expanded{background:#0284c7!important}
        .pplx-code-copy{flex-shrink:0!important;cursor:pointer!important;font-size:13px!important;padding:2px 6px!important;border-radius:8px!important;line-height:1.4!important;transition:background .15s,transform .1s!important;user-select:none!important;margin-left:2px!important}
        .pplx-code-copy:hover{background:rgba(14,165,233,.15)!important;transform:scale(1.12)!important}
        .pplx-code-collapsed{max-height:0!important;min-height:0!important;overflow:hidden!important;opacity:.75!important;cursor:pointer!important;border-bottom:3px dashed #0ea5e9!important;padding:0!important;margin:0!important;transition:max-height .3s ease,opacity .2s!important}
        .pplx-code-collapsed:hover{opacity:1!important}

        /* DRAG HANDLE */
        #pplx-resize-handle{width:8px;height:100vh;position:fixed;top:0;left:var(--pplx-sidebar-width,185px);z-index:9999999;cursor:col-resize;background:transparent}
        #pplx-resize-handle::after{content:'';position:absolute;left:50%;top:0;bottom:0;width:1px;background:#22d3ee;transform:translateX(-50%)}
        #pplx-resize-handle:hover::after,#pplx-resize-handle.active::after{background:#06b6d4;width:2px}
        #pplx-drag-curtain{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:9999998;cursor:col-resize;display:none}
        body.pplx-resizing #pplx-drag-curtain{display:block}
        body.pplx-resizing *{user-select:none!important}

        /* GLOBAL COLLAPSE BTN */
        #pplx-global-collapse-btn{position:fixed!important;right:18px!important;bottom:60px!important;z-index:9999999!important;background:#0ea5e9!important;color:#fff!important;border:none!important;border-radius:20px!important;width:32px!important;height:32px!important;font-size:15px!important;cursor:pointer!important;box-shadow:0 3px 8px rgba(0,0,0,.25)!important;display:flex!important;align-items:center!important;justify-content:center!important;transition:transform .2s,background .2s!important;user-select:none!important}
        #pplx-global-collapse-btn:hover{transform:scale(1.08)!important;background:#0284c7!important}
    `;

    if (typeof GM_addStyle !== 'undefined') { GM_addStyle(css); }
    else { const s=document.createElement('style'); s.textContent=css; document.documentElement.appendChild(s); }

    function applySidebarWidth(px) {
        savedWidth = px;
        document.documentElement.style.setProperty('--pplx-sidebar-width', px + 'px');
        ['div.w-sideBarWidth','nav.groupsidebar','nav[class*="groupsidebar"]'].forEach(sel => {
            document.querySelectorAll(sel).forEach(el => {
                el.style.setProperty('width', px+'px','important');
                el.style.setProperty('min-width', px+'px','important');
                el.style.setProperty('max-width', px+'px','important');
            });
        });
        const h=document.getElementById('pplx-resize-handle');
        if(h)h.style.left=px+'px';
    }
    function watchSidebarElement(){
        const el=document.querySelector('div.w-sideBarWidth');
        if(!el||el._pplxWatched)return; el._pplxWatched=true;
        new MutationObserver(()=>{if(parseInt(el.style.width)!==savedWidth)applySidebarWidth(savedWidth);}).observe(el,{attributes:true,attributeFilter:['style']});
    }
    function fixThreadSectionWidth(){
        document.querySelectorAll('nav [style*="width: 200"]').forEach(el=>el.style.setProperty('width','9999px','important'));
        document.querySelectorAll('nav span[style*="mask-image"]').forEach(el=>{el.style.removeProperty('mask-image');el.style.removeProperty('-webkit-mask-image');});
    }
    function fixPageContentWidth(){
        document.querySelectorAll('[style*="--page-content-width"]').forEach(el=>{
            if(el.style.getPropertyValue('--page-content-width')!=='9999px')
                el.style.setProperty('--page-content-width','9999px');
        });
        document.querySelectorAll('[class*="bg-subtle"][class*="rounded-2xl"][style*="max-width"]').forEach(el=>{
            el.style.removeProperty('max-width');
        });
        // v1.7.10: remove inline max-width from user query bubble (rounded-tl-2xl variant)
        document.querySelectorAll('[class*="bg-subtle"][class*="rounded-tl-2xl"][style*="max-width"]').forEach(el=>{
            el.style.removeProperty('max-width');
        });
        // v1.7.10: ensure grouptitle is flex (not inline-flex) for full-width layout
        document.querySelectorAll('[class*="grouptitle"]').forEach(el=>{
            if(getComputedStyle(el).display==='inline-flex'){
                el.style.setProperty('display','flex','important');
                el.style.setProperty('width','100%','important');
            }
        });
    }

    applySidebarWidth(savedWidth);
    setInterval(()=>{
        const el=document.querySelector('div.w-sideBarWidth');
        if(el&&parseInt(el.style.width)!==savedWidth)applySidebarWidth(savedWidth);
        fixThreadSectionWidth();
    },400);

    let scrollToBottomTimers=[];
    function scheduleScrollToBottom(){
        scrollToBottomTimers.forEach(t=>clearTimeout(t));
        scrollToBottomTimers=[];
        [300,700,1300,2500,4000].forEach(delay=>{
            scrollToBottomTimers.push(setTimeout(()=>{
                const sc=document.querySelector('.scrollable-container');
                if(sc)sc.scrollTop=sc.scrollHeight;
            },delay));
        });
    }

    let lastUrl=location.href, lastBubbleScan=0;
    function checkUrlChange(){
        if(location.href===lastUrl)return;
        lastUrl=location.href;
        lastBubbleScan=0;
        setTimeout(fixPageContentWidth,300);
        setTimeout(fixPageContentWidth,800);
        scheduleScrollToBottom();
    }

    function expandUserBubbles(){
        const now=Date.now();
        if(now-lastBubbleScan<600)return;
        lastBubbleScan=now;
        const sc=document.querySelector('.scrollable-container');
        if(!sc||sc.getBoundingClientRect().width<10)return;
        sc.querySelectorAll('[class*="justify-end"]').forEach(row=>{
            if(row._pplxRowFixed||row.children.length<2)return;
            const children=Array.from(row.children);
            const bubble=children[children.length-1];
            const ac=children.slice(0,-1).filter(c=>{
                const cls=typeof c.className==='string'?c.className:'';
                return cls.includes('invisible')||cls.includes('opacity-0')||c.querySelectorAll('button').length>0;
            });
            if(!ac.length)return;
            row._pplxRowFixed=true;row.style.position='relative';
            ac.forEach(el=>{el.style.position='absolute';el.style.right='4px';el.style.left='auto';el.style.top='4px';el.style.zIndex='20';});
            bubble.style.setProperty('width','100%','important');
            bubble.style.setProperty('max-width','100%','important');
            bubble.style.setProperty('text-align','left','important');
        });
    }

    function compressNavIcons(){
        ['32px','28px'].forEach(px=>{
            document.querySelectorAll(`nav [style*="width: ${px}"]`).forEach(el=>{
                if(el.style.width===px||el.style.minWidth===px){
                    el.style.setProperty('width','20px','important');
                    el.style.setProperty('min-width','20px','important');
                }
            });
        });
    }

    const LANG_RE=/^(javascript|typescript|python|java|css|html|sql|bash|sh|shell|json|yaml|xml|go|rust|c\+\+|cpp|c#|ruby|php|swift|kotlin|r|matlab|scala|perl|lua|dart|vue|jsx|tsx|sass|scss|less|toml|ini|dockerfile|makefile|plaintext|text)$/i;
    function getCleanCode(pre){
        const codeEl=pre.querySelector('code');
        if(codeEl)return codeEl.innerText;
        const clone=pre.cloneNode(true);
        clone.querySelectorAll('[data-testid="code-language-indicator"]').forEach(el=>el.remove());
        clone.querySelectorAll('.pplx-code-toolbar,.pplx-code-wrapper').forEach(el=>el.remove());
        const raw=clone.innerText.trim();
        const lines=raw.split('\n');
        if(LANG_RE.test((lines[0]||'').trim()))return lines.slice(1).join('\n').trimStart();
        return raw;
    }

    let isGlobalCollapsed=true;
    function ensureGlobalBtn(){
        if(document.getElementById('pplx-global-collapse-btn')||!document.body)return;
        const btn=document.createElement('div');
        btn.id='pplx-global-collapse-btn';btn.textContent='⏬';btn.title='全域折疊/展開代碼';
        btn.onclick=()=>{
            isGlobalCollapsed=!isGlobalCollapsed;
            btn.textContent=isGlobalCollapsed?'⏬':'⏫';
            document.querySelectorAll('pre.pplx-processed').forEach(pre=>{
                const arrow=pre._pplxArrow;
                if(isGlobalCollapsed){pre.classList.add('pplx-code-collapsed');if(arrow){arrow.textContent='▼ 展開';arrow.classList.remove('pplx-expanded');}}
                else{pre.classList.remove('pplx-code-collapsed');if(arrow){arrow.textContent='▲ 折疊';arrow.classList.add('pplx-expanded');}}
            });
        };
        document.body.appendChild(btn);
    }

    // v1.7.10: processCodeBlocks — 改用 sticky height:0 wrapper
    function processCodeBlocks(){
        document.querySelectorAll('pre:not(.pplx-processed)').forEach(pre=>{
            pre.classList.add('pplx-processed');
            if(!pre.parentNode)return;
            const txt=pre.innerText||'';
            const nmMatch=txt.match(/@name\s+([^\n]+)/i);
            const vmMatch=txt.match(/@version\s+([^\n]+)/i);

            // Create container
            const container=document.createElement('div');
            container.className='pplx-pre-container';

            // Sticky zero-height wrapper
            const wrap=document.createElement('div');
            wrap.className='pplx-code-wrapper';

            const tbar=document.createElement('div');
            tbar.className='pplx-code-toolbar';

            const left=document.createElement('div');
            left.className='pplx-tb-left';

            if(nmMatch){
                const n=document.createElement('span');
                n.className='pplx-code-name';
                n.textContent=nmMatch[1].trim();
                n.title=nmMatch[1].trim();
                left.appendChild(n);
            }
            if(vmMatch){
                const v=document.createElement('span');
                v.className='pplx-code-ver';
                v.textContent='v'+vmMatch[1].trim();
                left.appendChild(v);
            }

            const arrow=document.createElement('span');
            arrow.className='pplx-code-arrow';
            arrow.title='展開 / 折疊代碼';
            if(isGlobalCollapsed){arrow.textContent='▼ 展開';pre.classList.add('pplx-code-collapsed');}
            else{arrow.textContent='▲ 折疊';arrow.classList.add('pplx-expanded');}
            arrow.onclick=e=>{
                e.preventDefault();e.stopPropagation();
                const collapsed=pre.classList.toggle('pplx-code-collapsed');
                arrow.textContent=collapsed?'▼ 展開':'▲ 折疊';
                arrow.classList.toggle('pplx-expanded',!collapsed);
            };
            left.appendChild(arrow);

            const copyBtn=document.createElement('span');
            copyBtn.className='pplx-code-copy';copyBtn.textContent='📋';copyBtn.title='複製代碼';
            copyBtn.onclick=e=>{
                e.preventDefault();e.stopPropagation();
                const code=getCleanCode(pre);
                const fb=()=>{copyBtn.textContent='✅';setTimeout(()=>copyBtn.textContent='📋',2000);};
                if(navigator.clipboard)navigator.clipboard.writeText(code).then(fb).catch(()=>{const ta=document.createElement('textarea');ta.value=code;document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);fb();});
                else{const ta=document.createElement('textarea');ta.value=code;document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);fb();}
            };

            pre.onclick=()=>{if(pre.classList.contains('pplx-code-collapsed')){pre.classList.remove('pplx-code-collapsed');arrow.textContent='▲ 折疊';arrow.classList.add('pplx-expanded');}};

            tbar.appendChild(left);
            tbar.appendChild(copyBtn);
            wrap.appendChild(tbar);

            pre.parentNode.insertBefore(container, pre);
            container.appendChild(wrap);
            container.appendChild(pre);
            pre._pplxArrow=arrow;
        });
    }

    let isResizing=false,animId=null;
    function ensureResizeHandle(){
        if(document.getElementById('pplx-resize-handle')||!document.body)return;
        const h=document.createElement('div');h.id='pplx-resize-handle';h.title='拖曳側欄寬度 | 雙擊還原';
        const c=document.createElement('div');c.id='pplx-drag-curtain';
        document.body.appendChild(h);document.body.appendChild(c);
        h.addEventListener('mousedown',e=>{isResizing=true;h.classList.add('active');document.body.classList.add('pplx-resizing');e.preventDefault();});
        window.addEventListener('mousemove',e=>{if(!isResizing)return;if(animId)cancelAnimationFrame(animId);animId=requestAnimationFrame(()=>applySidebarWidth(Math.min(Math.max(e.clientX,MIN_W),MAX_W)));});
        window.addEventListener('mouseup',()=>{if(!isResizing)return;isResizing=false;h.classList.remove('active');document.body.classList.remove('pplx-resizing');try{GM_setValue('pplx_sidebar_width',savedWidth);}catch(_){}});
        h.addEventListener('dblclick',()=>{applySidebarWidth(185);try{GM_setValue('pplx_sidebar_width',185);}catch(_){}});
    }

    let originalTitle=document.title,tabState='IDLE',wasGenerating=false;
    function setTabState(s){let t=originalTitle;if(s==='GENERATING')t='【⏳】 '+originalTitle;else if(s==='DONE')t='【✅】 '+originalTitle;if(document.title!==t)document.title=t;}
    window.addEventListener('focus',()=>{if(tabState==='DONE'){tabState='IDLE';setTabState('IDLE');}});
    window.addEventListener('click',()=>{if(tabState==='DONE'){tabState='IDLE';setTabState('IDLE');}});
    function detectGenerating(){
        const stop=document.querySelector('button[aria-label*="Stop"],button[aria-label*="停止"]');
        const gen=!!(stop&&stop.offsetParent!==null);
        if(gen&&!wasGenerating){tabState='GENERATING';setTabState(tabState);wasGenerating=true;if(!originalTitle.includes('【'))originalTitle=document.title;}
        else if(!gen&&wasGenerating){tabState='DONE';setTabState(tabState);wasGenerating=false;}
        if(tabState==='IDLE'&&!document.title.includes('【'))originalTitle=document.title;
    }

    function maintainUI(){
        ensureResizeHandle();
        watchSidebarElement();
        ensureGlobalBtn();
        processCodeBlocks();
        detectGenerating();
        checkUrlChange();
        expandUserBubbles();
        compressNavIcons();
        fixPageContentWidth();
    }
    setInterval(maintainUI,300);
    maintainUI();
    scheduleScrollToBottom();
})();