Perplexity Widescreen & Compact & Code Collapser

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

目前為 2026-03-24 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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