```// ==UserScript==
// @name         codecurber 2.1 - stable folding for streaming large code blocks
// @version      2.1
// @description  Fold large pre>code blocks in chat with stable fold state during streaming update and minimal flicker
// @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'; // Folded max height ~6-8 lines
  const DEBOUNCE_MS = 150;

  // Insert styles with !important for overriding chat CSS
  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;
      width:97vw;
    }
    .codecurber-toggle-btn {
      margin-top: 0.5em !important;
      cursor: pointer !important;
      color: #39ff14 !important;
      background: transparent !important;
      user-select: none !important;
      display: inline-block !important;
      transition: background-color 0.25s ease, color 0.25s ease !important;
width:97vw;
    }
    .codecurber-toggle-btn:hover {
      background-color: #39ff14 !important;
      color: black !important;
      width:97vw;
    }
  `;
  document.head.appendChild(style);

  // Track folded code blocks and last known content length to reduce flicker & redundant re-folding
  const foldedBlocks = new WeakMap();

  // Throttle folding due to rapid streaming updates
  let foldTimer = null;
  let pendingBlocks = new Set();

  function debounceFold() {
    if (foldTimer) clearTimeout(foldTimer);
    foldTimer = setTimeout(() => {
      const blocksToProcess = Array.from(pendingBlocks);
      pendingBlocks.clear();
      blocksToProcess.forEach(preEl => processCodeBlock(preEl));
    }, DEBOUNCE_MS);
  }

  // Check if this <pre> > <code> block is foldable (enough lines, etc)
  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;
  }

  // Fold or update folding on given <pre> element
  function processCodeBlock(preEl) {
    if (!isFoldable(preEl)) return;

    const codeEl = preEl.querySelector('code');
    if (!codeEl) return;

    const currentLength = codeEl.textContent.length;
    const prevLength = foldedBlocks.get(preEl);

    // If previously folded and content length unchanged, do nothing (stable)
    if (prevLength !== undefined && prevLength === currentLength && preEl.dataset.codecurberFolded === 'true') {
      return;
    }

    foldedBlocks.set(preEl, currentLength);

    // If not folded yet, fold it now
    if (preEl.dataset.codecurberFolded !== 'true') {
      foldCodeBlock(preEl);
    }
  }

  // Fold given <pre> element with toggle button
  function foldCodeBlock(preEl) {
    if (preEl.dataset.codecurberFolded === 'true') return;

    preEl.dataset.codecurberFolded = 'true';
    preEl.classList.add('codecurber-folded');

    // Wrap the pre in a flex container to host toggle button below
    const container = document.createElement('div');
    container.style.display = 'flex';
    container.style.flexDirection = 'column';
    container.style.width = '97vw';

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

    // Create and append 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');
      }
    });
  }

  // Initial scan and fold all eligible large code blocks within chat
  function foldAllLargeCodeBlocks() {
    const chat = document.querySelector('.bubble-wrap.svelte-gjtrl6');
    if (!chat) return;
    chat.querySelectorAll('pre').forEach(preEl => {
      if (!isFoldable(preEl)) return;
      pendingBlocks.add(preEl);
    });
    debounceFold();
  }

  // Observe chat for DOM mutations, trigger folding on newly added or changed blocks
  function observeChat() {
    const chat = document.querySelector('.bubble-wrap.svelte-gjtrl6');
    if (!chat) return;
    const observer = new MutationObserver(mutations => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (!(node instanceof HTMLElement)) continue;

          // If node is <pre> or contains <pre>, check those
          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) debounceFold();
    });
    observer.observe(chat, { childList: true, subtree: true });
  }

  // Wait for chat container and initialize
  const initInterval = setInterval(() => {
    if (document.querySelector('.bubble-wrap.svelte-gjtrl6')) {
      clearInterval(initInterval);
      foldAllLargeCodeBlocks();
      observeChat();
    }
  }, 300);
})();```