Restore YouTube Subscription List View
// ==UserScript==
// @name YouTube Subscription List View
// @namespace xyz.flaflo.yslv
// @version 2.0.1
// @author Flaflo
// @license MIT
// @homepageURL https://github.com/Flaflo/yslv-userscript
// @icon https://flaflo.xyz/yslv/icon-128.png
// @icon64 https://flaflo.xyz/yslv/icon-64.png
// @description Restore YouTube Subscription List View
// @match https://www.youtube.com/*
// @run-at document-end
// @grant none
// ==/UserScript==
(function() {
"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 * 1e3,
maxEntries: 1200,
saveDebounceMs: 250
},
list: {
maxWidth: 1320,
// or "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: 5e3
}
},
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"
}
};
function createState() {
return {
active: false,
view: "grid",
styleEl: null,
q: [],
qSet: /* @__PURE__ */ new Set(),
processing: false,
processedItems: /* @__PURE__ */ new WeakSet(),
movedAvatars: /* @__PURE__ */ new WeakMap(),
movedMetaAnchors: /* @__PURE__ */ new WeakMap(),
mo: null,
observedTarget: null,
pmMo: null,
descCache: /* @__PURE__ */ new Map(),
descInFlight: /* @__PURE__ */ new Map(),
descFetches: 0,
descActive: 0,
descQueue: [],
descQueued: /* @__PURE__ */ new Set(),
descTimer: 0,
descPumpRunning: false,
lastQueueSig: "",
lastPageSig: ""
};
}
function createShimmerState() {
return {
raf: 0,
running: false,
t0: 0
};
}
function createDescStoreState() {
return {
obj: null,
dirty: false,
saveT: 0
};
}
function isViewMode(v) {
return v === "grid" || v === "list";
}
function loadView(cfg) {
try {
const v = localStorage.getItem(cfg.storageKey);
return isViewMode(v) ? v : cfg.defaultView;
} catch {
return cfg.defaultView;
}
}
function saveView(cfg, v) {
try {
localStorage.setItem(cfg.storageKey, v);
} catch {
}
}
function applyViewAttr(cfg, v) {
document.documentElement.setAttribute(cfg.attr.view, v);
}
function clearViewAttr(cfg) {
document.documentElement.removeAttribute(cfg.attr.view);
}
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 pageSig() {
return `${location.pathname}|${location.search}|${document.querySelector("ytd-page-manager") ? "pm" : "nopm"}`;
}
function nowMs() {
return Date.now();
}
function ensureDescStoreLoaded(cfg, st) {
if (st.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 = {};
}
}
st.obj = obj;
pruneDescStore(cfg, st);
}
function scheduleDescStoreSave(cfg, st) {
if (st.saveT) return;
st.saveT = window.setTimeout(() => {
st.saveT = 0;
if (!st.dirty) return;
st.dirty = false;
try {
localStorage.setItem(cfg.descStore.key, JSON.stringify(st.obj || {}));
} catch {
}
}, Math.max(0, Number(cfg.descStore.saveDebounceMs) || 250));
}
function pruneDescStore(cfg, st) {
ensureDescStoreLoaded(cfg, st);
const ttl = Math.max(1, Number(cfg.descStore.ttlMs) || 36e5);
const maxEntries = Math.max(50, Number(cfg.descStore.maxEntries) || 1200);
const tNow = nowMs();
const obj = st.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];
st.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]];
st.dirty = true;
}
}
if (st.dirty) scheduleDescStoreSave(cfg, st);
}
function getStoredDesc(cfg, st, vid) {
if (!vid) return null;
ensureDescStoreLoaded(cfg, st);
const ttl = Math.max(1, Number(cfg.descStore.ttlMs) || 36e5);
const tNow = nowMs();
const obj = st.obj || {};
const e = obj[vid];
if (!e) return null;
const t = Number(e.t || 0);
if (!t || tNow - t >= ttl) {
delete obj[vid];
st.dirty = true;
scheduleDescStoreSave(cfg, st);
return null;
}
return e.d;
}
function setStoredDesc(cfg, st, vid, desc) {
if (!vid) return;
ensureDescStoreLoaded(cfg, st);
const obj = st.obj || {};
obj[vid] = { t: nowMs(), d: String(desc || "") };
st.dirty = true;
pruneDescStore(cfg, st);
scheduleDescStoreSave(cfg, st);
}
function skNorm(cfg) {
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) || 5e3);
return { enabled: s.enabled, lines, gap, h, w, r, maxW, ms };
}
const patchesCss = 'html[__ATTR__="list"] #content.ytd-rich-section-renderer{ margin:0 !important; }\r\n\r\nytd-masthead{ position:sticky !important; top:0 !important; z-index:3000 !important; }\r\n#title-text{ position:relative !important; z-index:1 !important; }\r\n\r\n#__TOGGLE_ID__{\r\n display:inline-flex;align-items:center;gap:10px;margin-left:12px;\r\n position:relative;\r\n}\r\n#__TOGGLE_ID__ .__BTN__{\r\n width:36px;height:36px;border-radius:10px;\r\n border:1px solid var(--yt-spec-10-percent-layer, rgba(255,255,255,.12));\r\n background:rgba(255,255,255,.06);\r\n color:var(--yt-spec-text-primary, #fff);\r\n display:inline-flex;align-items:center;justify-content:center;\r\n padding:0;cursor:pointer;\r\n box-sizing:border-box;\r\n user-select:none;\r\n -webkit-user-select:none;\r\n touch-action:manipulation;\r\n}\r\n#__TOGGLE_ID__ .__BTN__:hover{ background:rgba(255,255,255,.10); border-color:rgba(255,255,255,.18) }\r\n#__TOGGLE_ID__ .__BTN__[data-active]{ background:rgba(255,255,255,.16); border-color:rgba(255,255,255,.28) }\r\n#__TOGGLE_ID__ .__BTN__ .__BTN_ICON__{ width:20px;height:20px;display:block; pointer-events:none; }\r\n#__TOGGLE_ID__ .__BTN__ svg{ width:20px;height:20px;fill:currentColor;opacity:.95;display:block; pointer-events:none; }\r\n\r\nytd-browse[page-subtype="subscriptions"] ytd-shelf-renderer .grid-subheader #title-container,\r\nytd-two-column-browse-results-renderer[page-subtype="subscriptions"] ytd-shelf-renderer .grid-subheader #title-container{\r\n display:flex !important;align-items:center !important;\r\n position:relative !important;\r\n}\r\nytd-browse[page-subtype="subscriptions"] ytd-shelf-renderer .grid-subheader #title-container #spacer,\r\nytd-two-column-browse-results-renderer[page-subtype="subscriptions"] ytd-shelf-renderer .grid-subheader #title-container #spacer{\r\n flex:1 1 auto !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] ytd-rich-grid-renderer{ --ytd-rich-grid-item-max-width: 100% !important; }\r\nhtml[__ATTR__="list"] ytd-rich-grid-renderer #contents{\r\n display:block !important;\r\n max-width: __MAX_W__ !important;\r\n margin:0 auto !important;\r\n width:100% !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] ytd-rich-item-renderer{\r\n display:block !important;width:100% !important;\r\n padding:__ROW_PAD_Y__ 0 !important;margin:0 !important;\r\n border-bottom:1px solid var(--yt-spec-10-percent-layer, var(--yslv-sep, rgba(0,0,0,.10))) !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] ytd-rich-item-renderer.__ISSHORT__{\r\n display:inline-block !important;\r\n width:auto !important;\r\n padding:0 10px 0 0 !important;\r\n margin:0 !important;\r\n border-bottom:0 !important;\r\n vertical-align:top !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] ytd-rich-item-renderer:first-child{ padding-top:0 !important; }\r\nhtml[__ATTR__="list"] ytd-rich-item-renderer:last-child{ border-bottom:0 !important; }\r\n\r\nhtml[__ATTR__="list"] .__ROWHEAD__{\r\n display:flex !important;align-items:center !important;\r\n gap:__HEAD_GAP__ !important;margin:0 0 __HEAD_MB__ 0 !important;\r\n}\r\nhtml[__ATTR__="list"] .__ROWHEAD__ .yt-lockup-metadata-view-model__avatar{\r\n display:flex !important;\r\n width:__HEAD_AV__ !important;height:__HEAD_AV__ !important;min-width:__HEAD_AV__ !important;\r\n margin:0 !important;\r\n}\r\nhtml[__ATTR__="list"] .__ROWHEAD__ yt-avatar-shape,\r\nhtml[__ATTR__="list"] .__ROWHEAD__ .yt-spec-avatar-shape{\r\n width:__HEAD_AV__ !important;height:__HEAD_AV__ !important;\r\n}\r\nhtml[__ATTR__="list"] .__ROWHEAD_NAME__{\r\n color:var(--yt-spec-text-primary) !important;\r\n text-decoration:none !important;\r\n font-weight:700 !important;\r\n font-size:18px !important;\r\n line-height:1.15 !important;\r\n display:inline-flex !important;\r\n align-items:center !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] .yt-lockup-view-model.yt-lockup-view-model--vertical{\r\n display:grid !important;\r\n grid-template-columns:__THUMB_W__ minmax(0,1fr) auto !important;\r\n grid-template-rows:auto auto !important;\r\n column-gap:18px !important;row-gap:10px !important;\r\n align-items:start !important;\r\n}\r\nhtml[__ATTR__="list"] .yt-lockup-view-model__content-image{\r\n grid-column:1 !important;grid-row:1 / span 2 !important;\r\n width:__THUMB_W__ !important;min-width:__THUMB_W__ !important;max-width:__THUMB_W__ !important;\r\n}\r\nhtml[__ATTR__="list"] .yt-lockup-view-model__content-image img{\r\n width:100% !important;height:auto !important;border-radius:__RADIUS__ !important;\r\n object-fit:cover !important;display:block !important;\r\n}\r\nhtml[__ATTR__="list"] .yt-lockup-view-model__metadata{\r\n grid-column:2 !important;grid-row:1 / span 2 !important;min-width:0 !important;\r\n}\r\nhtml[__ATTR__="list"] .yt-lockup-metadata-view-model__menu-button{\r\n grid-column:3 !important;grid-row:1 !important;justify-self:end !important;align-self:start !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] .yt-lockup-metadata-view-model__title{\r\n display:-webkit-box !important;-webkit-box-orient:vertical !important;-webkit-line-clamp:__TITLE_CLAMP__ !important;\r\n overflow:hidden !important;white-space:normal !important;line-height:1.35 !important;font-weight:600 !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] .yt-lockup-view-model__metadata .yt-lockup-metadata-view-model__avatar{ display:none !important; }\r\n\r\nhtml[__ATTR__="list"] .yt-lockup-metadata-view-model__metadata yt-content-metadata-view-model{\r\n display:block !important;\r\n position:absolute !important;\r\n left:-99999px !important;\r\n top:auto !important;\r\n width:1px !important;\r\n height:1px !important;\r\n overflow:hidden !important;\r\n opacity:0 !important;\r\n pointer-events:none !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] .__METAROW__{\r\n display:flex !important;align-items:center !important;\r\n gap:__META_GAP__ !important;margin-top:6px !important;min-width:0 !important;\r\n}\r\nhtml[__ATTR__="list"] .__METACH__{\r\n min-width:0 !important;\r\n color:var(--yt-spec-text-secondary) !important;\r\n font-size:12px !important;line-height:1.35 !important;\r\n white-space:nowrap !important;overflow:hidden !important;text-overflow:ellipsis !important;\r\n flex:0 1 auto !important;\r\n display:flex !important;\r\n align-items:center !important;\r\n}\r\nhtml[__ATTR__="list"] .__METACH__ a{\r\n color:inherit !important;\r\n text-decoration:none !important;\r\n display:inline-flex !important;\r\n align-items:center !important;\r\n min-width:0 !important;\r\n}\r\nhtml[__ATTR__="list"] .__METACH__ a > span{\r\n display:inline-flex !important;\r\n align-items:center !important;\r\n}\r\nhtml[__ATTR__="list"] .__METACH__ .yt-icon-shape.ytSpecIconShapeHost{\r\n margin-right:2px !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] .__METART__{\r\n color:var(--yt-spec-text-secondary) !important;\r\n font-size:12px !important;line-height:1.35 !important;\r\n white-space:nowrap !important;\r\n opacity:.95 !important;\r\n flex:0 0 auto !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] .__DESC__{\r\n margin-top:__DESC_MT__ !important;\r\n font-size:12px !important;line-height:1.45 !important;\r\n color:var(--yt-spec-text-secondary) !important;\r\n display:-webkit-box !important;-webkit-box-orient:vertical !important;-webkit-line-clamp:__DESC_CLAMP__ !important;\r\n overflow:hidden !important;white-space:normal !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] .__DESC__.__DESCSKEL__{\r\n display:__SKL_ENABLED__ !important;\r\n -webkit-line-clamp:unset !important;\r\n overflow:hidden !important;\r\n color:transparent !important;\r\n position:relative !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] .__DESC__.__DESCSKEL__ > span{\r\n display:block !important;\r\n border-radius:__SKL_R__ !important;\r\n opacity:1 !important;\r\n pointer-events:none !important;\r\n\r\n background-color:var(--yslv-skel-base, rgba(0,0,0,.10)) !important;\r\n background-image:linear-gradient(\r\n 90deg,\r\n var(--yslv-skel-base, rgba(0,0,0,.10)) 0%,\r\n var(--yt-spec-10-percent-layer, rgba(255,255,255,.20)) 50%,\r\n var(--yslv-skel-base, rgba(0,0,0,.10)) 100%\r\n ) !important;\r\n background-size:220% 100% !important;\r\n background-position:var(__SHIMMER_VAR__, 200%) 0 !important;\r\n\r\n transform:translateZ(0) !important;\r\n will-change:background-position !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] .__DESC__.__DESCSKEL__ > span:nth-child(1){\r\n height:__SKL_H1__ !important;\r\n width:min(__SKL_MAXW__, __SKL_W1__) !important;\r\n margin-top:0 !important;\r\n}\r\nhtml[__ATTR__="list"] .__DESC__.__DESCSKEL__ > span:nth-child(2){\r\n height:__SKL_H2__ !important;\r\n width:min(__SKL_MAXW__, __SKL_W2__) !important;\r\n margin-top:__SKL_GAP__ !important;\r\n}\r\nhtml[__ATTR__="list"] .__DESC__.__DESCSKEL__ > span:nth-child(3){\r\n height:__SKL_H3__ !important;\r\n width:min(__SKL_MAXW__, __SKL_W3__) !important;\r\n margin-top:__SKL_GAP__ !important;\r\n}\r\n\r\nhtml[__ATTR__="list"]{\r\n __SHORTW_VAR__: __SHORT_W__;\r\n}\r\n\r\nhtml[__ATTR__="list"] ytd-rich-item-renderer.__ISSHORT__ #content{\r\n width:var(__SHORTW_VAR__) !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] ytd-rich-item-renderer.__ISSHORT__ a.reel-item-endpoint,\r\nhtml[__ATTR__="list"] ytd-rich-item-renderer.__ISSHORT__ a.shortsLockupViewModelHostEndpoint.reel-item-endpoint{\r\n width:var(__SHORTW_VAR__) !important;\r\n display:block !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] ytd-rich-item-renderer.__ISSHORT__ yt-thumbnail-view-model,\r\nhtml[__ATTR__="list"] ytd-rich-item-renderer.__ISSHORT__ .ytThumbnailViewModelImage,\r\nhtml[__ATTR__="list"] ytd-rich-item-renderer.__ISSHORT__ .shortsLockupViewModelHostThumbnailParentContainer{\r\n width:var(__SHORTW_VAR__) !important;\r\n aspect-ratio:2/3 !important;\r\n}\r\n\r\nhtml[__ATTR__="list"] ytd-rich-item-renderer.__ISSHORT__ img.ytCoreImageHost{\r\n width:100% !important;\r\n height:100% !important;\r\n object-fit:cover !important;\r\n border-radius:__RADIUS__ !important;\r\n display:block !important;\r\n}\r\n\r\nhtml[data-yslv-subs-view="list"] ytd-rich-shelf-renderer #dismissible{\r\n margin-top:2rem !important;\r\n}\r\nhtml[data-yslv-subs-view="list"] ytd-rich-item-renderer.yslv-is-short > #content{\r\n padding-bottom:3rem !important;\r\n}\r\n\r\nhtml[dark][__ATTR__="list"]{ --yslv-sep: rgba(255,255,255,.18); --yslv-skel-base: rgba(255,255,255,.10); }\r\nhtml:not([dark])[__ATTR__="list"]{ --yslv-sep: rgba(0,0,0,.10); --yslv-skel-base: rgba(0,0,0,.10); }\r\n\r\n/* "Notify Me" buttons for upcoming videos are full width fix by: https://greasyfork.org/en/users/1568138-boxfriend */\r\nhtml[__ATTR__="list"] .ytLockupAttachmentsViewModelHost{\r\n width: fit-content !important;\r\n}\r\n\r\n/* Patch: rich-section spacing */\r\nhtml[__ATTR__="list"]\r\nytd-counterfactual-renderer.ytd-rich-section-renderer,\r\nhtml[__ATTR__="list"]\r\n#content.ytd-rich-section-renderer > ytd-rich-list-header-renderer.ytd-rich-section-renderer,\r\nhtml[__ATTR__="list"]\r\n#content.ytd-rich-section-renderer > ytd-shelf-renderer.ytd-rich-section-renderer,\r\nhtml[__ATTR__="list"]\r\n#content.ytd-rich-section-renderer > ytd-rich-shelf-renderer.ytd-rich-section-renderer,\r\nhtml[__ATTR__="list"]\r\n#content.ytd-rich-section-renderer > ytd-inline-survey-renderer.ytd-rich-section-renderer[is-dismissed]{\r\n margin-bottom:1rem !important;\r\n}\r\n\r\n/* Patch: mini guide visible margin */\r\nhtml[__ATTR__="list"]\r\nytd-app[mini-guide-visible] ytd-page-manager.ytd-app{\r\n margin-left: unset !important;\r\n}\r\n';
function px(n) {
return `${n}px`;
}
function cssMaxWidth(v) {
if (typeof v === "number") return `${Math.max(0, v)}px`;
const s = String(v).trim();
return s || "1120px";
}
function applyTokens(template, tokens) {
let out = template;
for (const [k, v] of Object.entries(tokens)) out = out.split(k).join(v);
return out;
}
function skeletonTokens(S) {
const enabled = S.enabled ? "block" : "none";
return {
"__SKL_ENABLED__": enabled,
"__SKL_LINES__": String(S.lines),
"__SKL_GAP__": px(S.gap),
"__SKL_R__": px(S.r),
"__SKL_MAXW__": px(S.maxW),
"__SKL_MS__": String(S.ms),
"__SKL_H1__": px(S.h(0)),
"__SKL_H2__": px(S.h(1)),
"__SKL_H3__": px(S.h(2)),
"__SKL_W1__": `${S.w(0)}%`,
"__SKL_W2__": `${S.w(1)}%`,
"__SKL_W3__": `${S.w(2)}%`
};
}
function buildStyleText(cfg) {
const L = cfg.list;
const S = skNorm(cfg);
const tokens = {
"__ATTR__": cfg.attr.view,
"__TOGGLE_ID__": cfg.ids.toggle,
"__BTN__": cfg.cls.btn,
"__BTN_ICON__": cfg.cls.btnIcon,
"__ROWHEAD__": cfg.cls.rowHead,
"__ROWHEAD_NAME__": cfg.cls.rowHeadName,
"__METAROW__": cfg.cls.metaRow,
"__METACH__": cfg.cls.metaCh,
"__METART__": cfg.cls.metaRt,
"__DESC__": cfg.cls.desc,
"__DESCSKEL__": cfg.cls.descSkel,
"__ISSHORT__": cfg.cls.isShort,
"__MAX_W__": cssMaxWidth(L.maxWidth),
"__ROW_PAD_Y__": px(Math.max(8, Number(L.rowPadY) || 22)),
"__THUMB_W__": px(Math.max(240, Number(L.thumbW) || 240)),
"__RADIUS__": px(Math.max(0, Number(L.thumbRadius) || 14)),
"__TITLE_CLAMP__": String(Math.max(1, Number(L.titleClamp) || 2)),
"__DESC_CLAMP__": String(Math.max(1, Number(L.descClamp) || 2)),
"__DESC_MT__": px(Math.max(4, Number(L.desc.marginTop) || 10)),
"__HEAD_GAP__": px(Math.max(6, Number(L.rowHead.gap) || 12)),
"__HEAD_MB__": px(Math.max(6, Number(L.rowHead.marginBottom) || 20)),
"__HEAD_AV__": px(Math.max(20, Number(L.rowHead.avatarSize) || 32)),
"__META_GAP__": px(Math.max(6, Number(L.metaRow.gap) || 8)),
"__SHORT_W__": px(Math.max(110, Number(L.shorts.cardW) || 170)),
"__SHIMMER_VAR__": cfg.cssVars.shimmerX,
"__SHORTW_VAR__": cfg.cssVars.shortW,
...skeletonTokens(S)
};
return applyTokens(String(patchesCss), tokens).trim();
}
function ensureStyle(cfg, state) {
if (state.styleEl) return;
const el = document.createElement("style");
el.id = cfg.ids.style;
el.textContent = buildStyleText(cfg);
document.documentElement.appendChild(el);
state.styleEl = el;
}
function clearChildren(el) {
if (!el) return;
while (el.firstChild) el.removeChild(el.firstChild);
}
function normalizeText(s) {
return String(s || "").replace(/\u200B/g, "").replace(/\s+/g, " ").trim();
}
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));
const hosts = Array.from(frag.querySelectorAll?.(".ytIconWrapperHost, .yt-icon-shape") || []);
for (const host of hosts) {
const el = host;
if (!el.querySelector("svg")) el.remove();
}
dest.appendChild(frag);
}
function setTextOnly(dest, txt) {
if (!dest) return;
clearChildren(dest);
dest.textContent = normalizeText(txt);
}
function svgEl(paths, viewBox) {
const NS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(NS, "svg");
svg.setAttribute("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 paintToggle(cfg, state) {
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 removeToggle(cfg) {
const root = document.getElementById(cfg.ids.toggle);
if (root) root.remove();
}
function ensureToggle(cfg, state, deps) {
const existing = document.getElementById(cfg.ids.toggle);
if (existing && existing.isConnected) {
paintToggle(cfg, state);
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 target = e.target;
const btn = target?.closest?.("button[data-mode]");
if (!btn) return;
const mode = btn.getAttribute("data-mode");
if (mode !== "grid" && mode !== "list") return;
const next = mode;
if (next === state.view) return;
deps.onSwitchView(next);
});
subscribeBtn.insertAdjacentElement("afterend", root);
paintToggle(cfg, state);
}
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 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 ensureDesc(cfg, state, store, 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(cfg, store, 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(cfg);
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(void 0, { 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(cfg, state, store, 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(cfg, store, 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(cfg, store, vid, out);
pruneDescStore(cfg, store);
return out;
}
function updateDescDomForVid(cfg, vid, text) {
const nodes = document.querySelectorAll(`.${cfg.cls.desc}[data-yslv-vid="${CSS.escape(vid)}"]`);
for (const n of Array.from(nodes)) {
const el = n;
if (!el.isConnected) continue;
el.classList.remove(cfg.cls.descSkel);
clearChildren(el);
el.textContent = text || "";
el.style.display = text ? "" : "none";
}
}
function buildDescQueueFromDom(cfg, state, store) {
if (!state.active || state.view !== "list") return;
const descs = document.querySelectorAll(`.${cfg.cls.desc}[data-yslv-vid]`);
if (!descs.length) return;
let sig = "";
for (const d of Array.from(descs)) {
const vid = d.dataset.yslvVid || "";
if (!vid) continue;
sig += `${vid}|`;
}
if (sig === state.lastQueueSig) return;
state.lastQueueSig = sig;
for (const d of Array.from(descs)) {
const vid = d.dataset.yslvVid || "";
if (!vid) continue;
const stored = getStoredDesc(cfg, store, vid);
if (stored != null) {
state.descCache.set(vid, stored);
updateDescDomForVid(cfg, 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);
}
}
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) 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 = /* @__PURE__ */ new Set();
for (const el of Array.from(candidates)) {
const node = el;
let root = node.closest(".yt-core-attributed-string__image-element") || node.closest(".ytIconWrapperHost") || node.closest(".yt-core-attributed-string__image-element--image-alignment-vertical-center") || node;
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.isConnected) continue;
badges.push(r);
}
for (const b of badges) {
try {
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"))) {
const el = s;
if (!el.querySelector || !isIconish(el)) el.remove();
}
}
function detachMetaAnchorOnce(cfg, state, lockup) {
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(state) {
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 = /* @__PURE__ */ 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 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 = /* @__PURE__ */ 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(cfg, state, 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(cfg, state, 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 ensureRowHeader(cfg, state, 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);
if (state.movedAvatars.has(item)) return;
const avatarEl = lockup.querySelector(".yt-lockup-metadata-view-model__avatar");
if (!avatarEl || !avatarEl.parentNode) return;
const parent = avatarEl.parentNode;
const nextSibling = avatarEl.nextSibling;
state.movedAvatars.set(item, { avatarEl, parent, nextSibling });
try {
head.insertBefore(avatarEl, head.firstChild);
} catch {
}
}
function restoreMovedAvatars(state) {
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 = /* @__PURE__ */ new WeakMap();
}
function patchItem(cfg, state, store, item) {
if (!state.active || state.view !== "list") 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(cfg, state, item, lockup);
ensureInlineMeta(cfg, state, textContainer, lockup);
ensureDesc(cfg, state, store, textContainer, lockup);
}
function cleanupListArtifacts(cfg, state) {
restoreMovedAvatars(state);
restoreMovedMetaAnchors(state);
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 enqueue(cfg, state, store, node) {
if (!state.active || state.view !== "list") return;
if (node.tagName === "YTD-RICH-ITEM-RENDERER") {
if (state.qSet.has(node)) return;
state.qSet.add(node);
state.q.push(node);
scheduleProcess(cfg, state, store);
return;
}
const found = node.querySelectorAll ? node.querySelectorAll("ytd-rich-item-renderer") : [];
for (const it of Array.from(found)) enqueue(cfg, state, store, it);
}
function scheduleProcess(cfg, state, store) {
if (state.processing) return;
state.processing = true;
const run = () => {
state.processing = false;
processQueue(cfg, state, store);
};
const ric = window.requestIdleCallback;
if (ric) ric(run, { timeout: 300 });
else setTimeout(run, 80);
}
function processQueue(cfg, state, store) {
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();
if (!item) continue;
state.qSet.delete(item);
patchItem(cfg, state, store, item);
n++;
}
if (state.q.length) scheduleProcess(cfg, state, store);
}
function enqueueAllOnce(cfg, state, store) {
if (!state.active || state.view !== "list") return;
const root = getActiveSubsRoot();
const scope = root && root.querySelectorAll ? root : document;
const items = scope.querySelectorAll?.("ytd-rich-item-renderer") || [];
for (const it of Array.from(items)) enqueue(cfg, state, store, it);
}
function attachObserver(cfg, state, store) {
if (!state.active) return;
const target = getActiveSubsRoot() || document.documentElement;
if (state.observedTarget === target && state.mo) return;
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 Array.from(m.addedNodes)) {
if (node && node.nodeType === 1) enqueue(cfg, state, store, node);
}
}
});
state.mo.observe(target, { childList: true, subtree: true });
}
function attachPageManagerObserver(cfg, state, store, onTick) {
if (state.pmMo) return;
const pm = document.querySelector("ytd-page-manager");
if (!pm) return;
state.pmMo = new MutationObserver(() => {
if (!state.active) return;
onTick();
if (state.view === "list") {
setTimeout(() => {
if (!state.active || state.view !== "list") return;
enqueueAllOnce(cfg, state, store);
}, 60);
}
});
state.pmMo.observe(pm, { childList: true, subtree: true });
}
function hasSkeletons(cfg) {
return !!document.querySelector(`.${cfg.cls.desc}.${cfg.cls.descSkel}`);
}
function stopShimmer(cfg, shimmer) {
shimmer.running = false;
if (shimmer.raf) cancelAnimationFrame(shimmer.raf);
shimmer.raf = 0;
document.documentElement.style.removeProperty(cfg.cssVars.shimmerX);
}
function startShimmer(cfg, state, shimmer) {
if (shimmer.running) return;
shimmer.running = true;
shimmer.t0 = performance.now();
const tick = (t) => {
if (!shimmer.running) return;
const S = skNorm(cfg);
if (!state.active || state.view !== "list" || !S.enabled || !hasSkeletons(cfg)) {
stopShimmer(cfg, shimmer);
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);
}
async function pumpDescQueue(cfg, state, store) {
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 txt = await fetchDescriptionForVideoId(cfg, state, store, vid);
updateDescDomForVid(cfg, vid, txt || "");
}
} finally {
state.descPumpRunning = false;
}
}
function ensureDescQueueLoop(cfg, state, store, shimmer) {
if (state.descTimer) clearInterval(state.descTimer);
if (!state.active) return;
state.descTimer = window.setInterval(() => {
if (!state.active || state.view !== "list") {
stopShimmer(cfg, shimmer);
return;
}
pruneDescStore(cfg, store);
buildDescQueueFromDom(cfg, state, store);
void pumpDescQueue(cfg, state, store);
if (document.querySelector(`.${cfg.cls.desc}.${cfg.cls.descSkel}`)) startShimmer(cfg, state, shimmer);
else stopShimmer(cfg, shimmer);
}, cfg.perf.descQueueIntervalMs);
}
function createApp(cfg, state, shimmer, store) {
function resetNavState() {
state.processedItems = /* @__PURE__ */ 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(cfg, shimmer);
if (state.view === "list") {
cleanupListArtifacts(cfg, state);
}
state.mo?.disconnect();
state.mo = null;
state.observedTarget = null;
if (state.descTimer) {
clearInterval(state.descTimer);
state.descTimer = 0;
}
resetNavState();
removeToggle(cfg);
clearViewAttr(cfg);
}
function ensureToggleMountLoop() {
if (!state.active) return;
ensureToggle(cfg, state, {
onSwitchView(next) {
if (next === state.view) return;
if (state.view === "list") {
cleanupListArtifacts(cfg, state);
restoreMovedAvatars(state);
restoreMovedMetaAnchors(state);
}
resetNavState();
state.view = next;
saveView(cfg, next);
applyViewAttr(cfg, next);
attachObserver(cfg, state, store);
ensureDescQueueLoop(cfg, state, store, shimmer);
if (next === "list") {
enqueueAllOnce(cfg, state, store);
startShimmer(cfg, state, shimmer);
} else {
stopShimmer(cfg, shimmer);
}
paintToggle(cfg, state);
}
});
if (state.active && !document.getElementById(cfg.ids.toggle)) setTimeout(ensureToggleMountLoop, 250);
}
function apply() {
ensureDescStoreLoaded(cfg, store);
pruneDescStore(cfg, store);
ensureStyle(cfg, state);
ensureToggleMountLoop();
attachObserver(cfg, state, store);
attachPageManagerObserver(cfg, state, store, () => {
attachObserver(cfg, state, store);
ensureToggleMountLoop();
});
ensureDescQueueLoop(cfg, state, store, shimmer);
if (state.view === "list") {
enqueueAllOnce(cfg, state, store);
startShimmer(cfg, state, shimmer);
} else {
stopShimmer(cfg, shimmer);
}
}
function syncActive(isNavFinish) {
const shouldBeActive = isSubsPage();
const sig = pageSig();
if (shouldBeActive && !state.active) {
state.active = true;
state.lastPageSig = sig;
state.view = loadView(cfg);
applyViewAttr(cfg, state.view);
apply();
return;
}
if (!shouldBeActive && state.active) {
state.active = false;
state.lastPageSig = sig;
teardown();
return;
}
if (shouldBeActive && state.active) {
ensureToggleMountLoop();
paintToggle(cfg, state);
attachObserver(cfg, state, store);
if (state.view === "list") {
if (sig !== state.lastPageSig) {
state.lastPageSig = sig;
resetNavState();
enqueueAllOnce(cfg, state, store);
startShimmer(cfg, state, shimmer);
}
} else {
stopShimmer(cfg, shimmer);
}
}
}
function init() {
syncActive();
window.addEventListener("yt-navigate-finish", () => syncActive(), { passive: true });
window.addEventListener("popstate", () => syncActive(), { passive: true });
setTimeout(() => {
attachPageManagerObserver(cfg, state, store, () => {
attachObserver(cfg, state, store);
ensureToggleMountLoop();
});
}, 250);
}
return { init };
}
function initYSLV() {
const state = createState();
const shimmer = createShimmerState();
const store = createDescStoreState();
const app = createApp(CFG, state, shimmer, store);
app.init();
}
(() => {
initYSLV();
})();
})();