// ==UserScript== // @name bilibili-playlist-enhancer // @namespace https://github.com/niceKingsley/magic-monkey // @version 1.0.0 // @author kingsley // @description bilibili 全场景播放列表/选集/合集增强助手 // @license GPL-3.0-or-later // @icon https://static.hdslb.com/images/favicon.ico // @homepageURL https://github.com/niceKingsley/magic-monkey // @supportURL https://github.com/niceKingsley/magic-monkey/issues // @match *://www.bilibili.com/video/* // @match *://www.bilibili.com/list/* // @require https://cdn.jsdelivr.net/npm/vue@3.5.43/dist/vue.global.prod.js // @connect api.bilibili.com // @grant GM_addStyle // @grant GM_deleteValue // @grant GM_getValue // @grant GM_listValues // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant unsafeWindow // @run-at document-start // ==/UserScript== (function(vue) { "use strict"; var s$3 = new Set(); var _css = async (t) => { if (s$3.has(t)) return; s$3.add(t); ((c) => { if (typeof GM_addStyle === "function") GM_addStyle(c); else (document.head || document.documentElement).appendChild(document.createElement("style")).append(c); })(t); }; _css(" .settings-container[data-v-e52d7f12]{-webkit-user-select:none;user-select:none;overscroll-behavior:contain;flex-direction:column;gap:18px;display:flex}.settings-section[data-v-e52d7f12]{flex-direction:column;gap:8px;display:flex}.settings-section__title[data-v-e52d7f12]{color:var(--magic-primary-color,#00aeec);letter-spacing:.3px;align-items:center;gap:6px;font-size:13px;font-weight:600;display:flex}.settings-section__title .section-icon[data-v-e52d7f12]{opacity:.9;width:14px;height:14px}.settings-card[data-v-e52d7f12]{background:#ffffff0a;border:1px solid #ffffff14;border-radius:10px;flex-direction:column;padding:4px 16px;transition:all .25s cubic-bezier(.16,1,.3,1);display:flex}.settings-card[data-v-e52d7f12]:hover{background:#ffffff0f;border-color:#ffffff24}.settings-item[data-v-e52d7f12]{justify-content:space-between;align-items:center;gap:16px;padding:12px 0;transition:opacity .2s;display:flex}.settings-item[data-v-e52d7f12]:not(:last-child){border-bottom:1px solid #ffffff0f}.settings-item.is-disabled[data-v-e52d7f12]{opacity:.38;pointer-events:none}.settings-item__info[data-v-e52d7f12]{flex-direction:column;flex:1;gap:3px;min-width:0;display:flex}.settings-item__title[data-v-e52d7f12]{color:#e5e9ef;font-size:13.5px;font-weight:500;line-height:1.4}.settings-item__desc[data-v-e52d7f12]{color:#9499a0;font-size:12px;line-height:1.45}.settings-item__action[data-v-e52d7f12]{flex-shrink:0;align-items:center;display:flex}.settings-item__action .hotkey-badge[data-v-e52d7f12]{min-width:80px;height:28px;color:var(--magic-primary-color,#00aeec);cursor:pointer;-webkit-user-select:none;user-select:none;background:#ffffff0f;border:1px solid #ffffff26;border-radius:6px;outline:none;justify-content:center;align-items:center;padding:0 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:12px;font-weight:600;transition:all .2s cubic-bezier(.16,1,.3,1);display:inline-flex}.settings-item__action .hotkey-badge[data-v-e52d7f12]:hover{background:var(--magic-primary-light-bg,#00aeec1f);border-color:var(--magic-primary-color,#00aeec)}.settings-item__action .hotkey-badge.is-recording[data-v-e52d7f12]{background:var(--magic-primary-color,#00aeec);border-color:var(--magic-primary-color,#00aeec);color:#fff;box-shadow:0 0 0 3px var(--magic-primary-focus-shadow,#00aeec40);animation:1.2s ease-in-out infinite tk-hotkey-pulse-e52d7f12}@keyframes tk-hotkey-pulse-e52d7f12{0%,to{opacity:1}50%{opacity:.6}}.settings-modal-footer[data-v-e52d7f12]{justify-content:space-between;align-items:center;width:100%;display:flex}.settings-modal-footer .footer-buttons[data-v-e52d7f12]{align-items:center;gap:10px;display:flex}@keyframes tk-spin-81fdaee1{0%{transform:rotate(0)}to{transform:rotate(360deg)}}:root{--magic-primary-color:var(--brand_blue,#00aeec);--magic-primary-hover-color:var(--brand_blue_hover,color-mix(in srgb, var(--magic-primary-color) 85%, #fff));--magic-primary-active-color:var(--brand_blue_active,color-mix(in srgb, var(--magic-primary-color) 85%, #000));--magic-primary-light-bg:var(--brand_blue_thin,color-mix(in srgb, var(--magic-primary-color) 12%, transparent))}.tk-reaction[data-v-81fdaee1]{z-index:10;background-color:var(--graph_bg_regular);box-sizing:border-box;flex-direction:column;display:flex;position:absolute;left:4321px;overflow:hidden}.tk-reaction.is-animating[data-v-81fdaee1]{transition:max-height .3s ease-in-out,top .29s ease-in-out}.tk-reaction .tk-reaction-header[data-v-81fdaee1]{border-bottom:1px solid var(--line_regular);box-sizing:border-box;flex-shrink:0;justify-content:space-between;align-items:center;margin-bottom:6px;padding:6px 12px;display:flex}.tk-reaction .tk-reaction-header .header-left[data-v-81fdaee1]{align-items:center;gap:12px;display:flex}.tk-reaction .tk-reaction-header .header-left .sort-select[data-v-81fdaee1]{--magic-select-bg:var(--bg2);--magic-select-border:var(--line_regular);--magic-select-color:var(--text1);--magic-select-placeholder-color:var(--text3);--magic-select-suffix-color:var(--text3);--magic-select-hover-bg:var(--bg2_hover,var(--bg3));--magic-select-hover-border:var(--magic-primary-color,var(--brand_blue));--magic-select-active-color:var(--magic-primary-color,var(--brand_blue));--magic-select-dropdown-bg:var(--graph_bg_thick,var(--bg1));--magic-select-dropdown-border:var(--line_regular);--magic-select-dropdown-shadow:0 4px 16px #0003;--magic-select-option-hover-bg:var(--bg2);--magic-select-option-active-bg:var(--magic-primary-light-bg,var(--brand_blue_thin));width:104px}.tk-reaction .tk-reaction-header .header-left magic-switch[data-v-81fdaee1]{--magic-switch-active-color:var(--magic-primary-color,var(--brand_blue));--magic-switch-inactive-color:var(--text4)}.tk-reaction .tk-reaction-header .header-right[data-v-81fdaee1]{align-items:center;gap:6px;display:flex}.tk-reaction .tk-reaction-header .header-right .shuffle-btn[data-v-81fdaee1],.tk-reaction .tk-reaction-header .header-right .search-btn[data-v-81fdaee1]{cursor:pointer;width:24px;height:24px;color:var(--text3);background:0 0;border-radius:4px;justify-content:center;align-items:center;padding:0;transition:color .2s,background-color .2s,transform .15s;display:inline-flex}.tk-reaction .tk-reaction-header .header-right .shuffle-btn .icon[data-v-81fdaee1],.tk-reaction .tk-reaction-header .header-right .search-btn .icon[data-v-81fdaee1]{fill:currentColor;width:15px;height:15px}.tk-reaction .tk-reaction-header .header-right .shuffle-btn[data-v-81fdaee1]:hover,.tk-reaction .tk-reaction-header .header-right .search-btn[data-v-81fdaee1]:hover{color:var(--magic-primary-color,var(--brand_blue));background-color:var(--bg2)}.tk-reaction .tk-reaction-header .header-right .shuffle-btn.is-active[data-v-81fdaee1],.tk-reaction .tk-reaction-header .header-right .search-btn.is-active[data-v-81fdaee1]{color:var(--magic-primary-color,var(--brand_blue));background-color:var(--magic-primary-light-bg,var(--brand_blue_thin))}.tk-reaction .tk-reaction-header .header-right .shuffle-btn[data-v-81fdaee1]:active,.tk-reaction .tk-reaction-header .header-right .search-btn[data-v-81fdaee1]:active{transform:scale(.92)}.tk-reaction .tk-reaction-header .header-right .settings-btn[data-v-81fdaee1]{cursor:pointer;width:24px;height:24px;color:var(--text3);background:0 0;border:none;outline:none;justify-content:center;align-items:center;padding:0;transition:color .2s,transform .2s;display:inline-flex}.tk-reaction .tk-reaction-header .header-right .settings-btn .icon[data-v-81fdaee1]{width:16px;height:16px;color:inherit;fill:currentColor;transition:transform .3s}.tk-reaction .tk-reaction-header .header-right .settings-btn[data-v-81fdaee1]:hover{color:var(--magic-primary-color,var(--brand_blue));background:0 0}.tk-reaction .tk-reaction-header .header-right .settings-btn:hover .icon[data-v-81fdaee1]{transform:rotate(60deg)}.tk-reaction .tk-reaction-header .header-right .settings-btn[data-v-81fdaee1]:active{transform:scale(.92)}.tk-reaction .tk-reaction-search[data-v-81fdaee1]{flex-shrink:0;padding:0 12px 6px}.tk-reaction .tk-reaction-search .search-input[data-v-81fdaee1]{--magic-input-bg:var(--bg2);--magic-input-border:var(--line_regular);--magic-input-color:var(--text1);--magic-input-placeholder-color:var(--text3);--magic-input-hover-border:var(--magic-primary-color,var(--brand_blue));--magic-input-focus-border:var(--magic-primary-color,var(--brand_blue));--magic-input-focus-bg:var(--graph_bg_regular);--magic-input-prefix-color:var(--text3);--magic-input-suffix-color:var(--text3);--magic-input-clear-color:var(--text3)}.tk-reaction .tk-reaction-search .search-input .search-prefix-icon[data-v-81fdaee1]{width:14px;height:14px;color:var(--text3)}.tk-reaction .tk-reaction-search .search-input .match-badge[data-v-81fdaee1]{color:var(--text3);white-space:nowrap;font-size:11px}.tk-reaction .tk-reaction-video[data-v-81fdaee1]{overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#78787880 transparent;flex:1;min-height:0;position:relative;overflow:auto}.tk-reaction .tk-reaction-video.is-loading[data-v-81fdaee1]{overflow:hidden!important}.tk-reaction .tk-reaction-video[data-v-81fdaee1]::-webkit-scrollbar{width:8px;height:8px}.tk-reaction .tk-reaction-video[data-v-81fdaee1]::-webkit-scrollbar-thumb{background:#78787880;border-radius:8px}.tk-reaction .tk-reaction-video[data-v-81fdaee1]::-webkit-scrollbar-thumb:hover{background:#787878}.tk-reaction .tk-reaction-video .empty-state[data-v-81fdaee1]{min-height:160px;color:var(--text3);text-align:center;justify-content:center;align-items:center;padding:48px 16px;font-size:13px;display:flex}.tk-reaction .tk-reaction-video .loading-state[data-v-81fdaee1]{justify-content:center;align-items:center;min-height:160px;padding:48px 0;display:flex}.tk-reaction .tk-reaction-video .list[data-v-81fdaee1]{box-sizing:border-box;width:calc(100% - 12px);margin:0 auto;position:relative}.tk-reaction .tk-reaction-video .list .item[data-v-81fdaee1]{content-visibility:auto;contain-intrinsic-size:auto 36px;margin-bottom:4px}.tk-reaction .tk-reaction-video .list .item .header[data-v-81fdaee1]{cursor:pointer;border-radius:2px;align-items:center;padding:0 10px;transition:background-color .2s;display:flex}.tk-reaction .tk-reaction-video .list .item .header[data-v-81fdaee1]:hover{background-color:var(--bg2,#ffffff0d)}.tk-reaction .tk-reaction-video .list .item .header.is-active[data-v-81fdaee1]{background-color:var(--bg2,#ffffff14)}.tk-reaction .tk-reaction-video .list .item .header.is-active .mode-list .title[data-v-81fdaee1],.tk-reaction .tk-reaction-video .list .item .header.is-active .mode-card .info .title[data-v-81fdaee1]{color:var(--magic-primary-color,var(--brand_blue))!important}.tk-reaction .tk-reaction-video .list .item .header.is-active .mode-list .title .playing-gif[data-v-81fdaee1],.tk-reaction .tk-reaction-video .list .item .header.is-active .mode-card .info .title .playing-gif[data-v-81fdaee1]{display:block!important}.tk-reaction .tk-reaction-video .list .item .header .mode-list[data-v-81fdaee1]{justify-content:space-between;align-items:center;width:100%;display:flex}.tk-reaction .tk-reaction-video .list .item .header .mode-list .title[data-v-81fdaee1]{color:var(--text1);flex:1;align-items:center;height:31px;font-size:15px;transition:color .2s;display:flex}.tk-reaction .tk-reaction-video .list .item .header .mode-list .title[data-v-81fdaee1]:hover{color:var(--magic-primary-color,var(--brand_blue))}.tk-reaction .tk-reaction-video .list .item .header .mode-list .title .playing-gif[data-v-81fdaee1]{background-image:url(https://i0.hdslb.com/bfs/static/jinkela/playlist-video/asserts/playing.gif);background-position:50%;background-repeat:no-repeat;background-size:12px 12px;flex-shrink:0;width:12px;height:12px;margin-right:5px;display:none}.tk-reaction .tk-reaction-video .list .item .header .mode-list .title .title-text[data-v-81fdaee1]{-webkit-line-clamp:1;text-overflow:ellipsis;word-break:break-all;line-break:anywhere;-webkit-box-orient:vertical;line-height:31px;display:-webkit-box;overflow:hidden}.tk-reaction .tk-reaction-video .list .item .header .mode-list .actions[data-v-81fdaee1]{color:var(--text3);flex-shrink:0;align-items:center;gap:8px;margin-left:10px;font-size:14px;display:flex}.tk-reaction .tk-reaction-video .list .item .header .mode-list .actions .expand[data-v-81fdaee1]{cursor:pointer;align-items:center;display:flex}.tk-reaction .tk-reaction-video .list .item .header .mode-list .actions .expand .icon[data-v-81fdaee1]{color:var(--text3);width:14px;height:14px;transition:transform .3s;transform:rotate(180deg)}.tk-reaction .tk-reaction-video .list .item .header .mode-list .actions .expand.is-expanded .icon[data-v-81fdaee1]{transform:rotate(0)}.tk-reaction .tk-reaction-video .list .item .header .mode-card[data-v-81fdaee1]{align-items:stretch;gap:10px;width:100%;padding:6px 0;display:none}.tk-reaction .tk-reaction-video .list .item .header .mode-card .cover[data-v-81fdaee1]{background-color:var(--bg2,#0000001a);border-radius:4px;flex-shrink:0;width:114px;height:64px;overflow:hidden}.tk-reaction .tk-reaction-video .list .item .header .mode-card .cover .cover-img[data-v-81fdaee1]{object-fit:cover;width:100%;height:100%;display:block}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info[data-v-81fdaee1]{flex-direction:column;flex:1;justify-content:space-between;min-width:0;display:flex}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .title[data-v-81fdaee1]{color:var(--text1);align-items:flex-start;max-height:36px;font-size:13px;line-height:18px;transition:color .2s;display:flex;overflow:hidden}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .title[data-v-81fdaee1]:hover{color:var(--magic-primary-color,var(--brand_blue))}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .title .playing-gif[data-v-81fdaee1]{background-image:url(https://i0.hdslb.com/bfs/static/jinkela/playlist-video/asserts/playing.gif);background-position:50%;background-repeat:no-repeat;background-size:12px 12px;flex-shrink:0;width:12px;height:12px;margin-top:3px;margin-right:5px;display:none}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .title .title-text[data-v-81fdaee1]{-webkit-line-clamp:2;text-overflow:ellipsis;word-break:break-all;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .stats[data-v-81fdaee1]{color:var(--text3);align-items:center;gap:8px;min-width:0;font-size:12px;display:flex;overflow:hidden}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .stats .stat-item[data-v-81fdaee1]{flex-shrink:1;align-items:center;gap:3px;min-width:0;display:inline-flex}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .stats .stat-item .icon[data-v-81fdaee1]{fill:currentColor;flex-shrink:0;width:14px;height:14px}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .stats .stat-item .count-text[data-v-81fdaee1]{white-space:nowrap;text-overflow:ellipsis;line-height:14px;overflow:hidden}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .stats .stat-item.views[data-v-81fdaee1],.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .stats .stat-item.danmaku[data-v-81fdaee1]{flex-shrink:0}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .stats .expand-btn[data-v-81fdaee1]{cursor:pointer;flex-shrink:0;margin-left:auto}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .stats .expand-btn .icon[data-v-81fdaee1]{width:14px;height:14px;color:var(--text3);transition:transform .3s;transform:rotate(180deg)}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .stats .expand-btn.is-expanded .icon[data-v-81fdaee1]{transform:rotate(0)}.tk-reaction .tk-reaction-video .list .item .episodes[data-v-81fdaee1]{margin-top:4px}.tk-reaction .tk-reaction-video .list .item .episodes .episode[data-v-81fdaee1]{content-visibility:auto;contain-intrinsic-size:auto 31px;background:var(--bg1);cursor:pointer;border-radius:2px;justify-content:space-between;align-items:center;padding:0 10px 0 24px;transition:background-color .2s;display:flex}.tk-reaction .tk-reaction-video .list .item .episodes .episode[data-v-81fdaee1]:hover{background-color:var(--bg2,#ffffff0d)}.tk-reaction .tk-reaction-video .list .item .episodes .episode.is-active .episode-title[data-v-81fdaee1]{color:var(--magic-primary-color,var(--brand_blue))}.tk-reaction .tk-reaction-video .list .item .episodes .episode.is-active .episode-title .playing-gif[data-v-81fdaee1]{display:block}.tk-reaction .tk-reaction-video .list .item .episodes .episode .episode-title[data-v-81fdaee1]{color:var(--text1);flex:1;align-items:center;height:31px;font-size:15px;transition:color .2s;display:flex}.tk-reaction .tk-reaction-video .list .item .episodes .episode .episode-title[data-v-81fdaee1]:hover{color:var(--magic-primary-color,var(--brand_blue))}.tk-reaction .tk-reaction-video .list .item .episodes .episode .episode-title .playing-gif[data-v-81fdaee1]{background-image:url(https://i0.hdslb.com/bfs/static/jinkela/playlist-video/asserts/playing.gif);background-position:50%;background-repeat:no-repeat;background-size:12px 12px;flex-shrink:0;width:12px;height:12px;margin-right:5px;display:none}.tk-reaction .tk-reaction-video .list .item .episodes .episode .episode-title .title-txt[data-v-81fdaee1]{-webkit-line-clamp:1;text-overflow:ellipsis;word-break:break-all;line-break:anywhere;-webkit-box-orient:vertical;line-height:31px;display:-webkit-box;overflow:hidden}.tk-reaction .tk-reaction-video .list .item .episodes .episode .episode-meta[data-v-81fdaee1]{color:var(--text3);flex-shrink:0;margin-left:10px;font-size:14px}.tk-reaction.is-card-mode .item[data-v-81fdaee1]{contain-intrinsic-size:auto 80px!important}.tk-reaction.is-card-mode .header .mode-list[data-v-81fdaee1]{display:none!important}.tk-reaction.is-card-mode .header .mode-card[data-v-81fdaee1]{display:flex!important}\n/*$vite$:1*/ "); var STORAGE_NAMESPACE = "bilibili-playlist-enhancer"; var POD_MODE = { LIST: "LIST", CARD: "CARD" }; var POD_TYPE = { EPISODE: "EPISODE", COLLECTION: "COLLECTION", SERIES: "SERIES" }; var SORT_MODE = { DEFAULT: "DEFAULT", TIME_DESC: "TIME_DESC", TIME_ASC: "TIME_ASC" }; var CTRL_ACTION = { PREV: "PREV", NEXT: "NEXT" }; var SHUFFLE_SCOPE = { ALL: "ALL", GROUP: "GROUP" }; var DEFAULT_SETTINGS = { autoPlayNext: true, shuffle: false, shuffleScope: SHUFFLE_SCOPE.ALL, stopOnGroupEnd: false, scrollActiveOnClick: true, scrollToActiveOnSort: true, hotkeyPrev: "[", hotkeyNext: "]", sortMode: SORT_MODE.TIME_DESC }; var SORT_OPTIONS = [ { label: "官方排序", value: SORT_MODE.DEFAULT }, { label: "最新发布", value: SORT_MODE.TIME_DESC }, { label: "最早发布", value: SORT_MODE.TIME_ASC } ]; var SHUFFLE_SCOPE_OPTIONS = [{ label: "全合集随机", value: SHUFFLE_SCOPE.ALL }, { label: "仅当前分组", value: SHUFFLE_SCOPE.GROUP }]; var isArray = Array.isArray; var isFunction = (val) => typeof val === "function"; var isString = (val) => typeof val === "string"; var isNumber = (val) => typeof val === "number" && !Number.isNaN(val); var isDate = (val) => val instanceof Date && !Number.isNaN(val.getTime()); var isNil = (val) => val === null || val === void 0; var isObject = (val) => val !== null && typeof val === "object"; var assign = Object.assign; var pad2 = (n) => String(n).padStart(2, "0"); var DATE_FORMAT_REGEX = /\[([^\]]+)]|YYYY|YY|MM|M|DD|D|HH|H|hh|h|mm|m|ss|s|SSS|A|a/g; var UNITS_MAP = { zh: [{ value: 1e8, suffix: "亿" }, { value: 1e4, suffix: "万" }], en: [ { value: 1e9, suffix: "B" }, { value: 1e6, suffix: "M" }, { value: 1e3, suffix: "K" } ] }; function formatDuration(sec = 0) { const total = Math.max(0, Math.floor(Number(sec) || 0)); const h = Math.floor(total / 3600); const m = pad2(Math.floor(total % 3600 / 60)); const s = pad2(total % 60); if (h > 0) return `${pad2(h)}:${m}:${s}`; return `${m}:${s}`; } function formatNumberString(num, precision, trimZero) { const fixed = num.toFixed(precision); if (!trimZero) return fixed; return fixed.replace(/\.?0+$/, ""); } function addCommas(numStr) { const [integer, decimal] = numStr.split("."); const formattedInt = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ","); return decimal !== void 0 ? `${formattedInt}.${decimal}` : formattedInt; } function formatWithScale(absVal, precision, trimZero, unitType) { const scales = UNITS_MAP[unitType] || []; for (const scale of scales) if (absVal >= scale.value) return `${formatNumberString(absVal / scale.value, precision, trimZero)}${scale.suffix}`; return null; } function formatCount(num, options = {}) { const { precision = 1, trimZero = true, unit = "zh", comma = false, fallback = "0" } = options; if (isNil(num) || num === "") return fallback; const val = Number(num); if (Number.isNaN(val)) return fallback; if (val === 0) return "0"; const sign = val < 0 ? "-" : ""; const absVal = Math.abs(val); if (unit !== "none") { const scaledResult = formatWithScale(absVal, precision, trimZero, unit); if (scaledResult) return `${sign}${scaledResult}`; } const rawFormatted = formatNumberString(absVal, precision, trimZero); return `${sign}${comma ? addCommas(rawFormatted) : rawFormatted}`; } function parseTimestamp(val) { const timestamp = val < 1e11 ? val * 1e3 : val; const date = new Date(timestamp); return isDate(date) ? date : null; } function parseStringDate(str) { const trimmed = str.trim(); if (!trimmed) return null; if (/^\d+$/.test(trimmed)) return parseTimestamp(Number(trimmed)); const date = new Date(trimmed); return isDate(date) ? date : null; } function normalizeDate(input) { if (isNil(input) || input === "") return null; if (isDate(input)) return input; if (isNumber(input)) return parseTimestamp(input); if (isString(input)) return parseStringDate(input); return null; } function createTokenMap(date) { const y = date.getFullYear(); const m = date.getMonth() + 1; const d = date.getDate(); const H = date.getHours(); const h = H % 12 || 12; const i = date.getMinutes(); const s = date.getSeconds(); const ms = date.getMilliseconds(); return { YYYY: String(y), YY: String(y).slice(-2), MM: pad2(m), M: String(m), DD: pad2(d), D: String(d), HH: pad2(H), H: String(H), hh: pad2(h), h: String(h), mm: pad2(i), m: String(i), ss: pad2(s), s: String(s), SSS: String(ms).padStart(3, "0"), A: H >= 12 ? "PM" : "AM", a: H >= 12 ? "pm" : "am" }; } function formatDate(dateInput, template = "YYYY-MM-DD") { const date = normalizeDate(dateInput); if (!date) return ""; const tokens = createTokenMap(date); return template.replace(DATE_FORMAT_REGEX, (match, escaped) => escaped || tokens[match] || match); } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function queryElement(container, selector) { if (!isFunction(container?.querySelector)) return null; return container.querySelector(selector); } function getObserveTarget(container) { if (container instanceof Document) return container.documentElement; return container; } function waitElement(selector, { el = document, timeout = 1e4 } = {}) { return new Promise((resolve, reject) => { const existing = queryElement(el, selector); if (existing) { resolve(existing); return; } let timer = null; const observer = new MutationObserver(() => { const target = queryElement(el, selector); if (target) { stop(); resolve(target); } }); function stop() { observer.disconnect(); if (timer) clearTimeout(timer); } if (timeout > 0) timer = setTimeout(() => { stop(); reject(new Error(`Element not found: "${selector}"`)); }, timeout); observer.observe(getObserveTarget(el), { childList: true, subtree: true }); }); } var getEnvPrefix = () => { try { return ""; } catch { return ""; } }; var formatKey = (key, namespace = "") => { return `${getEnvPrefix()}${namespace ? `${namespace}:` : ""}${key}`; }; var getStorage = (key, defaultValue = null, namespace = "") => { if (!isString(key) || !key) return defaultValue; const finalKey = formatKey(key, namespace); const val = GM_getValue(finalKey, defaultValue); return isNil(val) ? defaultValue : val; }; var setStorage = (key, value, namespace = "") => { if (!isString(key) || !key) return; const finalKey = formatKey(key, namespace); GM_setValue(finalKey, value); }; var removeStorage = (key, namespace = "") => { if (!isString(key) || !key) return; const finalKey = formatKey(key, namespace); GM_deleteValue(finalKey); }; var clearStorage = (namespace = "") => { const prefix = formatKey("", namespace); const allKeys = GM_listValues(); for (const k of allKeys) if (k.startsWith(prefix)) GM_deleteValue(k); }; var createStorage = (namespace = "") => ({ get: (key, defaultValue) => getStorage(key, defaultValue, namespace), set: (key, value) => setStorage(key, value, namespace), remove: (key) => removeStorage(key, namespace), clear: () => clearStorage(namespace) }); var serializeParams = (params) => { if (!params) return ""; return Object.entries(params).filter(([, val]) => !isNil(val)).map(([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join("&"); }; var buildUrl = (baseUrl, params) => { if (!isObject(params)) return baseUrl; const queryString = serializeParams(params); if (!queryString) return baseUrl; return `${baseUrl}${baseUrl.includes("?") ? "&" : "?"}${queryString}`; }; var preparePayload = (data, headers) => { if (!isObject(data) || data instanceof FormData) return data; if (!headers["Content-Type"]) headers["Content-Type"] = "application/json"; return JSON.stringify(data); }; var parseResponseData = (responseText) => { if (!responseText) return responseText; try { return JSON.parse(responseText); } catch { return responseText; } }; var normalizeConfig = (config) => { return isString(config) ? { url: config } : config || {}; }; var handleResponse = (res, resolve, reject) => { if (res.status >= 200 && res.status < 300) resolve(parseResponseData(res.responseText)); else reject(res); }; var http = (config) => { const { url, method = "GET", data = null, params = null, headers = {}, timeout = 1e4, ...rest } = normalizeConfig(config); return new Promise((resolve, reject) => { const requestUrl = buildUrl(url, params); const requestData = preparePayload(data, headers); GM_xmlhttpRequest({ url: requestUrl, method: method.toUpperCase(), data: requestData, headers, timeout, ...rest, onload: (res) => handleResponse(res, resolve, reject), onerror: reject, ontimeout: () => reject(new Error("Timeout")) }); }); }; http.get = (url, config = {}) => http({ ...config, url, method: "GET" }); http.post = (url, data, config = {}) => http({ ...config, url, data, method: "POST" }); http.put = (url, data, config = {}) => http({ ...config, url, data, method: "PUT" }); http.delete = (url, config = {}) => http({ ...config, url, method: "DELETE" }); function getUrlParams() { let queryString = window.location.search; if (!queryString && window.location.hash.includes("?")) queryString = window.location.hash.slice(window.location.hash.indexOf("?")); return Object.fromEntries(new URLSearchParams(queryString)); } function on(target, type, listener, options) { if (!isFunction(listener)) return () => {}; const el = isString(target) ? document.querySelector(target) : target; if (!el || !isFunction(el.addEventListener)) return () => {}; el.addEventListener(type, listener, options); return () => { el.removeEventListener(type, listener, options); }; } function once(target, type, listener, options) { return on(target, type, listener, isObject(options) && options !== null ? { ...options, once: true } : { once: true }); } function getNativeInnerVm() { let vm = document.querySelector(".multip-list-item-inner")?.__vue__; let switchVideoVm = null; let playlistRouteVm = null; while (vm) { if (!switchVideoVm && isFunction(vm.switchVideo)) switchVideoVm = vm; if (!playlistRouteVm && isFunction(vm.handlePlaylistRoute)) playlistRouteVm = vm; if (switchVideoVm && playlistRouteVm) break; vm = vm.$parent; } return { switchVideoVm, playlistRouteVm }; } var playerState = () => { const player = unsafeWindow?.player; if (!player) return {}; return { pause: () => player.pause?.(), reload: (payload) => player.reload(payload), autoPlayNext: (flag = true) => player.setHandoff(flag ? 0 : 2), isAutoPlayNext: () => player.getHandoff() === 0, isWide: unsafeWindow?.isWide }; }; function executeVideoSwitch(podType, payload) { if (podType === POD_TYPE.SERIES) { const { switchVideoVm, playlistRouteVm } = getNativeInnerVm(); if (switchVideoVm) { switchVideoVm.switchVideo({ ...payload, type: 2 }); if (!playlistRouteVm.curResourceListItem) playlistRouteVm.$router.push({ query: { ...playlistRouteVm.$route.query, oid: payload.aid, bvid: payload.bvid, p: payload.p } }); return; } } playerState().reload(payload); } function interceptPlayerControls(callback) { return on(document, "click", (event) => { if (!event.target?.closest?.(".bpx-player-control-bottom-left")) return; const btn = event.target?.closest?.(".bpx-player-ctrl-prev, .bpx-player-ctrl-next"); if (!btn) return; event.stopImmediatePropagation(); event.preventDefault(); const action = btn.classList.contains("bpx-player-ctrl-prev") ? CTRL_ACTION.PREV : CTRL_ACTION.NEXT; callback?.(action, event); }, { capture: true }); } function interceptPlayerEnding(onEndedHandler) { return on(document, "ended", (event) => { if (event.target?.tagName !== "VIDEO") return; onEndedHandler?.(event); }, { capture: true }); } function getPodTargetSelectors(podType) { const isSeries = (0, vue.toValue)(podType) === POD_TYPE.SERIES; return { playlistContainerRight: isSeries ? ".playlist-container--right" : ".right-container-inner", listBody: isSeries ? "#playlist-video-action-list" : ".video-pod__body", actionHeader: isSeries ? ".action-list-header" : ".video-pod__header .right", activeActionHeader: isSeries ? ".action-list-header.action-list-header-folded" : ".video-pod.expanded" }; } function calculateMaxHeight(podType, wrapEl, defaultHeight) { if ((0, vue.toValue)(podType) !== POD_TYPE.SERIES) return defaultHeight; const bodyHeight = wrapEl.querySelector("#playlist-video-action-list-body")?.clientHeight || 0; const topHeight = wrapEl.querySelector(".action-list-body-top")?.clientHeight || 0; return Math.max(bodyHeight - (topHeight + 24), 0); } function observeSeriesContainers(ro, wrapEl) { const danmakuBox = wrapEl.querySelector(".danmaku-box"); const actionListContainer = wrapEl.querySelector(".action-list-container"); if (danmakuBox) ro.observe(danmakuBox); if (actionListContainer) ro.observe(actionListContainer); } function calculateRelativeOffset(targetEl, containerEl) { let offset = 0; let node = targetEl; while (node && node !== containerEl) { offset += node.offsetTop; node = node.offsetParent; } return offset; } function alignActiveItem(container, smooth) { const activeEl = container.querySelector(".episode.is-active") || container.querySelector(".header.is-active"); if (!activeEl?.clientHeight || !container.clientHeight) return false; const targetTop = calculateRelativeOffset(activeEl, container) - (container.clientHeight - activeEl.clientHeight) / 2; container.scrollTo({ top: Math.max(0, targetTop), behavior: smooth ? "smooth" : "auto" }); return true; } function observeContainerSettling(container, onSettle) { const ro = new ResizeObserver(() => { if (onSettle()) ro.disconnect(); }); ro.observe(container); } function scrollActiveItem(container, smooth = true) { if (!container) return; if (smooth) { (0, vue.nextTick)(() => alignActiveItem(container, true)); return; } observeContainerSettling(container, () => alignActiveItem(container, false)); } function calculateCollectionToggleTargetLayout(podType, isExpanded, currentTop) { if (podType === POD_TYPE.COLLECTION) { const toolbar = document.querySelector("#arc_toolbar_report"); const card = document.querySelector(".video-page-card-small, .video-page-special-card-small, .video-page-operator-card-small, .video-page-game-card-small"); const aboveModule = document.querySelector(".video-pod-above-modules.collapsed, .video-pod-above-modules"); const podBody = document.querySelector(".video-pod__body"); const s = toolbar?.getBoundingClientRect().bottom ?? 0; const c = card?.getBoundingClientRect().top ?? 0; const l = aboveModule?.getBoundingClientRect().height || aboveModule?.scrollHeight || 0; const n = podBody?.clientHeight ?? 0; if (isExpanded) { const diff = s && c ? s - c : 0; return { targetHeight: Math.max(250, Math.floor(n + diff + l)), targetTop: currentTop - l }; } return { targetHeight: 250, targetTop: currentTop + l }; } if (podType === POD_TYPE.SERIES) { const listEl = document.querySelector("#playlist-video-action-list"); const targetTop = listEl ? listEl.getBoundingClientRect().top + window.scrollY : currentTop; return { targetHeight: isExpanded ? 0 : document.querySelector(".action-list-body-bottom")?.clientHeight ?? 0, targetTop }; } return { targetHeight: 250, targetTop: currentTop }; } async function watchPositionSync(podType, hostEl, onPositionUpdate) { const currentType = (0, vue.toValue)(podType); const selectors = getPodTargetSelectors(currentType); const wrapEl = await waitElement(selectors.playlistContainerRight); const targetEl = await waitElement(selectors.listBody, { el: wrapEl }); let isAnimating = false; const getHost = () => isFunction(hostEl) ? hostEl() : (0, vue.toValue)(hostEl); const updatePosition = (force = false) => { if (isAnimating && !force) return; const host = getHost(); if (!targetEl.isConnected || host && !host.isConnected) return; const { top, left, width, height } = targetEl.getBoundingClientRect(); const maxHeight = calculateMaxHeight(currentType, wrapEl, height); onPositionUpdate({ top: top + window.scrollY, left: left + window.scrollX, width, maxHeight }); }; let isDestroyed = false; const ro = new ResizeObserver(() => updatePosition(false)); ro.observe(wrapEl); waitElement("#bilibili-player, .player-wrap, .left-container", { timeout: 5e3 }).then((playerEl) => { if (!isDestroyed && playerEl) ro.observe(playerEl); }).catch(() => {}); if (currentType === POD_TYPE.SERIES) observeSeriesContainers(ro, wrapEl); const offResize = on(window, "resize", () => updatePosition(false)); const cleanup = () => { isDestroyed = true; ro.disconnect(); offResize(); }; cleanup.setAnimating = (val) => { isAnimating = val; }; cleanup.updatePosition = updatePosition; cleanup.destroy = cleanup; cleanup.wrapEl = wrapEl; cleanup.targetEl = targetEl; return cleanup; } async function listenNativeToggle(podType, onToggle) { const currentType = (0, vue.toValue)(podType); if (currentType === POD_TYPE.EPISODE) return; const actionHeader = getPodTargetSelectors(currentType).actionHeader; const nativeToggleBtn = await waitElement(actionHeader); if (!nativeToggleBtn) return; const handler = () => { const { isWide } = playerState(); if (isWide && currentType === POD_TYPE.COLLECTION) return; requestAnimationFrame(() => { const expanded = document.querySelector(getPodTargetSelectors(currentType).activeActionHeader); onToggle?.(!!expanded); }); }; return on(nativeToggleBtn, "click", handler, { capture: true }); } function usePositionSync(podType, rootRef, onModeChange) { const position = (0, vue.ref)({ top: 0, left: 0, width: 0, maxHeight: 0 }); const isAnimating = (0, vue.ref)(false); let cleanupTracker = null; let cleanupResize = null; const containerStyle = (0, vue.computed)(() => ({ top: `${position.value.top}px`, left: `${position.value.left}px`, width: `${position.value.width}px`, maxHeight: `${position.value.maxHeight}px` })); const videoBodyStyle = (0, vue.computed)(() => ({ width: `${position.value.width}px` })); const stopSync = () => { cleanupTracker?.destroy?.(); cleanupResize?.(); cleanupTracker = null; cleanupResize = null; isAnimating.value = false; }; const startSync = async (type) => { stopSync(); const resolvedType = (0, vue.toValue)(type); if (!resolvedType) return; cleanupTracker = await watchPositionSync(resolvedType, rootRef, (newPos) => { position.value = newPos; }); cleanupResize = await listenNativeToggle(resolvedType, (isExpanded) => { isAnimating.value = true; cleanupTracker.setAnimating(true); const { targetHeight, targetTop } = calculateCollectionToggleTargetLayout(resolvedType, isExpanded, position.value.top); position.value = { ...position.value, top: targetTop, maxHeight: targetHeight }; onModeChange?.(isExpanded); setTimeout(() => { cleanupTracker?.setAnimating(false); isAnimating.value = false; }, 300); }); }; return { isAnimating, containerStyle, videoBodyStyle, startSync }; } var storage = createStorage(STORAGE_NAMESPACE); var settingsState = (0, vue.reactive)({ ...DEFAULT_SETTINGS }); var isInitialized = false; function setupSettingsWatchers() { (0, vue.watch)(settingsState, (newVal) => { storage.set("user_settings", { ...newVal }); }, { deep: true }); } function initSettings() { if (isInitialized) return settingsState; isInitialized = true; const savedSettings = storage.get("user_settings", null); if (savedSettings) assign(settingsState, savedSettings); playerState().autoPlayNext?.(false); setupSettingsWatchers(); return settingsState; } function useSettings() { const updateSettings = (patch) => { assign(settingsState, patch); }; const resetSettings = () => { assign(settingsState, DEFAULT_SETTINGS); }; return { settings: settingsState, updateSettings, resetSettings }; } function getCurrentBvid() { const { pathname, search } = window.location; const pathMatch = pathname.match(/\/video\/(BV[a-zA-Z0-9]+)/i); if (pathMatch) return pathMatch[1]; const paramBvid = new URLSearchParams(search).get("bvid"); if (paramBvid && /^BV[a-zA-Z0-9]+$/i.test(paramBvid)) return paramBvid; return null; } function getCurrentPage() { const { p } = getUrlParams(); return parseInt(p || "1", 10); } async function fetchMedialistEpisodes(type, bizId) { const rawList = []; const seenIds = new Set(); let hasMore = true; let lastOid; let safetyCounter = 30; while (hasMore && safetyCounter > 0) { safetyCounter -= 1; const res = await http({ url: "https://api.bilibili.com/x/v2/medialist/resource/list", params: { type, biz_id: bizId, ps: 100, otype: 2, mobi_app: "web", ...lastOid ? { oid: lastOid } : {} } }); const mediaList = res?.data?.media_list; if (!mediaList || res?.code !== 0) break; for (const item of mediaList) if (!seenIds.has(item.id)) { seenIds.add(item.id); rawList.push(item); } hasMore = !!(res.data.has_more && mediaList.length > 0); lastOid = mediaList[mediaList.length - 1]?.id; } if (!rawList.length) return null; return { sections: rawList, mode: POD_MODE.CARD, type: POD_TYPE.SERIES }; } async function fetchVideoEpisodes(bvid) { const res = await http({ url: "https://api.bilibili.com/x/web-interface/view", params: { bvid } }); if (res?.code !== 0 || !res?.data) return null; const { data } = res; const seasonEpisodes = data.ugc_season?.sections?.flatMap((s) => s.episodes || []) || []; if (seasonEpisodes.length > 0) return { sections: seasonEpisodes, mode: POD_MODE.LIST, type: POD_TYPE.COLLECTION }; const pages = data.pages || []; if (pages.length <= 1) return null; return { sections: pages, mode: POD_MODE.LIST, type: POD_TYPE.EPISODE }; } function getMediaListMid() { const { pathname } = window.location; const match = pathname.match(/\/list\/(?:ml)?(\d+)/i); return match ? match[1] : null; } async function fetchSeasonInfo(bvid, sid) { try { if (sid) return await fetchMedialistEpisodes(5, sid); const upMid = getMediaListMid(); if (upMid) return await fetchMedialistEpisodes(1, upMid); return await fetchVideoEpisodes(bvid); } catch (err) { console.error("[bilibili-playlist-enhancer] 获取合集信息失败:", err); return null; } } function buildEpisodes({ pages, title, duration, isGroupActive, currentPage, fallbackCid, fallbackPage = 1 }) { const defaultPage = fallbackPage || 1; return (isArray(pages) && pages.length > 0 ? pages : [{ cid: fallbackCid, page: defaultPage, part: title, duration }]).map((pageItem, pIdx) => { const pageNum = pageItem.page || defaultPage || pIdx + 1; return { cid: pageItem.cid || pageItem.id, page: pageNum, rawEpIndex: pIdx, title: pageItem.part || pageItem.title || title, duration: formatDuration(pageItem.duration || duration), isActive: isGroupActive && Number(pageNum) === Number(currentPage || 1) }; }); } function extractRawItem(item, currentBvid, index) { return { aid: item.aid || item.id, type: item.type, bvid: item.bvid || item.bv_id || currentBvid, title: item.title || item.part, cover: item.cover || item.pic || item.arc?.pic || "", pubdate: formatDate(item.arc?.pubdate || item.pubtime || item.pubdate || 0), duration: item.arc?.duration || item.duration || 0, views: formatCount(item.arc?.stat?.view ?? item.stat?.view ?? item.cnt_info?.play ?? 0), danmakus: formatCount(item.arc?.stat?.danmaku ?? item.stat?.danmaku ?? item.cnt_info?.danmaku ?? 0), pubTimestamp: item.pubdate || item.arc?.pubdate || item.pubtime || 0, page: item.page, rawIndex: index }; } function processSeasonData(podType, rawList = [], currentBvid = "", currentPage = 1) { const isEpisode = podType === POD_TYPE.EPISODE; return rawList.map((item, idx) => { const raw = extractRawItem(item, currentBvid, idx); const isGroupActive = raw.bvid === currentBvid; const hasPages = isArray(item.pages) && item.pages.length > 1; const episodes = buildEpisodes({ pages: item.pages, title: raw.title, duration: raw.duration, isGroupActive, currentPage, fallbackCid: raw.cid || item.cid, fallbackPage: raw.page }); const isActive = isEpisode ? isGroupActive && Number(raw.page) === Number(currentPage) : isGroupActive; return { ...raw, isActive, isMultiPage: hasPages, episodes }; }); } var SORT_STRATEGIES = { [SORT_MODE.TIME_ASC]: (list) => [...list].sort((a, b) => (a.pubTimestamp || 0) - (b.pubTimestamp || 0)), [SORT_MODE.TIME_DESC]: (list) => [...list].sort((a, b) => (b.pubTimestamp || 0) - (a.pubTimestamp || 0)), [SORT_MODE.DEFAULT]: (list) => [...list].sort((a, b) => (a.rawIndex || 0) - (b.rawIndex || 0)) }; var findActiveAccordionBvid = (current) => current?.group?.isMultiPage ? current.group.bvid : ""; function updateGroupActive(group, podType, activeBvid, activePage) { const currentP = Number(activePage) || 1; const isGroupActive = group.bvid === activeBvid; const isEpisodeMatch = Number(group.page || 1) === currentP; const isActive = podType === POD_TYPE.EPISODE ? isGroupActive && isEpisodeMatch : isGroupActive; const episodes = (group.episodes || []).map((ep) => ({ ...ep, isActive: isGroupActive && Number(ep.page || 1) === currentP })); return { ...group, isActive, episodes }; } async function fetchPodPayload(bvid, page) { const { sid } = getUrlParams(); const podData = await fetchSeasonInfo(bvid, sid); if (!podData) return null; return { type: podData.type, mode: podData.mode, groups: processSeasonData(podData.type, podData.sections, bvid, page) }; } var isSameVideoTarget = (currentBvid, currentPage, nextBvid, nextPage) => currentBvid === nextBvid && Number(currentPage || 1) === Number(nextPage || 1); var hasSeasonBvid = (groups, bvid) => groups.some((g) => g.bvid === bvid); var flattenEpisodes = (groups) => groups.flatMap((group, groupIndex) => (group.episodes || []).map((ep, epIndex) => ({ group, groupIndex, epIndex, episode: ep, bvid: group.bvid, page: Number(ep.page || 1) }))); function findCurrentPlaying(flatList, activeBvid, activePage) { const currentP = Number(activePage) || 1; const flatIndex = flatList.findIndex((item) => item.bvid === activeBvid && item.page === currentP); if (flatIndex === -1) return null; const currentItem = flatList[flatIndex]; const totalEpisodes = flatList.length; const groupEpisodes = currentItem.group?.episodes || []; return { groupIndex: currentItem.groupIndex, group: currentItem.group, epIndex: currentItem.epIndex, episode: currentItem.episode, flatIndex, totalEpisodes, isFirst: flatIndex === 0, isLast: flatIndex === totalEpisodes - 1, isGroupFirst: currentItem.epIndex === 0, isGroupLast: currentItem.epIndex === groupEpisodes.length - 1 }; } function getAdjacentVideo(flatList, currentFlatIndex, action) { if (currentFlatIndex === -1 || currentFlatIndex == null) return null; return flatList[currentFlatIndex + (action === CTRL_ACTION.PREV ? -1 : 1)] || null; } function useAccordion() { const expandedBvid = (0, vue.ref)(""); const isExpanded = (bvid) => expandedBvid.value === bvid; const toggleAccordion = (bvid) => { expandedBvid.value = expandedBvid.value === bvid ? "" : bvid; }; const setExpanded = (bvid) => { expandedBvid.value = bvid; }; return { expandedBvid, isExpanded, toggleAccordion, setExpanded }; } function usePodSort(rawGroups, settings) { const sortMode = (0, vue.computed)({ get: () => settings.sortMode, set: (val) => { settings.sortMode = val; } }); const groups = (0, vue.computed)(() => (SORT_STRATEGIES[sortMode.value] || SORT_STRATEGIES[SORT_MODE.DEFAULT])(rawGroups.value)); const setSortMode = (mode) => { settings.sortMode = mode; }; return { sortMode, groups, setSortMode }; } function usePodPlayback({ rawGroups, podType, activeBvid, activePage, flatEpisodes, currentPlaying, scrollContainerRef, setExpanded, toggleAccordion, settings, shuffleActions }) { const refreshActiveStates = (isManualClick = false) => { rawGroups.value = rawGroups.value.map((g) => updateGroupActive(g, podType.value, activeBvid.value, activePage.value)); setExpanded(findActiveAccordionBvid(currentPlaying.value)); if (isManualClick && settings && !settings.scrollActiveOnClick) return; scrollActiveItem(scrollContainerRef.value, true); }; const switchActiveVideo = (type, data, index, isManual = true) => { const { bvid, aid, episodes } = data; const { page, cid, isActive } = episodes[index]; if (isActive) return; if (isManual) shuffleActions?.recordManualPlay?.({ bvid, page }); activeBvid.value = bvid; activePage.value = page; executeVideoSwitch(podType.value, { aid, bvid, cid, p: page }); refreshActiveStates(isManual); }; const handleHeaderClick = (group) => { if (group.isActive && group.isMultiPage) { toggleAccordion(group.bvid); return; } switchActiveVideo(podType.value, group, 0, true); }; const getNextTarget = (action) => { if (settings?.shuffle) { const shuffleTarget = action === CTRL_ACTION.PREV ? shuffleActions?.getPrevShuffleTarget?.() : shuffleActions?.getNextShuffleTarget?.(); if (shuffleTarget) return shuffleTarget; } const currentIndex = currentPlaying.value?.flatIndex; return getAdjacentVideo(flatEpisodes.value, currentIndex, action); }; const handlePlayerCtrl = (action) => { const target = getNextTarget(action); if (target) switchActiveVideo(podType.value, target.group, target.epIndex, false); }; const setActiveTarget = (bvid, page) => { activeBvid.value = bvid; activePage.value = page; }; return { refreshActiveStates, switchActiveVideo, handleHeaderClick, handlePlayerCtrl, setActiveTarget }; } function usePodLoader({ rawGroups, podType, activeBvid, activePage, rootRef, scrollContainerRef, setExpanded, currentPlaying, setActiveTarget, refreshActiveStates }) { const isLoading = (0, vue.ref)(false); const podMode = (0, vue.ref)(POD_MODE.LIST); const onModeChange = (isExpandedMode) => { podMode.value = isExpandedMode ? POD_MODE.CARD : POD_MODE.LIST; setTimeout(() => { scrollActiveItem(scrollContainerRef.value, true); }, 300); }; const { isAnimating, containerStyle, videoBodyStyle, startSync } = usePositionSync(podType, rootRef, onModeChange); const trySwitchSameSeason = (nextBvid, nextPage) => { if (!hasSeasonBvid(rawGroups.value, nextBvid)) return false; if (isSameVideoTarget(activeBvid.value, activePage.value, nextBvid, nextPage)) return true; setActiveTarget(nextBvid, nextPage); refreshActiveStates(); return true; }; const initPodState = async (payload, bvid, page) => { if (!payload) { rawGroups.value = []; setExpanded(""); podType.value = null; return; } podType.value = payload.type; podMode.value = payload.mode; rawGroups.value = payload.groups; setActiveTarget(bvid, page); setExpanded(findActiveAccordionBvid(currentPlaying.value)); await startSync(podType.value); scrollActiveItem(scrollContainerRef.value, false); }; const loadPod = async () => { const nextBvid = getCurrentBvid(); const nextPage = getCurrentPage(); if (!nextBvid) return; if (isSameVideoTarget(activeBvid.value, activePage.value, nextBvid, nextPage)) return; if (trySwitchSameSeason(nextBvid, nextPage)) return; setActiveTarget(nextBvid, nextPage); rawGroups.value = []; podType.value = null; isLoading.value = true; try { const payload = await fetchPodPayload(nextBvid, nextPage); await initPodState(payload, nextBvid, nextPage); } finally { isLoading.value = false; } }; return { isAnimating, isLoading, podMode, containerStyle, videoBodyStyle, initPodState, loadPod, trySwitchSameSeason }; } function isInputElement(event) { return (event.composedPath ? event.composedPath() : [event.target]).some((el) => { const tag = el?.tagName?.toLowerCase(); return tag === "input" || tag === "textarea" || tag === "magic-input" || el?.isContentEditable; }); } function formatKeyboardKey(event) { if ([ "Control", "Shift", "Alt", "Meta" ].includes(event.key)) return ""; const parts = []; if (event.ctrlKey) parts.push("Ctrl"); if (event.altKey) parts.push("Alt"); if (event.shiftKey) parts.push("Shift"); if (event.metaKey) parts.push("Meta"); let key = event.key; if (key === " ") key = "Space"; else if (key.length === 1) key = key.toUpperCase(); parts.push(key); return parts.join("+"); } function matchesHotkey(event, targetHotkey) { if (!targetHotkey) return false; const currentKey = formatKeyboardKey(event); if (!currentKey) return false; return currentKey.toLowerCase() === targetHotkey.trim().toLowerCase(); } function usePodHotkeys({ settings, handlePlayerCtrl }) { const onKeyDown = (event) => { if (isInputElement(event.target)) return; if (matchesHotkey(event, settings.hotkeyPrev)) { event.preventDefault(); event.stopImmediatePropagation(); handlePlayerCtrl?.(CTRL_ACTION.PREV); return; } if (matchesHotkey(event, settings.hotkeyNext)) { event.preventDefault(); event.stopImmediatePropagation(); handlePlayerCtrl?.(CTRL_ACTION.NEXT); } }; return on(window, "keydown", onKeyDown, { capture: true }); } var t$1 = globalThis; var e$2 = t$1.ShadowRoot && (void 0 === t$1.ShadyCSS || t$1.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype; var s$2 = Symbol(); var o$3 = new WeakMap(); var n$2 = class { constructor(t, e, o) { if (this._$cssResult$ = !0, o !== s$2) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); this.cssText = t, this.t = e; } get styleSheet() { let t = this.o; const s = this.t; if (e$2 && void 0 === t) { const e = void 0 !== s && 1 === s.length; e && (t = o$3.get(s)), void 0 === t && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), e && o$3.set(s, t)); } return t; } toString() { return this.cssText; } }; var r$2 = (t) => new n$2("string" == typeof t ? t : t + "", void 0, s$2); var S$1 = (s, o) => { if (e$2) s.adoptedStyleSheets = o.map((t) => t instanceof CSSStyleSheet ? t : t.styleSheet); else for (const e of o) { const o = document.createElement("style"), n = t$1.litNonce; void 0 !== n && o.setAttribute("nonce", n), o.textContent = e.cssText, s.appendChild(o); } }; var c$2 = e$2 ? (t) => t : (t) => t instanceof CSSStyleSheet ? ((t) => { let e = ""; for (const s of t.cssRules) e += s.cssText; return r$2(e); })(t) : t; var { is: i$2, defineProperty: e$1, getOwnPropertyDescriptor: h$1, getOwnPropertyNames: r$1, getOwnPropertySymbols: o$2, getPrototypeOf: n$1 } = Object, a$1 = globalThis, c$1 = a$1.trustedTypes, l$1 = c$1 ? c$1.emptyScript : "", p$1 = a$1.reactiveElementPolyfillSupport, d$1 = (t, s) => t, u$1 = { toAttribute(t, s) { switch (s) { case Boolean: t = t ? l$1 : null; break; case Object: case Array: t = null == t ? t : JSON.stringify(t); } return t; }, fromAttribute(t, s) { let i = t; switch (s) { case Boolean: i = null !== t; break; case Number: i = null === t ? null : Number(t); break; case Object: case Array: try { i = JSON.parse(t); } catch (t) { i = null; } } return i; } }, f$1 = (t, s) => !i$2(t, s), b$1 = { attribute: !0, type: String, converter: u$1, reflect: !1, useDefault: !1, hasChanged: f$1 }; Symbol.metadata ??= Symbol("metadata"), a$1.litPropertyMetadata ??= new WeakMap(); var y$1 = class extends HTMLElement { static addInitializer(t) { this._$Ei(), (this.l ??= []).push(t); } static get observedAttributes() { return this.finalize(), this._$Eh && [...this._$Eh.keys()]; } static createProperty(t, s = b$1) { if (s.state && (s.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(t) && ((s = Object.create(s)).wrapped = !0), this.elementProperties.set(t, s), !s.noAccessor) { const i = Symbol(), h = this.getPropertyDescriptor(t, i, s); void 0 !== h && e$1(this.prototype, t, h); } } static getPropertyDescriptor(t, s, i) { const { get: e, set: r } = h$1(this.prototype, t) ?? { get() { return this[s]; }, set(t) { this[s] = t; } }; return { get: e, set(s) { const h = e?.call(this); r?.call(this, s), this.requestUpdate(t, h, i); }, configurable: !0, enumerable: !0 }; } static getPropertyOptions(t) { return this.elementProperties.get(t) ?? b$1; } static _$Ei() { if (this.hasOwnProperty(d$1("elementProperties"))) return; const t = n$1(this); t.finalize(), void 0 !== t.l && (this.l = [...t.l]), this.elementProperties = new Map(t.elementProperties); } static finalize() { if (this.hasOwnProperty(d$1("finalized"))) return; if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(d$1("properties"))) { const t = this.properties, s = [...r$1(t), ...o$2(t)]; for (const i of s) this.createProperty(i, t[i]); } const t = this[Symbol.metadata]; if (null !== t) { const s = litPropertyMetadata.get(t); if (void 0 !== s) for (const [t, i] of s) this.elementProperties.set(t, i); } this._$Eh = new Map(); for (const [t, s] of this.elementProperties) { const i = this._$Eu(t, s); void 0 !== i && this._$Eh.set(i, t); } this.elementStyles = this.finalizeStyles(this.styles); } static finalizeStyles(s) { const i = []; if (Array.isArray(s)) { const e = new Set(s.flat(1 / 0).reverse()); for (const s of e) i.unshift(c$2(s)); } else void 0 !== s && i.push(c$2(s)); return i; } static _$Eu(t, s) { const i = s.attribute; return !1 === i ? void 0 : "string" == typeof i ? i : "string" == typeof t ? t.toLowerCase() : void 0; } constructor() { super(), this._$Ep = void 0, this.isUpdatePending = !1, this.hasUpdated = !1, this._$Em = null, this._$Ev(); } _$Ev() { this._$ES = new Promise((t) => this.enableUpdating = t), this._$AL = new Map(), this._$E_(), this.requestUpdate(), this.constructor.l?.forEach((t) => t(this)); } addController(t) { (this._$EO ??= new Set()).add(t), void 0 !== this.renderRoot && this.isConnected && t.hostConnected?.(); } removeController(t) { this._$EO?.delete(t); } _$E_() { const t = new Map(), s = this.constructor.elementProperties; for (const i of s.keys()) this.hasOwnProperty(i) && (t.set(i, this[i]), delete this[i]); t.size > 0 && (this._$Ep = t); } createRenderRoot() { const t = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions); return S$1(t, this.constructor.elementStyles), t; } connectedCallback() { this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(!0), this._$EO?.forEach((t) => t.hostConnected?.()); } enableUpdating(t) {} disconnectedCallback() { this._$EO?.forEach((t) => t.hostDisconnected?.()); } attributeChangedCallback(t, s, i) { this._$AK(t, i); } _$ET(t, s) { const i = this.constructor.elementProperties.get(t), e = this.constructor._$Eu(t, i); if (void 0 !== e && !0 === i.reflect) { const h = (void 0 !== i.converter?.toAttribute ? i.converter : u$1).toAttribute(s, i.type); this._$Em = t, null == h ? this.removeAttribute(e) : this.setAttribute(e, h), this._$Em = null; } } _$AK(t, s) { const i = this.constructor, e = i._$Eh.get(t); if (void 0 !== e && this._$Em !== e) { const t = i.getPropertyOptions(e), h = "function" == typeof t.converter ? { fromAttribute: t.converter } : void 0 !== t.converter?.fromAttribute ? t.converter : u$1; this._$Em = e; const r = h.fromAttribute(s, t.type); this[e] = r ?? this._$Ej?.get(e) ?? r, this._$Em = null; } } requestUpdate(t, s, i, e = !1, h) { if (void 0 !== t) { const r = this.constructor; if (!1 === e && (h = this[t]), i ??= r.getPropertyOptions(t), !((i.hasChanged ?? f$1)(h, s) || i.useDefault && i.reflect && h === this._$Ej?.get(t) && !this.hasAttribute(r._$Eu(t, i)))) return; this.C(t, s, i); } !1 === this.isUpdatePending && (this._$ES = this._$EP()); } C(t, s, { useDefault: i, reflect: e, wrapped: h }, r) { i && !(this._$Ej ??= new Map()).has(t) && (this._$Ej.set(t, r ?? s ?? this[t]), !0 !== h || void 0 !== r) || (this._$AL.has(t) || (this.hasUpdated || i || (s = void 0), this._$AL.set(t, s)), !0 === e && this._$Em !== t && (this._$Eq ??= new Set()).add(t)); } async _$EP() { this.isUpdatePending = !0; try { await this._$ES; } catch (t) { Promise.reject(t); } const t = this.scheduleUpdate(); return null != t && await t, !this.isUpdatePending; } scheduleUpdate() { return this.performUpdate(); } performUpdate() { if (!this.isUpdatePending) return; if (!this.hasUpdated) { if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) { for (const [t, s] of this._$Ep) this[t] = s; this._$Ep = void 0; } const t = this.constructor.elementProperties; if (t.size > 0) for (const [s, i] of t) { const { wrapped: t } = i, e = this[s]; !0 !== t || this._$AL.has(s) || void 0 === e || this.C(s, void 0, i, e); } } let t = !1; const s = this._$AL; try { t = this.shouldUpdate(s), t ? (this.willUpdate(s), this._$EO?.forEach((t) => t.hostUpdate?.()), this.update(s)) : this._$EM(); } catch (s) { throw t = !1, this._$EM(), s; } t && this._$AE(s); } willUpdate(t) {} _$AE(t) { this._$EO?.forEach((t) => t.hostUpdated?.()), this.hasUpdated || (this.hasUpdated = !0, this.firstUpdated(t)), this.updated(t); } _$EM() { this._$AL = new Map(), this.isUpdatePending = !1; } get updateComplete() { return this.getUpdateComplete(); } getUpdateComplete() { return this._$ES; } shouldUpdate(t) { return !0; } update(t) { this._$Eq &&= this._$Eq.forEach((t) => this._$ET(t, this[t])), this._$EM(); } updated(t) {} firstUpdated(t) {} }; y$1.elementStyles = [], y$1.shadowRootOptions = { mode: "open" }, y$1[d$1("elementProperties")] = new Map(), y$1[d$1("finalized")] = new Map(), p$1?.({ ReactiveElement: y$1 }), (a$1.reactiveElementVersions ??= []).push("2.1.2"); var t = globalThis; var i$1 = (t) => t; var s$1 = t.trustedTypes; var e = s$1 ? s$1.createPolicy("lit-html", { createHTML: (t) => t }) : void 0; var h = "$lit$"; var o$1 = `lit$${Math.random().toFixed(9).slice(2)}$`; var n = "?" + o$1; var r = `<${n}>`; var l = document; var c = () => l.createComment(""); var a = (t) => null === t || "object" != typeof t && "function" != typeof t; var u = Array.isArray; var d = (t) => u(t) || "function" == typeof t?.[Symbol.iterator]; var f = "[ \n\f\r]"; var v = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g; var _ = /-->/g; var m = />/g; var p = RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`, "g"); var g = /'/g; var $ = /"/g; var y = /^(?:script|style|textarea|title)$/i; var x = (t) => (i, ...s) => ({ _$litType$: t, strings: i, values: s }); var b = x(1); var E = Symbol.for("lit-noChange"); var A = Symbol.for("lit-nothing"); var C = new WeakMap(); var P = l.createTreeWalker(l, 129); function V(t, i) { if (!u(t) || !t.hasOwnProperty("raw")) throw Error("invalid template strings array"); return void 0 !== e ? e.createHTML(i) : i; } var N = (t, i) => { const s = t.length - 1, e = []; let n, l = 2 === i ? "" : 3 === i ? "" : "")), e]; }; var S = class S { constructor({ strings: t, _$litType$: i }, e) { let r; this.parts = []; let l = 0, a = 0; const u = t.length - 1, d = this.parts, [f, v] = N(t, i); if (this.el = S.createElement(f, e), P.currentNode = this.el.content, 2 === i || 3 === i) { const t = this.el.content.firstChild; t.replaceWith(...t.childNodes); } for (; null !== (r = P.nextNode()) && d.length < u;) { if (1 === r.nodeType) { if (r.hasAttributes()) for (const t of r.getAttributeNames()) if (t.endsWith(h)) { const i = v[a++], s = r.getAttribute(t).split(o$1), e = /([.?@])?(.*)/.exec(i); d.push({ type: 1, index: l, name: e[2], strings: s, ctor: "." === e[1] ? I : "?" === e[1] ? L : "@" === e[1] ? z : H }), r.removeAttribute(t); } else t.startsWith(o$1) && (d.push({ type: 6, index: l }), r.removeAttribute(t)); if (y.test(r.tagName)) { const t = r.textContent.split(o$1), i = t.length - 1; if (i > 0) { r.textContent = s$1 ? s$1.emptyScript : ""; for (let s = 0; s < i; s++) r.append(t[s], c()), P.nextNode(), d.push({ type: 2, index: ++l }); r.append(t[i], c()); } } } else if (8 === r.nodeType) if (r.data === n) d.push({ type: 2, index: l }); else { let t = -1; for (; -1 !== (t = r.data.indexOf(o$1, t + 1));) d.push({ type: 7, index: l }), t += o$1.length - 1; } l++; } } static createElement(t, i) { const s = l.createElement("template"); return s.innerHTML = t, s; } }; function M(t, i, s = t, e) { if (i === E) return i; let h = void 0 !== e ? s._$Co?.[e] : s._$Cl; const o = a(i) ? void 0 : i._$litDirective$; return h?.constructor !== o && (h?._$AO?.(!1), void 0 === o ? h = void 0 : (h = new o(t), h._$AT(t, s, e)), void 0 !== e ? (s._$Co ??= [])[e] = h : s._$Cl = h), void 0 !== h && (i = M(t, h._$AS(t, i.values), h, e)), i; } var R = class { constructor(t, i) { this._$AV = [], this._$AN = void 0, this._$AD = t, this._$AM = i; } get parentNode() { return this._$AM.parentNode; } get _$AU() { return this._$AM._$AU; } u(t) { const { el: { content: i }, parts: s } = this._$AD, e = (t?.creationScope ?? l).importNode(i, !0); P.currentNode = e; let h = P.nextNode(), o = 0, n = 0, r = s[0]; for (; void 0 !== r;) { if (o === r.index) { let i; 2 === r.type ? i = new k(h, h.nextSibling, this, t) : 1 === r.type ? i = new r.ctor(h, r.name, r.strings, this, t) : 6 === r.type && (i = new Z(h, this, t)), this._$AV.push(i), r = s[++n]; } o !== r?.index && (h = P.nextNode(), o++); } return P.currentNode = l, e; } p(t) { let i = 0; for (const s of this._$AV) void 0 !== s && (void 0 !== s.strings ? (s._$AI(t, s, i), i += s.strings.length - 2) : s._$AI(t[i])), i++; } }; var k = class k { get _$AU() { return this._$AM?._$AU ?? this._$Cv; } constructor(t, i, s, e) { this.type = 2, this._$AH = A, this._$AN = void 0, this._$AA = t, this._$AB = i, this._$AM = s, this.options = e, this._$Cv = e?.isConnected ?? !0; } get parentNode() { let t = this._$AA.parentNode; const i = this._$AM; return void 0 !== i && 11 === t?.nodeType && (t = i.parentNode), t; } get startNode() { return this._$AA; } get endNode() { return this._$AB; } _$AI(t, i = this) { t = M(this, t, i), a(t) ? t === A || null == t || "" === t ? (this._$AH !== A && this._$AR(), this._$AH = A) : t !== this._$AH && t !== E && this._(t) : void 0 !== t._$litType$ ? this.$(t) : void 0 !== t.nodeType ? this.T(t) : d(t) ? this.k(t) : this._(t); } O(t) { return this._$AA.parentNode.insertBefore(t, this._$AB); } T(t) { this._$AH !== t && (this._$AR(), this._$AH = this.O(t)); } _(t) { this._$AH !== A && a(this._$AH) ? this._$AA.nextSibling.data = t : this.T(l.createTextNode(t)), this._$AH = t; } $(t) { const { values: i, _$litType$: s } = t, e = "number" == typeof s ? this._$AC(t) : (void 0 === s.el && (s.el = S.createElement(V(s.h, s.h[0]), this.options)), s); if (this._$AH?._$AD === e) this._$AH.p(i); else { const t = new R(e, this), s = t.u(this.options); t.p(i), this.T(s), this._$AH = t; } } _$AC(t) { let i = C.get(t.strings); return void 0 === i && C.set(t.strings, i = new S(t)), i; } k(t) { u(this._$AH) || (this._$AH = [], this._$AR()); const i = this._$AH; let s, e = 0; for (const h of t) e === i.length ? i.push(s = new k(this.O(c()), this.O(c()), this, this.options)) : s = i[e], s._$AI(h), e++; e < i.length && (this._$AR(s && s._$AB.nextSibling, e), i.length = e); } _$AR(t = this._$AA.nextSibling, s) { for (this._$AP?.(!1, !0, s); t !== this._$AB;) { const s = i$1(t).nextSibling; i$1(t).remove(), t = s; } } setConnected(t) { void 0 === this._$AM && (this._$Cv = t, this._$AP?.(t)); } }; var H = class { get tagName() { return this.element.tagName; } get _$AU() { return this._$AM._$AU; } constructor(t, i, s, e, h) { this.type = 1, this._$AH = A, this._$AN = void 0, this.element = t, this.name = i, this._$AM = e, this.options = h, s.length > 2 || "" !== s[0] || "" !== s[1] ? (this._$AH = Array(s.length - 1).fill(new String()), this.strings = s) : this._$AH = A; } _$AI(t, i = this, s, e) { const h = this.strings; let o = !1; if (void 0 === h) t = M(this, t, i, 0), o = !a(t) || t !== this._$AH && t !== E, o && (this._$AH = t); else { const e = t; let n, r; for (t = h[0], n = 0; n < h.length - 1; n++) r = M(this, e[s + n], i, n), r === E && (r = this._$AH[n]), o ||= !a(r) || r !== this._$AH[n], r === A ? t = A : t !== A && (t += (r ?? "") + h[n + 1]), this._$AH[n] = r; } o && !e && this.j(t); } j(t) { t === A ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? ""); } }; var I = class extends H { constructor() { super(...arguments), this.type = 3; } j(t) { this.element[this.name] = t === A ? void 0 : t; } }; var L = class extends H { constructor() { super(...arguments), this.type = 4; } j(t) { this.element.toggleAttribute(this.name, !!t && t !== A); } }; var z = class extends H { constructor(t, i, s, e, h) { super(t, i, s, e, h), this.type = 5; } _$AI(t, i = this) { if ((t = M(this, t, i, 0) ?? A) === E) return; const s = this._$AH, e = t === A && s !== A || t.capture !== s.capture || t.once !== s.once || t.passive !== s.passive, h = t !== A && (s === A || e); e && this.element.removeEventListener(this.name, this, s), h && this.element.addEventListener(this.name, this, t), this._$AH = t; } handleEvent(t) { "function" == typeof this._$AH ? this._$AH.call(this.options?.host ?? this.element, t) : this._$AH.handleEvent(t); } }; var Z = class { constructor(t, i, s) { this.element = t, this.type = 6, this._$AN = void 0, this._$AM = i, this.options = s; } get _$AU() { return this._$AM._$AU; } _$AI(t) { M(this, t); } }; var B = t.litHtmlPolyfillSupport; B?.(S, k), (t.litHtmlVersions ??= []).push("3.3.3"); var D = (t, i, s) => { const e = s?.renderBefore ?? i; let h = e._$litPart$; if (void 0 === h) { const t = s?.renderBefore ?? null; e._$litPart$ = h = new k(i.insertBefore(c(), t), t, void 0, s ?? {}); } return h._$AI(t), h; }; var s = globalThis; var i = class extends y$1 { constructor() { super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; } createRenderRoot() { const t = super.createRenderRoot(); return this.renderOptions.renderBefore ??= t.firstChild, t; } update(t) { const r = this.render(); this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t), this._$Do = D(r, this.renderRoot, this.renderOptions); } connectedCallback() { super.connectedCallback(), this._$Do?.setConnected(!0); } disconnectedCallback() { super.disconnectedCallback(), this._$Do?.setConnected(!1); } render() { return E; } }; i._$litElement$ = !0, i["finalized"] = !0, s.litElementHydrateSupport?.({ LitElement: i }); var o = s.litElementPolyfillSupport; o?.({ LitElement: i }); (s.litElementVersions ??= []).push("4.2.2"); var style_default$7 = ":host{font-family:inherit;display:block}@keyframes magic-toast-in{0%{opacity:0;transform:translateY(-16px)scale(.95)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes magic-toast-out{0%{opacity:1;transform:translateY(0)scale(1)}to{opacity:0;transform:translateY(-16px)scale(.95)}}.magic-toast-container{z-index:var(--magic-toast-z-index,999999);pointer-events:none;box-sizing:border-box;flex-direction:column;align-items:center;gap:10px;width:max-content;max-width:90vw;display:flex;position:fixed;left:50%;transform:translate(-50%)}.magic-toast-container--top{top:24px}.magic-toast-container--center{top:50%;transform:translate(-50%,-50%)}.magic-toast-container--bottom{bottom:24px}.magic-toast{box-sizing:border-box;border-radius:var(--magic-toast-radius,8px);background:var(--magic-toast-bg,#1c1e24f0);min-height:40px;color:var(--magic-toast-color,#e5e9ef);border:1px solid var(--magic-toast-border,#ffffff1f);box-shadow:var(--magic-toast-shadow,0 10px 30px #00000073);-webkit-backdrop-filter:blur(12px);pointer-events:auto;-webkit-user-select:none;user-select:none;align-items:center;gap:10px;padding:10px 18px;font-size:14px;line-height:1.4;transition:all .25s;animation:.25s cubic-bezier(.16,1,.3,1) forwards magic-toast-in;display:inline-flex}.magic-toast.is-leaving{animation:.2s cubic-bezier(.4,0,1,1) forwards magic-toast-out}.magic-toast__icon{flex-shrink:0;justify-content:center;align-items:center;width:18px;height:18px;display:inline-flex}.magic-toast__icon svg{width:100%;height:100%;display:block}.magic-toast__content{word-break:break-word;align-items:center;display:inline-flex}.magic-toast__close{cursor:pointer;opacity:.6;color:inherit;background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;margin-left:6px;padding:2px;transition:opacity .2s,background-color .2s;display:inline-flex}.magic-toast__close:hover{opacity:1;background-color:#ffffff1a}.magic-toast__close svg{width:14px;height:14px}.magic-toast--info .magic-toast__icon{color:var(--magic-toast-info-color,var(--magic-primary-color))}.magic-toast--success .magic-toast__icon{color:var(--magic-toast-success-color,#2ac864)}.magic-toast--warning .magic-toast__icon{color:var(--magic-toast-warning-color,#fa9600)}.magic-toast--error .magic-toast__icon{color:var(--magic-toast-error-color,#ff5c7c)}"; var theme_default = ":root{--magic-primary-color:#e11483;--magic-primary-hover-color:color-mix(in srgb, var(--magic-primary-color) 85%, #fff);--magic-primary-active-color:color-mix(in srgb, var(--magic-primary-color) 85%, #000);--magic-primary-light-bg:color-mix(in srgb, var(--magic-primary-color) 12%, transparent);--magic-primary-shadow:color-mix(in srgb, var(--magic-primary-color) 35%, transparent);--magic-primary-focus-shadow:color-mix(in srgb, var(--magic-primary-color) 20%, transparent)}"; function ensureTheme() { if (typeof document === "undefined") return; if (document.getElementById("magic-theme-tokens")) return; const styleEl = document.createElement("style"); styleEl.id = "magic-theme-tokens"; styleEl.textContent = theme_default; const target = document.head || document.documentElement; if (target) target.insertBefore(styleEl, target.firstChild); } ensureTheme(); var TOAST_ICONS = { info: b` `, success: b` `, warning: b` `, error: b` `, close: b` ` }; var MagicToast = class MagicToast extends i { static properties = { message: String, type: String, duration: Number, position: String, visible: { type: Boolean, reflect: true }, closable: Boolean, _isLeaving: { state: true } }; static styles = r$2(style_default$7); #timer = null; constructor() { super(); this.message = ""; this.type = "info"; this.duration = 2500; this.position = "top"; this.visible = false; this.closable = false; this._isLeaving = false; } connectedCallback() { super.connectedCallback(); if (this.visible) this.startTimer(); } disconnectedCallback() { super.disconnectedCallback(); this.clearTimer(); } updated(changedProperties) { if (changedProperties.has("visible")) { if (this.visible) { this._isLeaving = false; this.startTimer(); } else { this.clearTimer(); this._isLeaving = false; } } } startTimer() { this.clearTimer(); if (this.duration > 0) this.#timer = setTimeout(() => { this.close(); }, this.duration); } clearTimer() { if (this.#timer) { clearTimeout(this.#timer); this.#timer = null; } } async close() { if (!this.visible && !this._isLeaving) return; this.clearTimer(); this._isLeaving = true; await sleep(200); this.visible = false; this._isLeaving = false; this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true })); } static getOrCreateContainer(position = "top") { const containerId = `magic-toast-root-${position}`; let container = document.getElementById(containerId); if (!container) { container = document.createElement("div"); container.id = containerId; container.className = `magic-toast-container magic-toast-container--${position}`; const posStyle = position === "bottom" ? "bottom: 24px; transform: translateX(-50%);" : position === "center" ? "top: 50%; transform: translate(-50%, -50%);" : "top: 24px; transform: translateX(-50%);"; container.style.cssText = `position: fixed; left: 50%; ${posStyle} z-index: var(--magic-toast-z-index, 999999); display: flex; flex-direction: column; align-items: center; gap: 10px; pointer-events: none; width: max-content; max-width: 90vw; box-sizing: border-box;`; document.body.appendChild(container); } return container; } static show(options = {}) { const { message = "", type = "info", duration = 2500, position = "top", closable = false } = isString(options) ? { message: options } : options; const container = MagicToast.getOrCreateContainer(position); const toastEl = document.createElement("magic-toast"); toastEl.message = message; toastEl.type = type; toastEl.duration = duration; toastEl.position = position; toastEl.closable = closable; container.appendChild(toastEl); toastEl.visible = true; once(toastEl, "close", () => { toastEl.remove(); if (!container.children.length) container.remove(); }); return { close: () => toastEl.close() }; } static info(message, duration) { return MagicToast.show({ message, type: "info", duration }); } static success(message, duration) { return MagicToast.show({ message, type: "success", duration }); } static warning(message, duration) { return MagicToast.show({ message, type: "warning", duration }); } static error(message, duration) { return MagicToast.show({ message, type: "error", duration }); } render() { if (!this.visible && !this._isLeaving) return b``; return b`