```// ==UserScript==
// @name         codecurber 2.2 - stable folding with viewport width limit and debounce
// @version      2.2
// @description  Fold large pre>code blocks with stable state during streaming, limit width to 97vw to avoid overflow scrolling, debounce updates.
// @match        *://yuntian-deng-chatgpt.hf.space/*
// @match        *://yuntian-deng-o1mini.hf.space/*
// @match        *://yuntian-deng-chatgpt4.hf.space/*
// @match        *://yuntian-deng-o1.hf.space/*
// @grant        none
// ==/UserScript==

(function() {
  'use strict';

  const MIN_LINES = 6;
  const FOLD_MAX_HEIGHT = '8em';
  const MAX_WIDTH = '97vw';
  const DEBOUNCE_MS = 150;

  // Inject styling with !important overrides, including max width to prevent horizontal overflow
  const style = document.createElement('style');
  style.textContent = `
    pre.codecurber-folded {
      max-height: ${FOLD_MAX_HEIGHT} !important;
      overflow-y: auto !important;
      position: relative !important;
      transition: max-height 0.3s ease !important;
      white-space: pre-wrap !important;
      word-break: break-word !important;
      max-width: ${MAX_WIDTH} !important;
      box-sizing: border-box !important;
    }
    .codecurber-container {
      width: ${MAX_WIDTH};
      display: flex;
      flex-direction: column;
      margin-top: 0.25em;
      margin-bottom: 1em;
      box-sizing: border-box;
    }
    .codecurber-toggle-btn {
      margin-top: 0.5em !important;
      cursor: pointer !important;
      color: #39ff14 !important;
      background: transparent !important;
      user-select: none !important;
      padding: 6px 14px !important;
      display: inline-block !important;
      transition: background-color 0.25s ease, color 0.25s ease !important;
      align-self: flex-start;
      max-width: ${MAX_WIDTH} !important;
      box-sizing: border-box;
    }
    .codecurber-toggle-btn:hover {
      background-color: #39ff14 !important;
      color: black !important;
    }
  `;
  document.head.appendChild(style);

  // Track folded blocks and content length to avoid flicker during streaming
  const foldedBlocks = new WeakMap();

  // Set to accumulate blocks for debounced processing
  let pendingBlocks = new Set();
  let foldTimer = null;

  function debounceProcess() {
    if (foldTimer) clearTimeout(foldTimer);
    foldTimer = setTimeout(() => {
      const blocks = Array.from(pendingBlocks);
      pendingBlocks.clear();
      blocks.forEach(pre => processCodeBlock(pre));
    }, DEBOUNCE_MS);
  }

  function isFoldable(preEl) {
    if (!preEl) return false;
    const codeEl = preEl.querySelector('code');
    if (!codeEl) return false;
    const lines = (codeEl.textContent || '').split('\n').length;
    return lines >= MIN_LINES;
  }

  function processCodeBlock(preEl) {
    if (!isFoldable(preEl)) return;
    const codeEl = preEl.querySelector('code');
    if (!codeEl) return;

    const currLen = codeEl.textContent.length;
    const prevLen = foldedBlocks.get(preEl);

    if (prevLen !== undefined && prevLen === currLen && preEl.dataset.codecurberFolded === 'true') {
      return; // no change and already folded
    }

    foldedBlocks.set(preEl, currLen);

    if (preEl.dataset.codecurberFolded !== 'true') {
      foldCodeBlock(preEl);
    }
  }

  function foldCodeBlock(preEl) {
    if (preEl.dataset.codecurberFolded === 'true') return;
    preEl.dataset.codecurberFolded = 'true';

    preEl.classList.add('codecurber-folded');

    // Wrap <pre> in container
    const container = document.createElement('div');
    container.className = 'codecurber-container';

    preEl.parentNode.insertBefore(container, preEl);
    container.appendChild(preEl);

    // Create toggle button
    const btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'codecurber-toggle-btn';
    btn.textContent = 'Show full code';
    btn.setAttribute('aria-expanded', 'false');
    btn.setAttribute('aria-label', 'Show full code block');

    container.appendChild(btn);

    btn.addEventListener('click', () => {
      const folded = preEl.classList.contains('codecurber-folded');
      if (folded) {
        preEl.style.maxHeight = 'none';
        preEl.style.overflow = 'visible';
        preEl.classList.remove('codecurber-folded');
        btn.textContent = 'Fold code';
        btn.setAttribute('aria-expanded', 'true');
      } else {
        preEl.style.maxHeight = FOLD_MAX_HEIGHT;
        preEl.style.overflow = 'auto';
        preEl.classList.add('codecurber-folded');
        btn.textContent = 'Show full code';
        btn.setAttribute('aria-expanded', 'false');
      }
    });
  }

  function scanAndFoldAll() {
    const chat = document.querySelector('.bubble-wrap.svelte-gjtrl6');
    if (!chat) return;
    chat.querySelectorAll('pre').forEach(preEl => {
      if (!isFoldable(preEl)) return;
      pendingBlocks.add(preEl);
    });
    debounceProcess();
  }

  function observeChatMutations() {
    const chat = document.querySelector('.bubble-wrap.svelte-gjtrl6');
    if (!chat) return;

    const observer = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        mutation.addedNodes.forEach(node => {
          if (!(node instanceof HTMLElement)) return;

          if (node.tagName === 'PRE') {
            if (isFoldable(node)) pendingBlocks.add(node);
          } else {
            node.querySelectorAll && node.querySelectorAll('pre').forEach(preEl => {
              if (isFoldable(preEl)) pendingBlocks.add(preEl);
            });
          }
        });
      });
      if (pendingBlocks.size > 0) debounceProcess();
    });

    observer.observe(chat, { childList: true, subtree: true });
  }

  // Initialize folding when chat is ready
  const initTimer = setInterval(() => {
    if (document.querySelector('.bubble-wrap.svelte-gjtrl6')) {
      clearInterval(initTimer);
      scanAndFoldAll();
      observeChatMutations();
    }
  }, 300);
})();```