YouTube Subscription List View

Restore YouTube Subscription List View

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         YouTube Subscription List View
// @namespace    xyz.flaflo.yslv
// @version      1.4.5
// @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: 1120, // or for non centered "90%"
      rowPadY: 22,
      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()
})()