Perplexity Widescreen & Compact & Code Collapser

V1.7.7: pill回V0極窄(僅max-width,零JS) | header改auto高度解決icon clip

24.03.2026 itibariyledir. En son verisyonu görün.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         Perplexity Widescreen & Compact & Code Collapser
// @namespace    http://tampermonkey.net/
// @version      1.7.7
// @description  V1.7.7: pill回V0極窄(僅max-width,零JS) | header改auto高度解決icon clip
// @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}

        /* ============================================================
           v1.7.7 HEADER — 改用 auto 高度 + 減少 padding
           不再強制 height:32px,避免 h-54px tablist 被父層 clip
           僅壓縮 tab button 的 py,讓整體自然縮小
           ============================================================ */
        [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}

        /* ============================================================
           v1.7.7 PILL — 純 V0:不覆蓋寬度/transition/border-radius
           只加 max-width 極窄限制,其餘完全讓 Perplexity 原生處理
           零 JS、零 reflow、零卡頓
           ============================================================ */
        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}

        /* 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}

        /* 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}

        /* COMPACT NAV */
        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}

        /* COMPACT CHAT TEXT */
        .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}

        /* CODE */
        pre,code{max-width:100%!important;word-break:break-all!important;white-space:pre-wrap!important}
        pre.pplx-processed{margin-top:0!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}

        /* CODE COLLAPSER */
        .pplx-code-wrapper{position:relative!important;height:36px!important;width:100%!important;display:flex!important;justify-content:flex-end!important;align-items:center!important;z-index:10!important;pointer-events:none!important;overflow:visible!important;margin-bottom:0!important}
        .pplx-code-toolbar{pointer-events:auto!important;margin-right:6px!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 8px rgba(14,165,233,.12)!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}
        #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); }

    // ============================================================
    // SIDEBAR
    // ============================================================
    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');
        });
    }

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

    // ============================================================
    // CODE — 複製排除 language label
    // ============================================================
    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);
    }

    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);
            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(wrap,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();
        // v1.7.7: 完全無 pill JS
    }
    setInterval(maintainUI,300);
    maintainUI();
    scheduleScrollToBottom();
})();