// ==UserScript== // @name bilibili-playlist-enhancer // @namespace https://github.com/niceKingsley/magic-monkey // @version 1.1.1 // @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:var(--magic-bg-subtle,var(--graph_bg_thin,var(--bg2)));border:1px solid var(--magic-border-color,var(--line_regular));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{border-color:var(--magic-border-color-hover,var(--line_regular));background:var(--magic-bg-hover,var(--line_regular))}.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 var(--magic-border-color,var(--line_regular))}.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:var(--text1);font-size:13.5px;font-weight:500;line-height:1.4}.settings-item__desc[data-v-e52d7f12]{color:var(--text3);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]{border:1px solid var(--magic-border-color,var(--line_regular));background:var(--magic-bg-subtle,var(--graph_bg_thin));min-width:80px;height:28px;color:var(--magic-primary-color,var(--brand_blue));cursor:pointer;-webkit-user-select:none;user-select:none;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,var(--brand_blue_thin));border-color:var(--magic-primary-color,var(--brand_blue))}.settings-item__action .hotkey-badge.is-recording[data-v-e52d7f12]{background:var(--magic-primary-color,var(--brand_blue));border-color:var(--magic-primary-color,var(--brand_blue));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-58ef3700{0%{transform:rotate(0)}to{transform:rotate(360deg)}}:root{--magic-primary-color:var(--brand_blue);--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));--magic-text-primary:var(--text1);--magic-text-secondary:var(--text2);--magic-text-tertiary:var(--text3);--magic-bg-subtle:var(--graph_bg_thin,var(--bg2));--magic-bg-hover:var(--line_regular,var(--bg3));--magic-border-color:var(--line_regular);--magic-tabs-mask-bg:var(--graph_bg_regular);--magic-tabs-hover-color:var(--brand_blue);--magic-bg-overlay:var(--bg1_float,var(--graph_bg_regular));--magic-modal-bg:var(--bg1_float,var(--graph_bg_regular));--magic-modal-border:var(--line_regular);--magic-modal-divider:var(--line_regular);--magic-modal-title-color:var(--text1);--magic-modal-color:var(--text1);--magic-modal-close-color:var(--text3)}.tk-reaction[data-v-58ef3700]{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-58ef3700]{transition:height .3s ease-in-out,top .29s ease-in-out;overflow:hidden}.tk-reaction .tk-reaction-pod-slide[data-v-58ef3700]{z-index:15;border-bottom:1px solid var(--line_regular);box-sizing:border-box;flex-shrink:0;align-items:center;min-width:0;padding:4px 0;display:flex;position:relative}.tk-reaction .tk-reaction-pod-slide magic-tabs[data-v-58ef3700]{flex:1;min-width:0}.tk-reaction .tk-reaction-pod-slide .more-sections-btn[data-v-58ef3700]{cursor:pointer;width:26px;height:26px;color:var(--text3);border-radius:4px;justify-content:center;align-items:center;margin-left:6px;transition:all .2s cubic-bezier(.4,0,.2,1);display:inline-flex}.tk-reaction .tk-reaction-pod-slide .more-sections-btn[data-v-58ef3700]:hover{color:var(--text1)}.tk-reaction .tk-reaction-pod-slide .more-sections-btn:hover .icon[data-v-58ef3700],.tk-reaction .tk-reaction-pod-slide .more-sections-btn:hover svg[data-v-58ef3700]{transform:rotate(180deg)}.tk-reaction .tk-reaction-pod-slide .more-sections-btn[data-v-58ef3700]:active{transform:scale(.92)}.tk-reaction .tk-reaction-pod-slide .more-sections-btn.is-active[data-v-58ef3700],.tk-reaction .tk-reaction-pod-slide .more-sections-btn.is-expanded[data-v-58ef3700]{color:var(--text1)}.tk-reaction .tk-reaction-pod-slide .more-sections-btn.is-active .icon[data-v-58ef3700],.tk-reaction .tk-reaction-pod-slide .more-sections-btn.is-active svg[data-v-58ef3700],.tk-reaction .tk-reaction-pod-slide .more-sections-btn.is-expanded .icon[data-v-58ef3700],.tk-reaction .tk-reaction-pod-slide .more-sections-btn.is-expanded svg[data-v-58ef3700]{transform:rotate(180deg)}.tk-reaction .tk-reaction-pod-slide .more-sections-btn .icon[data-v-58ef3700],.tk-reaction .tk-reaction-pod-slide .more-sections-btn svg[data-v-58ef3700]{width:14px;height:14px;transition:transform .2s cubic-bezier(.4,0,.2,1);display:block}.tk-reaction .tk-reaction-pod-slide .sections-dropdown-menu[data-v-58ef3700]{z-index:50;overscroll-behavior:contain;background:var(--graph_bg_thick,var(--bg1));border:1px solid var(--line_regular);box-sizing:border-box;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);scrollbar-width:none;-ms-overflow-style:none;border-radius:6px;max-height:200px;padding:4px;position:absolute;top:calc(100% + 2px);left:0;right:0;overflow-y:auto;box-shadow:0 6px 20px #00000059}.tk-reaction .tk-reaction-pod-slide .sections-dropdown-menu[data-v-58ef3700]::-webkit-scrollbar{width:0;height:0;display:none}.tk-reaction .tk-reaction-pod-slide .sections-dropdown-menu .dropdown-item[data-v-58ef3700]{cursor:pointer;height:32px;color:var(--text2);-webkit-user-select:none;user-select:none;border-radius:4px;justify-content:space-between;align-items:center;gap:8px;padding:0 10px;font-size:13px;transition:all .15s;display:flex}.tk-reaction .tk-reaction-pod-slide .sections-dropdown-menu .dropdown-item[data-v-58ef3700]:hover{color:var(--text1);background-color:var(--bg2)}.tk-reaction .tk-reaction-pod-slide .sections-dropdown-menu .dropdown-item.is-active[data-v-58ef3700]{color:var(--magic-primary-color,var(--brand_blue));background-color:var(--magic-primary-light-bg,var(--brand_blue_thin));font-weight:500}.tk-reaction .tk-reaction-pod-slide .sections-dropdown-menu .dropdown-item .item-label[data-v-58ef3700]{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.tk-reaction .dropdown-fade-enter-active[data-v-58ef3700],.tk-reaction .dropdown-fade-leave-active[data-v-58ef3700]{transition:opacity .2s cubic-bezier(.16,1,.3,1),transform .2s cubic-bezier(.16,1,.3,1)}.tk-reaction .dropdown-fade-enter-from[data-v-58ef3700],.tk-reaction .dropdown-fade-leave-to[data-v-58ef3700]{opacity:0;transform:translateY(-4px)}.tk-reaction .tk-reaction-header[data-v-58ef3700]{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-58ef3700]{align-items:center;gap:12px;display:flex}.tk-reaction .tk-reaction-header .header-left .sort-select[data-v-58ef3700]{--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);--magic-select-hover-border:var(--magic-primary-color);--magic-select-active-color:var(--magic-primary-color);--magic-select-dropdown-bg:var(--graph_bg_thick);--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);width:104px}.tk-reaction .tk-reaction-header .header-left magic-switch[data-v-58ef3700]{--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-58ef3700]{align-items:center;gap:6px;display:flex}.tk-reaction .tk-reaction-header .header-right .shuffle-btn[data-v-58ef3700],.tk-reaction .tk-reaction-header .header-right .search-btn[data-v-58ef3700]{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-58ef3700],.tk-reaction .tk-reaction-header .header-right .search-btn .icon[data-v-58ef3700]{fill:currentColor;width:15px;height:15px}.tk-reaction .tk-reaction-header .header-right .shuffle-btn[data-v-58ef3700]:hover,.tk-reaction .tk-reaction-header .header-right .search-btn[data-v-58ef3700]: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-58ef3700],.tk-reaction .tk-reaction-header .header-right .search-btn.is-active[data-v-58ef3700]{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-58ef3700]:active,.tk-reaction .tk-reaction-header .header-right .search-btn[data-v-58ef3700]:active{transform:scale(.92)}.tk-reaction .tk-reaction-header .header-right .settings-btn[data-v-58ef3700]{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-58ef3700]{width:16px;height:16px;color:inherit;fill:currentColor;transition:transform .3s}.tk-reaction .tk-reaction-header .header-right .settings-btn[data-v-58ef3700]: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-58ef3700]{transform:rotate(60deg)}.tk-reaction .tk-reaction-header .header-right .settings-btn[data-v-58ef3700]:active{transform:scale(.92)}.tk-reaction .tk-reaction-search[data-v-58ef3700]{flex-shrink:0;padding:0 12px 6px}.tk-reaction .tk-reaction-search .search-input[data-v-58ef3700]{--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-58ef3700]{width:14px;height:14px;color:var(--text3)}.tk-reaction .tk-reaction-search .search-input .match-badge[data-v-58ef3700]{color:var(--text3);white-space:nowrap;font-size:11px}.tk-reaction .tk-reaction-video[data-v-58ef3700]{overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#78787880 transparent;flex:1;position:relative;overflow:auto}.tk-reaction .tk-reaction-video.is-loading[data-v-58ef3700]{overflow:hidden!important}.tk-reaction .tk-reaction-video[data-v-58ef3700]::-webkit-scrollbar{width:8px;height:8px}.tk-reaction .tk-reaction-video[data-v-58ef3700]::-webkit-scrollbar-thumb{background:#78787880;border-radius:8px}.tk-reaction .tk-reaction-video[data-v-58ef3700]::-webkit-scrollbar-thumb:hover{background:#787878}.tk-reaction .tk-reaction-video .empty-state[data-v-58ef3700]{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-58ef3700]{justify-content:center;align-items:center;min-height:160px;padding:48px 0;display:flex}.tk-reaction .tk-reaction-video .list[data-v-58ef3700]{box-sizing:border-box;width:calc(100% - 12px);margin:0 auto;position:relative}.tk-reaction .tk-reaction-video .list .item[data-v-58ef3700]{content-visibility:auto;contain-intrinsic-size:auto 36px;margin-bottom:4px}.tk-reaction .tk-reaction-video .list .item .header[data-v-58ef3700]{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-58ef3700]:hover{background-color:var(--bg2,#ffffff0d)}.tk-reaction .tk-reaction-video .list .item .header.is-active[data-v-58ef3700]{background-color:var(--bg2,#ffffff14)}.tk-reaction .tk-reaction-video .list .item .header.is-active .mode-list .title[data-v-58ef3700],.tk-reaction .tk-reaction-video .list .item .header.is-active .mode-card .info .title[data-v-58ef3700]{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-58ef3700],.tk-reaction .tk-reaction-video .list .item .header.is-active .mode-card .info .title .playing-gif[data-v-58ef3700]{display:block!important}.tk-reaction .tk-reaction-video .list .item .header .mode-list[data-v-58ef3700]{justify-content:space-between;align-items:center;width:100%;display:flex}.tk-reaction .tk-reaction-video .list .item .header .mode-list .title[data-v-58ef3700]{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-58ef3700]:hover{color:var(--magic-primary-color,var(--brand_blue))}.tk-reaction .tk-reaction-video .list .item .header .mode-list .title .playing-gif[data-v-58ef3700]{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-58ef3700]{-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-58ef3700]{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-58ef3700]{cursor:pointer;align-items:center;display:flex}.tk-reaction .tk-reaction-video .list .item .header .mode-list .actions .expand .icon[data-v-58ef3700]{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-58ef3700]{transform:rotate(0)}.tk-reaction .tk-reaction-video .list .item .header .mode-card[data-v-58ef3700]{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-58ef3700]{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-58ef3700]{object-fit:cover;width:100%;height:100%;display:block}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info[data-v-58ef3700]{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-58ef3700]{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-58ef3700]: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-58ef3700]{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-58ef3700]{-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-58ef3700]{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-58ef3700]{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-58ef3700]{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-58ef3700]{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-58ef3700],.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .stats .stat-item.danmaku[data-v-58ef3700]{flex-shrink:0}.tk-reaction .tk-reaction-video .list .item .header .mode-card .info .stats .expand-btn[data-v-58ef3700]{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-58ef3700]{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-58ef3700]{transform:rotate(0)}.tk-reaction .tk-reaction-video .list .item .episodes[data-v-58ef3700]{margin-top:4px}.tk-reaction .tk-reaction-video .list .item .episodes .episode[data-v-58ef3700]{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-58ef3700]:hover{background-color:var(--bg2,#ffffff0d)}.tk-reaction .tk-reaction-video .list .item .episodes .episode.is-active .episode-title[data-v-58ef3700]{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-58ef3700]{display:block}.tk-reaction .tk-reaction-video .list .item .episodes .episode .episode-title[data-v-58ef3700]{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-58ef3700]:hover{color:var(--magic-primary-color,var(--brand_blue))}.tk-reaction .tk-reaction-video .list .item .episodes .episode .episode-title .playing-gif[data-v-58ef3700]{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-58ef3700]{-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-58ef3700]{color:var(--text3);flex-shrink:0;margin-left:10px;font-size:14px}.tk-reaction.is-card-mode .item[data-v-58ef3700]{contain-intrinsic-size:auto 80px!important}.tk-reaction.is-card-mode .header .mode-list[data-v-58ef3700]{display:none!important}.tk-reaction.is-card-mode .header .mode-card[data-v-58ef3700]{display:flex!important}\n/*$vite$:1*/ ', ); 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 }, ); } 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: true, 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, }, ]; 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 }, ); } var correctionOffset = 17; 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 setBilibiliVideoPodMinHeight() { document.querySelector(getPodTargetSelectors(POD_TYPE.COLLECTION).listBody).style.minHeight = '250px'; } function calculateCollectionToggleTargetLayout( podType, isExpanded, currentTop, hasMultipleSections, ) { if (podType === POD_TYPE.COLLECTION) { const toolbar = document.querySelector('#arc_toolbar_report'); const card = document.querySelector('.video-page-card-small'); const aboveModule = document.querySelector('.video-pod-above-modules'); const podBody = document.querySelector(getPodTargetSelectors(podType).listBody); 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; const totalOffset = correctionOffset + ((0, vue.toValue)(hasMultipleSections) ? document.querySelector('.video-pod__slide')?.clientHeight || 0 : 0); const diff = s && c ? s - c : 0; const baseHeight = isExpanded ? Math.max(250, Math.floor(n + diff + l)) : 250; const targetTop = isExpanded ? currentTop - l : currentTop + l; return { targetHeight: baseHeight + totalOffset, targetTop, }; } if (podType === POD_TYPE.SERIES) { const listEl = document.querySelector('#playlist-video-action-list'); let targetTop = listEl ? listEl.getBoundingClientRect().top + window.scrollY - correctionOffset : currentTop; const bodyBottomHeight = document.querySelector('.action-list-body-bottom')?.clientHeight ?? 0; return { targetHeight: isExpanded ? 0 : bodyBottomHeight + correctionOffset, targetTop, }; } return { targetHeight: 250, targetTop: currentTop, }; } async function watchPositionSync(podType, hostEl, onPositionUpdate, hasMultipleSections) { 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 slideHeight = ((0, vue.toValue)(hasMultipleSections) ? document.querySelector('.video-pod__slide') : null) ?.clientHeight || 0; let maxHeight = calculateMaxHeight(currentType, wrapEl, height) + slideHeight; maxHeight = maxHeight ? maxHeight + correctionOffset : 0; onPositionUpdate({ top: top + window.scrollY - slideHeight - correctionOffset, left: left + window.scrollX, width, height: 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, hasMultipleSections) { const position = (0, vue.ref)({ top: 0, left: 0, width: 0, height: 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`, height: `${position.value.height}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; }, hasMultipleSections, ); cleanupResize = await listenNativeToggle(resolvedType, (isExpanded) => { isAnimating.value = true; cleanupTracker.setAnimating(true); if (podType.value === POD_TYPE.COLLECTION && !isExpanded) setBilibiliVideoPodMinHeight(); const { targetHeight, targetTop } = calculateCollectionToggleTargetLayout( resolvedType, isExpanded, position.value.top, hasMultipleSections, ); position.value = { ...position.value, top: targetTop, height: 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, seasonSections: [], hasMultipleSections: false, currentSectionId: '', 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 rawSections = data.ugc_season?.sections || []; if (rawSections.length > 0) { const seasonSections = rawSections.map((s) => { const sectionId = String(s.id || s.season_id || ''); const title = s.title || ''; return { id: sectionId, title, label: title, value: sectionId, episodes: s.episodes || [], }; }); const currentSection = seasonSections.find((s) => s.episodes.some((ep) => ep.bvid === bvid)) || seasonSections[0]; return { sections: currentSection?.episodes || [], seasonSections, hasMultipleSections: seasonSections.length > 1, currentSectionId: currentSection?.value || '', mode: POD_MODE.LIST, type: POD_TYPE.COLLECTION, }; } const pages = data.pages || []; if (pages.length <= 1) return null; return { sections: pages, seasonSections: [], hasMultipleSections: false, currentSectionId: '', 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, seasonSections: podData.seasonSections || [], hasMultipleSections: !!podData.hasMultipleSections, currentSectionId: podData.currentSectionId || '', 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 findSectionByBvid = (seasonSections = [], bvid = '') => seasonSections.find((s) => (s.episodes || []).some((ep) => ep.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 usePodSections({ rawGroups, podType, activeBvid, activePage, setExpanded, setActiveTarget, refreshActiveStates, }) { const seasonSections = (0, vue.ref)([]); const hasMultipleSections = (0, vue.ref)(false); const currentSectionId = (0, vue.ref)(''); const initSections = (payload) => { seasonSections.value = payload?.seasonSections || []; hasMultipleSections.value = !!payload?.hasMultipleSections; currentSectionId.value = payload?.currentSectionId || ''; }; const resetSections = () => { seasonSections.value = []; hasMultipleSections.value = false; currentSectionId.value = ''; }; const applySectionEpisodes = (section, targetBvid, targetPage) => { currentSectionId.value = String(section.value); setExpanded(''); rawGroups.value = processSeasonData(podType.value, section.episodes, targetBvid, targetPage); }; const switchSection = (sectionId) => { const targetId = String(sectionId); if (targetId === String(currentSectionId.value)) return; const targetSection = seasonSections.value.find((s) => String(s.value) === targetId); if (!targetSection) return; applySectionEpisodes(targetSection, activeBvid.value, activePage.value); refreshActiveStates(); }; const trySwitchSectionByBvid = (nextBvid, nextPage) => { if (!(0, vue.toValue)(hasMultipleSections)) return false; const targetSection = findSectionByBvid(seasonSections.value, nextBvid); if (!targetSection) return false; applySectionEpisodes(targetSection, nextBvid, nextPage); setActiveTarget(nextBvid, nextPage); refreshActiveStates(); return true; }; return { seasonSections, hasMultipleSections, currentSectionId, initSections, resetSections, switchSection, trySwitchSectionByBvid, }; } 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 { seasonSections, hasMultipleSections, currentSectionId, initSections, resetSections, switchSection, trySwitchSectionByBvid, } = usePodSections({ rawGroups, podType, activeBvid, activePage, setExpanded, setActiveTarget, refreshActiveStates, }); 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, hasMultipleSections, ); const resetPodState = () => { rawGroups.value = []; podType.value = null; resetSections(); }; 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) { resetPodState(); setExpanded(''); return; } podType.value = payload.type; podMode.value = payload.mode; rawGroups.value = payload.groups; setActiveTarget(bvid, page); initSections(payload); setExpanded(findActiveAccordionBvid(currentPlaying.value)); await startSync(podType.value); scrollActiveItem(scrollContainerRef.value, false); }; const fetchFullPod = async (nextBvid, nextPage) => { setActiveTarget(nextBvid, nextPage); resetPodState(); isLoading.value = true; try { const payload = await fetchPodPayload(nextBvid, nextPage); await initPodState(payload, nextBvid, nextPage); } finally { isLoading.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; if (trySwitchSectionByBvid(nextBvid, nextPage)) return; await fetchFullPod(nextBvid, nextPage); }; return { isAnimating, isLoading, podMode, seasonSections, hasMultipleSections, currentSectionId, containerStyle, videoBodyStyle, initPodState, loadPod, switchSection, 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$8 = ':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);--magic-color-success:#2ac864;--magic-color-success-hover:#3ad473;--magic-color-success-active:#23b056;--magic-color-success-shadow:#2ac86459;--magic-color-warning:#fa9600;--magic-color-warning-hover:#ffaa1a;--magic-color-warning-active:#e08700;--magic-color-warning-shadow:#fa960059;--magic-color-danger:#ff5c7c;--magic-color-danger-hover:#ff7591;--magic-color-danger-active:#e64567;--magic-color-danger-shadow:#ff5c7c59;--magic-text-primary:#fff;--magic-text-secondary:#ffffffb3;--magic-text-tertiary:#9499a0;--magic-text-disabled:#ffffff59;--magic-bg-base:#ffffff0a;--magic-bg-subtle:#ffffff14;--magic-bg-hover:#ffffff24;--magic-bg-active:#fff3;--magic-bg-overlay:#1c1e24f0;--magic-mask-bg:#000000a6;--magic-border-color:#ffffff1f;--magic-border-color-hover:#ffffff40;--magic-radius-sm:4px;--magic-radius-base:6px;--magic-radius-lg:8px;--magic-shadow-dropdown:0 4px 16px #0003;--magic-shadow-modal:0 16px 40px #00000073;--magic-shadow-toast:0 10px 30px #00000073;--magic-z-index-dropdown:1000;--magic-z-index-modal:99999;--magic-z-index-toast:999999;--magic-btn-default-bg:var(--magic-bg-subtle);--magic-btn-default-color:var(--magic-text-primary);--magic-btn-default-border:var(--magic-border-color);--magic-btn-default-hover-bg:var(--magic-bg-hover);--magic-btn-default-hover-border:var(--magic-border-color-hover);--magic-btn-default-active-bg:var(--magic-bg-active);--magic-btn-primary-bg:var(--magic-primary-color);--magic-btn-primary-hover-bg:var(--magic-primary-hover-color);--magic-btn-primary-active-bg:var(--magic-primary-active-color);--magic-btn-success-bg:var(--magic-color-success);--magic-btn-success-hover-bg:var(--magic-color-success-hover);--magic-btn-success-active-bg:var(--magic-color-success-active);--magic-btn-warning-bg:var(--magic-color-warning);--magic-btn-warning-hover-bg:var(--magic-color-warning-hover);--magic-btn-warning-active-bg:var(--magic-color-warning-active);--magic-btn-danger-bg:var(--magic-color-danger);--magic-btn-danger-hover-bg:var(--magic-color-danger-hover);--magic-btn-danger-active-bg:var(--magic-color-danger-active);--magic-btn-text-color:var(--magic-text-secondary);--magic-btn-text-hover-bg:var(--magic-bg-subtle);--magic-btn-text-active-bg:var(--magic-bg-hover);--magic-input-radius:var(--magic-radius-base);--magic-input-bg:var(--magic-bg-subtle);--magic-input-border:var(--magic-border-color);--magic-input-color:var(--magic-text-primary);--magic-input-hover-border:var(--magic-border-color-hover);--magic-input-hover-bg:var(--magic-bg-hover);--magic-input-focus-border:var(--magic-primary-color);--magic-input-focus-bg:var(--magic-bg-subtle);--magic-input-focus-shadow:var(--magic-primary-focus-shadow);--magic-input-prefix-color:var(--magic-text-tertiary);--magic-input-placeholder-color:var(--magic-text-tertiary);--magic-input-suffix-color:var(--magic-text-tertiary);--magic-input-clear-color:var(--magic-text-tertiary);--magic-input-clear-hover-color:var(--magic-text-primary);--magic-select-bg:var(--magic-bg-subtle);--magic-select-color:var(--magic-text-primary);--magic-select-border:var(--magic-border-color);--magic-select-placeholder-color:var(--magic-text-tertiary);--magic-select-suffix-color:var(--magic-text-tertiary);--magic-select-clear-color:var(--magic-text-tertiary);--magic-select-clear-hover-color:var(--magic-text-primary);--magic-select-hover-border:var(--magic-border-color-hover);--magic-select-hover-bg:var(--magic-bg-hover);--magic-select-active-color:var(--magic-primary-color);--magic-select-z-index:var(--magic-z-index-dropdown);--magic-select-dropdown-bg:var(--magic-bg-overlay);--magic-select-dropdown-border:var(--magic-border-color);--magic-select-dropdown-shadow:var(--magic-shadow-dropdown);--magic-select-option-hover-bg:var(--magic-bg-hover);--magic-select-option-active-bg:var(--magic-primary-light-bg);--magic-tabs-capsule-bg:var(--magic-bg-subtle);--magic-tabs-color:var(--magic-text-secondary);--magic-tabs-hover-color:var(--magic-text-primary);--magic-tabs-hover-bg:var(--magic-bg-hover);--magic-tabs-border:transparent;--magic-tabs-active-border:var(--magic-primary-light-bg);--magic-tabs-line-border:var(--magic-border-color);--magic-tabs-mask-bg:var(--magic-mask-bg);--magic-switch-color:var(--magic-text-secondary);--magic-switch-active-label-color:var(--magic-text-primary);--magic-switch-inactive-color:#8c8c8c59;--magic-switch-active-color:var(--magic-primary-color);--magic-spinner-size:24px;--magic-spinner-border-width:3px;--magic-spinner-track:var(--magic-bg-subtle);--magic-spinner-color:var(--magic-primary-color);--magic-spinner-text-color:var(--magic-text-tertiary);--magic-modal-z-index:var(--magic-z-index-modal);--magic-modal-mask-bg:var(--magic-mask-bg);--magic-modal-bg:var(--magic-bg-overlay);--magic-modal-border:var(--magic-border-color);--magic-modal-radius:var(--magic-radius-lg);--magic-modal-shadow:var(--magic-shadow-modal);--magic-modal-color:var(--magic-text-primary);--magic-modal-divider:var(--magic-border-color);--magic-modal-title-color:var(--magic-text-primary);--magic-modal-close-color:var(--magic-text-tertiary);--magic-radio-text-color:var(--magic-text-primary);--magic-radio-border-color:var(--magic-border-color);--magic-radio-bg:var(--magic-bg-subtle);--magic-radio-color:var(--magic-primary-color);--magic-radio-disabled-opacity:.45;--magic-toast-z-index:var(--magic-z-index-toast);--magic-toast-radius:var(--magic-radius-lg);--magic-toast-bg:var(--magic-bg-overlay);--magic-toast-color:var(--magic-text-primary);--magic-toast-border:var(--magic-border-color);--magic-toast-shadow:var(--magic-shadow-toast);--magic-toast-info-color:var(--magic-primary-color);--magic-toast-success-color:var(--magic-color-success);--magic-toast-warning-color:var(--magic-color-warning);--magic-toast-error-color:var(--magic-color-danger)}'; 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$8); #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`