YouTube Subscription List View

Restore YouTube Subscription List View

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         YouTube Subscription List View
// @namespace    xyz.flaflo.yslv
// @version      1.4.6
// @author       Flaflo
// @license      MIT
// @homepageURL  https://flaflo.xyz/yslv
// @description  Restore YouTube Subscription List View
// @match        https://www.youtube.com/*
// @run-at       document-end
// @grant        none
// ==/UserScript==

;(() => {
  "use strict"

  const CFG = {
    storageKey: "yslv",
    defaultView: "grid", // or "list"

    toggleMountSelector:
      'ytd-browse[page-subtype="subscriptions"] ytd-shelf-renderer .grid-subheader #title-container #subscribe-button,' +
      'ytd-two-column-browse-results-renderer[page-subtype="subscriptions"] ytd-shelf-renderer .grid-subheader #title-container #subscribe-button',

    descStore: {
      key: "yslv_desc_cache_v1",
      ttlMs: 60 * 60 * 1000,
      maxEntries: 1200,
      saveDebounceMs: 250,
    },

    list: {
      maxWidth: 1320, // or for non centered "90%"
      rowPadY: 20,
      separator: true,

      thumbW: 240,
      thumbRadius: 14,

      shorts: {
        enabled: true,
        cardW: 170,
      },

      titleClamp: 2,
      descClamp: 2,

      rowHead: {
        enabled: true,
        gap: 12,
        marginBottom: 20,
        avatarSize: 32,
      },

      metaRow: {
        gap: 8,
      },

      desc: {
        marginTop: 10,
        skeleton: {
          enabled: true,
          lines: 2,
          lineGap: 6,
          lineHeights: [12, 12, 12],
          lineWidthsPct: [82, 74, 58],
          radius: 9,
          maxW: 520,
          animMs: 5000,
        },
      },

      descFetch: {
        enabled: true,
        maxTotalFetchesPerNav: 60,
        maxConcurrent: 1,
        sentenceCount: 2,
        maxChars: 260,
      },
    },

    perf: {
      maxItemsPerTick: 60,
      descQueueIntervalMs: 350,
    },

    ids: {
      style: "yslv-subs-style",
      toggle: "yslv-subs-toggle",
    },

    cls: {
      rowHead: "yslv-subs-rowhead",
      rowHeadName: "yslv-subs-rowhead-name",
      metaRow: "yslv-subs-mrow",
      metaCh: "yslv-subs-mch",
      metaRt: "yslv-subs-mrt",
      desc: "yslv-subs-desc",
      descSkel: "yslv-subs-desc-skel",
      btn: "yslv-btn",
      btnIcon: "yslv-btn-ic",
      isShort: "yslv-is-short",
    },

    attr: {
      view: "data-yslv-subs-view",
    },

    cssVars: {
      shimmerX: "--yslvSkelX",
      shortW: "--yslvShortW",
    },
  }

  const STATE = {
    active: false,
    view: "grid",
    styleEl: null,

    q: [],
    qSet: new Set(),
    processing: false,

    processedItems: new WeakSet(),

    movedAvatars: new WeakMap(),
    movedMetaAnchors: new WeakMap(),

    mo: null,
    observedTarget: null,

    pmMo: null,

    descCache: new Map(),
    descInFlight: new Map(),
    descFetches: 0,
    descActive: 0,

    descQueue: [],
    descQueued: new Set(),
    descTimer: 0,
    descPumpRunning: false,
    lastQueueSig: "",

    lastPageSig: "",
  }

  const SHIMMER = {
    raf: 0,
    running: false,
    t0: 0,
  }

  const DESC_STORE = {
    obj: null,
    dirty: false,
    saveT: 0,
  }

  function clearChildren(el) {
    if (!el) return
    while (el.firstChild) el.removeChild(el.firstChild)
  }

  function cloneInto(dest, src) {
    if (!dest) return
    clearChildren(dest)
    if (!src) return

    const frag = document.createDocumentFragment()
    for (const n of Array.from(src.childNodes || [])) frag.appendChild(n.cloneNode(true))

    for (const host of Array.from(frag.querySelectorAll?.(".ytIconWrapperHost, .yt-icon-shape") || [])) {
      if (!host.querySelector("svg")) host.remove()
    }

    dest.appendChild(frag)
  }

  function setTextOnly(dest, txt) {
    if (!dest) return
    clearChildren(dest)
    dest.textContent = normalizeText(txt)
  }

  function isSubsPage() {
    return location.pathname === "/feed/subscriptions"
  }

  function getActiveSubsBrowse() {
    return (
      document.querySelector('ytd-page-manager ytd-browse[page-subtype="subscriptions"]:not([hidden])') ||
      document.querySelector('ytd-browse[page-subtype="subscriptions"]:not([hidden])') ||
      null
    )
  }

  function getActiveSubsRoot() {
    const b = getActiveSubsBrowse()
    if (!b) return null
    return b.querySelector("ytd-rich-grid-renderer #contents") || b.querySelector("ytd-rich-grid-renderer") || b
  }

  function getActiveSubsDoc() {
    return getActiveSubsBrowse() || document
  }

  function normalizeText(s) {
    return String(s || "")
      .replace(/\u200B/g, "")
      .replace(/\s+/g, " ")
      .trim()
  }

  function loadView() {
    try {
      const v = localStorage.getItem(CFG.storageKey)
      return v === "list" || v === "grid" ? v : CFG.defaultView
    } catch {
      return CFG.defaultView
    }
  }

  function saveView(v) {
    try {
      localStorage.setItem(CFG.storageKey, v)
    } catch {}
  }

  function applyViewAttr(v) {
    STATE.view = v
    saveView(v)
    document.documentElement.setAttribute(CFG.attr.view, v)
    paintToggle()
  }

  function clearViewAttr() {
    document.documentElement.removeAttribute(CFG.attr.view)
  }

  function svgEl(paths, viewBox) {
    const NS = "http://www.w3.org/2000/svg"
    const svg = document.createElementNS(NS, "svg")
    svg.setAttribute("viewBox", viewBox || "0 0 24 24")
    svg.setAttribute("aria-hidden", "true")
    for (const d of paths) {
      const p = document.createElementNS(NS, "path")
      p.setAttribute("d", d)
      svg.appendChild(p)
    }
    return svg
  }

  function skNorm() {
    const s = CFG.list.desc.skeleton || {}
    const lines = Math.max(1, Math.min(3, Number(s.lines) || 1))
    const gap = Math.max(0, Number(s.lineGap) || 6)
    const heights = Array.isArray(s.lineHeights) ? s.lineHeights : [12, 12, 12]
    const widths = Array.isArray(s.lineWidthsPct) ? s.lineWidthsPct : [82, 74, 58]
    const h = i => Math.max(10, Number(heights[i] ?? heights[0] ?? 12))
    const w = i => Math.max(35, Math.min(100, Number(widths[i] ?? widths[0] ?? 82)))
    const r = Math.max(6, Number(s.radius) || 9)
    const maxW = Math.max(160, Number(s.maxW) || 520)
    const ms = Math.max(650, Number(s.animMs) || 5000)
    return { enabled: !!s.enabled, lines, gap, h, w, r, maxW, ms }
  }

  function nowMs() {
    return Date.now()
  }

  function ensureDescStoreLoaded() {
    if (DESC_STORE.obj) return
    let raw = ""
    try {
      raw = localStorage.getItem(CFG.descStore.key) || ""
    } catch {
      raw = ""
    }
    let obj = {}
    if (raw) {
      try {
        const parsed = JSON.parse(raw)
        if (parsed && typeof parsed === "object") obj = parsed
      } catch {
        obj = {}
      }
    }
    DESC_STORE.obj = obj
    pruneDescStore()
  }

  function scheduleDescStoreSave() {
    if (DESC_STORE.saveT) return
    DESC_STORE.saveT = setTimeout(() => {
      DESC_STORE.saveT = 0
      if (!DESC_STORE.dirty) return
      DESC_STORE.dirty = false
      try {
        localStorage.setItem(CFG.descStore.key, JSON.stringify(DESC_STORE.obj || {}))
      } catch {}
    }, Math.max(0, Number(CFG.descStore.saveDebounceMs) || 250))
  }

  function pruneDescStore() {
    ensureDescStoreLoaded()
    const ttl = Math.max(1, Number(CFG.descStore.ttlMs) || 3600000)
    const maxEntries = Math.max(50, Number(CFG.descStore.maxEntries) || 1200)
    const tNow = nowMs()
    const obj = DESC_STORE.obj || {}
    const entries = []
    for (const k of Object.keys(obj)) {
      const e = obj[k]
      const t = Number(e?.t || 0)
      if (!t || tNow - t >= ttl) {
        delete obj[k]
        DESC_STORE.dirty = true
        continue
      }
      entries.push([k, t])
    }
    if (entries.length > maxEntries) {
      entries.sort((a, b) => a[1] - b[1])
      const drop = entries.length - maxEntries
      for (let i = 0; i < drop; i++) {
        delete obj[entries[i][0]]
        DESC_STORE.dirty = true
      }
    }
    if (DESC_STORE.dirty) scheduleDescStoreSave()
  }

  function getStoredDesc(vid) {
    if (!vid) return null
    ensureDescStoreLoaded()
    const ttl = Math.max(1, Number(CFG.descStore.ttlMs) || 3600000)
    const tNow = nowMs()
    const obj = DESC_STORE.obj || {}
    const e = obj[vid]
    if (!e) return null
    const t = Number(e.t || 0)
    const d = typeof e.d === "string" ? e.d : ""
    if (!t || tNow - t >= ttl) {
      delete obj[vid]
      DESC_STORE.dirty = true
      scheduleDescStoreSave()
      return null
    }
    return d
  }

  function setStoredDesc(vid, desc) {
    if (!vid) return
    ensureDescStoreLoaded()
    const obj = DESC_STORE.obj || {}
    obj[vid] = { t: nowMs(), d: String(desc || "") }
    DESC_STORE.dirty = true
    pruneDescStore()
    scheduleDescStoreSave()
  }

  function ensureStyle() {
    if (STATE.styleEl) return

    const L = CFG.list
    const maxW = Math.max(0, Number(L.maxWidth)) || L.maxWidth || 1120
    const thumbW = Math.max(240, Number(L.thumbW) || 420)
    const radius = Math.max(0, Number(L.thumbRadius) || 14)
    const rowPadY = Math.max(8, Number(L.rowPadY) || 22)
    const titleClamp = Math.max(1, Number(L.titleClamp) || 2)
    const descClamp = Math.max(1, Number(L.descClamp) || 2)
    const descMt = Math.max(4, Number(L.desc.marginTop) || 10)

    const headGap = Math.max(6, Number(L.rowHead.gap) || 12)
    const headMb = Math.max(6, Number(L.rowHead.marginBottom) || 20)
    const headAv = Math.max(20, Number(L.rowHead.avatarSize) || 32)

    const metaGap = Math.max(6, Number(L.metaRow.gap) || 8)

    const shortW = Math.max(110, Number(L.shorts.cardW) || 170)

    const S = skNorm()
    const attr = CFG.attr.view

    const skLineRules = Array.from({ length: 3 })
      .map((_, i) => {
        const n = i + 1
        return `
html[${attr}="list"] .${CFG.cls.desc}.${CFG.cls.descSkel} > span:nth-child(${n}){
  height:${S.h(i)}px !important;
  width:min(${S.maxW}px, ${S.w(i)}%) !important;
  margin-top:${i === 0 ? 0 : S.gap}px !important;
}`.trim()
      })
      .join("\n")

    const el = document.createElement("style")
    el.id = CFG.ids.style
    el.textContent = `
html[${attr}="list"] #content.ytd-rich-section-renderer{ margin:0 !important; }

ytd-masthead{ position:sticky !important; top:0 !important; z-index:3000 !important; }
#title-text{ position:relative !important; z-index:1 !important; }

#${CFG.ids.toggle}{
  display:inline-flex;align-items:center;gap:10px;margin-left:12px;
  position:relative;
}
#${CFG.ids.toggle} .${CFG.cls.btn}{
  width:36px;height:36px;border-radius:10px;
  border:1px solid var(--yt-spec-10-percent-layer, rgba(255,255,255,.12));
  background:rgba(255,255,255,.06);
  color:var(--yt-spec-text-primary, #fff);
  display:inline-flex;align-items:center;justify-content:center;
  padding:0;cursor:pointer;
  box-sizing:border-box;
  user-select:none;
  -webkit-user-select:none;
  touch-action:manipulation;
}
#${CFG.ids.toggle} .${CFG.cls.btn}:hover{ background:rgba(255,255,255,.10); border-color:rgba(255,255,255,.18) }
#${CFG.ids.toggle} .${CFG.cls.btn}[data-active]{ background:rgba(255,255,255,.16); border-color:rgba(255,255,255,.28) }
#${CFG.ids.toggle} .${CFG.cls.btn} .${CFG.cls.btnIcon}{ width:20px;height:20px;display:block; pointer-events:none; }
#${CFG.ids.toggle} .${CFG.cls.btn} svg{ width:20px;height:20px;fill:currentColor;opacity:.95;display:block; pointer-events:none; }

ytd-browse[page-subtype="subscriptions"] ytd-shelf-renderer .grid-subheader #title-container,
ytd-two-column-browse-results-renderer[page-subtype="subscriptions"] ytd-shelf-renderer .grid-subheader #title-container{
  display:flex !important;align-items:center !important;
  position:relative !important;
}
ytd-browse[page-subtype="subscriptions"] ytd-shelf-renderer .grid-subheader #title-container #spacer,
ytd-two-column-browse-results-renderer[page-subtype="subscriptions"] ytd-shelf-renderer .grid-subheader #title-container #spacer{
  flex:1 1 auto !important;
}

html[${attr}="list"] ytd-rich-grid-renderer{ --ytd-rich-grid-item-max-width: 100% !important; }
html[${attr}="list"] ytd-rich-grid-renderer #contents{
  display:block !important;
  max-width: ${maxW + (Number(maxW) ? "px" : "")} !important;
  margin:0 auto !important;
  width:100% !important;
}

html[${attr}="list"] ytd-rich-item-renderer{
  display:block !important;width:100% !important;
  padding:${rowPadY}px 0 !important;margin:0 !important;
  ${L.separator ? "border-bottom:1px solid var(--yt-spec-10-percent-layer, var(--yslv-sep, rgba(0,0,0,.10))) !important;" : ""}
}

html[${attr}="list"] ytd-rich-item-renderer.${CFG.cls.isShort}{
  display:inline-block !important;
  width:auto !important;
  padding:0 10px 0 0 !important;
  margin:0 !important;
  border-bottom:0 !important;
  vertical-align:top !important;
}

html[${attr}="list"] ytd-rich-item-renderer:first-child{ padding-top:0 !important; }
html[${attr}="list"] ytd-rich-item-renderer:last-child{ border-bottom:0 !important; }

html[${attr}="list"] .${CFG.cls.rowHead}{
  display:flex !important;align-items:center !important;
  gap:${headGap}px !important;margin:0 0 ${headMb}px 0 !important;
}
html[${attr}="list"] .${CFG.cls.rowHead} .yt-lockup-metadata-view-model__avatar{
  display:flex !important;
  width:${headAv}px !important;height:${headAv}px !important;min-width:${headAv}px !important;
  margin:0 !important;
}
html[${attr}="list"] .${CFG.cls.rowHead} yt-avatar-shape,
html[${attr}="list"] .${CFG.cls.rowHead} .yt-spec-avatar-shape{
  width:${headAv}px !important;height:${headAv}px !important;
}
html[${attr}="list"] .${CFG.cls.rowHeadName}{
  color:var(--yt-spec-text-primary) !important;
  text-decoration:none !important;
  font-weight:700 !important;
  font-size:18px !important;
  line-height:1.15 !important;
  display:inline-flex !important;
  align-items:center !important;
}

html[${attr}="list"] .yt-lockup-view-model.yt-lockup-view-model--vertical{
  display:grid !important;
  grid-template-columns:${thumbW}px minmax(0,1fr) auto !important;
  grid-template-rows:auto auto !important;
  column-gap:18px !important;row-gap:10px !important;
  align-items:start !important;
}
html[${attr}="list"] .yt-lockup-view-model__content-image{
  grid-column:1 !important;grid-row:1 / span 2 !important;
  width:${thumbW}px !important;min-width:${thumbW}px !important;max-width:${thumbW}px !important;
}
html[${attr}="list"] .yt-lockup-view-model__content-image img{
  width:100% !important;height:auto !important;border-radius:${radius}px !important;
  object-fit:cover !important;display:block !important;
}
html[${attr}="list"] .yt-lockup-view-model__metadata{
  grid-column:2 !important;grid-row:1 / span 2 !important;min-width:0 !important;
}
html[${attr}="list"] .yt-lockup-metadata-view-model__menu-button{
  grid-column:3 !important;grid-row:1 !important;justify-self:end !important;align-self:start !important;
}

html[${attr}="list"] .yt-lockup-metadata-view-model__title{
  display:-webkit-box !important;-webkit-box-orient:vertical !important;-webkit-line-clamp:${titleClamp} !important;
  overflow:hidden !important;white-space:normal !important;line-height:1.35 !important;font-weight:600 !important;
}

html[${attr}="list"] .yt-lockup-view-model__metadata .yt-lockup-metadata-view-model__avatar{ display:none !important; }

html[${attr}="list"] .yt-lockup-metadata-view-model__metadata yt-content-metadata-view-model{
  display:block !important;
  position:absolute !important;
  left:-99999px !important;
  top:auto !important;
  width:1px !important;
  height:1px !important;
  overflow:hidden !important;
  opacity:0 !important;
  pointer-events:none !important;
}

html[${attr}="list"] .${CFG.cls.metaRow}{
  display:flex !important;align-items:center !important;
  gap:${metaGap}px !important;margin-top:6px !important;min-width:0 !important;
}
html[${attr}="list"] .${CFG.cls.metaCh}{
  min-width:0 !important;
  color:var(--yt-spec-text-secondary) !important;
  font-size:12px !important;line-height:1.35 !important;
  white-space:nowrap !important;overflow:hidden !important;text-overflow:ellipsis !important;
  flex:0 1 auto !important;
  display:flex !important;
  align-items:center !important;
}
html[${attr}="list"] .${CFG.cls.metaCh} a{
  color:inherit !important;
  text-decoration:none !important;
  display:inline-flex !important;
  align-items:center !important;
  min-width:0 !important;
}
html[${attr}="list"] .${CFG.cls.metaCh} a > span{
  display:inline-flex !important;
  align-items:center !important;
}
html[${attr}="list"] .${CFG.cls.metaCh} .yt-icon-shape.ytSpecIconShapeHost{
  margin-right:2px !important;
}

html[${attr}="list"] .${CFG.cls.metaRt}{
  color:var(--yt-spec-text-secondary) !important;
  font-size:12px !important;line-height:1.35 !important;
  white-space:nowrap !important;
  opacity:.95 !important;
  flex:0 0 auto !important;
}

html[${attr}="list"] .${CFG.cls.desc}{
  margin-top:${descMt}px !important;
  font-size:12px !important;line-height:1.45 !important;
  color:var(--yt-spec-text-secondary) !important;
  display:-webkit-box !important;-webkit-box-orient:vertical !important;-webkit-line-clamp:${descClamp} !important;
  overflow:hidden !important;white-space:normal !important;
}

html[${attr}="list"] .${CFG.cls.desc}.${CFG.cls.descSkel}{
  display:${S.enabled ? "block" : "none"} !important;
  -webkit-line-clamp:unset !important;
  overflow:hidden !important;
  color:transparent !important;
  position:relative !important;
}

html[${attr}="list"] .${CFG.cls.desc}.${CFG.cls.descSkel} > span{
  display:block !important;
  border-radius:${S.r}px !important;
  opacity:1 !important;
  pointer-events:none !important;

  background-color:var(--yslv-skel-base, rgba(0,0,0,.10)) !important;
  background-image:linear-gradient(
    90deg,
    var(--yslv-skel-base, rgba(0,0,0,.10)) 0%,
    var(--yt-spec-10-percent-layer, rgba(255,255,255,.20)) 50%,
    var(--yslv-skel-base, rgba(0,0,0,.10)) 100%
  ) !important;
  background-size:220% 100% !important;
  background-position:var(${CFG.cssVars.shimmerX}, 200%) 0 !important;

  transform:translateZ(0) !important;
  will-change:background-position !important;
}

${skLineRules}

html[${attr}="list"]{
  ${CFG.cssVars.shortW}:${shortW}px;
}

html[${attr}="list"] ytd-rich-item-renderer.${CFG.cls.isShort} #content{
  width:var(${CFG.cssVars.shortW}) !important;
}

html[${attr}="list"] ytd-rich-item-renderer.${CFG.cls.isShort} a.reel-item-endpoint,
html[${attr}="list"] ytd-rich-item-renderer.${CFG.cls.isShort} a.shortsLockupViewModelHostEndpoint.reel-item-endpoint{
  width:var(${CFG.cssVars.shortW}) !important;
  display:block !important;
}

html[${attr}="list"] ytd-rich-item-renderer.${CFG.cls.isShort} yt-thumbnail-view-model,
html[${attr}="list"] ytd-rich-item-renderer.${CFG.cls.isShort} .ytThumbnailViewModelImage,
html[${attr}="list"] ytd-rich-item-renderer.${CFG.cls.isShort} .shortsLockupViewModelHostThumbnailParentContainer{
  width:var(${CFG.cssVars.shortW}) !important;
  aspect-ratio:2/3 !important;
}

html[${attr}="list"] ytd-rich-item-renderer.${CFG.cls.isShort} img.ytCoreImageHost{
  width:100% !important;
  height:100% !important;
  object-fit:cover !important;
  border-radius:${radius}px !important;
  display:block !important;
}

html[data-yslv-subs-view="list"]
ytd-rich-shelf-renderer #dismissible{
  margin-top:2rem !important;
}
html[data-yslv-subs-view="list"]
ytd-rich-item-renderer.yslv-is-short > #content{
  padding-bottom:3rem !important;
}

html[dark][${attr}="list"]{ --yslv-sep: rgba(255,255,255,.18); --yslv-skel-base: rgba(255,255,255,.10); }
html:not([dark])[${attr}="list"]{ --yslv-sep: rgba(0,0,0,.10); --yslv-skel-base: rgba(0,0,0,.10); }

/* "Notify Me" buttons for upcoming videos are full width fix by: https://greasyfork.org/en/users/1568138-boxfriend */
html[${attr}="list"] .ytLockupAttachmentsViewModelHost{
  width: fit-content !important;
}

html[${attr}="list"]
ytd-counterfactual-renderer.ytd-rich-section-renderer,
html[${attr}="list"]
#content.ytd-rich-section-renderer > ytd-rich-list-header-renderer.ytd-rich-section-renderer,
html[${attr}="list"]
#content.ytd-rich-section-renderer > ytd-shelf-renderer.ytd-rich-section-renderer,
html[${attr}="list"]
#content.ytd-rich-section-renderer > ytd-rich-shelf-renderer.ytd-rich-section-renderer,
html[${attr}="list"]
#content.ytd-rich-section-renderer > ytd-inline-survey-renderer.ytd-rich-section-renderer[is-dismissed]{
  margin-bottom:1rem !important;
}
`.trim()

    document.documentElement.appendChild(el)
    STATE.styleEl = el
  }

  function ensureToggle() {
    const existing = document.getElementById(CFG.ids.toggle)
    if (existing && existing.isConnected) {
      paintToggle()
      return
    }

    const subscribeBtn = getActiveSubsDoc().querySelector(CFG.toggleMountSelector)
    const titleContainer = subscribeBtn?.closest?.("#title-container") || null
    if (!subscribeBtn || !titleContainer) return

    document.querySelectorAll(`#${CFG.ids.toggle}`).forEach(n => n.remove())

    const root = document.createElement("div")
    root.id = CFG.ids.toggle

    const mkBtn = (mode, label, svg) => {
      const b = document.createElement("button")
      b.className = CFG.cls.btn
      b.type = "button"
      b.setAttribute("data-mode", mode)
      b.setAttribute("aria-label", label)

      const ic = document.createElement("span")
      ic.className = CFG.cls.btnIcon
      ic.appendChild(svg)
      b.appendChild(ic)

      return b
    }

    const bGrid = mkBtn("grid", "Grid", svgEl(["M4 4h7v7H4V4zm9 0h7v7h-7V4zM4 13h7v7H4v-7zm9 0h7v7h-7v-7z"]))
    const bList = mkBtn(
      "list",
      "List",
      svgEl(["M4 6h3v3H4V6zm5 0h11v3H9V6zM4 11h3v3H4v-3zm5 0h11v3H9v-3zM4 16h3v3H4v-3zm5 0h11v3H9v-3z"])
    )

    root.appendChild(bGrid)
    root.appendChild(bList)

    root.addEventListener("click", e => {
      const btn = e.target?.closest?.("button[data-mode]")
      if (!btn) return
      const mode = btn.getAttribute("data-mode")
      if (mode !== "grid" && mode !== "list") return
      if (mode === STATE.view) return

      if (STATE.view === "list") cleanupListArtifacts()
      resetNavState()
      applyViewAttr(mode)
      attachObserver()
      ensureDescQueueLoop()
      if (mode === "list") {
        enqueueAllOnce()
        startShimmer()
      } else {
        stopShimmer()
      }
    })

    subscribeBtn.insertAdjacentElement("afterend", root)
    paintToggle()
  }

  function removeToggle() {
    const root = document.getElementById(CFG.ids.toggle)
    if (root) root.remove()
  }

  function paintToggle() {
    const root = document.getElementById(CFG.ids.toggle)
    if (!root) return
    root.querySelectorAll("button[data-mode]").forEach(b => {
      const m = b.getAttribute("data-mode")
      if (m === STATE.view) b.setAttribute("data-active", "")
      else b.removeAttribute("data-active")
    })
  }

  function pickChannelDisplaySource(lockup) {
    const a =
      lockup.querySelector('yt-content-metadata-view-model .yt-content-metadata-view-model__metadata-row a[href^="/@"]') ||
      lockup.querySelector('yt-content-metadata-view-model .yt-content-metadata-view-model__metadata-row a[href^="/channel/"]') ||
      lockup.querySelector('a[href^="/@"]') ||
      lockup.querySelector('a[href^="/channel/"]') ||
      null

    if (a) return a

    return (
      lockup.querySelector(
        'yt-content-metadata-view-model .yt-content-metadata-view-model__metadata-row span.yt-content-metadata-view-model__metadata-text'
      ) || null
    )
  }

  function pickChannelAnchor(lockup) {
    return (
      lockup.querySelector('yt-content-metadata-view-model .yt-content-metadata-view-model__metadata-row a[href^="/@"]') ||
      lockup.querySelector(
        'yt-content-metadata-view-model .yt-content-metadata-view-model__metadata-row a[href^="/channel/"]'
      ) ||
      lockup.querySelector('a[href^="/@"]') ||
      lockup.querySelector('a[href^="/channel/"]') ||
      null
    )
  }

  function getChannelHref(lockup) {
    const a = pickChannelAnchor(lockup)
    const href = String(a?.getAttribute?.("href") || "").trim()
    if (!href) return ""
    try {
      return new URL(href, location.origin).href
    } catch {
      return ""
    }
  }

  function getChannelName(lockup) {
    const src = pickChannelDisplaySource(lockup)
    return normalizeText(src?.textContent || "")
  }

  function isIconish(node) {
    if (!node || node.nodeType !== 1) return false
    if (node.matches("yt-icon-shape, .yt-icon-shape")) return true
    if (node.querySelector("yt-icon-shape, .yt-icon-shape")) return true
    if (node.querySelector("svg, img")) return true
    if (node.getAttribute("role") === "img") return true
    if (node.querySelector('[role="img"]')) return true
    return false
  }

  function collectBadgeNodesFromAnchor(a) {
    const out = []
    if (!a) return out

    const candidates = a.querySelectorAll(
      ".yt-core-attributed-string__image-element, .ytIconWrapperHost, .yt-core-attributed-string__image-element--image-alignment-vertical-center, yt-icon-shape, .yt-icon-shape"
    )

    const seen = new Set()
    for (const el of candidates) {
      if (!el) continue
      let root =
        el.closest(".yt-core-attributed-string__image-element") ||
        el.closest(".ytIconWrapperHost") ||
        el.closest(".yt-core-attributed-string__image-element--image-alignment-vertical-center") ||
        el

      if (!root || root === a) continue
      if (!isIconish(root)) continue

      const key =
        root.tagName + "|" + (root.getAttribute("class") || "") + "|" + (root.getAttribute("aria-label") || "")
      if (seen.has(key)) continue
      seen.add(key)
      out.push(root)
    }

    return out
  }

  function normalizeMetaAnchorInPlace(a, nameText) {
    if (!a) return
    const name = normalizeText(nameText || "")
    if (!name) return

    const badgeRoots = collectBadgeNodesFromAnchor(a)
    const badges = []

    for (const r of badgeRoots) {
      if (!r || !r.isConnected) continue
      badges.push(r)
    }

    for (const b of badges) {
      try {
        if (b.parentNode) b.parentNode.removeChild(b)
      } catch {}
    }

    clearChildren(a)
    a.appendChild(document.createTextNode(name))

    for (const b of badges) {
      if (!isIconish(b)) continue
      const wrap = document.createElement("span")
      wrap.style.display = "inline-flex"
      wrap.style.alignItems = "center"
      wrap.style.marginLeft = "4px"
      wrap.appendChild(b)
      a.appendChild(wrap)
    }

    for (const s of Array.from(a.querySelectorAll(":scope > span"))) {
      if (!s.querySelector || !isIconish(s)) s.remove()
    }
  }

  function detachMetaAnchorOnce(lockup) {
    if (!lockup) return null
    if (STATE.movedMetaAnchors.has(lockup)) return STATE.movedMetaAnchors.get(lockup)?.a || null

    const a = pickChannelAnchor(lockup)
    if (!a || !a.parentNode) return null

    const parent = a.parentNode
    const nextSibling = a.nextSibling
    STATE.movedMetaAnchors.set(lockup, { a, parent, nextSibling })
    return a
  }

  function restoreMovedMetaAnchors() {
    const entries = []
    document.querySelectorAll("yt-lockup-view-model").forEach(lockup => {
      const info = STATE.movedMetaAnchors.get(lockup)
      if (!info) return
      entries.push(info)
    })

    for (const info of entries) {
      const { a, parent, nextSibling } = info
      if (!a || !parent) continue
      if (!a.isConnected) continue
      if (a.parentNode === parent) continue
      try {
        if (nextSibling && nextSibling.parentNode === parent) parent.insertBefore(a, nextSibling)
        else parent.appendChild(a)
      } catch {}
    }

    STATE.movedMetaAnchors = new WeakMap()
  }

  function setHeaderNameTextOnly(destLink, lockup) {
    if (!destLink) return
    const href = getChannelHref(lockup)
    destLink.href = href || "javascript:void(0)"

    const src = pickChannelDisplaySource(lockup)
    setTextOnly(destLink, src?.textContent || "")
  }

  function moveAvatarToHeaderOnce(item, lockup, head) {
    if (!item || !lockup || !head) return null
    if (STATE.movedAvatars.has(item)) return STATE.movedAvatars.get(item)?.avatarEl || null

    const avatarEl = lockup.querySelector(".yt-lockup-metadata-view-model__avatar")
    if (!avatarEl || !avatarEl.parentNode) return null

    const parent = avatarEl.parentNode
    const nextSibling = avatarEl.nextSibling
    STATE.movedAvatars.set(item, { avatarEl, parent, nextSibling })

    try {
      head.insertBefore(avatarEl, head.firstChild)
    } catch {}

    return avatarEl
  }

  function ensureRowHeader(item, lockup) {
    if (!CFG.list.rowHead.enabled) return

    let head = item.querySelector(`:scope > .${CFG.cls.rowHead}`)
    if (!head) {
      head = document.createElement("div")
      head.className = CFG.cls.rowHead
      item.prepend(head)
    }

    head.style.display = "flex"

    let name = head.querySelector(`:scope > a.${CFG.cls.rowHeadName}`)
    if (!name) {
      name = document.createElement("a")
      name.className = CFG.cls.rowHeadName
      head.appendChild(name)
    }

    setHeaderNameTextOnly(name, lockup)
    moveAvatarToHeaderOnce(item, lockup, head)
  }

  function getRightMetaRowsText(lockup) {
    const chName = getChannelName(lockup)
    const rows = Array.from(
      lockup.querySelectorAll("yt-content-metadata-view-model .yt-content-metadata-view-model__metadata-row")
    )
      .map(r => normalizeText(r.textContent || ""))
      .filter(Boolean)
      .filter(t => (chName ? t !== chName : true))

    if (!rows.length) return ""

    const out = []
    const seen = new Set()
    for (const t of rows) {
      const k = t.toLowerCase()
      if (seen.has(k)) continue
      seen.add(k)
      out.push(t)
    }

    if (!out.length) return ""
    if (out.length === 1) {
      if (chName && out[0] === chName) return ""
      return out[0]
    }

    return out.slice(1).join(" • ")
  }

  function ensureInlineMeta(textContainer, lockup) {
    let row = textContainer.querySelector(`.${CFG.cls.metaRow}`)
    if (!row) {
      row = document.createElement("div")
      row.className = CFG.cls.metaRow

      const heading =
        textContainer.querySelector(".yt-lockup-metadata-view-model__heading-reset") || textContainer.querySelector("h3")
      if (heading && heading.parentNode) heading.parentNode.insertBefore(row, heading.nextSibling)
      else textContainer.appendChild(row)
    }

    row.style.display = "flex"

    let left = row.querySelector(`:scope > .${CFG.cls.metaCh}`)
    if (!left) {
      left = document.createElement("div")
      left.className = CFG.cls.metaCh
      row.appendChild(left)
    }

    const srcA = detachMetaAnchorOnce(lockup)
    const chName = getChannelName(lockup)

    if (srcA) {
      try {
        srcA.style.margin = "0"
      } catch {}
      normalizeMetaAnchorInPlace(srcA, chName)
      clearChildren(left)
      left.appendChild(srcA)
    } else {
      let link = left.querySelector("a")
      if (!link) {
       link = document.createElement("a")
        left.appendChild(link)
      }

      link.href = getChannelHref(lockup) || "javascript:void(0)"

      const src = pickChannelDisplaySource(lockup)
      if (src) {
        cloneInto(link, src)
      } else {
        setTextOnly(link, chName || "")
      }
    }

    const right = getRightMetaRowsText(lockup)
    let r = row.querySelector(`:scope > .${CFG.cls.metaRt}`)
    if (right) {
      if (!r) {
        r = document.createElement("div")
        r.className = CFG.cls.metaRt
        row.appendChild(r)
      }
      r.textContent = right
      r.style.display = ""
    } else if (r) {
      r.textContent = ""
      r.style.display = "none"
    }

    return row
  }

  function pickPrimaryVideoAnchor(lockup) {
    return (
      lockup.querySelector('a.yt-lockup-view-model__content-image[href^="/watch"]') ||
      lockup.querySelector('a.yt-lockup-view-model__content-image[href^="/shorts/"]') ||
      lockup.querySelector('a[href^="/watch"][id="thumbnail"]') ||
      lockup.querySelector('a[href^="/shorts/"][id="thumbnail"]') ||
      lockup.querySelector('a[href^="/shorts/"].reel-item-endpoint') ||
      lockup.querySelector('a[href^="/watch"]') ||
      lockup.querySelector('a[href^="/shorts/"]') ||
      null
    )
  }

  function isShortsHref(href) {
    const h = String(href || "")
    return h.startsWith("/shorts/") || h.includes("youtube.com/shorts/")
  }

  function extractVideoIdFromHref(href) {
    const h = String(href || "")
    if (!h) return ""

    if (isShortsHref(h)) {
      try {
        const u = new URL(h, location.origin)
        const parts = u.pathname.split("/").filter(Boolean)
        const idx = parts.indexOf("shorts")
        const id = idx >= 0 ? String(parts[idx + 1] || "") : ""
        return id
      } catch {
        const m = h.match(/\/shorts\/([^?&#/]+)/)
        return m ? m[1] : ""
      }
    }

    try {
      const u = new URL(h, location.origin)
      return u.searchParams.get("v") || ""
    } catch {
      const m = h.match(/[?&]v=([^&]+)/)
      return m ? m[1] : ""
    }
  }

  function ensureDesc(textContainer, lockup) {
    let desc = textContainer.querySelector(`.${CFG.cls.desc}`)
    if (!desc) {
      desc = document.createElement("div")
      desc.className = CFG.cls.desc
      textContainer.appendChild(desc)
    }

    const vLink = pickPrimaryVideoAnchor(lockup)
    const href = vLink?.getAttribute?.("href") || ""
    const vid = extractVideoIdFromHref(href)
    if (!vid) {
      desc.textContent = ""
      desc.style.display = "none"
      desc.classList.remove(CFG.cls.descSkel)
      delete desc.dataset.yslvVid
      return
    }

    desc.dataset.yslvVid = vid

    const mem = STATE.descCache.get(vid)
    if (mem != null) {
      desc.textContent = mem
      desc.style.display = mem ? "" : "none"
      desc.classList.remove(CFG.cls.descSkel)
      return
    }

    const stored = getStoredDesc(vid)
    if (stored != null) {
      STATE.descCache.set(vid, stored)
      desc.textContent = stored
      desc.style.display = stored ? "" : "none"
      desc.classList.remove(CFG.cls.descSkel)
      return
    }

    const S = skNorm()
    if (!S.enabled) {
      desc.textContent = ""
      desc.style.display = "none"
      desc.classList.remove(CFG.cls.descSkel)
      return
    }

    desc.style.display = ""
    desc.classList.add(CFG.cls.descSkel)

    const needs = desc.childElementCount !== S.lines || !desc.querySelector(":scope > span")
    if (needs) {
      clearChildren(desc)
      for (let i = 0; i < S.lines; i++) desc.appendChild(document.createElement("span"))
    }
  }

  function summarizeDesc(raw, sentenceCount, maxChars) {
    let s = String(raw || "").trim()
    if (!s) return ""

    s = s.replace(/\r/g, "").replace(/\n{2,}/g, "\n").replace(/[ \t]{2,}/g, " ").trim()

    const seg =
      typeof Intl !== "undefined" && Intl.Segmenter ? new Intl.Segmenter(undefined, { granularity: "sentence" }) : null
    if (seg) {
      const out = []
      for (const part of seg.segment(s)) {
        const t = String(part.segment || "").trim()
        if (!t) continue
        out.push(t)
        if (out.length >= sentenceCount) break
      }
      s = out.join(" ").trim()
    } else {
      const urls = []
      s = s.replace(/\bhttps?:\/\/[^\s]+|\bwww\.[^\s]+/gi, m => {
        const k = `__YSU${urls.length}__`
        urls.push(m)
        return k
      })

      const parts = s.split(/(?<=[.!?])\s+/).map(x => x.trim()).filter(Boolean)
      s = parts.slice(0, sentenceCount).join(" ").trim()

      s = s.replace(/__YSU(\d+)__/g, (_, i) => urls[Number(i)] || "")
    }

    if (s.length > maxChars) s = s.slice(0, maxChars).trimEnd() + "…"
    return s
  }

  async function fetchDescriptionForVideoId(vid) {
    const F = CFG.list.descFetch
    if (!F.enabled) return ""
    if (!vid) return ""

    const mem = STATE.descCache.get(vid)
    if (mem != null) return mem

    const stored = getStoredDesc(vid)
    if (stored != null) {
      STATE.descCache.set(vid, stored)
      return stored
    }

    if (STATE.descInFlight.has(vid)) return STATE.descInFlight.get(vid)
    if (STATE.descFetches >= F.maxTotalFetchesPerNav) return ""

    const p = (async () => {
      while (STATE.descActive >= F.maxConcurrent) {
        await new Promise(r => setTimeout(r, 35))
      }
      STATE.descActive++
      STATE.descFetches++
      try {
        const res = await fetch(`https://www.youtube.com/watch?v=${encodeURIComponent(vid)}`, {
          credentials: "same-origin",
        })
        const html = await res.text()
        const m = html.match(/ytInitialPlayerResponse\s*=\s*(\{.*?\});/s)
        if (!m) return ""
        const json = JSON.parse(m[1])
        const raw = String(json?.videoDetails?.shortDescription || "").trim()
        if (!raw) return ""
        return summarizeDesc(raw, F.sentenceCount, F.maxChars)
      } catch {
        return ""
      } finally {
        STATE.descActive--
      }
    })()

    STATE.descInFlight.set(vid, p)
    const out = await p
    STATE.descInFlight.delete(vid)

    STATE.descCache.set(vid, out)
    setStoredDesc(vid, out)

    return out
  }

  function updateDescDomForVid(vid, text) {
    const nodes = document.querySelectorAll(`.${CFG.cls.desc}[data-yslv-vid="${CSS.escape(vid)}"]`)
    for (const n of nodes) {
      if (!n || !n.isConnected) continue
      n.classList.remove(CFG.cls.descSkel)
      clearChildren(n)
      n.textContent = text || ""
      n.style.display = text ? "" : "none"
    }
  }

  function buildDescQueueFromDom() {
    if (!STATE.active || STATE.view !== "list") return
    const root = getActiveSubsRoot()
    const scope = root && root.querySelectorAll ? root : document
    const descs = scope.querySelectorAll(`.${CFG.cls.desc}[data-yslv-vid]`)
    if (!descs.length) return

    let sig = ""
    for (const d of descs) {
      const vid = d?.dataset?.yslvVid || ""
      if (!vid) continue
      sig += vid + "|"
    }
    if (sig === STATE.lastQueueSig) return
    STATE.lastQueueSig = sig

    for (const d of descs) {
      const vid = d?.dataset?.yslvVid || ""
      if (!vid) continue

      const stored = getStoredDesc(vid)
      if (stored != null) {
        STATE.descCache.set(vid, stored)
        updateDescDomForVid(vid, stored)
        continue
      }

      if (STATE.descCache.has(vid)) continue
      if (STATE.descInFlight.has(vid)) continue
      if (STATE.descQueued.has(vid)) continue
      STATE.descQueued.add(vid)
      STATE.descQueue.push(vid)
    }

    pumpDescQueue()
  }

  async function pumpDescQueue() {
    if (STATE.descPumpRunning) return
    STATE.descPumpRunning = true
    try {
      while (STATE.active && STATE.view === "list" && STATE.descQueue.length) {
        const vid = STATE.descQueue.shift()
        if (!vid) continue
        STATE.descQueued.delete(vid)

        const stored = getStoredDesc(vid)
        if (stored != null) {
          STATE.descCache.set(vid, stored)
          updateDescDomForVid(vid, stored)
          continue
        }

        if (STATE.descCache.has(vid)) {
          updateDescDomForVid(vid, STATE.descCache.get(vid) || "")
          continue
        }

        const txt = await fetchDescriptionForVideoId(vid)
        updateDescDomForVid(vid, txt || "")
      }
    } finally {
      STATE.descPumpRunning = false
    }
  }

  function hasSkeletons() {
    return !!document.querySelector(`.${CFG.cls.desc}.${CFG.cls.descSkel}`)
  }

  function stopShimmer() {
    SHIMMER.running = false
    if (SHIMMER.raf) cancelAnimationFrame(SHIMMER.raf)
    SHIMMER.raf = 0
    document.documentElement.style.removeProperty(CFG.cssVars.shimmerX)
  }

  function startShimmer() {
    if (SHIMMER.running) return
    SHIMMER.running = true
    SHIMMER.t0 = performance.now()

    const tick = t => {
      if (!SHIMMER.running) return

      const S = skNorm()
      if (!STATE.active || STATE.view !== "list" || !S.enabled || !hasSkeletons()) {
        stopShimmer()
        return
      }

      const phase = ((t - SHIMMER.t0) % S.ms) / S.ms
      const x = 200 - phase * 400
      document.documentElement.style.setProperty(CFG.cssVars.shimmerX, `${x}%`)
      SHIMMER.raf = requestAnimationFrame(tick)
    }

    SHIMMER.raf = requestAnimationFrame(tick)
  }

  function ensureDescQueueLoop() {
    if (STATE.descTimer) clearInterval(STATE.descTimer)
    if (!STATE.active) return
    STATE.descTimer = setInterval(() => {
      if (!STATE.active || STATE.view !== "list") {
        stopShimmer()
        return
      }
      pruneDescStore()
      buildDescQueueFromDom()
      if (hasSkeletons()) startShimmer()
      else stopShimmer()
    }, CFG.perf.descQueueIntervalMs)
  }

  function patchItem(item) {
    if (!STATE.active || STATE.view !== "list") return
    if (!item || item.nodeType !== 1) return
    if (item.tagName !== "YTD-RICH-ITEM-RENDERER") return
    if (STATE.processedItems.has(item)) return

    const shortsLockup = item.querySelector("ytm-shorts-lockup-view-model-v2, ytm-shorts-lockup-view-model")
    if (shortsLockup && CFG.list.shorts.enabled) {
      STATE.processedItems.add(item)
      item.classList.add(CFG.cls.isShort)
      return
    }

    item.classList.remove(CFG.cls.isShort)

    const lockup = item.querySelector("yt-lockup-view-model")
    if (!lockup) return

    const textContainer =
      lockup.querySelector(".yt-lockup-metadata-view-model__text-container") ||
      lockup.querySelector("yt-lockup-metadata-view-model")
    if (!textContainer) return

    STATE.processedItems.add(item)

    ensureRowHeader(item, lockup)
    ensureInlineMeta(textContainer, lockup)
    ensureDesc(textContainer, lockup)
  }

  function enqueue(node) {
    if (!STATE.active || STATE.view !== "list") return
    if (!node || node.nodeType !== 1) return

    if (node.tagName === "YTD-RICH-ITEM-RENDERER") {
      if (STATE.qSet.has(node)) return
      STATE.qSet.add(node)
      STATE.q.push(node)
      scheduleProcess()
      return
    }

    const found = node.querySelectorAll ? node.querySelectorAll("ytd-rich-item-renderer") : []
    if (found && found.length) {
      for (const it of found) enqueue(it)
    }
  }

  function scheduleProcess() {
    if (STATE.processing) return
    STATE.processing = true

    const run = () => {
      STATE.processing = false
      processQueue()
    }

    if (window.requestIdleCallback) requestIdleCallback(run, { timeout: 300 })
    else setTimeout(run, 80)
  }

  function processQueue() {
    if (!STATE.active || STATE.view !== "list") {
      STATE.q.length = 0
      STATE.qSet.clear()
      return
    }

    let n = 0
    while (STATE.q.length && n < CFG.perf.maxItemsPerTick) {
      const item = STATE.q.shift()
      STATE.qSet.delete(item)
      patchItem(item)
      n++
    }

    buildDescQueueFromDom()

    if (STATE.q.length) scheduleProcess()
  }

  function enqueueAllOnce() {
    if (!STATE.active || STATE.view !== "list") return
    const root = getActiveSubsRoot()
    const scope = root && root.querySelectorAll ? root : document
    const items = scope.querySelectorAll ? scope.querySelectorAll("ytd-rich-item-renderer") : []
    for (const it of items) enqueue(it)
  }

  function attachObserver() {
    if (!STATE.active) return
    const target = getActiveSubsRoot() || document.documentElement
    if (STATE.observedTarget === target && STATE.mo) return

    if (STATE.mo) STATE.mo.disconnect()
    STATE.observedTarget = target

    STATE.mo = new MutationObserver(muts => {
      if (!STATE.active || STATE.view !== "list") return
      for (const m of muts) {
        for (const node of m.addedNodes) enqueue(node)
      }
    })

    STATE.mo.observe(target, { childList: true, subtree: true })
  }

  function attachPageManagerObserver() {
    if (STATE.pmMo) return
    const pm = document.querySelector("ytd-page-manager")
    if (!pm) return

    STATE.pmMo = new MutationObserver(() => {
      if (!STATE.active) return
      attachObserver()
      ensureToggleMountLoop()
      if (STATE.view === "list") {
        setTimeout(() => {
          if (!STATE.active || STATE.view !== "list") return
          enqueueAllOnce()
        }, 60)
      }
    })

    STATE.pmMo.observe(pm, { childList: true, subtree: true })
  }

  function restoreMovedAvatars() {
    document.querySelectorAll("ytd-rich-item-renderer").forEach(item => {
      const info = STATE.movedAvatars.get(item)
      if (!info) return
      const { avatarEl, parent, nextSibling } = info
      if (!avatarEl || !parent) return
      if (!avatarEl.isConnected) return
      if (avatarEl.parentNode === parent) return
      try {
        if (nextSibling && nextSibling.parentNode === parent) parent.insertBefore(avatarEl, nextSibling)
        else parent.appendChild(avatarEl)
      } catch {}
    })
    STATE.movedAvatars = new WeakMap()
  }

  function cleanupListArtifacts() {
    restoreMovedAvatars()
    restoreMovedMetaAnchors()
    document.querySelectorAll(`.${CFG.cls.rowHead}`).forEach(n => n.remove())
    document.querySelectorAll(`.${CFG.cls.metaRow}`).forEach(n => n.remove())
    document.querySelectorAll(`.${CFG.cls.desc}`).forEach(n => n.remove())
    STATE.descQueue.length = 0
    STATE.descQueued.clear()
    STATE.lastQueueSig = ""
  }

  function resetNavState() {
    STATE.processedItems = new WeakSet()
    STATE.q.length = 0
    STATE.qSet.clear()

    STATE.descInFlight.clear()
    STATE.descCache.clear()
    STATE.descFetches = 0
    STATE.descActive = 0

    STATE.descQueue.length = 0
    STATE.descQueued.clear()
    STATE.descPumpRunning = false
    STATE.lastQueueSig = ""

    STATE.observedTarget = null
  }

  function teardown() {
    stopShimmer()
    if (STATE.view === "list") cleanupListArtifacts()
    if (STATE.mo) {
      STATE.mo.disconnect()
      STATE.mo = null
    }
    STATE.observedTarget = null
    if (STATE.descTimer) {
      clearInterval(STATE.descTimer)
      STATE.descTimer = 0
    }
    resetNavState()
    removeToggle()
    clearViewAttr()
  }

  function ensureToggleMountLoop() {
    if (!STATE.active) return
    ensureToggle()
    if (STATE.active && !document.getElementById(CFG.ids.toggle)) setTimeout(ensureToggleMountLoop, 250)
  }

  function pageSig() {
    return `${location.pathname}|${location.search}|${document.querySelector("ytd-page-manager") ? "pm" : "nopm"}`
  }

  function apply() {
    ensureDescStoreLoaded()
    pruneDescStore()
    ensureStyle()
    ensureToggleMountLoop()
    attachObserver()
    attachPageManagerObserver()
    ensureDescQueueLoop()
    if (STATE.view === "list") {
      enqueueAllOnce()
      startShimmer()
    } else {
      stopShimmer()
    }
  }

  function syncActive(isNavFinish) {
    const shouldBeActive = isSubsPage()
    const sig = pageSig()

    if (shouldBeActive && !STATE.active) {
      STATE.active = true
      STATE.lastPageSig = sig
      applyViewAttr(loadView())
      apply()
      return
    }

    if (!shouldBeActive && STATE.active) {
      STATE.active = false
      STATE.lastPageSig = sig
      teardown()
      return
    }

    if (shouldBeActive && STATE.active) {
      ensureToggleMountLoop()
      paintToggle()
      attachObserver()

      if (STATE.view === "list") {
        if (isNavFinish && sig !== STATE.lastPageSig) {
          STATE.lastPageSig = sig
          resetNavState()
          enqueueAllOnce()
          startShimmer()
        }
      } else {
        stopShimmer()
      }
    }
  }

  function init() {
    syncActive(true)

    window.addEventListener(
      "yt-navigate-finish",
      () => {
        syncActive(true)
      },
      { passive: true }
    )

    window.addEventListener(
      "popstate",
      () => {
        syncActive(true)
      },
      { passive: true }
    )

    setTimeout(() => {
      attachPageManagerObserver()
    }, 250)
  }

  init()
})()