Il player migliore di sempre — riscritto da zero.
// ==UserScript==
// @name AnimeWorld Better Player
// @namespace aw-better-player
// @version 3.1.0
// @match *://www.animeworld.ac/play/*
// @run-at document-start
// @description Il player migliore di sempre — riscritto da zero.
// @description:it Il player migliore di sempre — riscritto da zero.
// @license MIT
// @grant unsafeWindow
// @connect cibernetic-gg.workers.dev
// ==/UserScript==
// Icone UI: Lucide (ISC) — https://lucide.dev · Loghi brand: Simple Icons (CC0) — https://simpleicons.org
"use strict";
(() => {
// src/core/block-src.js
function installSrcBlocker() {
const _srcDesc = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, "src");
Object.defineProperty(HTMLScriptElement.prototype, "src", {
configurable: true,
enumerable: true,
get() {
return _srcDesc.get.call(this);
},
set(val) {
if (typeof val === "string" && val.includes("playerServersAndDownloads")) return;
_srcDesc.set.call(this, val);
}
});
}
// src/lib/env.js
var isMobile = /Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent);
var pageWin = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;
// src/styles/css-panels.js
function reportCss() {
const sel = "#aw-np-report-panel";
return `
${sel} .aw-np-pn-title-ic{display:flex;align-items:center;justify-content:center;flex-shrink:0}
${sel} .aw-np-pn-title-ic svg{width:1em;height:1em;color:var(--pn-accent)}
${sel} .aw-np-rp-body{gap:0}
${sel} .aw-np-rp-tabs{display:flex;flex-shrink:0;margin-bottom:18px;border-bottom:var(--pn-line)}
${sel} .aw-np-rp-tab{flex:1;min-width:0;background:none;color:var(--pn-fg);border:none;border-radius:0;padding:0 4px 11px;margin-bottom:-1px;font-size:var(--pn-fs-small);font-weight:500;font-family:inherit;cursor:pointer;opacity:.5;border-bottom:2px solid transparent;transition:opacity var(--pn-t),border-color var(--pn-t);white-space:nowrap;text-align:center}
${sel} .aw-np-rp-tab.sel{opacity:1;font-weight:600;border-bottom-color:var(--pn-accent)}
@media (hover:hover){${sel} .aw-np-rp-tab:hover:not(.sel){opacity:.78}}
${sel} .aw-np-rp-stage{position:relative;flex:none;height:13.5em}
${sel} .aw-np-rp-sec{position:absolute;inset:0;display:none;flex-direction:column}
${sel} .aw-np-rp-sec.show{display:flex}
${sel} .aw-np-rp-sec-skip{justify-content:space-between;gap:6px}
${sel} .aw-np-rp-seghead{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:1px}
${sel} .aw-np-rp-subsel{display:flex;gap:4px}
${sel} .aw-np-rp-sub{display:flex;align-items:center;justify-content:center;min-width:5.5em;background:var(--pn-hover);color:inherit;border:1px solid transparent;border-radius:var(--pn-r);padding:5px 14px;font-size:var(--pn-fs-micro);font-weight:600;font-family:inherit;cursor:pointer;transition:background-color var(--pn-t),border-color var(--pn-t)}
${sel} .aw-np-rp-sub.sel{background:var(--pn-sel)}
${sel} .aw-np-rp-sub.filled{border-color:var(--pn-accent)}
@media (hover:hover){${sel} .aw-np-rp-sub:hover:not(.sel){background:var(--pn-hover-2)}}
${sel} .aw-np-arow{opacity:0;transform:translateY(18px) scale(.98);transition:opacity var(--np-t-slow) var(--np-ease),transform var(--np-t-slower) var(--np-ease-spring)}
${sel}.open .aw-np-arow{opacity:1;transform:translateY(0) scale(1)}
${sel}.open .aw-np-rp-privacy.aw-np-arow{opacity:.55}
${sel}.open .aw-np-rp-group.dim{opacity:.35;transform:translateY(0) scale(1)}
${sel} .aw-np-rp-group{display:flex;flex-direction:column;gap:7px;transition:opacity var(--pn-t)}
${sel} .aw-np-rp-group.dim{opacity:.35;pointer-events:none}
${sel} .aw-np-rp-grouplbl{font-size:var(--pn-fs-body);font-weight:600}
${sel} .aw-np-rp-optrow{display:flex;gap:7px}
${sel} .aw-np-rp-opt{flex:1;min-width:0;background:var(--pn-hover);color:inherit;border:1px solid transparent;border-radius:var(--pn-r);padding:10px 6px;font-size:var(--pn-fs-small);font-weight:500;font-family:inherit;cursor:pointer;transition:background-color var(--pn-t),border-color var(--pn-t),transform var(--pn-t);white-space:nowrap;text-align:center}
${sel} .aw-np-rp-opt.sel{background:var(--pn-sel);border-color:var(--pn-accent)}
@media (hover:hover){${sel} .aw-np-rp-opt:hover{background:var(--pn-hover-2)}}
${sel} .aw-np-rp-opt:active{transform:scale(.97)}
${sel} .aw-np-rp-msg{font-size:var(--pn-fs-micro);font-weight:600;line-height:1.3;height:1.3em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:.02em;transition:color var(--pn-t)}
${sel} .aw-np-rp-msg.empty,${sel} .aw-np-rp-msg.idle{color:transparent}
${sel} .aw-np-rp-msg.good{color:var(--np-ok)}
${sel} .aw-np-rp-msg.bad{color:var(--np-danger)}
${sel} .aw-np-rp-msg.mod{color:var(--np-warn)}
${sel} .aw-np-rp-privacy{font-size:var(--pn-fs-micro);font-style:italic;opacity:.55;text-align:center;line-height:1.4;padding-top:2px}
${sel} .aw-np-rp-sec-player{align-items:center;justify-content:flex-start;text-align:center;gap:16px;padding-top:8px}
${sel} .aw-np-rp-pl-txt{font-size:var(--pn-fs-body);line-height:1.6;opacity:.9;max-width:32em;text-align:justify;text-align-last:justify}
${sel} .aw-np-rp-pl-txt2{font-size:var(--pn-fs-body);line-height:1.6;font-weight:600;opacity:.95;max-width:32em;text-align:justify;text-align-last:justify}
${sel} .aw-np-rp-pl-btns{display:flex;gap:9px;width:100%;max-width:32em;margin-top:4px}
${sel} .aw-np-rp-extlink{flex:1;display:flex;align-items:center;justify-content:center;gap:8px;background:var(--pn-hover);color:var(--pn-fg);text-decoration:none;border-radius:var(--pn-r);padding:12px 8px;font-size:var(--pn-fs-small);font-weight:600;border:1px solid transparent;transition:background-color var(--pn-t),border-color var(--pn-t),transform var(--pn-t)}
${sel} .aw-np-rp-extlink-ic{display:flex;align-items:center;flex-shrink:0;color:var(--pn-accent)}
${sel} .aw-np-rp-extlink-ic svg{width:1.2em;height:1.2em;fill:currentColor}
@media (hover:hover){${sel} .aw-np-rp-extlink:hover{background:var(--pn-hover-2);border-color:color-mix(in srgb,var(--pn-accent) 45%,transparent)}}
${sel} .aw-np-rp-extlink:active{transform:scale(.97)}
${sel} .aw-np-rp-footer{display:flex;align-items:center;gap:10px;flex-shrink:0;margin-top:14px;padding-top:13px;border-top:var(--pn-line)}
${sel} .aw-np-rp-status{flex:1;font-size:var(--pn-fs-micro);opacity:.7;min-height:1em;line-height:1.3}
${sel} .aw-np-rp-cancel,${sel} .aw-np-rp-send{border:none;border-radius:var(--pn-r);padding:9px 22px;font-size:var(--pn-fs-small);font-weight:700;font-family:inherit;cursor:pointer;letter-spacing:.02em;transition:background-color var(--pn-t),opacity var(--pn-t),transform var(--pn-t)}
${sel} .aw-np-rp-cancel{background:transparent;color:var(--pn-fg);opacity:.65}
@media (hover:hover){${sel} .aw-np-rp-cancel:hover{opacity:1}}
${sel} .aw-np-rp-send{background:var(--pn-accent);color:#fff}
${sel} .aw-np-rp-send:active{transform:scale(.97)}
${sel} .aw-np-rp-send:disabled{opacity:.35;cursor:default;transform:none}`;
}
function mailCss(p) {
return `
#aw-np-mail-panel{--mb-fg:var(--np-accent-bg-fg,#fff);--mb-accent:var(--np-accent,#1565C0);--mb-fs-micro:12px;--mb-fs-small:14px;--mb-fs-date:11px;--mb-fs-body:16px;--mb-fs-subj:23px;--mb-badge-sz:18px;--mb-line:var(--np-line-soft);--mb-hover:var(--np-accent-state-1,rgba(255,255,255,.08));--mb-hover-2:var(--np-accent-state-2,rgba(255,255,255,.16));--mb-sel:color-mix(in srgb,var(--np-accent,#1565C0) 26%,var(--np-accent-bg,#223A56));--mb-r:var(--np-r-md);--mb-t:var(--np-t-fast);--mb-w:${p.w};--mb-h:${p.h};--mb-bar-pad:16px 20px;--mb-list-w:${p.listW};--mb-list-min:${p.listMin};--mb-list-max:${p.listMax};--mb-list-pad:5px 6px 6px;--mb-empty-gap:10px;--mb-empty-pad:32px 16px;--mb-empty-ic:30px;--mb-empty-fs:var(--mb-fs-small);--mb-row-gap:8px;--mb-row-pad:12px;--mb-subj-fs:var(--mb-fs-small);--mb-date-fs:var(--mb-fs-micro);--mb-del-sz:24px;--mb-del-ic:13px;--mb-del-op:${p.delOp};--mb-read-empty-gap:10px;--mb-read-empty-pad:20px;--mb-read-empty-ic:38px;--mb-read-pad:28px 32px;position:absolute;inset:0;background:rgba(0,0,0,.62);z-index:30;opacity:0;pointer-events:none;transition:opacity var(--np-t) var(--np-ease);display:flex;align-items:center;justify-content:center;overflow:hidden;container-type:size}
#aw-np-mail-panel.open{opacity:1;pointer-events:auto}
.aw-np-mail-inner{width:var(--mb-w);height:var(--mb-h);flex:none;background:var(--np-surf-1);border:var(--np-line);border-radius:var(--np-r-lg);display:flex;flex-direction:column;box-shadow:var(--np-elev-3);overflow:hidden;transform:scale(calc(var(--mb-scale,0.9) * 0.96));transform-origin:center center;opacity:0;transition:transform var(--np-t-slow) var(--np-ease-spring),opacity var(--np-t-slow) var(--np-ease)}
#aw-np-mail-panel.open .aw-np-mail-inner{transform:scale(var(--mb-scale,0.9));opacity:1}
.aw-np-mail-iconbtn{display:flex;align-items:center;justify-content:center;background:none;border:none;color:var(--mb-fg);border-radius:var(--np-r-sm);cursor:pointer;flex-shrink:0;opacity:.55;transition:opacity var(--mb-t),background-color var(--mb-t)}
.aw-np-mail-iconbtn:hover{opacity:1;background:var(--mb-hover)}
.aw-np-mail-iconbtn svg{fill:currentColor}
.aw-np-mail-bar{display:flex;align-items:center;justify-content:space-between;padding:var(--mb-bar-pad);border-bottom:var(--mb-line);flex-shrink:0}
.aw-np-mail-bar-title{display:flex;align-items:center;gap:8px;font-size:25px;font-weight:700;color:var(--mb-fg)}
.aw-np-mail-bar-title svg{width:1em;height:1em;color:var(--mb-accent)}
.aw-np-mail-bar-close{width:26px;height:26px}
.aw-np-mail-bar-close svg{width:13px;height:13px}
.aw-np-mail-body{flex:1;overflow:hidden;display:flex;min-height:0}
.aw-np-mail-list-pane{width:var(--mb-list-w);min-width:var(--mb-list-min);max-width:var(--mb-list-max);flex-shrink:0;border-right:var(--mb-line);overflow-y:auto;scrollbar-gutter:stable}
.aw-np-mail-read-pane{flex:1;min-width:0;display:flex;flex-direction:column;overflow-y:auto;scrollbar-gutter:stable}
.aw-np-mail-list-pane,.aw-np-mail-read-pane{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--mb-fg) 22%,transparent) transparent}
.aw-np-mail-list-pane::-webkit-scrollbar,.aw-np-mail-read-pane::-webkit-scrollbar{width:5px}
.aw-np-mail-list-pane::-webkit-scrollbar-track,.aw-np-mail-read-pane::-webkit-scrollbar-track{background:rgba(255,255,255,.04);border-radius:var(--np-r-md);margin:4px 0}
.aw-np-mail-list-pane::-webkit-scrollbar-thumb,.aw-np-mail-read-pane::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--mb-fg) 22%,transparent);border-radius:var(--np-r-md)}
.aw-np-mail-list-pane::-webkit-scrollbar-thumb:hover,.aw-np-mail-read-pane::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--mb-fg) 38%,transparent)}
.aw-np-mail-tabs{display:flex;gap:4px;padding:8px 8px 4px}
.aw-np-mail-tab{flex:1;background:none;border:none;color:var(--mb-fg);opacity:.5;font-size:var(--mb-fs-micro);font-weight:600;font-family:inherit;padding:6px 4px;border-radius:var(--mb-r);cursor:pointer;display:flex;align-items:center;justify-content:center;gap:5px;transition:opacity var(--mb-t),background-color var(--mb-t)}
.aw-np-mail-tab:hover{opacity:.85}
.aw-np-mail-tab.active{opacity:1;background:var(--mb-hover)}
.aw-np-mail-tab-count{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;height:var(--mb-badge-sz);min-width:var(--mb-badge-sz);padding:0 calc(var(--mb-badge-sz) * .28);font-size:var(--mb-fs-date);font-weight:700;line-height:1;background:var(--mb-accent);color:var(--np-accent-on,#fff);border-radius:var(--np-r-pill);flex-shrink:0}
.aw-np-mail-tab-count:empty{display:none}
.aw-np-mail-list{display:flex;flex-direction:column;padding:var(--mb-list-pad);gap:1px}
.aw-np-mail-empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--mb-empty-gap);opacity:.38;padding:var(--mb-empty-pad);text-align:center}
.aw-np-mail-empty-ic svg{width:var(--mb-empty-ic);height:var(--mb-empty-ic);fill:currentColor}
.aw-np-mail-empty div:last-child{font-size:var(--mb-empty-fs)}
.aw-np-mail-row{display:flex;align-items:flex-start;gap:var(--mb-row-gap);padding:var(--mb-row-pad);border-radius:var(--mb-r);cursor:pointer;position:relative;opacity:0;transform:translateY(18px) scale(.98);transition:background-color var(--mb-t),box-shadow var(--mb-t),opacity var(--np-t-slow) var(--np-ease),transform var(--np-t-slower) var(--np-ease-spring)}
.aw-np-mail-row.show{opacity:1;transform:translateY(0) scale(1)}
.aw-np-mail-row.selected{background:var(--mb-sel);box-shadow:inset 2px 0 0 var(--mb-accent)}
.aw-np-mail-dot{width:6px;height:6px;border-radius:50%;background:var(--mb-accent);flex-shrink:0;margin-top:5px;opacity:0;transition:opacity var(--mb-t)}
.aw-np-mail-row.unread .aw-np-mail-dot{opacity:1}
.aw-np-mail-row.selected .aw-np-mail-dot{background:#fff}
.aw-np-mail-col{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.aw-np-mail-subject{font-size:var(--mb-subj-fs);opacity:.7;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:opacity var(--mb-t),color var(--mb-t)}
.aw-np-mail-row.unread .aw-np-mail-subject{font-weight:700;opacity:1;color:var(--mb-fg)}
.aw-np-mail-row.selected .aw-np-mail-subject{opacity:1;color:#fff}
.aw-np-mail-date{font-size:var(--mb-date-fs);opacity:.45}
.aw-np-mail-row.selected .aw-np-mail-date{opacity:.85;color:#fff}
.aw-np-mail-del{width:var(--mb-del-sz);height:var(--mb-del-sz);align-self:center;opacity:var(--mb-del-op)}
.aw-np-mail-del svg{width:var(--mb-del-ic);height:var(--mb-del-ic)}
.aw-np-mail-read-empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--mb-read-empty-gap);opacity:.3;padding:var(--mb-read-empty-pad);text-align:center}
.aw-np-mail-read-empty .aw-np-mail-empty-ic svg{width:var(--mb-read-empty-ic);height:var(--mb-read-empty-ic)}
.aw-np-mail-read-content{display:flex;flex-direction:column;flex:1;padding:var(--mb-read-pad)}
.aw-np-mail-d-head{display:flex;flex-direction:column;gap:5px;margin-bottom:18px;padding-bottom:14px;border-bottom:var(--mb-line)}
.aw-np-mail-d-date-row{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
.aw-np-mail-d-subject{font-size:var(--mb-fs-subj);font-weight:700;line-height:1.35}
.aw-np-mail-d-date{font-size:var(--mb-fs-date);opacity:.5;text-transform:uppercase;letter-spacing:.05em;font-weight:600;flex-shrink:0;margin-top:2px}
.aw-np-mail-d-close{width:22px;height:22px}
.aw-np-mail-d-close svg{width:12px;height:12px}
.aw-np-mail-d-card{display:flex;flex-direction:column;gap:20px}
.aw-np-mail-d-body{font-size:var(--mb-fs-body);line-height:1.65;opacity:.9;white-space:pre-wrap}
.aw-np-mail-d-sign{font-size:var(--mb-fs-small);font-weight:700;color:var(--mb-accent);font-style:italic;opacity:.95}
.aw-np-mail-d-actions{display:flex;gap:8px;margin-top:auto;padding-top:16px;border-top:var(--mb-line)}
.aw-np-mail-toggle{display:flex;align-items:center;gap:6px;background:var(--mb-hover);border:none;color:inherit;border-radius:var(--mb-r);padding:8px 14px;font-size:var(--mb-fs-small);font-weight:600;font-family:inherit;cursor:pointer;transition:background-color var(--mb-t),transform var(--mb-t)}
.aw-np-mail-toggle:active{transform:translateY(1px)}
.aw-np-mail-toggle svg{width:13px;height:13px;fill:currentColor}
.aw-np-mail-toggle.is-danger{color:var(--np-danger)}
@media (hover:hover){
.aw-np-mail-row:hover{background:var(--mb-hover)}
.aw-np-mail-row:hover .aw-np-mail-del{opacity:.5}
.aw-np-mail-row:hover .aw-np-mail-del:hover{opacity:1;background:var(--mb-hover)}
.aw-np-mail-toggle:hover{background:var(--mb-hover-2);transform:translateY(-1px)}
.aw-np-mail-toggle:hover:active{transform:translateY(0)}
}`;
}
function panelCss(sel, p) {
return `
${sel}{--pn-fg:var(--np-accent-bg-fg,#fff);--pn-accent:var(--np-accent,#1565C0);--pn-fs-micro:12px;--pn-fs-small:14px;--pn-fs-body:17px;--pn-fs-subj:25px;--pn-line:var(--np-line-soft);--pn-hover:var(--np-accent-state-1,rgba(255,255,255,.08));--pn-hover-2:var(--np-accent-state-2,rgba(255,255,255,.16));--pn-sel:color-mix(in srgb,var(--np-accent,#1565C0) 26%,var(--np-accent-bg,#223A56));--pn-r:var(--np-r-md);--pn-t:var(--np-t-fast);--pn-native-w:${p.nativeW};--pn-bar-pad:12px 20px;--pn-body-pad:16px 20px;--pn-body-gap:16px;--pn-col-gap:20px;--pn-row-gap:12px;--pn-sec-gap:12px;--pn-ctrl-h:29px;--pn-close:33px;--pn-swatch:32px;--pn-slot:21px;position:absolute;inset:0;background:rgba(0,0,0,.62);z-index:25;opacity:0;pointer-events:none;transition:opacity var(--np-t) var(--np-ease);display:flex;align-items:center;justify-content:center;overflow:hidden;container-type:size}
${sel}.open{opacity:1;pointer-events:auto}
${sel} .aw-np-pn-inner{width:var(--pn-native-w);flex:none;background:var(--np-surf-1);border:var(--np-line);border-radius:var(--np-r-lg);box-shadow:var(--np-elev-3);display:flex;flex-direction:column;overflow:visible;transform:scale(calc(var(--pn-scale,0.7) * 0.96));transform-origin:center center;opacity:0;transition:transform var(--np-t-slow) var(--np-ease-spring),opacity var(--np-t-slow) var(--np-ease)}
${sel}.open .aw-np-pn-inner{transform:scale(var(--pn-scale,0.7));opacity:1}
${sel} .aw-np-pn-bar{display:flex;align-items:center;justify-content:space-between;padding:var(--pn-bar-pad);border-bottom:var(--pn-line);flex-shrink:0}
${sel} .aw-np-pn-title{display:flex;align-items:center;gap:8px;font-size:var(--pn-fs-subj);font-weight:700;color:var(--pn-fg)}
${sel} .aw-np-pn-title svg{width:1em;height:1em;color:var(--pn-accent)}
${sel} .aw-np-pn-close{display:flex;align-items:center;justify-content:center;width:var(--pn-close);height:var(--pn-close);background:none;border:none;color:var(--pn-fg);border-radius:var(--pn-r);cursor:pointer;flex-shrink:0;opacity:.55;transition:opacity var(--pn-t),background-color var(--pn-t)}
${sel} .aw-np-pn-close svg{width:16px;height:16px;fill:currentColor}
${sel} .aw-np-pn-body{flex:1;min-height:0;display:flex;flex-direction:column;gap:var(--pn-body-gap);padding:var(--pn-body-pad);font-size:var(--pn-fs-body);color:var(--pn-fg)}
${sel} .aw-np-pn-cols{display:grid;grid-template-columns:1fr 1px 1fr;gap:0 var(--pn-col-gap);align-items:stretch}
${sel} .aw-np-pn-divider{background:var(--np-hairline-soft);width:1px;align-self:stretch}
${sel} .aw-np-pn-col{display:flex;flex-direction:column;gap:var(--pn-row-gap);min-width:0}
${sel} .aw-np-pn-col>div{flex:1}
${sel} .aw-np-pn-global{padding-bottom:var(--pn-row-gap);border-bottom:var(--pn-line)}
${sel} .aw-np-pn-sec{display:flex;flex-direction:column;gap:var(--pn-sec-gap);padding-top:var(--pn-row-gap);border-top:var(--pn-line)}
${sel} .aw-np-pn-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--pn-sec-gap) var(--pn-col-gap);transition:opacity var(--pn-t)}
${sel} .aw-np-pn-grid.disabled{opacity:.32;pointer-events:none}
${sel} .np-row-txt{display:flex;flex-direction:column;gap:3px;min-width:0}
${sel} .np-row-label{font-size:var(--pn-fs-body);font-weight:600;line-height:1.2}
${sel} .np-row-desc{font-size:var(--pn-fs-micro);opacity:.5;line-height:1.25}
${sel} .aw-np-pn-body .np-switch{flex-shrink:0}
${sel} .aw-np-pn-title-dot{width:.85em;height:.85em;border-radius:50%;background:var(--pn-accent);box-shadow:0 0 0 .18em color-mix(in srgb,var(--pn-accent) 28%,transparent);flex-shrink:0}
${sel} .aw-np-pn-colorsec{display:flex;flex-direction:column;gap:var(--pn-row-gap);padding-bottom:var(--pn-row-gap);border-bottom:var(--pn-line)}
${sel} .aw-np-color-custom{display:flex;align-items:center;flex-wrap:nowrap;gap:12px}
${sel} .aw-np-color-custom-lbl{font-size:var(--pn-fs-body);font-weight:600;flex-shrink:0}
${sel} .aw-np-color-custom-prev{width:var(--pn-slot);height:var(--pn-slot);border-radius:50%;flex-shrink:0;border:1px solid color-mix(in srgb,var(--pn-fg) 30%,transparent);box-sizing:border-box}
${sel} .aw-np-color-custom-prev.empty{background:transparent;border-color:color-mix(in srgb,#fff 28%,transparent)}
${sel} .aw-np-color-custom-input{flex:0 0 auto;width:136px;height:var(--pn-ctrl-h);box-sizing:border-box;background:var(--pn-hover);border:1px solid color-mix(in srgb,var(--pn-fg) 18%,transparent);color:var(--pn-fg);font-size:var(--pn-fs-small);padding:0 10px;border-radius:var(--pn-r);outline:none;font-family:monospace;user-select:text;transition:border-color var(--pn-t),background-color var(--pn-t)}
${sel} .aw-np-color-custom-input::placeholder{color:color-mix(in srgb,var(--pn-fg) 40%,transparent);font-family:inherit;font-size:.92em}
${sel} .aw-np-color-custom-input:focus{border-color:var(--pn-accent);background:var(--pn-hover-2)}
${sel} .aw-np-color-custom-btns{display:flex;flex-direction:row;gap:6px;flex-shrink:0}
${sel} .aw-np-color-custom-btn{display:flex;align-items:center;justify-content:center;width:var(--pn-ctrl-h);height:var(--pn-ctrl-h);box-sizing:border-box;background:var(--pn-hover);border:none;color:var(--pn-fg);border-radius:var(--pn-r);cursor:pointer;font-size:16px;font-weight:700;line-height:1;padding:0;transition:background-color var(--pn-t)}
${sel} .aw-np-color-custom-btn svg{width:14px;height:14px;fill:currentColor}
@media (hover:hover){${sel} .aw-np-color-custom-btn:hover{background:var(--pn-hover-2)}}
${sel} .aw-np-color-slots{display:grid;grid-template-columns:repeat(9,1fr);flex:1;min-width:0;gap:5px;align-items:center;justify-items:center;margin-left:12px}
${sel} .np-swatch-slot{position:relative}
${sel} .np-swatch-empty{width:min(100%,var(--pn-slot));aspect-ratio:1;border-radius:50%;border:1px solid color-mix(in srgb,#fff 28%,transparent);box-sizing:border-box}
${sel} .aw-np-pn-body .aw-np-arow{opacity:0;transform:translateY(18px) scale(.98);transition:opacity var(--np-t-slow) var(--np-ease),transform var(--np-t-slower) var(--np-ease-spring),background-color var(--pn-t)}
${sel}.open .aw-np-pn-body .aw-np-arow{opacity:1;transform:translateY(0) scale(1)}
${sel} .aw-np-pn-col>div,${sel} .aw-np-pn-global>div,${sel} .aw-np-pn-grid>div,${sel} .aw-np-pn-sec>div:not(.aw-np-pn-grid){border-radius:var(--pn-r);padding:5px 8px;margin:-5px -8px}
@media (hover:hover){${sel} .aw-np-pn-close:hover{opacity:1;background:var(--pn-hover)}
${sel} .aw-np-pn-col>div:hover,${sel} .aw-np-pn-global>div:hover,${sel} .aw-np-pn-grid>div:hover,${sel} .aw-np-pn-sec>div:not(.aw-np-pn-grid):hover{background:var(--pn-hover)}}`;
}
function bpCss(sel) {
return `
${sel} .aw-np-pn-body{gap:var(--pn-sec-gap)}
${sel} .aw-np-bp-link{display:flex;align-items:center;gap:14px;padding:11px 13px;border-radius:var(--pn-r);text-decoration:none;color:var(--pn-fg);background:var(--pn-hover);border:none;font-family:inherit;text-align:left;width:100%;box-sizing:border-box;min-height:calc(var(--pn-fs-body) * 1.2 + var(--pn-fs-micro) * 1.25 + 24px);cursor:pointer;transition:background-color var(--pn-t),transform var(--pn-t)}
@media (hover:hover){${sel} .aw-np-bp-link:not(.aw-np-bp-soon):hover{background:var(--pn-hover-2)}}
${sel} .aw-np-bp-link:not(.aw-np-bp-soon):active{transform:scale(.99)}
${sel} .aw-np-bp-soon{cursor:default}
${sel} .aw-np-bp-ic{display:flex;align-items:center;justify-content:center;flex-shrink:0}
${sel} .aw-np-bp-ic svg{width:1.5em;height:1.5em;color:var(--pn-accent);transform:scale(.917)}
${sel} .aw-np-bp-ic-world svg{transform:scale(1.1)}
${sel} .aw-np-bp-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
${sel} .aw-np-bp-label{font-size:var(--pn-fs-body);font-weight:600;line-height:1.2}
${sel} .aw-np-bp-desc{font-size:var(--pn-fs-micro);opacity:.5;line-height:1.25}
${sel} .aw-np-bp-arrow{flex-shrink:0;font-size:1.15em;opacity:.4;line-height:1}
${sel} .aw-np-bp-soon-lbl{flex-shrink:0;font-size:var(--pn-fs-micro);font-weight:600;opacity:.5;letter-spacing:.02em}`;
}
// src/styles/anim.js
var KEYFRAMES_CSS = `
@keyframes np-ripple{0%{transform:scale(0);opacity:1}60%{opacity:.6}100%{transform:scale(2.8);opacity:0}}
@keyframes np-spin{to{transform:rotate(360deg)}}
@keyframes aw-np-fade{from{opacity:0}to{opacity:1}}
@keyframes aw-np-pop{from{opacity:0;transform:scale(.94)}to{opacity:1;transform:scale(1)}}`;
var TOAST_SHOW_MS = 4e3;
var TOAST_FADE_MS = 500;
var CONN_TOAST_MS = 3e3;
var STAGGER_BASE = 0.05;
var STAGGER_STEP = 0.09;
var MAIL_STAGGER_STEP = 0.07;
var MAIL_REVEAL_STEP = 0.06;
var MAIL_REVEAL_CAP = 0.5;
// src/styles/css-base.js
function rootVars({ seekH, topH, touch }) {
return ` #aw-np{--ui-scale:1;--np-top-mul:1.2;--np-ctrl-mul:1;--np-bar-h:calc(48px * var(--np-chrome-scale,1) * var(--ui-scale) * var(--np-ctrl-mul));--np-btn-size:calc(44px * var(--np-chrome-scale,1) * var(--ui-scale) * var(--np-ctrl-mul));--np-svg-size:calc(26px * var(--np-chrome-scale,1) * var(--ui-scale) * var(--np-ctrl-mul));--np-seek-h:calc(${seekH} * var(--np-chrome-scale,1) * var(--ui-scale) * var(--np-ctrl-mul));--np-top-h:calc(${topH} * var(--np-chrome-scale,1) * var(--ui-scale) * var(--np-top-mul));--np-fs-title:calc(17px * var(--np-chrome-scale,1) * var(--ui-scale) * var(--np-top-mul));--np-fs-body:calc(16px * var(--np-chrome-scale,1) * var(--ui-scale) * var(--np-ctrl-mul));--np-fs-small:calc(14px * var(--np-chrome-scale,1) * var(--ui-scale) * var(--np-ctrl-mul));--np-fs-micro:calc(12px * var(--np-chrome-scale,1) * var(--ui-scale) * var(--np-ctrl-mul));--np-spinner-size:calc(48px * var(--np-chrome-scale,1) * var(--ui-scale) * var(--np-ctrl-mul));--np-ease:cubic-bezier(.4,0,.2,1);--np-ease-spring:cubic-bezier(.34,1.5,.64,1);--np-t-fast:.15s;--np-t:.2s;--np-t-slow:.3s;--np-t-slower:.42s;--np-r-xs:4px;--np-r-sm:6px;--np-r-md:8px;--np-r-lg:12px;--np-r-pill:24px;--np-elev-1:0 1px 2px rgba(0,0,0,.28),0 2px 6px rgba(0,0,0,.26);--np-elev-2:0 2px 6px rgba(0,0,0,.30),0 8px 18px rgba(0,0,0,.34);--np-elev-3:0 6px 16px rgba(0,0,0,.34),0 18px 40px rgba(0,0,0,.42);--np-surf-1:color-mix(in srgb,var(--np-accent,#8ab4f8) 8%,#16171a);--np-surf-2:color-mix(in srgb,var(--np-accent,#8ab4f8) 13%,#202227);--np-surf-3:color-mix(in srgb,var(--np-accent,#8ab4f8) 20%,#292b31);--np-hairline:color-mix(in srgb,var(--np-accent,#8ab4f8) 22%,transparent);--np-hairline-soft:color-mix(in srgb,var(--np-accent,#8ab4f8) 12%,transparent);--np-line:1px solid var(--np-hairline);--np-line-soft:1px solid var(--np-hairline-soft);--np-tip-bg:color-mix(in srgb,var(--np-accent,#8ab4f8) 10%,#1b1c1f);--np-ok:#5fd38a;--np-ok-bg:#1c9a41;--np-warn:#e0c84a;--np-warn-bg:#b8791a;--np-danger:#f06464;--np-danger-bg:#b41e1e;container-type:size;position:relative;isolation:isolate;width:100%;height:100%;background:#000;display:flex;flex-direction:column;overflow:hidden;font-family:'Google Sans',Roboto,'Helvetica Neue',sans-serif;user-select:none;touch-action:${touch}}`;
}
var HEADER = ` #player{background:#000}#aw-np,#aw-np *,#aw-np *::before,#aw-np *::after{box-sizing:border-box}#aw-np :focus{outline:none!important}#aw-np button{-webkit-tap-highlight-color:transparent}
#aw-np,#aw-np *{-webkit-touch-callout:none!important;-webkit-user-drag:none;user-select:none!important;-webkit-user-select:none!important}`;
var FS = ` #aw-np.fs{--np-top-mul:.9;--np-ctrl-mul:.8}`;
var UI_LAYER = ` #aw-np.ui .np-grad{opacity:1}
#aw-np-gradient{bottom:0;background:linear-gradient(to top,rgba(0,0,0,.9) 0%,rgba(0,0,0,.4) 60%,transparent 100%)}
#aw-np-gradient-top{top:0;background:linear-gradient(to bottom,rgba(0,0,0,.9) 0%,rgba(0,0,0,.4) 60%,transparent 100%)}
.np-ui-layer{opacity:0;transition:opacity var(--np-t-slow) var(--np-ease),transform var(--np-t-slow) var(--np-ease);pointer-events:none}
#aw-np-top.np-ui-layer{transform:translateY(-12px)}
#aw-np-controls.np-ui-layer{transform:translateY(8px)}
#aw-np.ui #aw-np-top.np-ui-layer,#aw-np.ui #aw-np-controls.np-ui-layer{transform:translateY(0)}
#aw-np.ui .np-ui-layer{opacity:1;pointer-events:all}`;
var DOT = ` #aw-np-dot-btn{width:calc(var(--np-fs-title) * 0.95);height:calc(var(--np-fs-title) * 0.95);border-radius:var(--np-r-sm);background:var(--np-surf-2);border:var(--np-line);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;transition:transform var(--np-t) var(--np-ease),background-color var(--np-t),border-color var(--np-t);position:relative}
#aw-np-dot{width:calc(var(--np-fs-title) * 0.5);height:calc(var(--np-fs-title) * 0.5);border-radius:50%;background:var(--np-accent,#fff);flex-shrink:0;position:relative}`;
var ACTION_SVG = ` #aw-np-browser-btn svg,#aw-np-report-btn svg,#aw-np-mail-btn svg{width:calc(var(--np-fs-title) * 0.62);height:calc(var(--np-fs-title) * 0.62);fill:currentColor}`;
var SWATCH_GRID = ` #aw-np-color-panel #aw-np-color-swatches{display:grid;grid-template-columns:repeat(16,1fr);gap:7px;align-items:center;justify-items:center}
#aw-np-color-panel .np-swatch{width:min(100%,var(--pn-swatch));aspect-ratio:1;height:auto;border-radius:50%;cursor:pointer;border:2px solid transparent;transition:transform var(--np-t) var(--np-ease),border-color var(--np-t-fast),box-shadow var(--np-t)}
#aw-np-color-panel .np-swatch-slot{width:min(100%,var(--pn-slot));aspect-ratio:1;height:auto}`;
var SWATCH_HOVER = ` .np-swatch:hover{transform:scale(1.12);box-shadow:var(--np-elev-1)}`;
var SWATCH_ACTIVE = ` .np-swatch.active{border-color:var(--np-accent,#fff);box-shadow:0 0 0 3px color-mix(in srgb,var(--np-accent,#fff) 35%,transparent),0 2px 6px rgba(0,0,0,.4);transform:scale(1.08)}`;
var CONTROLS_SEEK = ` #aw-np-controls{position:absolute;bottom:0;left:0;right:0;display:flex;flex-direction:column;padding:0 max(6px,env(safe-area-inset-right)) max(6px,env(safe-area-inset-bottom)) max(6px,env(safe-area-inset-left))}
#aw-np-seek-wrap{height:var(--np-seek-h);display:flex;align-items:center;cursor:pointer;padding:0 4px;touch-action:none;margin-bottom:calc(-8px * var(--np-chrome-scale,1) * var(--ui-scale))}`;
var SEEK_BUF = ` #aw-np-seek-buf{position:absolute;inset:0;border-radius:inherit;background:var(--np-accent-dim,rgba(255,255,255,.5));width:0;opacity:.6}`;
var BAR = ` #aw-np-bar{display:flex;align-items:center;height:var(--np-bar-h);gap:0}`;
var ICON = ` .np-icon{display:contents}`;
var SPACER = ` #aw-np-spacer{flex:1}`;
var ACCENT_BTN = ` .np-btn:active{background:var(--np-accent-state-2,rgba(255,255,255,.18))}
.np-btn svg{display:block;fill:currentColor;flex-shrink:0;width:var(--np-svg-size);height:var(--np-svg-size);transition:transform var(--np-t) var(--np-ease)}
.np-btn:active svg{transition-duration:.1s;transform:scale(.88)}
.np-btn svg line{stroke:currentColor}
.accent-icons .np-btn{color:var(--np-accent,#fff)}
.accent-icons .np-btn svg{fill:var(--np-accent,#fff)}
.accent-icons .np-btn svg line{stroke:var(--np-accent,#fff)}
.accent-top #aw-np-title{color:var(--np-accent,#fff)}
.accent-top #aw-np-epinfo{color:var(--np-accent,#fff);opacity:.75}`;
var ACCENT_BRAND_RIPPLE = ` .accent-top #aw-np-brand{color:var(--np-accent,#fff);opacity:.5}
.np-ripple{position:absolute;border-radius:50%;background:var(--np-accent-state-2,rgba(255,255,255,.35));transform:scale(0);opacity:1;animation:np-ripple .55s var(--np-ease);pointer-events:none}
.np-ripple-layer{position:absolute;inset:0;border-radius:inherit;overflow:hidden;pointer-events:none;z-index:0}`;
var SWITCH = ` .np-switch{position:relative;width:31px;height:18px;flex-shrink:0;cursor:pointer}
.np-switch input{opacity:0;width:0;height:0;position:absolute}
.np-switch-track{position:absolute;inset:0;border-radius:10px;background:rgba(255,255,255,.2);transition:background var(--np-t) var(--np-ease)}
.np-switch input:checked~.np-switch-track{background:var(--np-accent,#fff)}
.np-switch-thumb{position:absolute;top:3px;left:3px;width:12px;height:12px;background:#fff;border-radius:50%;transition:transform var(--np-t) var(--np-ease);box-shadow:var(--np-elev-1)}
.np-switch input:checked~.np-switch-thumb{transform:translateX(14px)}
.np-switch input:not(:checked)~.np-switch-thumb{background:rgba(255,255,255,.8)}
.np-switch input:checked~.np-switch-thumb{background:var(--np-accent-on,#fff)}`;
var ROWTIP_AFTER_DATATIP = ` .np-row-tip::after{content:'';position:absolute;top:50%;left:100%;transform:translateY(-50%);border:5px solid transparent;border-left-color:var(--np-tip-bg,#000)}
[data-tip]{margin:0 -8px;padding:6px 8px;border-radius:var(--np-r-md);transition:background var(--np-t-fast) var(--np-ease)}[data-tip]:hover{background:var(--np-accent-state-1,rgba(255,255,255,.06))}[data-tip]:hover .np-row-tip{opacity:1;transition-delay:.5s}`;
var SPEED_POPUP = ` #aw-np-speed-popup{position:absolute;top:max(10px,env(safe-area-inset-top));left:max(10px,env(safe-area-inset-left));background:transparent;color:rgba(255,255,255,.6);font-size:var(--np-fs-title);font-weight:500;padding:2px 4px;border-radius:var(--np-r-xs);pointer-events:none;opacity:0;transition:opacity var(--np-t-slow) var(--np-ease);z-index:20;letter-spacing:.02em;text-shadow:0 1px 6px rgba(0,0,0,.95)}
#aw-np-speed-popup.on{opacity:1}
#aw-np.ui #aw-np-speed-popup{opacity:0!important}`;
var CLOCK_CONN = ` #aw-np-clock svg{width:1em;height:1em;flex-shrink:0;display:block;filter:drop-shadow(0 0 1px rgba(0,0,0,.95)) drop-shadow(0 1px 3px rgba(0,0,0,.7))}
#aw-np-clock .hand-h{stroke:var(--np-accent,#fff)}#aw-np-clock .pin{fill:var(--np-accent,#fff);stroke:none}#aw-np-clock .hand-m{stroke:currentColor}
#aw-np.ui #aw-np-clock{opacity:0!important;visibility:hidden}
#aw-np.clock-hidden #aw-np-clock{display:none!important}
#aw-np.speed-popup-hidden #aw-np-speed-popup{display:none!important}
#aw-np-conn-toast{position:absolute;top:clamp(12px,1.7cqh,18px);left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:8px;padding:6px 12px;border-radius:var(--np-r-lg);font-size:calc(var(--np-fs-body) * .82);font-weight:500;color:rgba(255,255,255,.95);z-index:22;box-shadow:var(--np-elev-2);pointer-events:all;white-space:nowrap;opacity:0;transition:opacity var(--np-t-slow) var(--np-ease);border:var(--np-line-soft)}
#aw-np-conn-toast.slow{background:var(--np-warn-bg)}
#aw-np-conn-toast.good{background:var(--np-ok-bg)}
#aw-np-conn-toast .conn-msg{display:flex;flex-direction:column;gap:2px;line-height:1.3;text-align:center;letter-spacing:.025em;flex:1}`;
var ERROR_SPINNER = ` #aw-np-error-toast,#aw-np-load-error-toast{position:absolute;bottom:calc(var(--np-bar-h) + var(--np-seek-h) + 16px);left:50%;transform:translateX(-50%);background:var(--np-danger-bg);color:#fff;font-size:var(--np-fs-body);font-weight:500;letter-spacing:.02em;padding:7px 18px;border-radius:var(--np-r-pill);border:var(--np-line-soft);pointer-events:all;white-space:nowrap;z-index:21;box-shadow:var(--np-elev-2);cursor:pointer}
#aw-np-spinner{position:absolute;inset:0;margin:auto;width:var(--np-spinner-size);height:var(--np-spinner-size);border:3px solid rgba(255,255,255,.12);border-top-color:var(--np-accent,#fff);border-radius:50%;animation:np-spin .65s linear infinite;pointer-events:none;display:none}
#aw-np-spinner.on{display:block}`;
var FLASH_TAIL = ` .np-flash-circle{position:absolute;top:50%;left:50%;width:72px;height:72px;border-radius:50%;display:flex;align-items:center;justify-content:center;pointer-events:none;opacity:0;z-index:12;color:var(--np-accent-bg-fg,rgba(255,255,255,.9))}
.np-flash-circle::before{content:'';position:absolute;inset:0;border-radius:50%;background:var(--np-accent-bg,#1a1a2e);opacity:.8;pointer-events:none}
.np-flash-circle.on{opacity:1}
.np-flash-circle svg{fill:var(--np-accent-bg-fg,rgba(255,255,255,.9));opacity:1;width:34px;height:34px;position:relative;z-index:1}
#aw-np-center{margin:-36px 0 0 -36px;transform:scale(.7);transition:opacity var(--np-t) var(--np-ease),transform var(--np-t) var(--np-ease-spring)}
#aw-np-center.on{transform:scale(1)}
#aw-np-vol-flash{transform:translate(-50%,-50%);transition:opacity var(--np-t) var(--np-ease)}
#aw-np-vol-flash svg{display:block}`;
var SKIP_POPUP = `
#aw-np-skip-popup{position:absolute;right:max(clamp(16px,3vw,34px),env(safe-area-inset-right));bottom:calc(var(--np-bar-h) + var(--np-seek-h) + 22px);display:flex;align-items:center;gap:9px;background:var(--np-surf-2);color:var(--np-accent-bg-fg,#fff);border:var(--np-line);border-radius:var(--np-r-md);padding:11px 20px;font-size:var(--np-fs-body);font-weight:600;font-family:inherit;letter-spacing:.02em;cursor:pointer;opacity:0;transform:translateY(10px);pointer-events:none;transition:opacity var(--np-t) var(--np-ease),transform var(--np-t) var(--np-ease),background-color var(--np-t),border-color var(--np-t);z-index:22;box-shadow:var(--np-elev-2)}
#aw-np-skip-popup.show{opacity:1;transform:translateY(0);pointer-events:auto}
#aw-np-skip-popup:hover{background:var(--np-surf-3);border-color:var(--np-accent,#fff)}
#aw-np-skip-popup:active{transform:scale(.97)}
#aw-np-skip-popup .aw-np-skip-popup-ic{display:flex;align-items:center;color:var(--np-accent,#fff)}
#aw-np-skip-popup .aw-np-skip-popup-ic:empty{display:none}
#aw-np-skip-popup .aw-np-skip-popup-ic svg{width:1.15em;height:1.15em;fill:currentColor}`;
var AWAKE_GUARD = ` #aw-np-awake-toast{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);display:flex;flex-direction:column;align-items:center;gap:3px;padding:10px 18px;border-radius:var(--np-r-lg);background:var(--np-surf-2);border:var(--np-line-soft);color:var(--np-accent-bg-fg,#fff);text-align:center;z-index:22;box-shadow:var(--np-elev-2);pointer-events:none;opacity:0;transition:opacity var(--np-t-slow) var(--np-ease)}
#aw-np-awake-toast.show{opacity:.58}
#aw-np-awake-toast .awake-q{font-size:calc(var(--np-fs-body) * .95);font-weight:600;letter-spacing:.02em}
#aw-np-awake-toast .awake-hint{font-size:calc(var(--np-fs-body) * .74);font-weight:500;opacity:.72;letter-spacing:.02em}
#aw-np-awake-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.62);z-index:32;padding:16px}
#aw-np-awake-card{position:relative;display:flex;flex-direction:column;gap:3px;max-width:min(88%,332px);padding:15px 20px 14px;border-radius:var(--np-r-lg);background:var(--np-surf-1);border:var(--np-line);box-shadow:var(--np-elev-3);color:var(--np-accent-bg-fg,#fff);text-align:center}
#aw-np-awake-card .awake-card-head{display:flex;align-items:center;gap:8px;margin-bottom:7px}
#aw-np-awake-card .awake-card-ic{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:calc(var(--np-fs-body) * 1.15);height:calc(var(--np-fs-body) * 1.15);color:var(--np-accent,#fff)}
#aw-np-awake-card .awake-card-ic svg{width:100%;height:100%;display:block;transform:translate(3.8%,-6.5%)}
#aw-np-awake-card .awake-card-title{flex:1;text-align:left;font-size:calc(var(--np-fs-body) * 1.02);font-weight:700;letter-spacing:.01em;white-space:nowrap}
#aw-np-awake-card .awake-card-body{font-size:calc(var(--np-fs-body) * .78);font-weight:500;opacity:.66;line-height:1.4}
#aw-np-awake-card .awake-card-mark{display:flex;align-items:baseline;justify-content:center;gap:5px;flex-wrap:wrap;font-size:calc(var(--np-fs-body) * .82);font-weight:500;opacity:.92;margin-top:11px}
#aw-np-awake-card .awake-card-go{font-family:inherit;font-size:inherit;font-weight:700;letter-spacing:.02em;padding:2px 6px;margin:-2px -2px;border:none;border-radius:var(--np-r-sm);background:none;color:var(--np-accent,#fff);cursor:pointer;text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px;transition:background var(--np-t-fast),opacity var(--np-t-fast)}
#aw-np-awake-card .awake-card-go:hover{background:var(--np-accent-state-1,rgba(255,255,255,.06))}
#aw-np-awake-card .awake-card-go:disabled{color:inherit;text-decoration:none;cursor:default;opacity:.6}
#aw-np-awake-card .awake-card-x{position:relative;flex-shrink:0;margin-right:-4px;display:flex;align-items:center;justify-content:center;width:calc(var(--np-fs-body) * 1.35);height:calc(var(--np-fs-body) * 1.35);padding:0;border:none;border-radius:var(--np-r-sm);background:none;color:var(--np-accent-bg-fg,#fff);opacity:.55;cursor:pointer;transition:opacity var(--np-t-fast),background var(--np-t-fast)}
#aw-np-awake-card .awake-card-x:hover{opacity:1;background:var(--np-accent-state-1,rgba(255,255,255,.06))}
#aw-np-awake-card .awake-card-x svg{width:72%;height:72%;display:block}`;
var PANEL_SUBROW = ` .aw-np-pn-icontog{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0;width:calc(var(--pn-fs-body) * 1.12);height:calc(var(--pn-fs-body) * 1.12);padding:0;margin:0 -3px 0 auto;background:none;border:none;border-radius:var(--np-r-sm);color:var(--np-accent-bg-fg,#fff);opacity:.35;cursor:pointer;transition:opacity var(--np-t) var(--np-ease),color var(--np-t)}
.aw-np-pn-icontog svg{width:100%;height:100%;display:block;transform:translate(3.8%,-6.5%)}
.aw-np-pn-icontog:hover{opacity:.6}
.aw-np-pn-icontog.on{opacity:1;color:var(--np-accent,#fff)}
.aw-np-pn-icontog.dep-off{opacity:.2;cursor:default}
.aw-np-pn-icontog .np-row-tip{right:0;left:auto;top:auto;bottom:calc(100% + 8px);transform:none}
.aw-np-pn-icontog:hover .np-row-tip{opacity:1;transition-delay:.5s}
.aw-np-pn-icontog .np-row-tip::after{top:100%;left:auto;right:10px;transform:none;border-top-color:var(--np-tip-bg,#000);border-left-color:transparent}`;
// src/styles/styles-desktop.js
var MAIL_CSS_DESKTOP = { w: "720px", h: "480px", listW: "36%", listMin: "180px", listMax: "340px", delOp: "0" };
var PANEL_CSS_DESKTOP = { nativeW: "650px" };
function injectStyleDesktop() {
if (document.getElementById("aw-np-style")) return;
const s = document.createElement("style");
s.id = "aw-np-style";
const D1 = `
${HEADER}
${rootVars({ seekH: "44px", topH: "64px", touch: "pan-x pan-y" })}
${FS}
#aw-np-video{flex:1;width:100%;min-height:0;display:block;background:#000;cursor:none}
#aw-np.ui #aw-np-video{cursor:pointer}
#aw-np-bar,#aw-np-top,.aw-np-toast,#aw-np-error-toast,#aw-np-conn-toast,#aw-np-settings-panel,#aw-np-color-panel,#aw-np-menu-panel,.np-tip,.np-row-tip,.np-btn,.np-swatch,.np-switch-track,.np-switch-thumb,#aw-np-seek-fill,#aw-np-seek-buf,#aw-np-seek-thumb,#aw-np-seek-tip,#aw-np-title,#aw-np-epinfo,#aw-np-brand,#aw-np-clock,#aw-np-clock svg circle,#aw-np-clock .hand-h,#aw-np-clock .hand-m,#aw-np-clock .pin,#aw-np-spinner,#aw-np-vol-popup,#aw-np-time,#aw-np-speed-ind,#aw-np-speed-popup,#aw-np-buf-pct,#aw-np-dot{transition-property:color,background,background-color,fill,stroke,border-color,box-shadow;transition-duration:.4s;transition-timing-function:var(--np-ease)}
.np-grad{position:absolute;left:0;right:0;height:160px;pointer-events:none;opacity:0;transition:opacity var(--np-t-slow) var(--np-ease)}
${UI_LAYER}
#aw-np-top{position:absolute;top:0;left:0;right:0;height:var(--np-top-h);display:flex;align-items:flex-start;justify-content:space-between;padding:calc(13px * var(--np-chrome-scale,1) * var(--ui-scale)) calc(10px * var(--np-chrome-scale,1) * var(--ui-scale))}
#aw-np-top-left{display:flex;flex-direction:column;gap:2px;overflow:hidden}
#aw-np-title{font-size:calc(var(--np-fs-title) * 1.2);font-weight:500;letter-spacing:.01em;color:var(--np-accent-bg-fg,#fff);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:38cqw;text-shadow:0 1px 3px rgba(0,0,0,.8),0 0 12px rgba(0,0,0,.6)}
#aw-np-epinfo{font-size:calc(var(--np-fs-title) * 0.8);font-weight:400;letter-spacing:.02em;color:var(--np-accent-bg-fg,rgba(255,255,255,.7));opacity:.8;text-shadow:0 1px 3px rgba(0,0,0,.8)}
#aw-np-top-right{display:flex;flex-direction:column;align-items:flex-end;gap:clamp(6px,1cqh,10px);flex-shrink:0}
#aw-np-top-actions{display:flex;align-items:center;justify-content:space-between;align-self:stretch}
#aw-np-brand{font-size:calc(var(--np-fs-title) * 0.8);font-weight:500;letter-spacing:.04em;color:var(--np-accent-bg-fg,rgba(255,255,255,.5));opacity:.6;white-space:nowrap;line-height:1;text-shadow:0 1px 3px rgba(0,0,0,.8);text-transform:uppercase}
${DOT}
#aw-np-kbd-btn{width:calc(var(--np-fs-title) * 0.95);height:calc(var(--np-fs-title) * 0.95);border-radius:var(--np-r-sm);background:var(--np-surf-2);border:var(--np-line);color:var(--np-accent-bg-fg,#CCDDF1);font-size:calc(var(--np-fs-title) * 0.58);font-weight:400;font-family:Arial,'Segoe UI','Roboto',sans-serif;display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;line-height:1;transition:transform var(--np-t) var(--np-ease),background-color var(--np-t),border-color var(--np-t);user-select:none;position:relative}
#aw-np-kbd-btn:hover{transform:scale(1.15)}
#aw-np-kbd-btn:active{transform:scale(1.05)}
#aw-np-browser-btn,#aw-np-report-btn,#aw-np-mail-btn{width:calc(var(--np-fs-title) * 0.95);height:calc(var(--np-fs-title) * 0.95);border-radius:var(--np-r-sm);background:var(--np-surf-2);border:var(--np-line);color:var(--np-accent-bg-fg,#CCDDF1);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;transition:transform var(--np-t) var(--np-ease),background-color var(--np-t),border-color var(--np-t);user-select:none;position:relative}
${ACTION_SVG}
#aw-np-browser-btn:hover,#aw-np-report-btn:hover,#aw-np-mail-btn:hover{transform:scale(1.15)}
#aw-np-browser-btn:active,#aw-np-report-btn:active,#aw-np-mail-btn:active{transform:scale(1.05)}
.aw-np-mail-badge{display:none;position:absolute;top:-2px;right:-2px;width:6px;height:6px;border-radius:50%;background:var(--np-accent,#ec4f4f)}
#aw-np-browser-btn .np-tip,#aw-np-report-btn .np-tip,#aw-np-mail-btn .np-tip,#aw-np-kbd-btn .np-tip{bottom:auto;top:calc(100% + 10px);left:auto;right:0;transform:none}
#aw-np-browser-btn .np-tip::after,#aw-np-report-btn .np-tip::after,#aw-np-mail-btn .np-tip::after,#aw-np-kbd-btn .np-tip::after{top:auto;bottom:100%;left:auto;right:6px;transform:none;border-top-color:transparent;border-bottom-color:var(--np-tip-bg,#1b1c1f)}
#aw-np-browser-btn:hover .np-tip,#aw-np-report-btn:hover .np-tip,#aw-np-mail-btn:hover .np-tip,#aw-np-kbd-btn:hover .np-tip{opacity:1;transition-delay:.3s}
#aw-np-hotkey-overlay{position:absolute;inset:0;background:rgba(0,0,0,.62);z-index:30;opacity:0;pointer-events:none;transition:opacity var(--np-t) var(--np-ease);font-family:Arial,'Segoe UI','Roboto',sans-serif;container-type:size;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:max(16px,env(safe-area-inset-top)) max(16px,env(safe-area-inset-right)) max(16px,env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left));box-sizing:border-box}
#aw-np-hotkey-overlay.open{opacity:1;pointer-events:auto}
`;
const D2 = `
#aw-np-hotkey-inner{position:relative;display:flex;flex-direction:column;align-items:center;gap:clamp(12px,2.4cqh,24px);padding:clamp(14px,2.4cqh,22px) clamp(16px,2.6cqw,26px) clamp(12px,2cqh,18px);box-sizing:border-box;overflow:visible;max-width:100%;max-height:100%;background:var(--np-surf-1);border:var(--np-line);border-radius:var(--np-r-lg);box-shadow:var(--np-elev-3);--ku:min(clamp(30px,6.6cqh,82px),5.9cqw);--kg:clamp(5px,1.3cqh,16px)}
`;
const D3 = `
.aw-np-hotkey-bartitle{display:flex;align-items:center;gap:clamp(7px,1.2cqw,11px);flex:1;min-width:0;font-size:clamp(17px,2.7cqw,34px);font-weight:700;color:var(--np-accent-bg-fg,#fff);letter-spacing:.01em;line-height:1}
.aw-np-hotkey-bartitle>span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.aw-np-hotkey-key-a{flex-shrink:0;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;width:1.28em;height:1.28em;font-size:.78em;border:1.7px solid var(--np-accent,#1565C0);border-radius:var(--np-r-sm);color:var(--np-accent,#1565C0);font-weight:700;font-family:Arial,'Segoe UI','Roboto',sans-serif;line-height:1;padding-top:.06em}
.aw-np-kbd{display:flex;flex-direction:column;gap:var(--kg)}
.aw-np-krow{display:flex;gap:var(--kg);opacity:0;transform:translateY(18px) scale(.98);transition:opacity var(--np-t-slow) var(--np-ease),transform var(--np-t-slower) var(--np-ease-spring)}
#aw-np-hotkey-overlay.open .aw-np-krow{opacity:1;transform:translateY(0) scale(1)}
#aw-np-hotkey-overlay.open .aw-np-krow:nth-child(1){transition-delay:.05s}
#aw-np-hotkey-overlay.open .aw-np-krow:nth-child(2){transition-delay:.14s}
#aw-np-hotkey-overlay.open .aw-np-krow:nth-child(3){transition-delay:.23s}
#aw-np-hotkey-overlay.open .aw-np-krow:nth-child(4){transition-delay:.32s}
#aw-np-hotkey-overlay.open .aw-np-krow:nth-child(5){transition-delay:.41s}
.aw-np-krow-bottom{margin-left:0 !important}
.aw-np-key-enter-flat{width:calc(var(--ku) * 1.5 + var(--kg)) !important}
.aw-np-key{width:var(--ku);height:var(--ku);border-radius:var(--np-r-sm);background:var(--np-surf-2);border:var(--np-line);display:flex;align-items:center;justify-content:center;color:var(--np-accent-bg-fg,#CCDDF1);font-size:calc(var(--ku) * .48);font-weight:500;flex-shrink:0;position:relative;line-height:1;box-sizing:border-box;transition:background-color var(--np-t-fast),border-color var(--np-t-fast),color var(--np-t-fast)}
.aw-np-key-fill-wide{width:calc(var(--ku) * var(--wmult, 1.5) + var(--kg) * var(--padmult, 0)) !important}
.aw-np-key-fill-wide{justify-content:flex-start}
.aw-np-key-fill-wide>.aw-np-glyph{display:inline-flex;align-items:center;justify-content:center;width:var(--ku);height:var(--ku)}
.aw-np-key-half{width:calc(var(--ku) * .5) !important}
.aw-np-key.off{opacity:.5}
.aw-np-key.on{cursor:pointer}
.aw-np-key.on.hovered{background:var(--np-accent-state-1,color-mix(in srgb,var(--np-accent,#1565C0) 22%,var(--np-accent-bg,#223A56)));color:#fff}
.aw-np-key-ico,.aw-np-key-letter{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;transition:opacity var(--np-t-fast)}
.aw-np-key-ico svg{width:56%;height:56%;color:var(--np-accent-bg-fg,#CCDDF1)}
.aw-np-key.hovered .aw-np-key-ico svg{color:#fff}
.aw-np-key-pct{font-size:calc(var(--ku) * .26);font-weight:600}
.aw-np-key-a-badge{display:flex;align-items:center;justify-content:center;width:calc(var(--ku) * .55);height:calc(var(--ku) * .55);border-radius:var(--np-r-xs);background:var(--np-accent-bg-fg,#CCDDF1);color:var(--np-accent-bg,#223A56);font-size:calc(var(--ku) * .32);font-weight:500;font-family:Arial,'Segoe UI','Roboto',sans-serif;line-height:1}
.aw-np-key-letter{opacity:0;font-size:calc(var(--ku) * .48);font-weight:500}
.aw-np-key-letter-slot.off .aw-np-key-letter{opacity:1}
.aw-np-key.hovered .aw-np-key-ico{opacity:0}
.aw-np-key.hovered .aw-np-key-letter{opacity:1}
.aw-np-key-mod{font-size:calc(var(--ku) * .19);font-weight:600;opacity:.5;letter-spacing:.04em}
.aw-np-key-space{width:calc(var(--ku) * 5 + var(--kg) * 4)}
`;
const D4 = `
.aw-np-kbd-bar{display:flex;justify-content:space-between;align-items:center;gap:12px;width:100%;max-width:calc(var(--ku) * 12 + var(--kg) * 11);box-sizing:border-box;position:relative;border-bottom:var(--np-line);padding-bottom:clamp(8px,1.6cqh,14px)}
.aw-np-hotkey-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;max-width:calc(var(--ku) * 12 + var(--kg) * 11);box-sizing:border-box}
.aw-np-hotkey-tools{display:flex;gap:clamp(6px,1cqw,10px);flex-shrink:0}
.aw-np-kbd-bar-btn{display:flex;align-items:center;justify-content:center;width:clamp(28px,4.4cqh,40px);height:clamp(28px,4.4cqh,40px);border-radius:var(--np-r-sm);cursor:pointer;user-select:none;background:transparent;color:var(--np-accent,#1565C0);transition:color var(--np-t-fast),transform var(--np-t-fast) var(--np-ease),opacity var(--np-t),background-color var(--np-t-fast);position:relative}
.aw-np-kbd-bar-btn .np-tip{bottom:auto;top:calc(100% + 8px)}
.aw-np-kbd-bar-btn .np-tip::after{top:auto;bottom:100%;border-top-color:transparent;border-bottom-color:var(--np-tip-bg,#1b1c1f)}
`;
const D5 = `
.aw-np-hotkey-tools .np-tip{top:auto;bottom:calc(100% + 8px)}
.aw-np-hotkey-tools .np-tip::after{bottom:auto;top:100%;border-bottom-color:transparent;border-top-color:var(--np-tip-bg,#1b1c1f)}
.aw-np-kbd-bar-btn:hover .np-tip{opacity:1;transition-delay:.4s}
.aw-np-kbd-bar-btn svg{width:64%;height:64%;display:block}
`;
const D6 = `
.aw-np-kbd-bar-btn.is-ok svg{width:74%;height:74%}
.aw-np-kbd-bar-btn.is-reset svg{width:66%;height:66%}
.aw-np-kbd-bar-btn.is-x svg,.aw-np-kbd-bar-btn.is-cancel svg{width:60%;height:60%}
.aw-np-kbd-bar-btn:hover{transform:translateY(-1px);opacity:.78}
.aw-np-kbd-bar-btn:active{transform:translateY(0) scale(.92)}
`;
const D7 = `
.aw-np-kbd-bar>.is-x{color:var(--np-accent-bg-fg,#fff);opacity:.55}
.aw-np-kbd-bar>.is-x:hover{opacity:1;transform:none;background:var(--np-accent-state-1,rgba(255,255,255,.08))}
`;
const D8 = `
.aw-np-hotkey-tools .is-ok,.aw-np-hotkey-tools .is-cancel,.aw-np-hotkey-tools .is-reset{display:none}
#aw-np-hotkey-overlay.editing .aw-np-hotkey-tools .is-edit{display:none}
#aw-np-hotkey-overlay.editing .aw-np-hotkey-tools .is-ok,#aw-np-hotkey-overlay.editing .aw-np-hotkey-tools .is-cancel,#aw-np-hotkey-overlay.editing .aw-np-hotkey-tools .is-reset{display:flex}
`;
const D9 = `
#aw-np-hotkey-overlay.editing .aw-np-key-special{opacity:.28!important;filter:grayscale(1)}
#aw-np-hotkey-overlay.editing .aw-np-key-letter-slot{cursor:pointer}
#aw-np-hotkey-overlay.editing .aw-np-key-letter-slot.on:hover,#aw-np-hotkey-overlay.editing .aw-np-key-letter-slot.off:hover{background:var(--np-accent-state-1,color-mix(in srgb,var(--np-accent,#1565C0) 22%,var(--np-accent-bg,#223A56)))}
#aw-np-hotkey-overlay.editing .aw-np-key-letter-slot.off:hover .aw-np-key-letter{opacity:1}
.aw-np-key-letter-slot.listening{box-shadow:0 0 0 2px #fff,0 0 14px 2px rgba(255,255,255,.85);z-index:1}
.aw-np-kbd-fn{height:clamp(20px,3cqh,30px);display:flex;align-items:center;justify-content:flex-start;flex:1;min-width:0;overflow:hidden;gap:clamp(10px,1.6cqw,18px);opacity:0;transform:translateY(4px);transition:opacity var(--np-t) var(--np-ease),transform var(--np-t) var(--np-ease)}
.aw-np-kbd-fn.show{opacity:1;transform:translateY(0)}
.aw-np-kbd-fn-name{font-size:clamp(10px,1.7cqh,14px);font-weight:600;text-transform:uppercase;letter-spacing:.09em;color:var(--np-accent-dim,#9db8e0);font-family:Arial,sans-serif;line-height:1;padding-right:clamp(10px,1.6cqw,18px);border-right:1px solid color-mix(in srgb,var(--np-accent-bg-fg,#CCDDF1) 22%,transparent)}
.aw-np-kbd-fn-desc{font-size:clamp(12px,2cqh,18px);font-weight:400;color:var(--np-accent-bg-fg,rgba(255,255,255,.92));font-family:Arial,sans-serif;line-height:1}
#aw-np-dot-btn:hover{transform:scale(1.15)}
#aw-np-dot-btn:hover .np-tip{opacity:1;transition-delay:.3s}
#aw-np-dot-btn .np-tip{bottom:auto;top:calc(100% + 10px);left:auto;right:0;transform:none}
#aw-np-dot-btn .np-tip::after{top:auto;bottom:100%;left:auto;right:5px;transform:none;border-top-color:transparent;border-bottom-color:var(--np-tip-bg,#1b1c1f)}
${panelCss("#aw-np-color-panel", PANEL_CSS_DESKTOP)}
${SWATCH_GRID}
#aw-np-color-panel .aw-np-color-custom-prev{width:clamp(16px,2cqh,22px);height:clamp(16px,2cqh,22px)}
${SWATCH_HOVER}
${SWATCH_ACTIVE}
${CONTROLS_SEEK}
#aw-np-seek-track{position:relative;width:100%;height:4px;border-radius:var(--np-r-xs);background:rgba(255,255,255,.2);transition:height var(--np-t-fast) var(--np-ease)}
#aw-np-seek-wrap:hover #aw-np-seek-track{height:6px}
${SEEK_BUF}
#aw-np-seek-fill{position:absolute;inset:0;border-radius:inherit;background:var(--np-accent,#fff);width:0;transition:background var(--np-t)}
#aw-np-seek-thumb{position:absolute;top:50%;left:0;width:14px;height:14px;background:var(--np-accent,#fff);border-radius:50%;transform:translate(-50%,-50%) scale(0);transition:transform var(--np-t-fast) var(--np-ease),box-shadow var(--np-t-fast);box-shadow:var(--np-elev-1)}
#aw-np-seek-wrap:hover #aw-np-seek-thumb{transform:translate(-50%,-50%) scale(1)}
#aw-np-seek-wrap.seeking #aw-np-seek-thumb{transform:translate(-50%,-50%) scale(1.2)}
#aw-np-seek-tip{position:absolute;bottom:calc(100% + 10px);left:0;transform:translateX(-50%);background:var(--np-tip-bg,#282828);color:var(--np-accent-bg-fg,#fff);font-size:var(--np-fs-small);font-weight:500;padding:4px 8px;border-radius:var(--np-r-sm);border:var(--np-line-soft);pointer-events:none;white-space:nowrap;visibility:hidden;box-shadow:var(--np-elev-1);letter-spacing:.02em}
${BAR}
${ICON}
.np-btn{position:relative;display:flex;align-items:center;justify-content:center;width:var(--np-btn-size);height:var(--np-btn-size);background:none;border:none;cursor:pointer;color:var(--np-accent-bg-fg,rgba(255,255,255,.75));padding:0;flex-shrink:0;border-radius:50%;transition:color var(--np-t) var(--np-ease),background var(--np-t)}
.np-btn:hover{color:var(--np-accent-bg-fg,#fff);background:var(--np-accent-state-1,rgba(255,255,255,.1))}
${ACCENT_BTN}
${ACCENT_BRAND_RIPPLE}
#aw-np-time{font-size:calc(15px * var(--np-chrome-scale,1) * var(--ui-scale) * var(--np-ctrl-mul));font-weight:400;color:var(--np-accent-bg-fg,rgba(255,255,255,.85));letter-spacing:.04em;white-space:nowrap;padding:0 6px;font-variant-numeric:tabular-nums;text-shadow:0 1px 3px rgba(0,0,0,.8)}
${SPACER}
#aw-np-vol-group{position:relative;display:flex;align-items:center}
#aw-np-vol-group::after{content:'';position:absolute;bottom:100%;left:-8px;right:-8px;height:calc(var(--np-seek-h) + 20px);pointer-events:none}
#aw-np-vol-group:hover::after{pointer-events:all}
#aw-np-vol-popup{position:absolute;bottom:calc(var(--np-bar-h) + var(--np-seek-h) + 8px);left:50%;transform:translateX(-50%);width:44px;height:0;overflow:hidden;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:6px;transition:height var(--np-t) var(--np-ease),padding var(--np-t) var(--np-ease);padding:0}
#aw-np-vol-group:hover #aw-np-vol-popup,#aw-np-vol-group:focus-within #aw-np-vol-popup{height:148px;padding:10px 0 12px;background:var(--np-surf-2);border:var(--np-line);border-radius:var(--np-r-lg);box-shadow:var(--np-elev-2)}
#aw-np-vol-pct{font-size:var(--np-fs-small);font-weight:500;color:var(--np-accent-bg-fg,rgba(255,255,255,.85));width:32px;text-align:center;font-variant-numeric:tabular-nums;flex-shrink:0;display:block;letter-spacing:.02em;text-shadow:0 1px 3px rgba(0,0,0,.8)}
#aw-np-vol{-webkit-appearance:none;appearance:none;width:4px;height:108px;border-radius:var(--np-r-xs);background:rgba(255,255,255,.2);cursor:pointer;outline:none;writing-mode:vertical-lr;direction:rtl;transition:background var(--np-t-fast)}
#aw-np-vol::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;background:var(--np-accent,#fff);border-radius:50%;cursor:pointer;box-shadow:var(--np-elev-1);transition:transform var(--np-t-fast) var(--np-ease)}
#aw-np-vol:hover::-webkit-slider-thumb{transform:scale(1.2)}
#aw-np-vol::-moz-range-thumb{width:14px;height:14px;background:var(--np-accent,#fff);border:none;border-radius:50%;cursor:pointer;box-shadow:var(--np-elev-1)}
${panelCss("#aw-np-settings-panel", PANEL_CSS_DESKTOP)}
${panelCss("#aw-np-report-panel", PANEL_CSS_DESKTOP)}
${reportCss()}
${mailCss(MAIL_CSS_DESKTOP)}
${panelCss("#aw-np-bp-panel", { nativeW: "560px" })}
${bpCss("#aw-np-bp-panel")}
${SWITCH}
.np-tip{position:absolute;bottom:calc(100% + 8px);left:50%;transform:translateX(-50%);background:var(--np-tip-bg,#1b1c1f);color:rgba(255,255,255,.92);font-size:var(--np-fs-small);font-weight:500;padding:5px 10px;border-radius:var(--np-r-sm);border:var(--np-line-soft);white-space:nowrap;pointer-events:none;opacity:0;transition:opacity var(--np-t-fast) var(--np-ease);z-index:20;box-shadow:var(--np-elev-1)}
.np-tip::after{content:'';position:absolute;top:100%;left:50%;transform:translateX(-50%);border:5px solid transparent;border-top-color:var(--np-tip-bg,#1b1c1f)}
.np-btn:hover .np-tip{opacity:1;transition-delay:.4s}
`;
const D10 = `
#aw-btn-play .np-tip{left:0;right:auto;transform:none}
#aw-btn-play .np-tip::after{left:calc(var(--np-btn-size) / 2)}
#aw-btn-fs .np-tip{left:auto;right:0;transform:none}
#aw-btn-fs .np-tip::after{left:auto;right:calc(var(--np-btn-size) / 2);transform:translateX(50%)}
.np-row-tip{position:absolute;top:50%;right:calc(100% + 14px);transform:translateY(-50%);background:var(--np-tip-bg,#000);color:var(--np-accent-bg-fg,rgba(255,255,255,.9));font-size:var(--np-fs-small);line-height:1.5;padding:6px 10px;border-radius:var(--np-r-md);border:var(--np-line-soft);white-space:nowrap;pointer-events:none;opacity:0;transition:opacity var(--np-t-fast) var(--np-ease);transition-delay:0s;z-index:20;box-shadow:var(--np-elev-2)}
${ROWTIP_AFTER_DATATIP}
.aw-np-toast{position:absolute;bottom:calc(var(--np-bar-h) + var(--np-seek-h) + 16px);left:50%;transform:translateX(-50%);background:color-mix(in srgb,var(--np-accent-bg,#282828) 80%,transparent);color:var(--np-accent-bg-fg,rgba(255,255,255,.92));font-size:var(--np-fs-body);font-weight:500;letter-spacing:.02em;padding:7px 16px;border-radius:var(--np-r-pill);pointer-events:none;white-space:nowrap;opacity:1;transition:opacity var(--np-t-slower) var(--np-ease);z-index:20;box-shadow:var(--np-elev-2)}
#aw-np-speed-ind{font-size:var(--np-fs-micro);font-weight:400;color:var(--np-accent-bg-fg,rgba(255,255,255,.55));letter-spacing:.03em;white-space:nowrap;padding:0 0 0 6px;font-variant-numeric:tabular-nums}
${SPEED_POPUP}
#aw-np-clock{position:absolute!important;inset:clamp(8px,1.2cqh,16px) clamp(10px,1.4cqh,18px) auto auto!important;display:flex;align-items:center;gap:6px;color:rgba(255,255,255,.6);font-size:calc(clamp(18px,3.1cqh,34px) * var(--ui-scale));font-weight:500;padding:2px 4px;pointer-events:none;z-index:20;letter-spacing:.03em;text-shadow:0 1px 6px rgba(0,0,0,.95);font-variant-numeric:tabular-nums;transition:opacity var(--np-t-slow) var(--np-ease);flex:none;transform:none}
${CLOCK_CONN}
#aw-np-buf-pct{position:absolute;inset:0;display:none;align-items:center;justify-content:center;font-size:var(--np-fs-title);font-weight:600;color:var(--np-accent-bg-fg,rgba(255,255,255,.85));pointer-events:none;font-variant-numeric:tabular-nums;text-shadow:0 1px 4px rgba(0,0,0,.9)}
#aw-np-buf-pct.on{display:flex}
${ERROR_SPINNER}
${KEYFRAMES_CSS}
${FLASH_TAIL}
${SKIP_POPUP}
${AWAKE_GUARD}
${PANEL_SUBROW}
`;
s.textContent = [D1, D2, D3, D4, D5, D6, D7, D8, D9, D10].join("\n");
document.head.appendChild(s);
}
// src/styles/styles-mobile.js
var MAIL_CSS_MOBILE = { w: "720px", h: "560px", listW: "36%", listMin: "120px", listMax: "240px", delOp: ".45" };
var PANEL_CSS_MOBILE = { nativeW: "650px" };
function injectStyleMobile() {
if (document.getElementById("aw-np-style")) return;
const s = document.createElement("style");
s.id = "aw-np-style";
s.textContent = `
${HEADER}
${rootVars({ seekH: "30px", topH: "58px", touch: "pan-y" })}
${FS}
#aw-np-video{flex:1;width:100%;min-height:0;display:block;background:#000}
#aw-np-bar,#aw-np-top,.aw-np-toast,#aw-np-error-toast,#aw-np-conn-toast,#aw-np-settings-panel,#aw-np-color-panel,#aw-np-menu-panel,.np-row-tip,.np-btn,.np-swatch,.np-switch-track,.np-switch-thumb,#aw-np-seek-fill,#aw-np-seek-buf,#aw-np-seek-thumb,#aw-np-title,#aw-np-epinfo,#aw-np-brand,#aw-np-clock,#aw-np-clock svg circle,#aw-np-clock .hand-h,#aw-np-clock .hand-m,#aw-np-clock .pin,#aw-np-spinner,#aw-np-time,#aw-np-speed-ind,#aw-np-speed-popup,#aw-np-buf-pct,#aw-np-dot,#aw-np-play-center{transition-property:color,background,background-color,fill,stroke,border-color,box-shadow;transition-duration:.4s;transition-timing-function:var(--np-ease)}
.np-grad{position:absolute;left:0;right:0;height:140px;pointer-events:none;opacity:0;transition:opacity var(--np-t-slow) var(--np-ease)}
${UI_LAYER}
#aw-np-top{position:absolute;top:0;left:0;right:0;height:var(--np-top-h);display:flex;align-items:center;justify-content:space-between;padding:0 max(calc(10px * var(--ui-scale)),env(safe-area-inset-right)) 0 max(calc(10px * var(--ui-scale)),env(safe-area-inset-left))}
#aw-np-top-left{display:flex;flex-direction:column;gap:1px;overflow:hidden;flex:1}
#aw-np-title{font-size:calc(var(--np-fs-title) * 1.2);font-weight:600;color:var(--np-accent-bg-fg,#fff);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:40cqw;text-shadow:0 1px 3px rgba(0,0,0,.8)}
#aw-np-epinfo{font-size:calc(var(--np-fs-title) * 0.8);font-weight:400;color:var(--np-accent-bg-fg,rgba(255,255,255,.7));opacity:.8;text-shadow:0 1px 3px rgba(0,0,0,.8)}
#aw-np-top-right{display:flex;flex-direction:column;align-items:flex-end;gap:clamp(5px,1.4cqw,9px);flex-shrink:0}
#aw-np-top-actions{display:flex;align-items:center;gap:clamp(10px,2.4cqw,16px)}
${DOT}
#aw-np-kbd-btn{display:none!important}
#aw-np-browser-btn,#aw-np-report-btn,#aw-np-mail-btn{width:calc(var(--np-fs-title) * 0.95);height:calc(var(--np-fs-title) * 0.95);border-radius:var(--np-r-sm);background:var(--np-surf-2);border:var(--np-line);color:var(--np-accent-bg-fg,#CCDDF1);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;position:relative;user-select:none}
${ACTION_SVG}
.aw-np-mail-badge{display:none;position:absolute;top:-2px;right:-2px;width:7px;height:7px;border-radius:50%;background:var(--np-accent,#ec4f4f)}
${panelCss("#aw-np-color-panel", PANEL_CSS_MOBILE)}
${SWATCH_GRID}
#aw-np-color-panel .aw-np-color-custom-prev{width:clamp(16px,2.6cqw,22px);height:clamp(16px,2.6cqw,22px)}
${SWATCH_ACTIVE}
${SWATCH_HOVER}
${CONTROLS_SEEK}
#aw-np-controls{padding-left:max(calc(10px * var(--ui-scale)),env(safe-area-inset-left));padding-right:max(calc(10px * var(--ui-scale)),env(safe-area-inset-right))}
#aw-np-seek-track{position:relative;width:100%;height:4px;border-radius:var(--np-r-xs);background:rgba(255,255,255,.2)}
${SEEK_BUF}
#aw-np-seek-fill{position:absolute;inset:0;border-radius:inherit;background:var(--np-accent,#fff);width:0}
#aw-np-seek-thumb{position:absolute;top:50%;left:0;width:16px;height:16px;background:var(--np-accent,#fff);border-radius:50%;transform:translate(-50%,-50%) scale(0);transition:transform var(--np-t-fast) var(--np-ease);box-shadow:var(--np-elev-1)}
#aw-np-seek-wrap:active #aw-np-seek-thumb,#aw-np-seek-wrap.seeking #aw-np-seek-thumb{transform:translate(-50%,-50%) scale(1.2)}
${BAR}
${SPACER}
${ICON}
.np-btn{position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center;width:var(--np-btn-size);height:var(--np-btn-size);min-width:unset;background:none;border:none;cursor:pointer;color:var(--np-accent-bg-fg,rgba(255,255,255,.85));padding:0;flex-shrink:0;border-radius:50%}
${ACCENT_BTN}
#aw-np-brand{font-size:calc(var(--np-fs-title) * 0.8);font-weight:500;letter-spacing:.04em;color:var(--np-accent-bg-fg,rgba(255,255,255,.5));opacity:.6;white-space:nowrap;line-height:1;text-transform:uppercase}
${ACCENT_BRAND_RIPPLE}
#aw-np-time{font-size:calc(15px * var(--np-chrome-scale,1) * var(--ui-scale) * var(--np-ctrl-mul));font-weight:400;color:var(--np-accent-bg-fg,rgba(255,255,255,.85));letter-spacing:.03em;white-space:nowrap;padding:0 4px;font-variant-numeric:tabular-nums}
#aw-np-menu-panel{position:absolute;bottom:calc(var(--np-bar-h) + var(--np-seek-h) + 8px);right:max(8px,env(safe-area-inset-right));background:var(--np-surf-2);border:var(--np-line);border-radius:var(--np-r-lg);padding:8px;display:flex;flex-direction:column;gap:2px;z-index:25;opacity:0;transform:scale(.9) translateY(8px);transform-origin:bottom right;pointer-events:none;transition:opacity var(--np-t) var(--np-ease),transform var(--np-t) var(--np-ease);box-shadow:var(--np-elev-3);max-width:calc(100vw - 16px);max-height:calc(100% - 120px);overflow-y:auto}
#aw-np-menu-panel.open{opacity:1;transform:scale(1) translateY(0);pointer-events:all}
.np-menu-btn{display:flex;align-items:center;gap:12px;padding:10px 14px;border-radius:var(--np-r-md);border:none;background:none;color:var(--np-accent-bg-fg,rgba(255,255,255,.9));font-size:var(--np-fs-body);cursor:pointer;width:100%;text-align:left}
.np-menu-btn:active{background:var(--np-accent-state-2,rgba(255,255,255,.18))}
.np-menu-btn svg{fill:var(--np-accent-bg-fg,rgba(255,255,255,.9));width:22px;height:22px;flex-shrink:0}
.np-menu-btn span{display:block}
@media (orientation:portrait){#aw-np-menu-panel{flex-direction:row;padding:4px}.np-menu-btn{padding:8px;width:auto;gap:0}.np-menu-btn span{display:none}}
${panelCss("#aw-np-settings-panel", PANEL_CSS_MOBILE)}
${panelCss("#aw-np-report-panel", PANEL_CSS_MOBILE)}
${reportCss()}
${mailCss(MAIL_CSS_MOBILE)}
${panelCss("#aw-np-bp-panel", { nativeW: "650px" })}
${bpCss("#aw-np-bp-panel")}
${SWITCH}
.np-row-tip{position:absolute;top:50%;right:calc(100% + 14px);transform:translateY(-50%);background:var(--np-tip-bg,#000);color:var(--np-accent-bg-fg,rgba(255,255,255,.9));font-size:var(--np-fs-small);line-height:1.5;padding:6px 10px;border-radius:var(--np-r-md);border:var(--np-line-soft);white-space:nowrap;pointer-events:none;opacity:0;transition:opacity var(--np-t-fast);z-index:20;box-shadow:var(--np-elev-2)}
${ROWTIP_AFTER_DATATIP}
.aw-np-toast{position:absolute;bottom:calc(var(--np-bar-h) + var(--np-seek-h) + 16px);left:50%;transform:translateX(-50%);background:color-mix(in srgb,var(--np-accent-bg,#282828) 80%,transparent);color:var(--np-accent-bg-fg,rgba(255,255,255,.92));font-size:var(--np-fs-body);font-weight:500;padding:7px 16px;border-radius:var(--np-r-pill);pointer-events:none;white-space:nowrap;opacity:1;transition:opacity var(--np-t-slower) var(--np-ease);z-index:20;box-shadow:var(--np-elev-2)}
#aw-np-speed-ind{font-size:var(--np-fs-micro);font-weight:400;color:var(--np-accent-bg-fg,rgba(255,255,255,.55));letter-spacing:.03em;white-space:nowrap;padding:0 0 0 4px;font-variant-numeric:tabular-nums;display:none}
@media (orientation:landscape){#aw-np-speed-ind{display:inline}}
${SPEED_POPUP}
#aw-np-speed-popup{font-size:calc(clamp(13px,26px * var(--np-chrome-scale,1),23px) * var(--ui-scale))}
#aw-np-clock{position:absolute!important;inset:max(10px,env(safe-area-inset-top)) max(14px,env(safe-area-inset-right)) auto auto!important;display:flex;align-items:center;gap:5px;color:rgba(255,255,255,.6);font-size:calc(clamp(13px,26px * var(--np-chrome-scale,1),23px) * var(--ui-scale));font-weight:500;padding:2px 4px;pointer-events:none;z-index:20;letter-spacing:.03em;text-shadow:0 1px 6px rgba(0,0,0,.95);font-variant-numeric:tabular-nums;transition:opacity var(--np-t-slow) var(--np-ease);flex:none;transform:none;margin:0}
${CLOCK_CONN}
#aw-np-buf-pct{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:var(--np-fs-title);font-weight:600;color:var(--np-accent-bg-fg,rgba(255,255,255,.85));pointer-events:none;display:none;font-variant-numeric:tabular-nums;text-shadow:0 1px 4px rgba(0,0,0,.9)}
#aw-np-buf-pct.on{display:block}
${ERROR_SPINNER}
${KEYFRAMES_CSS}
${FLASH_TAIL}
${SKIP_POPUP}
${AWAKE_GUARD}
${PANEL_SUBROW}
#aw-np-play-center{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;pointer-events:all;z-index:5;color:var(--np-accent-bg-fg,rgba(255,255,255,.9))}
#aw-np-play-center::before{content:'';position:absolute;inset:0;border-radius:50%;background:var(--np-accent-bg,#1a1a2e);opacity:.85;pointer-events:none}
#aw-np-play-center svg{fill:var(--np-accent-bg-fg,rgba(255,255,255,.9));width:30px;height:30px;position:relative;z-index:1}
.np-seek-flash{position:absolute;top:0;bottom:0;width:25%;display:flex;align-items:center;justify-content:center;pointer-events:none;opacity:0;transition:opacity var(--np-t)}
.np-seek-flash.on{opacity:1}
#aw-np-seek-flash-left{left:0}
#aw-np-seek-flash-right{right:0}
.np-seek-flash svg{color:rgba(255,255,255,.7);width:40px;height:40px}
.np-vol-bar{position:absolute;top:0;bottom:0;width:25%;display:flex;align-items:center;justify-content:center;pointer-events:none;opacity:0;transition:opacity var(--np-t-slow)}
.np-vol-bar.on{opacity:1}
#aw-np-vol-bar-left{left:0}
#aw-np-vol-bar-right{right:0}
#aw-np.fs #aw-np-top{top:var(--np-lb-y,0px)}
#aw-np.fs #aw-np-controls{bottom:var(--np-lb-y,0px)}
#aw-np.fs #aw-np-gradient{bottom:var(--np-lb-y,0px)}
#aw-np.fs #aw-np-gradient-top{top:var(--np-lb-y,0px)}
#aw-np.fs #aw-np-menu-panel{bottom:calc(var(--np-bar-h) + var(--np-seek-h) + 8px + var(--np-lb-y,0px))}
#aw-np.fs #aw-np-skip-popup{bottom:calc(var(--np-bar-h) + var(--np-seek-h) + 22px + var(--np-lb-y,0px))}
#aw-np.fs #aw-np-error-toast,#aw-np.fs #aw-np-load-error-toast{bottom:calc(var(--np-bar-h) + var(--np-seek-h) + 16px + var(--np-lb-y,0px))}
#aw-np.fs .aw-np-toast{bottom:calc(var(--np-bar-h) + var(--np-seek-h) + 16px + var(--np-lb-y,0px))}
#aw-np.fs #aw-np-clock{top:calc(max(10px,env(safe-area-inset-top)) + var(--np-lb-y,0px))!important}
#aw-np.fs #aw-np-speed-popup{top:calc(max(10px,env(safe-area-inset-top)) + var(--np-lb-y,0px))}
#aw-np.fs #aw-np-conn-toast{top:calc(clamp(12px,1.7cqh,18px) + var(--np-lb-y,0px))}
#aw-np.fs{--np-ctrl-mul:1;--np-top-mul:1.2}
@media (orientation:landscape){.np-portrait-only{display:none!important}}
@media (orientation:portrait){.np-landscape-only{display:none!important}}
`;
document.head.appendChild(s);
}
// src/lib/dom.js
function mk(tag, id) {
const e = document.createElement(tag);
if (id) e.id = id;
return e;
}
function mkBtn(id, html, tip) {
const b = mk("button");
b.className = "np-btn";
b.id = id;
b.innerHTML = html;
b.tabIndex = -1;
const rl = document.createElement("span");
rl.className = "np-ripple-layer";
b.appendChild(rl);
if (tip) {
const t = document.createElement("span");
t.className = "np-tip";
t.textContent = tip;
b.appendChild(t);
}
return b;
}
function mkIcon(btn, html) {
const s = document.createElement("span");
s.className = "np-icon";
s.innerHTML = html;
btn.prepend(s);
return s;
}
function setIcon(el2, html) {
if (el2) el2.innerHTML = html;
}
function setTip(btn, text) {
const t = btn.querySelector(".np-tip");
if (t) t.textContent = text;
}
function mkRowTip(text) {
const t = document.createElement("span");
t.className = "np-row-tip";
t.textContent = text;
return t;
}
function flashToast(wrap, text, pos = "bottom") {
wrap.querySelector(`.aw-np-toast[data-pos="${pos}"]`)?.remove();
const toast = mk("div");
toast.className = "aw-np-toast";
toast.dataset.pos = pos;
toast.textContent = text;
wrap.appendChild(toast);
setTimeout(() => {
toast.style.opacity = "0";
setTimeout(() => toast.remove(), TOAST_FADE_MS);
}, TOAST_SHOW_MS);
}
function mkSwitch(checked) {
const label = document.createElement("label");
label.className = "np-switch";
const input = document.createElement("input");
input.type = "checkbox";
input.checked = checked;
const track = document.createElement("span");
track.className = "np-switch-track";
const thumb = document.createElement("span");
thumb.className = "np-switch-thumb";
label.append(input, track, thumb);
return { label, input };
}
function el(tag, className, opts = {}) {
const e = document.createElement(tag);
if (className) e.className = className;
if (opts.id) e.id = opts.id;
if (opts.text != null) e.textContent = opts.text;
if (opts.html != null) e.innerHTML = opts.html;
if (opts.attrs) for (const k in opts.attrs) e.setAttribute(k, opts.attrs[k]);
if (opts.style) Object.assign(e.style, opts.style);
if (opts.on) for (const evt in opts.on) e.addEventListener(evt, opts.on[evt]);
if (opts.kids) e.append(...opts.kids);
return e;
}
// src/lib/icons.js
var svg = (p) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">${p}</svg>`;
var IC = {
play: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"/>'),
pause: svg('<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><rect width="5" height="18" x="14" y="3" rx="1"/><rect width="5" height="18" x="5" y="3" rx="1"/></g>'),
mute: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298zM22 9l-6 6m0-6l6 6"/>'),
vol: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298zM16 9a5 5 0 0 1 0 6m3.364 3.364a9 9 0 0 0 0-12.728"/>'),
volDown: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298zM16 9a5 5 0 0 1 0 6"/>'),
fsOn: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3m8 0h3a2 2 0 0 0 2-2v-3"/>'),
fsOff: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3m8 0v-3a2 2 0 0 1 2-2h3"/>'),
pip: svg('<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M21 9V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v10c0 1.1.9 2 2 2h4"/><rect width="10" height="7" x="12" y="13" rx="2"/></g>'),
settings: svg('<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/><circle cx="12" cy="12" r="3"/></g>'),
restart: svg('<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M3 12a9 9 0 1 0 9-9a9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></g>'),
skip: svg('<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 12 18z"/><path d="M2 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 2 18z"/></g>'),
undo: svg('<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M9 14L4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11"/></g>'),
prev: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.971 4.285A2 2 0 0 1 21 6v12a2 2 0 0 1-3.029 1.715l-9.997-5.998a2 2 0 0 1-.003-3.432zM3 20V4"/>'),
next: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 4v16M6.029 4.285A2 2 0 0 0 3 6v12a2 2 0 0 0 3.029 1.715l9.997-5.998a2 2 0 0 0 .003-3.432z"/>'),
seekFwd: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m6 17l5-5l-5-5m7 10l5-5l-5-5"/>'),
seekBwd: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m11 17l-5-5l5-5m7 10l-5-5l5-5"/>'),
menu: svg('<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></g>'),
unlock: svg('<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/></g>'),
flag: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528"/>'),
bookmark: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z"/>'),
warning: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m21.73 18l-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3M12 9v4m0 4h.01"/>'),
browser: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>'),
mail: svg('<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m22 7l-8.991 5.727a2 2 0 0 1-2.009 0L2 7"/><rect width="20" height="16" x="2" y="4" rx="2"/></g>'),
trash: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>'),
close: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 6L6 18M6 6l12 12"/>'),
restore: svg('<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><rect width="20" height="5" x="2" y="3" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h2M20 8v11a2 2 0 0 1-2 2h-2m-7-6l3-3l3 3m-3-3v9"/></g>'),
edit: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497zM15 5l4 4"/>'),
check: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 6L9 17l-5-5"/>'),
world: svg('<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20a14.5 14.5 0 0 0 0-20M2 12h20"/></g>'),
moon: svg('<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/>'),
palette: svg('<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 22a1 1 0 0 1 0-20a10 9 0 0 1 10 9a5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z"/><circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/></g>'),
github: svg('<path fill="currentColor" d="M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>'),
greasyfork: svg('<path fill="currentColor" d="M5.89 2.227a.28.28 0 0 1 .266.076l5.063 5.062c.54.54.509 1.652-.031 2.192l8.771 8.77c1.356 1.355-.36 3.097-1.73 1.728l-8.772-8.77c-.54.54-1.651.571-2.191.031l-5.063-5.06c-.304-.304.304-.911.608-.608l3.714 3.713L7.59 8.297L3.875 4.582c-.304-.304.304-.911.607-.607l3.715 3.714l1.067-1.066L5.549 2.91c-.228-.228.057-.626.342-.683ZM12 0C5.374 0 0 5.375 0 12s5.374 12 12 12c6.625 0 12-5.375 12-12S18.625 0 12 0"/>'),
telegram: svg('<path fill="currentColor" d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12a12 12 0 0 0 12-12A12 12 0 0 0 12 0zm4.962 7.224c.1-.002.321.023.465.14a.5.5 0 0 1 .171.325c.016.093.036.306.02.472c-.18 1.898-.962 6.502-1.36 8.627c-.168.9-.499 1.201-.82 1.23c-.696.065-1.225-.46-1.9-.902c-1.056-.693-1.653-1.124-2.678-1.8c-1.185-.78-.417-1.21.258-1.91c.177-.184 3.247-2.977 3.307-3.23c.007-.032.014-.15-.056-.212s-.174-.041-.249-.024q-.159.037-5.061 3.345q-.72.495-1.302.48c-.428-.008-1.252-.241-1.865-.44c-.752-.245-1.349-.374-1.297-.789q.04-.324.893-.663q5.247-2.286 6.998-3.014c3.332-1.386 4.025-1.627 4.476-1.635"/>'),
whatsapp: svg('<path fill="currentColor" d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967c-.273-.099-.471-.148-.67.15c-.197.297-.767.966-.94 1.164c-.173.199-.347.223-.644.075c-.297-.15-1.255-.463-2.39-1.475c-.883-.788-1.48-1.761-1.653-2.059c-.173-.297-.018-.458.13-.606c.134-.133.298-.347.446-.52s.198-.298.298-.497c.099-.198.05-.371-.025-.52s-.669-1.612-.916-2.207c-.242-.579-.487-.5-.669-.51a13 13 0 0 0-.57-.01c-.198 0-.52.074-.792.372c-.272.297-1.04 1.016-1.04 2.479c0 1.462 1.065 2.875 1.213 3.074s2.096 3.2 5.077 4.487c.709.306 1.262.489 1.694.625c.712.227 1.36.195 1.871.118c.571-.085 1.758-.719 2.006-1.413s.248-1.289.173-1.413c-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214l-3.741.982l.998-3.648l-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884c2.64 0 5.122 1.03 6.988 2.898a9.82 9.82 0 0 1 2.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.82 11.82 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.9 11.9 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.82 11.82 0 0 0-3.48-8.413"/>')
};
// src/core/constants.js
var HIDE_DELAY_MS = 3e3;
var SKIP_SECONDS = 85;
var SAVE_INTERVAL_MS = 5e3;
var RESUME_MIN_POS = 5;
var RESUME_END_GAP = 10;
var RESUME_MAX_AGE = 30 * 24 * 60 * 60 * 1e3;
var PRELOAD_THRESHOLD = 0.75;
var SKIP_POPUP_MS = 1e4;
var SKIP_UNDO_MS = 1e4;
var SKIP_END_GAP = 10;
var AUTONEXT_TAIL = 1;
var AWAKE_IDLE_MS = 30 * 60 * 1e3;
var KEY_VOL = "aw-np-vol";
var KEY_MUTE = "aw-np-muted";
var KEY_GLOBAL = "aw-np-global";
var KEY_RESUME_ENABLE = "aw-np-resume-enabled";
var KEY_RESUME_PFX = "aw-np-resume:";
var KEY_SEEK_SECS = "aw-np-seek-secs";
var KEY_AUTOEP_ENABLE = "aw-np-autoep-enabled";
var KEY_AUTOEP_PFX = "aw-np-autoep:";
var KEY_AUTOPLAY_ENABLE = "aw-np-autoplay-enabled";
var KEY_COLOR = "aw-np-color";
var KEY_COLOR_GLOBAL = "aw-np-color-global";
var KEY_ICON_COLOR = "aw-np-icon-color";
var KEY_TOP_COLOR = "aw-np-top-color";
var KEY_FLASH_ENABLE = "aw-np-flash-enabled";
var KEY_CLOCK = "aw-np-clock-enabled";
var KEY_SPEED_POPUP = "aw-np-speed-popup-enabled";
var KEY_CONN_MONITOR = "aw-np-conn-monitor-enabled";
var KEY_SPEED = "aw-np-speed";
var KEY_HOTKEYS = "aw-np-hotkeys";
var KEY_REPORT_LAST_TS = "aw-np-report-last-ts";
var KEY_MAIL_STATE = "aw-np-mail-state";
var KEY_UI_SCALE = "aw-np-ui-scale";
var KEY_CUSTOM_COLORS = "aw-np-custom-colors";
var KEY_SKIP_ENABLE = "aw-np-skip-enabled";
var KEY_SKIP_AUTO = "aw-np-skip-auto";
var KEY_SKIP_NEXT = "aw-np-skip-autonext";
var KEY_SKIP_OP = "aw-np-skip-op";
var KEY_SKIP_ED = "aw-np-skip-ed";
var KEY_SKIP_AWAKE = "aw-np-skip-awake";
var WORKER_BASE = "https://awbp-skip-collector.cibernetic-gg.workers.dev";
var REPORT_HEARTBEAT_MS = 6e4;
var SCRIPT_VERSION = "3.1.0";
var SEEK_DEFAULT = 5;
var SEEK_MIN = 5;
var SEEK_MAX = 30;
var SEEK_STEP = 5;
var SPEED_DEFAULT = 1;
var SPEED_MIN = 0.25;
var SPEED_MAX = 3;
var SPEED_STEP = 0.25;
var UISCALE_DEFAULT = 1;
var UISCALE_MIN = 0.5;
var UISCALE_MAX = 1.5;
var UISCALE_STEP = 0.1;
var PALETTE = [
{ name: "Bianco", hex: "#ffffff" },
{ name: "Rosso", hex: "#f44336" },
{ name: "Arancio", hex: "#ff9800" },
{ name: "Ambra", hex: "#ffc107" },
{ name: "Giallo", hex: "#ffeb3b" },
{ name: "Lime", hex: "#cddc39" },
{ name: "Verde", hex: "#4caf50" },
{ name: "Teal", hex: "#009688" },
{ name: "Ciano", hex: "#00bcd4" },
{ name: "Azzurro", hex: "#42a5f5" },
{ name: "Blu", hex: "#1565c0" },
{ name: "Indaco", hex: "#3f51b5" },
{ name: "Viola", hex: "#9c27b0" },
{ name: "Magenta", hex: "#ff4081" },
{ name: "Rosa", hex: "#e91e8c" },
{ name: "Grigio", hex: "#78909c" }
];
// src/core/storage.js
var lsGet = (k) => {
try {
return localStorage.getItem(k);
} catch {
return null;
}
};
var lsSet = (k, v) => {
try {
localStorage.setItem(k, v);
} catch {
}
};
var lsDel = (k) => {
try {
localStorage.removeItem(k);
} catch {
}
};
// src/state/volume.js
function loadVol() {
const v = parseFloat(lsGet(KEY_VOL) ?? "1");
return { vol: isNaN(v) ? 1 : Math.max(0, Math.min(1, v)), muted: lsGet(KEY_MUTE) === "true" };
}
var saveVol = (vol, muted) => {
lsSet(KEY_VOL, String(vol));
lsSet(KEY_MUTE, String(muted));
};
// src/core/settings.js
var _cachedAnimeId = "";
var animeId = () => {
if (!_cachedAnimeId) {
const el2 = document.querySelector("#player");
if (el2) _cachedAnimeId = el2.dataset.animeId || "";
}
return _cachedAnimeId;
};
var isGlobalOn = () => lsGet(KEY_GLOBAL) !== "0";
var pKey = (k) => isGlobalOn() ? k : k + ":" + (animeId() || "unknown");
var isResumeOn = () => lsGet(pKey(KEY_RESUME_ENABLE)) !== "0";
var isAutoEpOn = () => lsGet(pKey(KEY_AUTOEP_ENABLE)) === "1";
var isAutoPlayOn = () => lsGet(pKey(KEY_AUTOPLAY_ENABLE)) === "1";
var isColorGlobalOn = () => lsGet(KEY_COLOR_GLOBAL) !== "0";
var isIconColorOn = () => lsGet(KEY_ICON_COLOR) === "1";
var isTopColorOn = () => lsGet(KEY_TOP_COLOR) === "1";
var isFlashOn = () => lsGet(KEY_FLASH_ENABLE) !== "0";
var isConnMonitorOn = () => lsGet(KEY_CONN_MONITOR) !== "0";
var isClockOn = () => lsGet(KEY_CLOCK) !== "0";
var isSpeedPopupOn = () => lsGet(KEY_SPEED_POPUP) !== "0";
var isSkipOn = () => lsGet(KEY_SKIP_ENABLE) === "1";
var isAutoSkipOn = () => lsGet(KEY_SKIP_AUTO) === "1";
var isAutoNextOn = () => lsGet(KEY_SKIP_NEXT) === "1";
var isOpeningOn = () => lsGet(KEY_SKIP_OP) !== "0";
var isEndingOn = () => lsGet(KEY_SKIP_ED) !== "0";
var isAwakeGuardOn = () => lsGet(KEY_SKIP_AWAKE) !== "0";
// src/state/speed.js
var loadSpeed = () => {
const v = parseFloat(lsGet(pKey(KEY_SPEED)) ?? String(SPEED_DEFAULT));
return isNaN(v) ? SPEED_DEFAULT : Math.max(SPEED_MIN, Math.min(SPEED_MAX, v));
};
var fmtSpeed = (v) => v.toFixed(2) + "x";
// src/lib/fullscreen.js
var fsElement = () => document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || null;
var fsExit = () => (document.exitFullscreen || document.mozCancelFullScreen || document.webkitExitFullscreen)?.call(document).catch(() => {
});
var fsRequest = (el2) => (el2.requestFullscreen || el2.mozRequestFullScreen || el2.webkitRequestFullscreen)?.call(el2).catch(() => {
});
var fsChange = (cb) => {
["fullscreenchange", "mozfullscreenchange", "webkitfullscreenchange"].forEach((e) => document.addEventListener(e, cb));
return () => ["fullscreenchange", "mozfullscreenchange", "webkitfullscreenchange"].forEach((e) => document.removeEventListener(e, cb));
};
// src/player/init-video.js
function initVideo(videoUrl) {
const { vol, muted } = loadVol();
const video = mk("video", "aw-np-video");
video.autoplay = false;
video.preload = "auto";
video.playsInline = true;
video.setAttribute("playsinline", "");
video.setAttribute("webkit-playsinline", "");
video.src = videoUrl;
video.volume = vol;
video.muted = muted;
video.playbackRate = loadSpeed();
let _userInteracted = false;
const _play = () => {
_userInteracted = true;
return video.play().catch(() => {
});
};
const _enforceInitialPause = () => {
try {
if (_userInteracted) return;
if (video.paused) return;
if (isAutoPlayOn() && fsElement()) return;
video.pause();
} catch (e) {
}
};
video.addEventListener("play", _enforceInitialPause, { capture: true });
return { video, vol, muted, _play };
}
// src/widgets/shell.js
function buildShellElements() {
const wrap = mk("div", "aw-np");
const grad = el("div", "np-grad", { id: "aw-np-gradient" });
const gradTop = el("div", "np-grad", { id: "aw-np-gradient-top" });
const spinner = mk("div", "aw-np-spinner");
const center = el("div", "np-flash-circle", { id: "aw-np-center", html: IC.play });
const ctrls = el("div", "np-ui-layer", { id: "aw-np-controls" });
const timeEl = el("div", null, { id: "aw-np-time", text: "00:00 / 00:00" });
return { wrap, grad, gradTop, spinner, center, ctrls, timeEl };
}
// src/widgets/indicators.js
function buildSpeedIndicator(initSpeed) {
const speedIndEl = mk("span", "aw-np-speed-ind");
const speedPopup = mk("div", "aw-np-speed-popup");
const updateSpeedInd = (v) => {
speedIndEl.textContent = "(" + fmtSpeed(v) + ")";
};
const showSpeedPopup = (v) => {
speedPopup.textContent = fmtSpeed(v);
speedPopup.classList.toggle("on", Math.abs(v - 1) > 1e-3);
};
updateSpeedInd(initSpeed);
showSpeedPopup(initSpeed);
return { speedIndEl, speedPopup, updateSpeedInd, showSpeedPopup, clearTimer: () => {
} };
}
function buildBufferingIndicator(video, spinner) {
const bufPctEl = mk("div", "aw-np-buf-pct");
const TARGET_AHEAD = 1;
let bufStartCt = null;
const updateBufPct = () => {
if (bufStartCt === null || !video.buffered.length) {
bufPctEl.textContent = "0%";
return;
}
for (let i = 0; i < video.buffered.length; i++) {
if (video.buffered.start(i) <= bufStartCt + 0.1 && bufStartCt <= video.buffered.end(i) + 0.1) {
const ahead = Math.max(0, video.buffered.end(i) - bufStartCt);
const pct = Math.min(100, Math.round(ahead / TARGET_AHEAD * 100));
bufPctEl.textContent = pct + "%";
return;
}
}
bufPctEl.textContent = "0%";
};
let bufTimer = null;
const startBufTimer = () => {
if (bufTimer) return;
updateBufPct();
bufTimer = setInterval(updateBufPct, 250);
};
const stopBufTimer = () => {
clearInterval(bufTimer);
bufTimer = null;
bufStartCt = null;
};
video.addEventListener("progress", updateBufPct);
video.addEventListener("waiting", () => {
bufStartCt = video.currentTime;
bufPctEl.classList.add("on");
spinner.classList.add("on");
startBufTimer();
});
video.addEventListener("playing", () => {
bufPctEl.classList.remove("on");
spinner.classList.remove("on");
stopBufTimer();
});
video.addEventListener("canplay", () => {
bufPctEl.classList.remove("on");
spinner.classList.remove("on");
stopBufTimer();
});
return { bufPctEl, clearTimer: stopBufTimer };
}
// src/lib/time-fmt.js
function fmt(s) {
const t = Math.max(0, Math.floor(isFinite(s) ? s : 0));
const h = Math.floor(t / 3600);
const m = Math.floor(t % 3600 / 60);
const sec = t % 60;
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}` : `${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
}
// src/lib/fetch.js
function fetchWithRetry(url, opts, retries = 3, delay = 1e3, timeout = 15e3) {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), timeout);
const signal = opts?.signal ? void 0 : ctrl.signal;
return fetch(url, signal ? { ...opts, signal } : opts).then((r) => {
clearTimeout(timer);
if (!r.ok) {
const e = new Error(r.status);
e.status = r.status;
throw e;
}
return r;
}).catch((err) => {
clearTimeout(timer);
const status = err?.status;
const retriable = status == null || status >= 500 || status === 429;
if (retries <= 0 || !retriable) throw err;
return new Promise((res) => setTimeout(res, delay)).then(() => fetchWithRetry(url, opts, retries - 1, delay * 2, timeout));
});
}
// src/data/episode-nav.js
function getAdjacentEpisode(dir) {
const all = Array.from(document.querySelectorAll(".episode a"));
const idx = all.findIndex((a) => a.classList.contains("active"));
if (idx === -1) return null;
return dir === "next" ? all[idx + 1] ?? null : all[idx - 1] ?? null;
}
function getUrlForToken(token) {
return fetchWithRetry(`/api/episode/serverPlayerAnimeWorld?alt=1&id=${token}`, { credentials: "same-origin" }).then((r) => r.text()).then((html) => {
const m = html.match(/["']?file["']?\s*:\s*["']([^"']+)["']/i);
return m ? m[1].replace(/\\\//g, "/") : null;
}).catch(() => null);
}
// src/state/preload.js
var _cleanup = null;
var _preloadedToken = null;
var _preloadedUrl = null;
var _preloadedVideo = null;
var getCleanup = () => _cleanup;
var setCleanup = (fn) => {
_cleanup = fn;
};
var getPreloadedToken = () => _preloadedToken;
var setPreloadedToken = (t) => {
_preloadedToken = t;
};
var getPreloadedUrl = () => _preloadedUrl;
var setPreloadedUrl = (u) => {
_preloadedUrl = u;
};
var getPreloadedVideo = () => _preloadedVideo;
var setPreloadedVideo = (v) => {
_preloadedVideo = v;
};
// src/player/seek-bar.js
function buildSeekBar(video, withTip = false, onTimeUpdate = null) {
const seekWrap = mk("div", "aw-np-seek-wrap"), seekTrack = mk("div", "aw-np-seek-track"), seekBuf = mk("div", "aw-np-seek-buf"), seekFill = mk("div", "aw-np-seek-fill"), seekThumb = mk("div", "aw-np-seek-thumb");
let seekTip = null;
if (withTip) {
seekTip = mk("div", "aw-np-seek-tip");
seekTrack.appendChild(seekTip);
}
seekTrack.append(seekBuf, seekFill, seekThumb);
seekWrap.append(seekTrack);
let seeking = false;
const applySeek = (e) => {
const r = seekTrack.getBoundingClientRect();
const p = Math.max(0, Math.min(1, (e.clientX - r.left) / r.width));
if (video.duration) video.currentTime = p * video.duration;
const pct = p * 100 + "%";
seekFill.style.width = pct;
seekThumb.style.left = pct;
};
seekWrap.addEventListener("pointerdown", (e) => {
seeking = true;
seekWrap.classList.add("seeking");
seekWrap.setPointerCapture(e.pointerId);
applySeek(e);
e.preventDefault();
});
seekWrap.addEventListener("pointermove", (e) => {
if (seeking) applySeek(e);
if (seekTip) {
const r = seekTrack.getBoundingClientRect();
const p = Math.max(0, Math.min(1, (e.clientX - r.left) / r.width));
seekTip.textContent = fmt(p * (video.duration || 0));
seekTip.style.left = p * 100 + "%";
seekTip.style.visibility = "visible";
}
});
if (seekTip) seekWrap.addEventListener("pointerleave", () => {
seekTip.style.visibility = "hidden";
});
seekWrap.addEventListener("pointerup", () => {
seeking = false;
seekWrap.classList.remove("seeking");
});
seekWrap.addEventListener("pointercancel", () => {
seeking = false;
seekWrap.classList.remove("seeking");
});
let malSyncTriggered = false;
video.addEventListener("loadedmetadata", () => {
malSyncTriggered = false;
});
video.addEventListener("timeupdate", () => {
if (seeking || !video.duration) return;
const p = video.currentTime / video.duration * 100;
seekFill.style.width = p + "%";
seekThumb.style.left = p + "%";
if (onTimeUpdate) onTimeUpdate();
if (!malSyncTriggered && p >= 85) {
malSyncTriggered = true;
history.pushState({}, "", location.href);
}
if (!getPreloadedToken() && p >= PRELOAD_THRESHOLD * 100) {
const nxt = getAdjacentEpisode("next");
if (nxt) {
setPreloadedToken(nxt.dataset.id);
const _pTok = getPreloadedToken();
getUrlForToken(_pTok).then((url) => {
if (!url || !getCleanup() || getPreloadedToken() !== _pTok) return;
document.querySelectorAll("video[data-aw-preload]").forEach((v) => {
try {
v.pause();
v.src = "";
v.remove();
} catch (e) {
}
});
setPreloadedUrl(url);
const pv = document.createElement("video");
pv.preload = "auto";
pv.muted = true;
pv.dataset.awPreload = "1";
pv.style.cssText = "position:fixed;left:-9999px;top:-9999px;width:1px;height:1px;opacity:0;pointer-events:none";
pv.src = url;
document.body.appendChild(pv);
pv.load();
setPreloadedVideo(pv);
});
}
}
});
video.addEventListener("progress", () => {
if (!video.duration || !video.buffered.length) return;
seekBuf.style.width = video.buffered.end(video.buffered.length - 1) / video.duration * 100 + "%";
});
return { seekWrap, seekFill, seekThumb, seekBuf, isSeeking: () => seeking };
}
// src/panels/panel-shell.js
function buildPanelShell({ id, title, icon, iconClass, dot, bodyClass, closeAria, autoClose = true }) {
const panel = el("div", null, { id });
const barTitle = el("div", "aw-np-pn-title");
if (dot) barTitle.append(el("span", "aw-np-pn-title-dot"));
if (icon) {
if (iconClass) barTitle.append(el("span", iconClass, { html: icon }));
else barTitle.innerHTML = icon;
}
barTitle.append(el("span", null, { text: title }));
const closeBtn = el("button", "aw-np-pn-close", {
html: IC.close,
attrs: closeAria ? { "aria-label": closeAria } : void 0,
on: autoClose ? { click: (e) => {
e.stopPropagation();
panel.classList.remove("open");
} } : void 0
});
const bar = el("div", "aw-np-pn-bar", { kids: [barTitle, closeBtn] });
const body = el("div", bodyClass ? "aw-np-pn-body " + bodyClass : "aw-np-pn-body");
const inner = el("div", "aw-np-pn-inner", { kids: [bar, body] });
panel.append(inner);
if (autoClose) panel.addEventListener("click", (e) => {
if (e.target === panel) panel.classList.remove("open");
});
return { panel, inner, bar, body, closeBtn };
}
// src/panels/panel-rows.js
var BTN_CTRL_STYLE = "background:var(--np-accent-state-1,rgba(255,255,255,.15));border:none;color:var(--np-accent-bg-fg,#fff);width:26px;height:26px;border-radius:var(--np-r-md);cursor:pointer;font-size:16px;line-height:1;display:flex;align-items:center;justify-content:center;flex-shrink:0;";
function mkSwitchRow(label, tip, isOn, onChange, extraStyle = "", stopProp = false, desc = "") {
const row = document.createElement("div");
row.className = "aw-np-pn-row";
row.style.cssText = `position:relative;display:flex;align-items:center;justify-content:space-between;gap:12px;cursor:pointer;${extraStyle}`;
const txt = document.createElement("div");
txt.className = "np-row-txt";
const lbl = document.createElement("span");
lbl.className = "np-row-label";
lbl.textContent = label;
txt.append(lbl);
if (desc) {
const d = document.createElement("span");
d.className = "np-row-desc";
d.textContent = desc;
txt.append(d);
}
const { label: sw, input: toggle } = mkSwitch(typeof isOn === "function" ? isOn() : isOn);
toggle.addEventListener("change", (e) => {
if (stopProp) e.stopPropagation();
onChange(toggle.checked);
});
row.addEventListener("click", () => {
toggle.checked = !toggle.checked;
toggle.dispatchEvent(new Event("change"));
});
sw.addEventListener("click", (e) => e.stopPropagation());
row.append(txt, sw);
if (!desc) {
row.dataset.tip = "1";
row.append(mkRowTip(tip));
}
row.classList.add("aw-np-arow");
return { row, toggle };
}
function mkIconToggle(iconHtml, tip, isOn, onChange) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "aw-np-pn-icontog";
btn.tabIndex = -1;
btn.innerHTML = iconHtml;
let on = typeof isOn === "function" ? isOn() : isOn;
const paint = () => {
btn.classList.toggle("on", on);
btn.setAttribute("aria-pressed", on ? "true" : "false");
};
paint();
btn.addEventListener("click", (e) => {
e.stopPropagation();
if (btn.disabled) return;
on = !on;
paint();
onChange(on);
});
if (tip) {
btn.dataset.tip = "1";
btn.append(mkRowTip(tip));
}
return { btn, set: (v) => {
on = v;
paint();
}, get: () => on };
}
function mkStepRow(label, tipText, val, setVal, step, fmtFn, BTN_CTRL_STYLE2, desc = "") {
const row = document.createElement("div");
row.style.cssText = "position:relative;display:flex;align-items:center;justify-content:space-between;gap:10px;user-select:none;";
const valEl = document.createElement("span");
valEl.style.cssText = "min-width:44px;text-align:center;font-weight:500;";
const upd = () => valEl.textContent = fmtFn(val());
const bM = document.createElement("button");
bM.textContent = "−";
bM.style.cssText = BTN_CTRL_STYLE2;
bM.tabIndex = -1;
const bP = document.createElement("button");
bP.textContent = "+";
bP.style.cssText = BTN_CTRL_STYLE2;
bP.tabIndex = -1;
bM.addEventListener("click", (e) => {
e.stopPropagation();
setVal(-step);
upd();
});
bP.addEventListener("click", (e) => {
e.stopPropagation();
setVal(+step);
upd();
});
const ctrl = document.createElement("div");
ctrl.style.cssText = "display:flex;align-items:center;gap:6px;flex-shrink:0;";
ctrl.append(bM, valEl, bP);
const txt = document.createElement("div");
txt.className = "np-row-txt";
const lbl = document.createElement("span");
lbl.className = "np-row-label";
lbl.textContent = label;
txt.append(lbl);
if (desc) {
const d = document.createElement("span");
d.className = "np-row-desc";
d.textContent = desc;
txt.append(d);
}
row.append(txt, ctrl);
if (!desc) {
row.dataset.tip = "1";
row.append(mkRowTip(tipText));
}
row.classList.add("aw-np-arow");
upd();
return { row, update: upd };
}
// src/core/prefs.js
var loadMailState = () => {
try {
return JSON.parse(lsGet(KEY_MAIL_STATE) || "{}");
} catch {
return {};
}
};
var saveMailState = (st) => lsSet(KEY_MAIL_STATE, JSON.stringify(st));
var colorKey = () => isColorGlobalOn() ? KEY_COLOR : KEY_COLOR + ":" + (animeId() || "unknown");
var loadColor = () => lsGet(colorKey()) || "#ffffff";
var loadSeekSecs = () => {
const v = parseInt(lsGet(pKey(KEY_SEEK_SECS)) ?? String(SEEK_DEFAULT), 10);
return isNaN(v) ? SEEK_DEFAULT : Math.max(SEEK_MIN, Math.min(SEEK_MAX, v));
};
var loadUiScale = () => {
const v = parseFloat(lsGet(KEY_UI_SCALE) ?? String(UISCALE_DEFAULT));
return isNaN(v) ? UISCALE_DEFAULT : Math.max(UISCALE_MIN, Math.min(UISCALE_MAX, v));
};
// src/panels/settings-panel.js
function buildSettingsPanel(video, seekTip, onSpeedChange = null, onClose = null) {
const { panel: settingsPanel, body } = buildPanelShell({ id: "aw-np-settings-panel", title: "Impostazioni", icon: IC.settings });
const { row: resumeRow, toggle: resumeToggle } = mkSwitchRow("Ripresa automatica", "", isResumeOn(), (v) => lsSet(pKey(KEY_RESUME_ENABLE), v ? "1" : "0"), "", false, "Riprende dall'ultima interruzione.");
const { row: autoEpRow, toggle: autoEpToggle } = mkSwitchRow("Episodio automatico", "", isAutoEpOn(), (v) => lsSet(pKey(KEY_AUTOEP_ENABLE), v ? "1" : "0"), "", false, "Riapre l'ultimo episodio visto.");
const { row: autoPlayRow, toggle: autoPlayToggle } = mkSwitchRow("Autoplay", "", isAutoPlayOn(), (v) => lsSet(pKey(KEY_AUTOPLAY_ENABLE), v ? "1" : "0"), "", false, "Avvio automatico in fullscreen.");
const { row: connMonitorRow, toggle: connMonitorToggle } = mkSwitchRow("Monitor connessione", "", isConnMonitorOn(), (v) => lsSet(KEY_CONN_MONITOR, v ? "1" : "0"), "", false, "Avvisi sulla qualità di rete.");
let seekSecs = loadSeekSecs();
const { row: seekRow, update: updateSeekVal } = mkStepRow("Salto", "", () => seekSecs, (d) => {
if (seekSecs + d < SEEK_MIN || seekSecs + d > SEEK_MAX) return;
seekSecs += d;
lsSet(pKey(KEY_SEEK_SECS), String(seekSecs));
}, SEEK_STEP, (v) => String(v).padStart(2, "0") + " s", BTN_CTRL_STYLE, "Secondi saltati avanti/indietro.");
let speedVal = loadSpeed();
const { row: speedRow, update: updateSpeedVal } = mkStepRow("Velocità", "", () => speedVal, (d) => {
const nv = Math.round((speedVal + d) * 100) / 100;
if (nv < SPEED_MIN || nv > SPEED_MAX) return;
speedVal = nv;
lsSet(pKey(KEY_SPEED), String(speedVal));
if (video.readyState > 0) video.playbackRate = speedVal;
if (onSpeedChange) onSpeedChange(speedVal);
}, SPEED_STEP, fmtSpeed, BTN_CTRL_STYLE, "Velocità di riproduzione.");
const reloadSeek = () => {
seekSecs = loadSeekSecs();
updateSeekVal();
};
const reloadSpeed = () => {
speedVal = loadSpeed();
updateSpeedVal();
if (video.readyState > 0) video.playbackRate = speedVal;
};
const skipSec = el("div", "aw-np-pn-sec");
const { row: skMasterRow, toggle: skMasterToggle } = mkSwitchRow("Skip Intelligente", "", isSkipOn(), (v) => lsSet(KEY_SKIP_ENABLE, v ? "1" : "0"), "", false, "Sperimentale, database incompleto.");
const skipGrid = el("div", "aw-np-pn-grid");
const { row: skSkipRow, toggle: skSkipToggle } = mkSwitchRow("Auto-skip", "", isAutoSkipOn(), (v) => lsSet(KEY_SKIP_AUTO, v ? "1" : "0"), "", false, "Salto segmenti automatico.");
const { row: skNextRow, toggle: skNextToggle } = mkSwitchRow("Auto-next", "", isAutoNextOn(), (v) => lsSet(KEY_SKIP_NEXT, v ? "1" : "0"), "", false, "Episodio successivo automatico.");
const { row: skOpRow, toggle: skOpToggle } = mkSwitchRow("Opening", "", isOpeningOn(), (v) => lsSet(KEY_SKIP_OP, v ? "1" : "0"), "", false, "Salta la sigla iniziale.");
const { row: skEdRow, toggle: skEdToggle } = mkSwitchRow("Ending", "", isEndingOn(), (v) => lsSet(KEY_SKIP_ED, v ? "1" : "0"), "", false, "Salta la sigla finale.");
const { btn: skAwakeBtn, set: setAwakeBtn } = mkIconToggle(IC.moon, "Identifica i colpi di sonno e sospende auto-next.", isAwakeGuardOn(), (v) => lsSet(KEY_SKIP_AWAKE, v ? "1" : "0"));
skNextRow.insertBefore(skAwakeBtn, skNextRow.lastElementChild);
skipGrid.append(skSkipRow, skNextRow, skOpRow, skEdRow);
const skSubToggles = [skSkipToggle, skNextToggle, skOpToggle, skEdToggle];
const syncAwakeDep = () => {
const on = skNextToggle.checked && !skNextToggle.disabled;
skAwakeBtn.classList.toggle("dep-off", !on);
skAwakeBtn.disabled = !on;
};
const syncSkipEnabled = () => {
const on = skMasterToggle.checked;
skipGrid.classList.toggle("disabled", !on);
skSubToggles.forEach((t) => {
t.disabled = !on;
});
syncAwakeDep();
};
skNextToggle.addEventListener("change", syncAwakeDep);
skMasterToggle.addEventListener("change", syncSkipEnabled);
syncSkipEnabled();
skipSec.append(skMasterRow, skipGrid);
const { row: globalRow, toggle: globalToggle } = mkSwitchRow("Globale", "", isGlobalOn(), (v) => {
lsSet(KEY_GLOBAL, v ? "1" : "0");
resumeToggle.checked = isResumeOn();
autoEpToggle.checked = isAutoEpOn();
autoPlayToggle.checked = isAutoPlayOn();
reloadSeek();
reloadSpeed();
}, "", false, "Applica a tutte le serie.");
globalRow.dataset.tip = "1";
const globalSec = el("div", "aw-np-pn-global", { kids: [globalRow] });
const cols = el("div", "aw-np-pn-cols");
const colL = el("div", "aw-np-pn-col", { kids: [resumeRow, autoEpRow, autoPlayRow] });
const colDiv = el("div", "aw-np-pn-divider");
const colR = el("div", "aw-np-pn-col", { kids: [seekRow, speedRow, connMonitorRow] });
cols.append(colL, colDiv, colR);
body.append(globalSec, cols, skipSec);
return { settingsPanel, resumeToggle, autoEpToggle, autoPlayToggle, connMonitorToggle, globalToggle, skMasterToggle, skSkipToggle, skNextToggle, skOpToggle, skEdToggle, seekSecs: () => seekSecs, speedVal: () => speedVal, updateSeekVal, updateSpeedVal, reloadSeek, reloadSpeed };
}
// src/lib/color.js
function hexToRgb(h) {
if (typeof h !== "string") return null;
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(h.trim());
if (!m) return null;
let s = m[1];
if (s.length === 3) s = s[0] + s[0] + s[1] + s[1] + s[2] + s[2];
return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];
}
function rgbToHex(r) {
return "#" + r.map((x) => Math.max(0, Math.min(255, Math.round(x))).toString(16).padStart(2, "0")).join("");
}
function mixRgb(a, b, t) {
return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
}
function perceivedLum(r) {
return (0.299 * r[0] + 0.587 * r[1] + 0.114 * r[2]) / 255;
}
function relLum(rgb) {
const f = (v) => {
v /= 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
};
return 0.2126 * f(rgb[0]) + 0.7152 * f(rgb[1]) + 0.0722 * f(rgb[2]);
}
function contrastRatio(a, b) {
const l1 = relLum(a), l2 = relLum(b);
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
}
function ensureContrast(hex, bgHex, min) {
const c = hexToRgb(hex), bg = hexToRgb(bgHex);
if (!c || !bg) return hex;
if (contrastRatio(c, bg) >= min) return hex;
const target = relLum(bg) < 0.18 ? [255, 255, 255] : [0, 0, 0];
let out = c;
for (let i = 0; i < 40 && contrastRatio(out, bg) < min; i++) out = mixRgb(out, target, 0.06);
return rgbToHex(out);
}
function accentTextTone(rgb, theme) {
let r = rgb.slice(), g = 0;
if (theme === "dark") {
while (perceivedLum(r) < 0.62 && g < 16) {
r = mixRgb(r, [255, 255, 255], 0.12);
g++;
}
} else {
while (perceivedLum(r) > 0.42 && g < 16) {
r = mixRgb(r, [18, 40, 26], 0.12);
g++;
}
}
return rgbToHex(r);
}
function inkOnAccent(hex, darkSurface = "#16171a", darkTint = 0.08) {
const c = hexToRgb(hex);
if (!c) return "#ffffff";
if (perceivedLum(c) > 0.6) return rgbToHex(mixRgb(hexToRgb(darkSurface) || [22, 23, 26], c, darkTint));
return "#ffffff";
}
// src/state/color-apply.js
var CHROME_BG = "#16171a";
var CHROME_MIN_CR = 3;
function applyColor(hex, wrap, dotEl) {
const picked = hex;
hex = ensureContrast(hex, CHROME_BG, CHROME_MIN_CR);
if (wrap) {
wrap.style.setProperty("--np-accent", hex);
wrap.style.setProperty("--np-accent-bg", `color-mix(in srgb, ${hex} 30%, #282828)`);
wrap.style.setProperty("--np-accent-bg-fg", `color-mix(in srgb, #ffffff 78%, ${hex})`);
wrap.style.setProperty("--np-accent-state-1", `color-mix(in srgb, ${hex} 14%, transparent)`);
wrap.style.setProperty("--np-accent-state-2", `color-mix(in srgb, ${hex} 22%, transparent)`);
wrap.style.setProperty("--np-accent-dim", `color-mix(in srgb, #ffffff 60%, ${hex})`);
wrap.style.setProperty("--np-accent-on", inkOnAccent(hex));
}
if (dotEl) dotEl.style.background = hex;
try {
if (typeof document !== "undefined" && typeof CustomEvent === "function") {
document.dispatchEvent(new CustomEvent("aw-np-accent-changed", { detail: { hex: picked } }));
}
} catch {
}
}
// src/styles/panel-scale.js
var applyUiScale = (wrap, s) => {
if (wrap) wrap.style.setProperty("--ui-scale", String(s));
};
var PANEL_NATIVE_W = 650;
var PANEL_NATIVE_H = 440;
var PANEL_NATIVE_W_M = 650;
var PANEL_NATIVE_H_M = 490;
var MAIL_NATIVE_W = 720;
var MAIL_NATIVE_H = 480;
var MAIL_NATIVE_W_M = 720;
var MAIL_NATIVE_H_M = 560;
var fitScale = (pw, ph, nw, nh, tW, tH, smin, smax) => {
let s = Math.min(pw * tW / nw, ph * tH / nh);
return Math.max(smin, Math.min(s, smax));
};
var CHROME_MIN_M = 0.65;
var CHROME_REF_M = 365;
var CHROME_MAX_M = 1;
var FS_CHROME_PORT = 0.91;
var FS_CHROME_LAND = 0.86;
var computePanelScale = (pw, ph, isMobile2, uiScale = 1, cw = pw, ch = ph, isFs = false) => {
const nw = isMobile2 ? PANEL_NATIVE_W_M : PANEL_NATIVE_W, nh = isMobile2 ? PANEL_NATIVE_H_M : PANEL_NATIVE_H;
const mw = isMobile2 ? MAIL_NATIVE_W_M : MAIL_NATIVE_W, mh = isMobile2 ? MAIL_NATIVE_H_M : MAIL_NATIVE_H;
const tW = isMobile2 ? 0.92 : 0.7, tH = isMobile2 ? 0.9 : 0.86;
const smin = isMobile2 ? 0.5 : 0.78, smax = isMobile2 ? 1.35 : 1.42;
const capW = pw * 0.98, capH = ph * 0.98;
const scaled = (w, h) => {
const base = fitScale(pw, ph, w, h, tW, tH, smin, smax);
const cap = Math.min(capW / w, capH / h);
return Math.min(Math.max(base * uiScale, smin * 0.5), cap);
};
const pn = scaled(nw, nh), mb = scaled(mw, mh);
const chrome = !isMobile2 ? pn : isFs ? pw > ph ? FS_CHROME_LAND : FS_CHROME_PORT : Math.max(CHROME_MIN_M, Math.min(Math.sqrt(cw * ch) / CHROME_REF_M, CHROME_MAX_M));
return { pn, mb, chrome };
};
var applyPanelScale = (wrap, isMobile2, uiScale = 1) => {
if (!wrap) return;
const pw = wrap.clientWidth, ph = wrap.clientHeight;
const v = wrap.querySelector("#aw-np-video");
const ar = v && v.videoWidth > 0 && v.videoHeight > 0 ? v.videoWidth / v.videoHeight : 16 / 9;
let cw = pw, ch = ph;
if (pw && ph) {
if (pw / ph > ar) {
ch = ph;
cw = ch * ar;
} else {
cw = pw;
ch = cw / ar;
}
}
wrap.style.setProperty("--np-lb-y", Math.max(0, (ph - ch) / 2).toFixed(1) + "px");
const isFs = document.fullscreenElement === wrap || wrap.classList.contains("fs");
const s = computePanelScale(pw, ph, isMobile2, uiScale, cw, ch, isFs);
wrap.style.setProperty("--pn-scale", s.pn.toFixed(3));
wrap.style.setProperty("--mb-scale", s.mb.toFixed(3));
wrap.style.setProperty("--np-chrome-scale", s.chrome.toFixed(3));
};
// src/state/custom-colors.js
var CUSTOM_COLORS_MAX = 9;
var loadCustomColors = () => {
try {
const a = JSON.parse(lsGet(KEY_CUSTOM_COLORS) || "[]");
return Array.isArray(a) ? a.filter((c) => /^#[0-9a-f]{6}$/i.test(c)).slice(0, CUSTOM_COLORS_MAX) : [];
} catch (e) {
return [];
}
};
var saveCustomColors = (arr) => lsSet(KEY_CUSTOM_COLORS, JSON.stringify(arr.slice(0, CUSTOM_COLORS_MAX)));
var addCustomColor = (hex) => {
hex = hex.toLowerCase();
let arr = loadCustomColors().filter((c) => c.toLowerCase() !== hex);
arr.unshift(hex);
arr = arr.slice(0, CUSTOM_COLORS_MAX);
saveCustomColors(arr);
return arr;
};
// src/panels/color-panel.js
var HEX6 = /^#[0-9a-fA-F]{6}$/i;
function buildColorPanel(wrap, dotEl) {
const { panel: colorPanel, body } = buildPanelShell({ id: "aw-np-color-panel", title: "Aspetto", icon: IC.palette });
const swatchWrap = el("div", "aw-np-arow", { id: "aw-np-color-swatches" });
let currentColor = loadColor();
const getDot = () => dotEl || wrap.querySelector("#aw-np-dot");
const customInput = document.createElement("input"), customPreview = document.createElement("div");
const isPaletteColor = (hex) => PALETTE.some((p) => p.hex.toLowerCase() === String(hex).toLowerCase());
const syncCustomInput = (hex) => {
const custom = !isPaletteColor(hex);
customInput.value = custom ? hex : "";
customPreview.style.background = custom ? hex : "transparent";
customPreview.classList.toggle("empty", !custom);
};
let slotStrip = null;
const updateSwatches = (c) => {
swatchWrap.querySelectorAll(".np-swatch").forEach((s) => s.classList.toggle("active", s.dataset.hex === c));
if (slotStrip) slotStrip.querySelectorAll(".np-swatch").forEach((s) => s.classList.toggle("active", s.dataset.hex === c));
};
const setColor = (hex) => {
currentColor = hex;
lsSet(colorKey(), hex);
applyColor(hex, wrap, getDot());
syncCustomInput(hex);
updateSwatches(hex);
};
const applyCustomColor = () => {
let val = customInput.value.trim();
if (!val.startsWith("#")) val = "#" + val;
if (!HEX6.test(val)) {
syncCustomInput(currentColor);
return;
}
setColor(val.toLowerCase());
};
syncCustomInput(currentColor);
PALETTE.forEach(({ name, hex }) => {
const sw = el("div", "np-swatch" + (hex === currentColor ? " active" : ""), {
style: { background: hex },
on: { click: (e) => {
e.stopPropagation();
setColor(hex);
} }
});
sw.dataset.hex = hex;
sw.title = name;
swatchWrap.appendChild(sw);
});
const customRow = el("div", "aw-np-color-custom aw-np-arow", {
on: { click: (e) => e.stopPropagation() }
});
const customLabel = el("span", "aw-np-color-custom-lbl", { text: "Custom" });
customPreview.className = "aw-np-color-custom-prev";
customInput.type = "text";
customInput.maxLength = 7;
customInput.placeholder = "es. #1565c0";
customInput.spellcheck = false;
customInput.className = "aw-np-color-custom-input";
customInput.addEventListener("input", () => {
let v = customInput.value.trim();
if (!v.startsWith("#")) v = "#" + v;
if (HEX6.test(v)) customPreview.style.background = v;
});
let _applyingCustom = false;
customInput.addEventListener("blur", () => {
if (!_applyingCustom) applyCustomColor();
});
customInput.addEventListener("keydown", (e) => {
e.stopPropagation();
if (e.key === "Enter") {
_applyingCustom = true;
applyCustomColor();
customInput.blur();
_applyingCustom = false;
}
if (e.key === "Escape") {
syncCustomInput(currentColor);
customInput.blur();
}
});
customInput.addEventListener("click", (e) => e.stopPropagation());
customInput.addEventListener("focus", () => customInput.select());
slotStrip = el("div", "aw-np-color-slots");
const renderSlots = () => {
slotStrip.innerHTML = "";
const saved = loadCustomColors();
for (let i = 0; i < CUSTOM_COLORS_MAX; i++) {
const hex = saved[i];
let sw;
if (hex) {
sw = el("div", "np-swatch np-swatch-slot" + (hex === currentColor ? " active" : ""), {
style: { background: hex },
on: { click: (e) => {
e.stopPropagation();
setColor(hex);
} }
});
sw.dataset.hex = hex;
sw.title = hex;
} else {
sw = el("div", "np-swatch-slot np-swatch-empty");
}
slotStrip.appendChild(sw);
}
};
const btnCol = el("div", "aw-np-color-custom-btns");
const btnSave = el("button", "aw-np-color-custom-btn", { text: "+" });
btnSave.title = "Salva colore";
btnSave.tabIndex = -1;
const btnReset = el("button", "aw-np-color-custom-btn", { html: IC.undo });
btnReset.title = "Azzera salvati";
btnReset.tabIndex = -1;
btnSave.addEventListener("click", (e) => {
e.stopPropagation();
let v = customInput.value.trim();
if (!v.startsWith("#")) v = "#" + v;
if (!HEX6.test(v)) return;
addCustomColor(v.toLowerCase());
renderSlots();
});
btnReset.addEventListener("click", (e) => {
e.stopPropagation();
saveCustomColors([]);
renderSlots();
});
btnCol.append(btnSave, btnReset);
customRow.append(customLabel, customInput, customPreview, btnCol, slotStrip);
const colorSec = el("div", "aw-np-pn-colorsec", { kids: [swatchWrap, customRow] });
renderSlots();
const { row: topColorRow, toggle: topColorToggle } = mkSwitchRow("Top bar colorata", "", isTopColorOn, (v) => {
lsSet(KEY_TOP_COLOR, v ? "1" : "0");
wrap.classList.toggle("accent-top", v);
}, "", true, "Colora la barra superiore.");
const { row: iconColorRow, toggle: iconColorToggle } = mkSwitchRow("Icone colorate", "", isIconColorOn, (v) => {
lsSet(KEY_ICON_COLOR, v ? "1" : "0");
wrap.classList.toggle("accent-icons", v);
}, "", true, "Colora le icone del player.");
const { row: flashRow, toggle: flashToggle } = mkSwitchRow("Flash centrali", "", isFlashOn, (v) => {
lsSet(KEY_FLASH_ENABLE, v ? "1" : "0");
}, "", true, "Icona animata al centro a ogni comando.");
const { row: clockRow, toggle: clockToggle } = mkSwitchRow("Orologio", "", isClockOn, (v) => {
lsSet(KEY_CLOCK, v ? "1" : "0");
wrap.classList.toggle("clock-hidden", !v);
}, "", true, "Ora corrente, in alto a destra.");
const { row: speedPopupRow, toggle: speedPopupToggle } = mkSwitchRow("Popup velocità", "", isSpeedPopupOn, (v) => {
lsSet(KEY_SPEED_POPUP, v ? "1" : "0");
wrap.classList.toggle("speed-popup-hidden", !v);
}, "", true, "Velocità attuale, in alto a sinistra.");
let uiScale = loadUiScale();
const { row: uiScaleRow, update: updateUiScale } = mkStepRow("Scala UI", "", () => uiScale, (d) => {
const nv = Math.round((uiScale + d) * 100) / 100;
if (nv < UISCALE_MIN || nv > UISCALE_MAX) return;
uiScale = nv;
lsSet(KEY_UI_SCALE, String(uiScale));
applyUiScale(wrap, uiScale);
applyPanelScale(wrap, isMobile, uiScale);
}, UISCALE_STEP, (v) => Math.round(v * 100) + "%", BTN_CTRL_STYLE, "Dimensione di tutti gli elementi.");
const { row: colorGlobalRow, toggle: colorGlobalToggle } = mkSwitchRow("Globale", "", isColorGlobalOn, (v) => {
lsSet(KEY_COLOR_GLOBAL, v ? "1" : "0");
currentColor = loadColor();
applyColor(currentColor, wrap, getDot());
syncCustomInput(currentColor);
updateSwatches(currentColor);
}, "", true, "Applica a tutte le serie.");
colorGlobalRow.id = "aw-np-color-global";
const globalSec = el("div", "aw-np-pn-global", { kids: [colorGlobalRow] });
const colL = el("div", "aw-np-pn-col", { kids: [topColorRow, iconColorRow, flashRow] });
const colDiv = el("div", "aw-np-pn-divider");
const colR = el("div", "aw-np-pn-col", { kids: [clockRow, speedPopupRow, uiScaleRow] });
const cols = el("div", "aw-np-pn-cols", { kids: [colL, colDiv, colR] });
body.append(globalSec, colorSec, cols);
if (isIconColorOn()) wrap.classList.add("accent-icons");
if (isTopColorOn()) wrap.classList.add("accent-top");
if (!isClockOn()) wrap.classList.add("clock-hidden");
if (!isSpeedPopupOn()) wrap.classList.add("speed-popup-hidden");
return { colorPanel, swatchWrap, currentColor: () => currentColor, setCurrentColor: (c) => {
currentColor = c;
}, syncCustomInput, updateSwatches, topColorToggle, iconColorToggle, flashToggle, clockToggle, speedPopupToggle, colorGlobalToggle };
}
// src/data/page-meta.js
var getMalId = () => {
try {
const a = document.querySelector('a.watchlist-edit-mal[href*="myanimelist.net/anime/"],a[href*="myanimelist.net/anime/"]');
const m = a && a.getAttribute("href")?.match(/myanimelist\.net\/anime\/(\d+)/);
return m ? m[1] : null;
} catch {
return null;
}
};
var getEpisodeNum = () => {
try {
const a = document.querySelector(".episode a.active[data-episode-num],.episodes a.active[data-episode-num],a.active[data-episode-num]");
const n = a && (a.dataset.episodeNum || a.dataset.num);
if (n && /^\d+$/.test(n)) return n;
const m = (document.title || "").match(/Episodio\s+(\d+)/i);
return m ? m[1] : null;
} catch {
return null;
}
};
var getSeriesName = () => {
try {
return document.querySelector("h1.title, .title-1")?.textContent?.trim() || document.title.split(" Episodio")[0] || "";
} catch {
return "";
}
};
var getAudioType = () => {
try {
const link = document.querySelector('a[href*="filter?language="]');
if (link) {
const code = (link.getAttribute("href").match(/language=([a-z-]+)/i) || [])[1];
if (code) return /^it/i.test(code) ? "dub" : "sub";
const txt = (link.textContent || "").trim().toLowerCase();
if (txt) return /^it|ital/.test(txt) ? "dub" : "sub";
}
const nodes = document.querySelectorAll("dt,dd,span,div,li");
for (const n of nodes) {
const t = (n.textContent || "").trim();
const m = t.match(/^Audio:?\s*([A-Za-z][A-Za-z ]+)$/i);
if (m) {
return /ital/i.test(m[1]) ? "dub" : "sub";
}
}
} catch {
}
return "unknown";
};
// src/widgets/topbar.js
function buildTopBar(wrap, colorPanel) {
const topBar = el("div", "np-ui-layer", { id: "aw-np-top" });
const topLeft = mk("div", "aw-np-top-left"), topRight = mk("div", "aw-np-top-right");
const titleEl = mk("div", "aw-np-title"), epInfoEl = mk("div", "aw-np-epinfo"), dotEl = mk("div", "aw-np-dot");
const kbdBtn = el("div", null, { id: "aw-np-kbd-btn", attrs: { "aria-label": "Hotkey" }, kids: [el("span", "aw-np-kbd-letter", { text: "A" })] });
const browserBtn = el("div", null, { id: "aw-np-browser-btn", html: IC.browser, attrs: { "aria-label": "Homepage" } });
const reportBtn = el("div", null, { id: "aw-np-report-btn", html: IC.warning, attrs: { "aria-label": "Segnala problema" } });
const mailBtn = el("div", null, { id: "aw-np-mail-btn", html: IC.mail + '<span class="aw-np-mail-badge"></span>', attrs: { "aria-label": "Mail" } });
const brandEl = el("div", null, { id: "aw-np-brand", text: "AW Better Player" });
const dotBtn = el("div", null, { id: "aw-np-dot-btn", attrs: { "aria-label": "Aspetto" }, kids: [dotEl] });
const topActions = el("div", null, { id: "aw-np-top-actions", kids: [browserBtn, reportBtn, mailBtn, kbdBtn, dotBtn] });
if (!isMobile) {
browserBtn.appendChild(el("span", "np-tip", { text: "Homepage" }));
reportBtn.appendChild(el("span", "np-tip", { text: "Segnala problema" }));
mailBtn.appendChild(el("span", "np-tip", { text: "Mail" }));
kbdBtn.appendChild(el("span", "np-tip", { text: "Hotkey" }));
dotBtn.appendChild(el("span", "np-tip", { text: "Aspetto" }));
}
const allEps = Array.from(document.querySelectorAll(".episode a"));
const epIdx = allEps.findIndex((a) => a.classList.contains("active"));
const activeEp = epIdx !== -1 ? allEps[epIdx] : null;
const epNum = activeEp ? activeEp.textContent.trim() || String(epIdx + 1) : "?";
const epMaxNum = allEps.reduce((m, a) => {
const n = parseFloat(a.textContent.trim());
return isNaN(n) ? m : Math.max(m, n);
}, 0);
titleEl.textContent = getSeriesName();
epInfoEl.textContent = `Episodio ${epNum}/${epMaxNum > 0 ? String(epMaxNum) : allEps.length || "?"}`;
topLeft.append(titleEl, epInfoEl);
topRight.append(brandEl, topActions);
topBar.append(topLeft, topRight);
return { topBar, dotEl, dotBtn, kbdBtn, browserBtn, reportBtn, mailBtn, titleEl, epInfoEl };
}
// src/data/skip-db.js
var AUTOSKIP_RAW = "https://raw.githubusercontent.com/KeyMan98/Animeworld-Better-Player/main/autoskip";
var MIN_PART_S = 1;
function computeSkipParts(start, end, coverStart, coverEnd) {
const simple = [{ start, end }];
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return simple;
const cs = Number(coverStart), ce = Number(coverEnd);
const inSigla = (v) => Number.isFinite(v) && v >= start && v <= end;
const csIn = inSigla(cs), ceIn = inSigla(ce);
let parts;
if (csIn && ceIn) {
if (ce <= cs) return simple;
parts = [{ start, end: cs }, { start: ce, end }];
} else if (csIn) parts = [{ start, end: cs }];
else if (ceIn) parts = [{ start: ce, end }];
else return simple;
return parts.filter((p) => p.end - p.start >= MIN_PART_S);
}
function normSeg(s) {
if (!s || s.none) return null;
const start = Number(s.start), end = Number(s.end);
return {
start,
end,
parts: computeSkipParts(start, end, s.coverStart, s.coverEnd),
conf: Number(s.conf) || 0,
manual: !!s.manual
};
}
function fetchSkipDb(malId, audio) {
const au = audio === "dub" || audio === "sub" ? audio : "sub";
const url = `${AUTOSKIP_RAW}/${au}/${malId}.json`;
return fetch(url, { cache: "no-store" }).then((r) => {
if (!r.ok) return { exists: false, episodes: {} };
return r.json().then((d) => {
const episodes = {};
const e = d && d.episodes || {};
for (const [k, v] of Object.entries(e)) {
episodes[k] = { op: normSeg(v && v.op), ed: normSeg(v && v.ed) };
}
return { exists: true, episodes };
}).catch(() => ({ exists: false, episodes: {} }));
}).catch(() => ({ exists: false, episodes: {} }));
}
// src/data/report.js
var canSendReport = () => {
const last = parseFloat(lsGet(KEY_REPORT_LAST_TS) ?? "0");
return !last || Date.now() - last >= REPORT_HEARTBEAT_MS;
};
var reportCooldownLeft = () => {
const last = parseFloat(lsGet(KEY_REPORT_LAST_TS) ?? "0");
return last ? Math.max(0, REPORT_HEARTBEAT_MS - (Date.now() - last)) : 0;
};
function checkAutoskipDb(malId, audio) {
return fetchSkipDb(malId, audio).then((db) => {
if (!db.exists) return { exists: false, present: [], ep: {}, manual: {} };
const present = [], ep = {}, manual = {};
for (const [k, v] of Object.entries(db.episodes)) {
const hasOp = !!v.op, hasEd = !!v.ed;
if (hasOp || hasEd) present.push(parseInt(k, 10));
ep[k] = { op: hasOp, ed: hasEd };
manual[k] = { op: !!(v.op && v.op.manual), ed: !!(v.ed && v.ed.manual) };
}
present.sort((a, b) => a - b);
return { exists: true, present, ep, manual };
});
}
function sendReport(segment, issue, scope, deferCooldown) {
try {
if (!deferCooldown && !canSendReport()) return false;
const malId = getMalId(), ep = getEpisodeNum();
if (!malId || !ep) return false;
if (!deferCooldown) lsSet(KEY_REPORT_LAST_TS, String(Date.now()));
const body = JSON.stringify({ malId, ep, seriesName: getSeriesName(), audio: getAudioType(), scope: scope || "episode", segment, issue, v: SCRIPT_VERSION, ts: Date.now() });
fetch(WORKER_BASE + "/report", { method: "POST", headers: { "Content-Type": "application/json" }, body, keepalive: true }).catch(() => {
});
return true;
} catch {
return false;
}
}
function fetchMessages() {
return fetch(WORKER_BASE + "/messages").then((r) => r.ok ? r.json() : null).then((d) => Array.isArray(d?.messages) ? d.messages : null).catch(() => null);
}
// src/panels/report-panel.js
function buildReportPanel(wrap) {
const REPORT_GH = "https://github.com/KeyMan98/Animeworld-Better-Player/issues/new";
const REPORT_GF = "https://greasyfork.org/it/scripts/571464-animeworld-better-player/feedback";
const REPORT_TG = "https://t.me/KeyMan98";
const { panel: reportPanel, body: bodyWrap, closeBtn } = buildPanelShell({
id: "aw-np-report-panel",
title: "Segnala problema",
icon: IC.warning,
iconClass: "aw-np-pn-title-ic",
closeAria: "Chiudi",
bodyClass: "aw-np-rp-body",
autoClose: false
});
const tabSkip = el("button", "aw-np-rp-tab", { text: "Autoskip" });
const tabPlayer = el("button", "aw-np-rp-tab", { text: "Player" });
const tabs = el("div", "aw-np-rp-tabs aw-np-arow", { kids: [tabSkip, tabPlayer] });
const stage = el("div", "aw-np-rp-stage");
const ISSUE_OPTS = [["missing", "Mancante"], ["misaligned", "Disallineato"], ["wrong_point", "Punto sbagliato"], ["false_positive", "Falso positivo"]];
const SERIES_OPTS = [["series_missing", "Serie mancante"], ["incomplete", "Timestamp incompleti"], ["revision", "Revisione timestamp"]];
const segLbl = el("span", "aw-np-rp-grouplbl", { text: "Episodio" });
const subOp = el("button", "aw-np-rp-sub", { text: "Opening" });
const subEd = el("button", "aw-np-rp-sub", { text: "Ending" });
const segSel = el("div", "aw-np-rp-subsel", { kids: [subOp, subEd] });
const segHead = el("div", "aw-np-rp-seghead aw-np-arow", { kids: [segLbl, segSel] });
const segBtns = {};
const segRow = el("div", "aw-np-rp-optrow aw-np-arow", {
kids: ISSUE_OPTS.map(([k, t]) => {
const b = el("button", "aw-np-rp-opt", { text: t });
segBtns[k] = b;
return b;
})
});
const segMsgEl = el("div", "aw-np-rp-msg aw-np-arow");
const gSeg = el("div", "aw-np-rp-group", { kids: [segHead, segRow, segMsgEl] });
const srBtns = {};
const srRow = el("div", "aw-np-rp-optrow aw-np-arow", {
kids: SERIES_OPTS.map(([k, t]) => {
const b = el("button", "aw-np-rp-opt", { text: t });
srBtns[k] = b;
return b;
})
});
const srMsg = el("div", "aw-np-rp-msg aw-np-arow");
const gSr = {
group: el("div", "aw-np-rp-group", {
kids: [el("div", "aw-np-rp-grouplbl aw-np-arow", { text: "Tutta la serie" }), srRow, srMsg]
}),
row: srRow,
btns: srBtns,
msg: srMsg
};
const privacy = el("div", "aw-np-rp-privacy aw-np-arow", { text: "Nessun dato personale o del dispositivo viene registrato." });
const secSkip = el("div", "aw-np-rp-sec aw-np-rp-sec-skip", { kids: [gSeg, gSr.group, privacy] });
const plTxt = el("div", "aw-np-rp-pl-txt aw-np-arow", { text: "GreasyFork non permette di offuscare il codice, quindi non è possibile creare una segnalazione crittografata che rispetti la privacy dell'utente." });
const plTxt2 = el("div", "aw-np-rp-pl-txt2 aw-np-arow", { text: "Per questo tipo di segnalazione ti invito a contattarmi su:" });
const plBtns = el("div", "aw-np-rp-pl-btns aw-np-arow", {
kids: [["github", "GitHub", REPORT_GH], ["greasyfork", "GreasyFork", REPORT_GF], ["telegram", "Telegram", REPORT_TG]].map(([ic, txt, href]) => el("a", "aw-np-rp-extlink", { kids: [el("span", "aw-np-rp-extlink-ic", { html: IC[ic] }), el("span", null, { text: txt })], attrs: { href, target: "_blank", rel: "noopener" } }))
});
const secPlayer = el("div", "aw-np-rp-sec aw-np-rp-sec-player", { kids: [plTxt, plTxt2, plBtns] });
stage.append(secSkip, secPlayer);
const statusEl = el("div", "aw-np-rp-status");
const bCancel = el("button", "aw-np-rp-cancel", { text: "Annulla" });
const bSend = el("button", "aw-np-rp-send", { text: "Invia" });
const footer = el("div", "aw-np-rp-footer aw-np-arow", { kids: [statusEl, bCancel, bSend] });
bodyWrap.append(tabs, stage, footer);
let sec = "skip";
let pick = { op: null, ed: null, series: null };
let subSeg = "op";
let dbData = null, dbLoading = false, timer = null;
const loadDb = () => {
const malId = getMalId();
if (!malId) {
dbData = { exists: false, present: [], ep: {}, manual: {} };
renderMsgs();
return;
}
dbLoading = true;
renderMsgs();
checkAutoskipDb(malId, getAudioType()).then((res) => {
dbData = res;
dbLoading = false;
renderMsgs();
});
};
const segMsg = (segKey, issue) => {
if (dbLoading) return ["idle", "Verifica nel database…"];
if (!dbData) return ["idle", ""];
const epNum = String(getEpisodeNum());
const e = dbData.ep[epNum];
const has = !!(e && e[segKey]);
const isMan = !!(e && dbData.manual && dbData.manual[epNum] && dbData.manual[epNum][segKey]);
if (isMan) return ["mod", "Segmento convalidato dal moderatore"];
if (issue === "missing") {
return has ? ["bad", "Il timestamp risulta presente, possibile contraddizione"] : ["good", "Nessun dato presente, segnalazione coerente"];
}
return has ? ["good", "Segnalazione coerente con il database"] : ["bad", "Nessun timestamp presente da correggere"];
};
const seriesMsg = (reason) => {
if (dbLoading) return ["idle", "Verifica nel database…"];
if (!dbData) return ["idle", ""];
const present = (dbData.present || []).length > 0;
if (reason === "series_missing") return present ? ["bad", "La serie risulta già presente nel database"] : ["good", "Nessun dato presente, segnalazione coerente"];
if (reason === "incomplete") return present ? ["good", "Coerente con i dati parziali presenti"] : ["bad", "Nessun episodio presente nel database"];
return present ? ["good", "Dati disponibili per la revisione"] : ["bad", "Nessun dato da revisionare"];
};
const setMsg = (elem, kind, txt) => {
elem.className = "aw-np-rp-msg " + kind;
elem.textContent = txt;
};
const renderMsgs = () => {
if (pick[subSeg]) {
const [kind, txt] = segMsg(subSeg, pick[subSeg]);
setMsg(segMsgEl, kind, txt);
} else setMsg(segMsgEl, "empty", "");
if (pick.series) {
const [kind, txt] = seriesMsg(pick.series);
setMsg(gSr.msg, kind, txt);
} else setMsg(gSr.msg, "empty", "");
};
const syncUi = () => {
tabSkip.classList.toggle("sel", sec === "skip");
tabPlayer.classList.toggle("sel", sec === "player");
secSkip.classList.toggle("show", sec === "skip");
secPlayer.classList.toggle("show", sec === "player");
const seriesActive = !!pick.series;
const segActive = !!(pick.op || pick.ed);
gSeg.classList.toggle("dim", seriesActive);
gSr.group.classList.toggle("dim", segActive);
subOp.classList.toggle("sel", subSeg === "op");
subEd.classList.toggle("sel", subSeg === "ed");
subOp.classList.toggle("filled", !!pick.op);
subEd.classList.toggle("filled", !!pick.ed);
Object.entries(segBtns).forEach(([ik, b]) => b.classList.toggle("sel", pick[subSeg] === ik));
Object.entries(gSr.btns).forEach(([ik, b]) => b.classList.toggle("sel", pick.series === ik));
let ready = false;
if (sec === "skip") ready = !!(pick.op || pick.ed || pick.series);
footer.style.visibility = sec === "player" ? "hidden" : "visible";
const left = reportCooldownLeft();
if (left > 0) {
bSend.disabled = true;
statusEl.textContent = "Riprova tra " + Math.ceil(left / 1e3) + "s";
} else {
bSend.disabled = !ready;
statusEl.textContent = "";
}
renderMsgs();
};
const tick = () => {
if (!reportPanel.classList.contains("open")) {
clearInterval(timer);
timer = null;
return;
}
syncUi();
};
tabSkip.addEventListener("click", () => {
sec = "skip";
if (!dbData && !dbLoading) loadDb();
syncUi();
});
tabPlayer.addEventListener("click", () => {
sec = "player";
syncUi();
});
subOp.addEventListener("click", () => {
subSeg = "op";
syncUi();
});
subEd.addEventListener("click", () => {
subSeg = "ed";
syncUi();
});
Object.entries(segBtns).forEach(([ik, b]) => b.addEventListener("click", () => {
if (gSeg.classList.contains("dim")) return;
pick[subSeg] = pick[subSeg] === ik ? null : ik;
if (pick[subSeg]) pick.series = null;
syncUi();
}));
Object.entries(gSr.btns).forEach(([ik, b]) => b.addEventListener("click", () => {
if (gSr.group.classList.contains("dim")) return;
pick.series = pick.series === ik ? null : ik;
if (pick.series) {
pick.op = null;
pick.ed = null;
}
syncUi();
}));
closeBtn.addEventListener("click", () => reportPanel.classList.remove("open"));
bCancel.addEventListener("click", () => reportPanel.classList.remove("open"));
bSend.addEventListener("click", () => {
if (sec === "player") return;
if (!canSendReport()) return;
let ok = true, sent = 0;
if (pick.series) {
if (!sendReport(null, pick.series, "series", true)) ok = false;
sent++;
} else {
if (pick.op) {
if (!sendReport("op", pick.op, "episode", true)) ok = false;
sent++;
}
if (pick.ed) {
if (!sendReport("ed", pick.ed, "episode", true)) ok = false;
sent++;
}
}
if (!sent) return;
lsSet(KEY_REPORT_LAST_TS, String(Date.now()));
reportPanel.classList.remove("open");
if (ok) flashToast(wrap, "Segnalazione inviata, grazie!");
});
const reset = () => {
sec = "skip";
pick = { op: null, ed: null, series: null };
subSeg = "op";
dbData = null;
dbLoading = false;
loadDb();
syncUi();
if (!timer) timer = setInterval(tick, 1e3);
};
const clearReportTimer = () => {
clearInterval(timer);
timer = null;
};
return { reportPanel, reset, clearReportTimer };
}
// src/panels/mail-panel.js
function relDate(ts) {
if (!isFinite(ts)) return "";
const diff = Date.now() - ts, day = 864e5;
if (diff < day && new Date(ts).getDate() === (/* @__PURE__ */ new Date()).getDate()) return "Oggi";
if (diff < 2 * day) return "Ieri";
if (diff < 7 * day) return Math.floor(diff / day) + " giorni fa";
return new Date(ts).toLocaleDateString("it-IT", { day: "numeric", month: "short", year: new Date(ts).getFullYear() !== (/* @__PURE__ */ new Date()).getFullYear() ? "numeric" : void 0 });
}
function buildMailPanel(hooks) {
const mailPanel = el("div", null, { id: "aw-np-mail-panel" });
const inner = el("div", "aw-np-mail-inner");
const bar = el("div", "aw-np-mail-bar");
const barTitle = el("div", "aw-np-mail-bar-title", { html: IC.mail + "<span>Mail</span>" });
const btnClose = el("div", "aw-np-mail-iconbtn aw-np-mail-bar-close", { html: IC.close });
bar.append(barTitle, btnClose);
const body = el("div", "aw-np-mail-body");
const listPane = el("div", "aw-np-mail-list-pane");
const tabs = el("div", "aw-np-mail-tabs");
const tabInboxCount = el("span", "aw-np-mail-tab-count");
const tabInbox = el("button", "aw-np-mail-tab active", { kids: ["In arrivo", tabInboxCount] });
const tabTrashCount = el("span", "aw-np-mail-tab-count");
const tabTrash = el("button", "aw-np-mail-tab", { kids: ["Cestino", tabTrashCount] });
tabs.append(tabInbox, tabTrash);
const listView = el("div", "aw-np-mail-list");
listPane.append(tabs, listView);
const readPane = el("div", "aw-np-mail-read-pane");
const readEmptyIc = el("div", "aw-np-mail-empty-ic", { html: IC.mail });
const readEmptyTxt = el("div", null, { text: "Seleziona una comunicazione." });
const readEmpty = el("div", "aw-np-mail-read-empty", { kids: [readEmptyIc, readEmptyTxt] });
const readContent = el("div", "aw-np-mail-read-content", { style: { display: "none" } });
readPane.append(readEmpty, readContent);
body.append(listPane, readPane);
inner.append(bar, body);
mailPanel.appendChild(inner);
let cached = [], onUnreadChange = null, selectedId = null, activeTab = "inbox", revealTimer = null;
const computeUnread = () => {
const st = loadMailState();
return cached.some((m) => !st[m.id]?.deleted && !st[m.id]?.read);
};
const notifyUnread = () => {
if (onUnreadChange) onUnreadChange(computeUnread());
};
function updateTabBadge() {
const st = loadMailState();
const trash = cached.filter((m) => st[m.id]?.deleted && !st[m.id]?.read).length;
const unread = cached.filter((m) => !st[m.id]?.deleted && !st[m.id]?.read).length;
tabTrashCount.textContent = trash > 0 ? trash : "";
tabInboxCount.textContent = unread > 0 ? unread : "";
}
function switchTab(tab) {
if (activeTab === tab) return;
activeTab = tab;
tabInbox.classList.toggle("active", tab === "inbox");
tabTrash.classList.toggle("active", tab === "trash");
selectedId = null;
readContent.style.display = "none";
readEmpty.style.display = "flex";
renderList(true, 120);
}
tabInbox.addEventListener("click", () => switchTab("inbox"));
tabTrash.addEventListener("click", () => switchTab("trash"));
function renderList(animate, revealDelay = 0) {
clearTimeout(revealTimer);
revealTimer = null;
const st = loadMailState();
listView.innerHTML = "";
const visible = cached.filter((m) => activeTab === "trash" ? !!st[m.id]?.deleted : !st[m.id]?.deleted);
updateTabBadge();
if (!visible.length) {
const ic = el("div", "aw-np-mail-empty-ic", { html: activeTab === "trash" ? IC.trash : IC.mail });
const txt = el("div", null, { text: activeTab === "trash" ? "Cestino vuoto." : "Nessuna comunicazione." });
const empty = el("div", "aw-np-mail-empty", { kids: [ic, txt] });
listView.appendChild(empty);
} else {
visible.forEach((m, i) => {
const row = el("div", "aw-np-mail-row" + (st[m.id]?.read ? "" : " unread") + (m.id === selectedId ? " selected" : "") + (animate ? "" : " show"));
if (animate) row.style.transitionDelay = Math.min(i, 7) * MAIL_STAGGER_STEP + "s";
const dot = el("span", "aw-np-mail-dot");
const subj = el("div", "aw-np-mail-subject", { text: m.subject });
const date = el("div", "aw-np-mail-date", { text: relDate(m.created_at) });
const col = el("div", "aw-np-mail-col", { kids: [subj, date] });
const actionBtn = el("button", "aw-np-mail-iconbtn aw-np-mail-del");
if (activeTab === "trash") {
actionBtn.innerHTML = IC.restore;
actionBtn.addEventListener("click", (e) => {
e.stopPropagation();
const s = loadMailState();
s[m.id] = { ...s[m.id], deleted: false };
saveMailState(s);
if (selectedId === m.id) clearSelection();
else renderList();
notifyUnread();
});
} else {
actionBtn.innerHTML = IC.trash;
actionBtn.addEventListener("click", (e) => {
e.stopPropagation();
const s = loadMailState();
s[m.id] = { ...s[m.id], deleted: true };
saveMailState(s);
if (selectedId === m.id) clearSelection();
else renderList();
notifyUnread();
});
}
row.append(dot, col, actionBtn);
row.addEventListener("click", () => {
if (selectedId === m.id) clearSelection();
else selectMessage(m);
});
listView.appendChild(row);
});
if (animate) {
const reveal = () => {
revealTimer = null;
listView.querySelectorAll(".aw-np-mail-row").forEach((r, idx) => {
r.style.transitionDelay = Math.min(idx * MAIL_REVEAL_STEP, MAIL_REVEAL_CAP) + "s";
r.classList.add("show");
});
};
if (revealDelay > 0) revealTimer = setTimeout(() => requestAnimationFrame(reveal), revealDelay);
else requestAnimationFrame(() => requestAnimationFrame(reveal));
}
}
}
function clearSelection() {
selectedId = null;
readContent.style.display = "none";
readEmpty.style.display = "flex";
renderList();
}
function selectMessage(m) {
selectedId = m.id;
if (activeTab !== "trash") {
const st = loadMailState();
st[m.id] = { ...st[m.id], read: true };
saveMailState(st);
notifyUnread();
}
renderList();
readContent.innerHTML = "";
const subj = el("div", "aw-np-mail-d-subject", { text: m.subject });
const date = el("span", "aw-np-mail-d-date", { text: isFinite(m.created_at) ? new Date(m.created_at).toLocaleDateString("it-IT", { day: "numeric", month: "long", year: "numeric" }) : "" });
const closeMsg = el("button", "aw-np-mail-iconbtn aw-np-mail-d-close", { html: IC.close, on: { click: (e) => {
e.stopPropagation();
clearSelection();
} } });
const dateClose = el("div", "aw-np-mail-d-date-row", { kids: [date, closeMsg] });
const head = el("div", "aw-np-mail-d-head", { kids: [subj, dateClose] });
const text = el("div", "aw-np-mail-d-body", { text: m.body });
const sign = el("div", "aw-np-mail-d-sign", { text: "— KeyMan98" });
const card = el("div", "aw-np-mail-d-card", { kids: [text, sign] });
const actions = el("div", "aw-np-mail-d-actions");
if (activeTab === "trash") {
const restoreBtn = el("button", "aw-np-mail-toggle", { html: IC.restore + "<span>Ripristina</span>" });
restoreBtn.addEventListener("click", () => {
const s = loadMailState();
s[m.id] = { ...s[m.id], deleted: false };
saveMailState(s);
notifyUnread();
clearSelection();
});
actions.append(restoreBtn);
} else {
const toggleRead = el("button", "aw-np-mail-toggle");
const syncToggle = () => {
const s = loadMailState();
toggleRead.textContent = s[m.id]?.read ? "Segna come non letta" : "Segna come letta";
};
syncToggle();
toggleRead.addEventListener("click", () => {
const s = loadMailState();
s[m.id] = { ...s[m.id], read: !s[m.id]?.read };
saveMailState(s);
syncToggle();
renderList();
notifyUnread();
});
const delBtn = el("button", "aw-np-mail-toggle is-danger", { html: IC.trash + "<span>Elimina</span>" });
delBtn.addEventListener("click", () => {
const s = loadMailState();
s[m.id] = { ...s[m.id], deleted: true };
saveMailState(s);
notifyUnread();
clearSelection();
});
actions.append(toggleRead, delBtn);
}
readContent.append(head, card, actions);
readEmpty.style.display = "none";
readContent.style.display = "flex";
}
function refresh() {
return fetchMessages().then((list) => {
if (list == null) {
renderList(true);
notifyUnread();
return;
}
cached = list;
const st = loadMailState(), ids = new Set(list.map((m) => String(m.id)));
Object.keys(st).forEach((id) => {
if (!ids.has(id)) delete st[id];
});
saveMailState(st);
if (selectedId != null && !list.some((m) => m.id === selectedId)) selectedId = null;
readContent.style.display = "none";
readEmpty.style.display = "flex";
renderList(true);
notifyUnread();
});
}
btnClose.addEventListener("click", (e) => {
e.stopPropagation();
mailPanel.classList.remove("open");
});
inner.addEventListener("click", (e) => e.stopPropagation());
mailPanel.addEventListener("click", (e) => {
if (e.target === mailPanel) mailPanel.classList.remove("open");
});
let wasOpen = false;
const mo = new MutationObserver(() => {
const isOpen = mailPanel.classList.contains("open");
if (isOpen && !wasOpen) {
wasOpen = true;
activeTab = "inbox";
tabInbox.classList.add("active");
tabTrash.classList.remove("active");
selectedId = null;
readContent.style.display = "none";
readEmpty.style.display = "flex";
renderList(true, 500);
hooks?.onOpen?.();
} else if (!isOpen && wasOpen) {
wasOpen = false;
clearTimeout(revealTimer);
revealTimer = null;
hooks?.onClose?.();
}
});
mo.observe(mailPanel, { attributes: true, attributeFilter: ["class"] });
return { mailPanel, refresh, setOnUnreadChange: (fn) => {
onUnreadChange = fn;
}, disconnectMail: () => mo.disconnect() };
}
// src/data/hotkeys.js
var HOTKEY_ACTIONS = ["fullscreen", "mute", "restart", "skipOp", "undoSkip", "prev", "next", "reload", "legend", "homepage", "report", "mailbox"];
var DEFAULT_HOTKEYS = { fullscreen: "f", mute: "m", restart: "r", skipOp: "o", undoSkip: "b", prev: "p", next: "n", reload: "s", legend: "a", homepage: "i", report: "h", mailbox: "l" };
var loadHotkeys = () => {
let raw;
try {
raw = JSON.parse(lsGet(KEY_HOTKEYS) || "null");
} catch {
raw = null;
}
const out = {}, used = /* @__PURE__ */ new Set();
if (raw && typeof raw === "object") {
for (const a of HOTKEY_ACTIONS) {
const v = raw[a];
if (typeof v === "string" && /^[a-z]$/.test(v) && !used.has(v)) {
out[a] = v;
used.add(v);
}
}
}
for (const a of HOTKEY_ACTIONS) {
if (out[a]) continue;
let v = DEFAULT_HOTKEYS[a];
if (used.has(v)) {
v = null;
for (let c = 97; c <= 122; c++) {
const ch = String.fromCharCode(c);
if (!used.has(ch)) {
v = ch;
break;
}
}
}
if (v) {
out[a] = v;
used.add(v);
}
}
return out;
};
var saveHotkeys = (map) => lsSet(KEY_HOTKEYS, JSON.stringify(map));
// src/panels/hotkey-panel.js
function buildHotkeyPanel(initMap, onCommit, hooks) {
const ACT = {
mute: { ic: IC.mute, f: "TOGGLE VOLUME", d: "Attiva/disattiva l'audio" },
fullscreen: { ic: IC.fsOn, f: "FULL SCREEN", d: "Entra/esce dal full screen" },
skipOp: { ic: IC.skip, f: "SKIP OP/ED", d: "Salto in avanti di 1:25 min." },
undoSkip: { ic: IC.undo, f: "ANNULLA SKIP", d: "Torna indietro di 1:25 min." },
prev: { ic: IC.prev, f: "PRECEDENTE", d: "Episodio precedente" },
next: { ic: IC.next, f: "SUCCESSIVO", d: "Episodio successivo" },
restart: { ic: IC.restart, f: "RESTART", d: "Riparte da inizio episodio" },
reload: { ic: IC.unlock, f: "SBLOCCA VIDEO", d: "Forza un refresh del video" },
legend: { ic: '<span class="aw-np-key-a-badge">A</span>', f: "HOTKEY", d: "Apre/chiude il pannello hotkey" },
homepage: { ic: IC.browser, f: "HOMEPAGE", d: "Apre la Homepage del player" },
report: { ic: IC.warning, f: "SEGNALA PROBLEMA", d: "Apre il pannello segnalazioni" },
mailbox: { ic: IC.mail, f: "MAIL", d: "Apre la casella di posta" }
};
let curMap = Object.assign({}, initMap);
let editMap = null;
let editing = false, listening = null;
const overlay = mk("div", "aw-np-hotkey-overlay");
const inner = mk("div", "aw-np-hotkey-inner");
const bar = el("div", "aw-np-kbd-bar");
const BAR_IC = {
edit: IC.edit,
ok: IC.check,
cancel: IC.undo,
reset: IC.restart,
x: IC.close
};
const mkBarBtn = (cls, ic, title) => el("div", "aw-np-kbd-bar-btn " + cls, {
html: ic,
kids: [el("span", "np-tip", { text: title })]
});
const btnEdit = mkBarBtn("is-edit", BAR_IC.edit, "Modifica le scorciatoie");
const btnReset = mkBarBtn("is-reset", BAR_IC.reset, "Ripristina i tasti predefiniti");
const btnOk = mkBarBtn("is-ok", BAR_IC.ok, "Salva le modifiche");
const btnCancel = mkBarBtn("is-cancel", BAR_IC.x, "Esci dalla modifica");
const btnX = mkBarBtn("is-x", BAR_IC.x, "Chiudi");
const keyA = el("span", "aw-np-hotkey-key-a");
const barTitle = el("div", "aw-np-hotkey-bartitle", { kids: [keyA, el("span", null, { text: "Hotkey" })] });
bar.append(barTitle, btnX);
const tools = el("div", "aw-np-hotkey-tools", { kids: [btnEdit, btnReset, btnOk, btnCancel] });
const kbd = el("div", "aw-np-kbd");
const fnStrip = el("div", "aw-np-kbd-fn");
const fnName = el("span", "aw-np-kbd-fn-name"), fnDesc = el("span", "aw-np-kbd-fn-desc");
fnStrip.append(fnName, fnDesc);
const showFn = (s) => {
fnName.textContent = s.f;
fnDesc.textContent = s.d;
fnStrip.classList.add("show");
};
const clearFn = () => {
if (!editing) fnStrip.classList.remove("show");
};
const showHint = (t) => {
fnName.textContent = "";
fnDesc.textContent = t;
fnStrip.classList.add("show");
};
const letterSlots = /* @__PURE__ */ new Map();
const mkLetterSlot = (ch) => {
const key = el("div", "aw-np-key aw-np-key-letter-slot", {
attrs: { "data-ch": ch.toLowerCase() },
kids: [el("span", "aw-np-key-ico"), el("span", "aw-np-key-letter", { text: ch })],
on: {
mouseenter: () => {
const act = actionAt(ch.toLowerCase());
if (editing) {
key.classList.add("hovered");
} else if (act) {
key.classList.add("hovered");
showFn(ACT[act]);
}
},
mouseleave: () => {
key.classList.remove("hovered");
clearFn();
},
click: (e) => {
if (!editing) return;
e.stopPropagation();
const c = ch.toLowerCase();
if (!listening) {
startListening(c);
} else if (listening === c) {
listening = null;
letterSlots.forEach((s) => s.classList.remove("listening"));
showHint("Seleziona il tasto da modificare");
} else {
assignLetter(c);
}
}
}
});
letterSlots.set(ch.toLowerCase(), key);
return key;
};
const mkFiller = (label, wide, pad) => {
const k = el("div", "aw-np-key off aw-np-key-special");
if (wide || pad) {
k.classList.add("aw-np-key-fill-wide");
if (wide) k.style.setProperty("--wmult", wide);
if (pad) k.style.setProperty("--padmult", pad);
k.appendChild(el("span", "aw-np-glyph", { text: label }));
} else {
k.textContent = label;
}
return k;
};
const mkSpecial = (spec, extraCls, label) => {
const kids = [el("span", "aw-np-key-ico", { html: spec.ic })];
if (label) kids.push(el("span", "aw-np-key-letter", { text: label }));
const key = el("div", "aw-np-key on aw-np-key-special" + (extraCls ? " " + extraCls : ""), {
kids,
on: {
mouseenter: () => {
if (editing) return;
key.classList.add("hovered");
showFn(spec);
},
mouseleave: () => {
key.classList.remove("hovered");
clearFn();
}
}
});
return key;
};
const r1 = el("div", "aw-np-krow");
r1.appendChild(el("div", "aw-np-key off aw-np-key-half aw-np-key-special", { text: "\\" }));
for (let i = 0; i < 10; i++) {
const ch = String((i + 1) % 10);
const pct = (i + 1) % 10 * 10;
const spec = { ic: IC.seekFwd, f: "AVANZAMENTO RAPIDO", d: `${pct}% della durata totale` };
const key = el("div", "aw-np-key on aw-np-key-num aw-np-key-special", {
kids: [el("span", "aw-np-key-ico aw-np-key-pct", { text: `${pct}%` }), el("span", "aw-np-key-letter", { text: ch })],
on: {
mouseenter: () => {
if (editing) return;
key.classList.add("hovered");
showFn(spec);
},
mouseleave: () => {
key.classList.remove("hovered");
clearFn();
}
}
});
r1.appendChild(key);
}
r1.appendChild(mkFiller("🠔", 1.5));
kbd.appendChild(r1);
const r2 = el("div", "aw-np-krow");
r2.appendChild(mkFiller("⮀"));
for (const ch of "QWERTYUIOP") r2.appendChild(mkLetterSlot(ch));
r2.appendChild(mkFiller("+"));
kbd.appendChild(r2);
const r3 = el("div", "aw-np-krow");
r3.appendChild(mkFiller("⇪", 1.5));
for (const ch of "ASDFGHJKL") r3.appendChild(mkLetterSlot(ch));
const enterKey = el("div", "aw-np-key off aw-np-key-enter-flat aw-np-key-special", {
html: '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width:60%;height:60%;display:block"><path fill="currentColor" d="M20.94,7 C20.27,7 19.61,7 18.95,7 C18.95,8.33 18.95,9.66 18.95,11 C15.25,11 11.55,11 7.85,11 C7.85,9.66 7.85,8.33 7.85,7 C6.25,8.66 4.66,10.31 3.07,11.97 C4.66,13.65 6.25,15.32 7.85,17 C7.85,15.66 7.85,14.33 7.85,12.99 C12.21,12.99 16.57,12.99 20.94,12.99 C20.94,10.99 20.94,9 20.94,7 Z"/></svg>'
});
r3.appendChild(enterKey);
kbd.appendChild(r3);
const r4 = el("div", "aw-np-krow");
r4.appendChild(mkFiller("⇧", 2, 1));
for (const ch of "ZXCVBNM") r4.appendChild(mkLetterSlot(ch));
r4.appendChild(mkFiller("⇧"));
r4.appendChild(mkSpecial({ ic: IC.vol, f: "VOLUME +", d: "Alza il volume" }, null, "▲"));
r4.appendChild(mkFiller("-"));
kbd.appendChild(r4);
const r5 = el("div", "aw-np-krow aw-np-krow-bottom");
const mkMod = (l, wide) => {
const k = el("div", "aw-np-key off aw-np-key-mod aw-np-key-special", { text: l });
if (wide) {
k.classList.add("aw-np-key-fill-wide");
k.style.setProperty("--wmult", wide);
}
return k;
};
const space = mkSpecial({ ic: IC.play, f: "PLAY/PAUSA", d: "play/pausa del video" }, "aw-np-key-space");
r5.append(
mkMod("CTRL"),
mkMod("AW"),
mkMod("ALT"),
space,
mkMod("ALT GR"),
mkSpecial({ ic: IC.seekBwd, f: "SEEK INDIETRO", d: "Breve riavvolgimento di 5-30 s" }, null, "◀"),
mkSpecial({ ic: IC.volDown, f: "VOLUME −", d: "Abbassa il volume" }, null, "▼"),
mkSpecial({ ic: IC.seekFwd, f: "SEEK AVANTI", d: "Breve avanzamento di 5-30 s" }, null, "▶")
);
kbd.appendChild(r5);
const updateHint = () => {
keyA.textContent = (curMap.legend || "a").toUpperCase();
};
updateHint();
const footer = el("div", "aw-np-hotkey-footer", { kids: [fnStrip, tools] });
inner.append(bar, kbd, footer);
overlay.append(inner);
const liveMap = () => editing ? editMap : curMap;
const actionAt = (ch) => {
const m = liveMap();
for (const a of HOTKEY_ACTIONS) if (m[a] === ch) return a;
return null;
};
const renderBindings = () => {
letterSlots.forEach((slot, ch) => {
const ico = slot.querySelector(".aw-np-key-ico");
const a = actionAt(ch);
if (a) {
slot.classList.add("on");
slot.classList.remove("off");
ico.innerHTML = a === "legend" ? '<span class="aw-np-key-a-badge">' + ch.toUpperCase() + "</span>" : ACT[a].ic;
slot.dataset.act = a;
} else {
slot.classList.remove("on");
slot.classList.add("off");
ico.innerHTML = "";
delete slot.dataset.act;
}
});
};
renderBindings();
const refreshBar = () => {
overlay.classList.toggle("editing", editing);
};
const enterEdit = () => {
editing = true;
editMap = Object.assign({}, curMap);
listening = null;
refreshBar();
renderBindings();
showHint("Seleziona il tasto da modificare");
};
const exitEdit = () => {
editing = false;
editMap = null;
listening = null;
letterSlots.forEach((s) => s.classList.remove("listening"));
refreshBar();
renderBindings();
clearFn();
fnStrip.classList.remove("show");
};
const startListening = (ch) => {
listening = ch;
letterSlots.forEach((s) => s.classList.toggle("listening", s.dataset.ch === ch));
showHint("Premi la nuova lettera");
};
const assignLetter = (newCh) => {
if (!listening || newCh === listening) {
listening = null;
letterSlots.forEach((s) => s.classList.remove("listening"));
showHint("Seleziona il tasto da modificare");
return;
}
const lFirst = listening, lSecond = newCh;
const aFirst = actionAt(lFirst);
const aSecond = actionAt(lSecond);
if (aFirst) editMap[aFirst] = lSecond;
if (aSecond) editMap[aSecond] = lFirst;
listening = null;
letterSlots.forEach((s) => s.classList.remove("listening"));
renderBindings();
showHint("Seleziona il tasto da modificare");
};
const onEditKey = (e) => {
if (!editing) return;
if (e.key === "Escape") {
e.preventDefault();
e.stopPropagation();
doCancel();
return;
}
if (!listening) return;
const k = (e.key || "").toLowerCase();
if (/^[a-z]$/.test(k)) {
e.preventDefault();
e.stopPropagation();
assignLetter(k);
}
};
btnEdit.addEventListener("click", (e) => {
e.stopPropagation();
enterEdit();
});
btnReset.addEventListener("click", (e) => {
e.stopPropagation();
if (!editing) return;
editMap = Object.assign({}, DEFAULT_HOTKEYS);
listening = null;
letterSlots.forEach((s) => s.classList.remove("listening"));
renderBindings();
showHint("Seleziona il tasto da modificare");
});
const doOk = () => {
curMap = Object.assign({}, editMap);
saveHotkeys(curMap);
if (onCommit) onCommit(Object.assign({}, curMap));
exitEdit();
updateHint();
};
const doCancel = () => {
exitEdit();
};
btnOk.addEventListener("click", (e) => {
e.stopPropagation();
if (editing) doOk();
});
btnCancel.addEventListener("click", (e) => {
e.stopPropagation();
if (editing) doCancel();
});
const isOpen = () => overlay.classList.contains("open");
const open = () => {
overlay.classList.add("open");
hooks?.onOpen?.();
};
const closeFn = () => {
if (editing) doCancel();
overlay.classList.remove("open");
hooks?.onClose?.();
};
btnX.addEventListener("click", (e) => {
e.stopPropagation();
closeFn();
});
overlay.addEventListener("click", (e) => {
if (e.target === overlay && !editing) closeFn();
});
document.addEventListener("keydown", onEditKey, true);
const setMap = (m) => {
curMap = Object.assign({}, m);
updateHint();
if (!editing) renderBindings();
};
const destroy = () => document.removeEventListener("keydown", onEditKey, true);
return { overlay, open, close: closeFn, isOpen, setMap, isEditing: () => editing, destroy };
}
// src/panels/bp-homepage-panel.js
var LINKS = [
{ ic: "world", label: "Sito web", soon: true },
{ ic: "github", label: "GitHub", desc: "Codice sorgente e DB skip", href: "https://github.com/KeyMan98/Animeworld-Better-Player" },
{ ic: "greasyfork", label: "GreasyFork", desc: "Installa o aggiorna lo script", href: "https://greasyfork.org/it/scripts/571464-animeworld-better-player" },
{ ic: "telegram", label: "Telegram", desc: "Chat diretta, info e assistenza", href: "https://t.me/KeyMan98" },
{ ic: "whatsapp", label: "WhatsApp", soon: true }
];
function buildBpHomepage() {
const { panel: bpPanel, body } = buildPanelShell({ id: "aw-np-bp-panel", title: "Homepage", icon: IC.browser });
LINKS.forEach((l) => {
const txtKids = [el("span", "aw-np-bp-label", { text: l.label })];
if (l.desc) txtKids.push(el("span", "aw-np-bp-desc", { text: l.desc }));
const kids = [
el("span", "aw-np-bp-ic aw-np-bp-ic-" + l.ic, { html: IC[l.ic] }),
el("span", "aw-np-bp-txt", { kids: txtKids }),
l.soon ? el("span", "aw-np-bp-soon-lbl", { text: "In arrivo" }) : el("span", "aw-np-bp-arrow", { text: "↗" })
];
const row = l.soon ? el("div", "aw-np-bp-link aw-np-bp-soon aw-np-arow", { kids }) : el("a", "aw-np-bp-link aw-np-arow", { attrs: { href: l.href, target: "_blank", rel: "noopener noreferrer" }, kids });
body.appendChild(row);
});
return { bpPanel };
}
// src/widgets/flash.js
function buildVolFlash(video) {
const volFlashIco = mk("div", "aw-np-vol-flash-icon");
const volFlash = el("div", "np-flash-circle", { id: "aw-np-vol-flash", kids: [volFlashIco] });
let volFlashTimer = null;
const showVolFlash = (dir) => {
if (!isFlashOn()) return;
const m = video.muted || video.volume === 0;
volFlashIco.innerHTML = m ? IC.mute : dir === "down" ? IC.volDown : IC.vol;
volFlash.classList.add("on");
clearTimeout(volFlashTimer);
volFlashTimer = setTimeout(() => volFlash.classList.remove("on"), 400);
};
return { volFlash, showVolFlash, clearTimer: () => clearTimeout(volFlashTimer) };
}
function buildCenterFlash(center) {
let cTimer = null, skipFlash = false;
const flashAt = (html, dur) => {
if (!isFlashOn()) return;
center.innerHTML = html;
center.classList.add("on");
clearTimeout(cTimer);
cTimer = setTimeout(() => center.classList.remove("on"), dur);
};
return {
flash: (html) => flashAt(html, 700),
flashBrief: (html) => flashAt(html, 400),
playPauseFlash: (html) => {
if (!skipFlash) flashAt(html, 700);
},
setSkipFlash: (v) => {
skipFlash = v;
},
clearTimer: () => clearTimeout(cTimer)
};
}
// src/lib/pip.js
var pipElement = () => document.pictureInPictureElement || null;
var pipExit = () => document.exitPictureInPicture?.().catch(() => {
});
var pipRequest = (el2) => {
if (el2.readyState >= 1) return el2.requestPictureInPicture?.().catch(() => {
});
};
// src/widgets/player-events.js
function buildErrorHandler(video, wrap, _play, errorMsg) {
const hideErrorToast = () => wrap.querySelector("#aw-np-error-toast")?.remove();
let _reloading = false;
const reloadVideo = (silent = false) => {
if (_reloading) return;
_reloading = true;
hideErrorToast();
const t = video.currentTime;
const wasPlaying = !video.paused;
const s = video.src;
if (silent) video.dataset.silentReload = "1";
video.src = "";
video.src = s;
const failsafe = setTimeout(() => {
_reloading = false;
}, 2e4);
video.addEventListener("loadedmetadata", () => {
clearTimeout(failsafe);
_reloading = false;
if (t > 0) video.currentTime = t;
if (wasPlaying) _play();
setTimeout(() => {
delete video.dataset.silentReload;
}, 100);
}, { once: true });
};
const showErrorToast = (msg) => {
hideErrorToast();
const t = el("div", null, { id: "aw-np-error-toast", text: msg, on: { click: () => reloadVideo() } });
wrap.appendChild(t);
};
video.addEventListener("error", () => {
if (video.networkState !== 0) showErrorToast(errorMsg);
});
let _lastTime = -1, _freezeTimer = null;
const _checkFreeze = () => {
if (video.paused || video.ended) return;
if (video.currentTime === _lastTime && video.readyState < 3) showErrorToast(errorMsg);
_lastTime = video.currentTime;
};
video.addEventListener("play", () => {
hideErrorToast();
_lastTime = video.currentTime;
clearInterval(_freezeTimer);
_freezeTimer = setInterval(_checkFreeze, 5e3);
});
video.addEventListener("playing", () => {
hideErrorToast();
_lastTime = video.currentTime;
});
video.addEventListener("canplaythrough", () => hideErrorToast());
video.addEventListener("pause", () => clearInterval(_freezeTimer));
video.addEventListener("ended", () => clearInterval(_freezeTimer));
video.addEventListener("seeking", () => {
_lastTime = -1;
});
return { reloadVideo, clearTimer: () => clearInterval(_freezeTimer) };
}
function buildPipHandler({ video, btnPip, wrap, onEnter, onLeave }) {
btnPip.addEventListener("click", () => pipElement() ? pipExit() : pipRequest(video));
video.addEventListener("enterpictureinpicture", () => {
wrap.style.visibility = "hidden";
onEnter?.();
});
video.addEventListener("leavepictureinpicture", () => {
wrap.style.visibility = "";
onLeave?.();
});
}
function wirePlayPauseEvents({ video, onUpdate, playPauseFlash }) {
video.addEventListener("play", () => {
if (video.dataset.silentReload) return;
onUpdate(true);
playPauseFlash?.(IC.pause);
});
video.addEventListener("pause", () => {
if (video.dataset.silentReload) return;
onUpdate(false);
playPauseFlash?.(IC.play);
});
video.addEventListener("ended", () => onUpdate(false));
}
// src/player/episode-actions.js
function buildEpisodeActions({ video, _play, flash }) {
return {
restart: () => {
video.currentTime = 0;
_play();
flash(IC.restart);
},
undo: () => {
video.currentTime = Math.max(0, video.currentTime - SKIP_SECONDS);
flash(IC.undo);
},
skip: () => {
video.currentTime = Math.min(video.duration || 0, video.currentTime + SKIP_SECONDS);
flash(IC.skip);
},
prev: () => {
const t = getAdjacentEpisode("prev");
if (t) {
flash(IC.prev);
loadEpisode(t.dataset.id);
}
},
next: () => {
const t = getAdjacentEpisode("next");
if (t) {
flash(IC.next);
loadEpisode(t.dataset.id);
}
},
// Apre un episodio per token (non adiacente): usato dal «Vai a <ep> - <m:ss>» della
// Protezione sonno, che deve tornare a dove l'utente si e assopito.
goTo: (token) => {
if (token) loadEpisode(token);
}
};
}
// src/state/resume.js
var _activeToken = "";
var _stopSavingFn = null;
var getActiveToken = () => _activeToken;
var setActiveToken = (t) => {
_activeToken = t;
};
var getStopSavingFn = () => _stopSavingFn;
var setStopSavingFn = (fn) => {
_stopSavingFn = fn;
};
var resumeKey = () => KEY_RESUME_PFX + (_activeToken || location.pathname);
var resumeTs = () => resumeKey() + ":ts";
function saveResumePos(t) {
if (!isResumeOn()) return;
if (!isFinite(t) || t <= RESUME_MIN_POS) {
lsDel(resumeKey());
lsDel(resumeTs());
return;
}
lsSet(resumeKey(), String(t));
lsSet(resumeTs(), String(Date.now()));
}
function cleanupResumeStorage() {
try {
const now = Date.now();
Array.from({ length: localStorage.length }, (_, i) => localStorage.key(i)).forEach((k) => {
if (!k?.startsWith(KEY_RESUME_PFX) || k.endsWith(":ts")) return;
const ts = parseFloat(lsGet(k + ":ts") ?? "");
if (isNaN(ts) || now - ts > RESUME_MAX_AGE) {
lsDel(k);
lsDel(k + ":ts");
}
});
} catch {
}
}
// src/state/pending-seek.js
var pending = null;
function setPendingSeek(token, time) {
if (!token || !Number.isFinite(time) || time < 0) return;
pending = { token: String(token), time };
}
function takePendingSeek(token) {
const p = pending;
pending = null;
if (!p) return null;
return token && String(token) === p.token ? p.time : null;
}
// src/player/resume-logic.js
function buildResumeLogic(video, wrap, speedValFn) {
let boundToken = getActiveToken();
const myResumeKey = () => KEY_RESUME_PFX + (boundToken || location.pathname);
const myResumeTs = () => myResumeKey() + ":ts";
function showResumeToast(seconds) {
flashToast(wrap, `▶ Ripreso da ${fmt(seconds)}`);
}
function attemptResume() {
if (!isResumeOn() || !isFinite(video.duration)) return;
const saved = parseFloat(lsGet(myResumeKey()) ?? "");
if (!saved || saved < RESUME_MIN_POS) return;
if (video.duration - saved < RESUME_END_GAP) {
lsDel(myResumeKey());
lsDel(myResumeTs());
return;
}
video.currentTime = saved;
showResumeToast(saved);
}
video.addEventListener("loadedmetadata", () => {
if (video.playbackRate !== speedValFn()) video.playbackRate = speedValFn();
const marked = takePendingSeek(boundToken);
if (marked != null && isFinite(video.duration)) {
video.currentTime = Math.min(marked, video.duration - 0.1);
showResumeToast(video.currentTime);
return;
}
attemptResume();
}, { once: true });
let saveTimer = null, episodeEnded = false;
const saveNow = () => {
if (!isResumeOn()) return;
if (!isFinite(video.currentTime) || video.currentTime <= RESUME_MIN_POS) {
lsDel(myResumeKey());
lsDel(myResumeTs());
return;
}
if (isFinite(video.duration) && video.duration - video.currentTime < RESUME_END_GAP) return;
lsSet(myResumeKey(), String(video.currentTime));
lsSet(myResumeTs(), String(Date.now()));
};
const startSaving = () => {
if (saveTimer) return;
saveTimer = setInterval(saveNow, SAVE_INTERVAL_MS);
};
const stopSaving = () => {
clearInterval(saveTimer);
saveTimer = null;
};
setStopSavingFn(stopSaving);
video.addEventListener("play", startSaving);
video.addEventListener("pause", () => {
stopSaving();
saveNow();
});
video.addEventListener("seeked", () => {
if (isFinite(video.duration) && video.duration - video.currentTime >= RESUME_END_GAP) {
episodeEnded = false;
saveNow();
}
});
video.addEventListener("ended", () => {
stopSaving();
lsDel(myResumeKey());
lsDel(myResumeTs());
episodeEnded = true;
});
return { showResumeToast, stopSaving, saveNow, setToken: (t) => {
boundToken = t;
}, episodeEnded: () => episodeEnded };
}
// src/panels/panel-toggle.js
function collectPanelRows(container, rows) {
Array.from(container.children).forEach((child) => {
if (!child.classList) {
rows.push([child]);
return;
}
if (child.classList.contains("aw-np-pn-cols")) {
const colArrs = Array.from(child.querySelectorAll(":scope > .aw-np-pn-col")).map((c) => Array.from(c.children));
if (colArrs.length) {
const maxLen = Math.max.apply(null, colArrs.map((a) => a.length));
for (let i = 0; i < maxLen; i++) {
const rn = [];
colArrs.forEach((a) => {
if (a[i]) rn.push(a[i]);
});
if (rn.length) rows.push(rn);
}
return;
}
}
if (child.classList.contains("aw-np-pn-grid")) {
const cells = Array.from(child.children);
for (let i = 0; i < cells.length; i += 2) {
const pair = [cells[i]];
if (cells[i + 1]) pair.push(cells[i + 1]);
rows.push(pair);
}
return;
}
if (child.classList.contains("aw-np-pn-colorsec") || child.classList.contains("aw-np-pn-sec") || child.classList.contains("aw-np-pn-global") || child.classList.contains("aw-np-rp-group")) {
collectPanelRows(child, rows);
return;
}
rows.push([child]);
});
}
function staggerPanelRows(panel) {
const STEP = STAGGER_STEP, BASE = STAGGER_BASE;
const rows = [];
const activeSkip = panel.querySelector(".aw-np-rp-sec.show");
const body = activeSkip || panel.querySelector(".aw-np-pn-body,.aw-np-rp-body");
if (!body) return;
if (activeSkip) {
const tabs = panel.querySelector(".aw-np-rp-tabs");
if (tabs) rows.push([tabs]);
collectPanelRows(activeSkip, rows);
const footer = panel.querySelector(".aw-np-rp-footer");
if (footer) rows.push([footer]);
} else {
collectPanelRows(body, rows);
}
rows.forEach((nodes, idx) => {
const d = (BASE + idx * STEP).toFixed(3) + "s";
nodes.forEach((n) => {
if (n && n.style) {
n.style.transitionDelay = d;
}
});
});
}
function bindPanelToggle(trigger, target, others, afterClose, onToggle) {
trigger.addEventListener("click", (e) => {
e.stopPropagation();
others.forEach((o) => o.classList.remove("open"));
const willOpen = !target.classList.contains("open");
if (willOpen) {
if (onToggle) onToggle(true);
staggerPanelRows(target);
void target.offsetHeight;
target.classList.add("open");
} else {
target.classList.remove("open");
if (onToggle) onToggle(false);
if (afterClose) afterClose();
}
});
target.addEventListener("click", (e) => e.stopPropagation());
}
// src/lib/ripple.js
function addRipple(btn) {
btn.addEventListener("pointerdown", (e) => {
const r = btn.getBoundingClientRect(), size = Math.max(r.width, r.height);
const ripple = document.createElement("span");
ripple.className = "np-ripple";
ripple.style.cssText = `width:${size}px;height:${size}px;left:${e.clientX - r.left - size / 2}px;top:${e.clientY - r.top - size / 2}px;`;
(btn.querySelector(".np-ripple-layer") || btn).appendChild(ripple);
ripple.addEventListener("animationend", () => ripple.remove());
});
}
// src/player/player-lifecycle.js
function attachExitSavers(onUnload, onStorage) {
const onVisibility = () => {
if (document.visibilityState === "hidden") onUnload();
};
window.addEventListener("beforeunload", onUnload);
window.addEventListener("pagehide", onUnload);
document.addEventListener("visibilitychange", onVisibility);
window.addEventListener("storage", onStorage);
return () => {
window.removeEventListener("beforeunload", onUnload);
window.removeEventListener("pagehide", onUnload);
document.removeEventListener("visibilitychange", onVisibility);
window.removeEventListener("storage", onStorage);
};
}
function teardownVideo(video) {
try {
video.pause();
} catch {
}
video.src = "";
const pv = getPreloadedVideo();
if (pv) {
try {
pv.pause();
} catch {
}
pv.src = "";
pv.remove();
setPreloadedVideo(null);
}
setPreloadedToken(null);
setPreloadedUrl(null);
}
// src/player/overlay-pause.js
function buildOverlayPause(video, panels = []) {
let depth = 0, wasPlaying = false;
const enter = () => {
if (depth === 0) wasPlaying = !video.paused;
depth++;
if (wasPlaying) video.pause();
};
const exit = () => {
depth = Math.max(0, depth - 1);
if (depth === 0 && wasPlaying) video.play().catch(() => {
});
};
const observers = panels.map((panel) => {
let wasOpen = false;
const obs = new MutationObserver(() => {
const o = panel.classList.contains("open");
if (o && !wasOpen) {
wasOpen = true;
enter();
} else if (!o && wasOpen) {
wasOpen = false;
exit();
}
});
obs.observe(panel, { attributes: true, attributeFilter: ["class"] });
return obs;
});
const disconnect = () => observers.forEach((o) => o.disconnect());
return { enter, exit, disconnect };
}
// src/widgets/connection-monitor.js
var CONN_SVG_SLOW = '<path d="M12 2L1 21h22L12 2zm0 3.5L21 20H3L12 5.5zM11 10v4h2v-4zm0 6v2h2v-2z" fill="#fff"/>';
var CONN_SVG_GOOD = '<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" fill="#fff"/>';
var CONN_MSG_SLOW = "<span>Connessione lenta, video</span><span>potenzialmente instabile</span>";
var CONN_MSG_GOOD = "<span>Connessione buona, video</span><span>potenzialmente stabile</span>";
function _buildConnToast(wrap) {
let toast = wrap.querySelector("#aw-np-conn-toast");
if (toast) return toast;
toast = mk("div", "aw-np-conn-toast");
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
icon.setAttribute("viewBox", "0 0 24 24");
icon.setAttribute("width", "13");
icon.setAttribute("height", "13");
icon.style.flexShrink = "0";
const msg = mk("div");
msg.className = "conn-msg";
toast.append(icon, msg);
wrap.appendChild(toast);
return toast;
}
function _showConnToast(wrap, slow, hideTimerRef) {
const toast = _buildConnToast(wrap);
const icon = toast.querySelector("svg"), msg = toast.querySelector(".conn-msg");
icon.innerHTML = slow ? CONN_SVG_SLOW : CONN_SVG_GOOD;
msg.innerHTML = slow ? CONN_MSG_SLOW : CONN_MSG_GOOD;
toast.classList.remove("slow", "good");
toast.classList.add(slow ? "slow" : "good");
toast.style.opacity = "1";
clearTimeout(hideTimerRef.id);
hideTimerRef.id = setTimeout(() => {
toast.style.opacity = "0";
}, CONN_TOAST_MS);
}
function buildConnectionMonitor(video, wrap) {
const SLOW_TH = 1.3, GOOD_TH = 1.8, WIN = 6, SLOW_COUNT = 4, GOOD_COUNT = 6, MIN_SAMPLE_INTERVAL = 950;
const samples = new Float32Array(WIN);
let sIdx = 0, sCount = 0, sSum = 0;
let lastBufEnd = null, lastTs2 = null, connState = null, slowN = 0, goodN = 0, initialShown = false;
const hideRef = { id: null };
const getBufEnd = () => {
const n = video.buffered.length;
if (!n) return null;
const ct = video.currentTime;
for (let i = n - 1; i >= 0; i--) {
if (video.buffered.start(i) <= ct + 0.5) return video.buffered.end(i);
}
return null;
};
const pushSample = (v) => {
if (sCount < WIN) {
samples[sIdx] = v;
sSum += v;
sCount++;
} else {
sSum += v - samples[sIdx];
samples[sIdx] = v;
}
sIdx = (sIdx + 1) % WIN;
};
const onProgress = () => {
if (!isConnMonitorOn()) {
const t = wrap.querySelector("#aw-np-conn-toast");
if (t) t.style.opacity = "0";
return;
}
if (video.networkState !== 2) return;
const now = Date.now();
if (lastTs2 !== null && now - lastTs2 < MIN_SAMPLE_INTERVAL) return;
const bufEnd = getBufEnd();
if (bufEnd === null) {
lastBufEnd = null;
lastTs2 = null;
return;
}
if (bufEnd - video.currentTime > 25) {
lastBufEnd = bufEnd;
lastTs2 = now;
return;
}
if (lastBufEnd !== null && lastTs2 !== null) {
const dt = (now - lastTs2) / 1e3, db = bufEnd - lastBufEnd;
if (dt > 0.1 && db >= 0) {
pushSample(db / dt);
if (sCount >= WIN) {
const avg = sSum / WIN;
const isSlow = avg < SLOW_TH, isGood = avg >= GOOD_TH;
if (isSlow) {
slowN++;
goodN = 0;
} else if (isGood) {
goodN++;
slowN = 0;
}
if (!initialShown) {
if (slowN >= SLOW_COUNT || goodN >= GOOD_COUNT) {
initialShown = true;
connState = slowN >= SLOW_COUNT ? "slow" : "good";
_showConnToast(wrap, connState === "slow", hideRef);
}
} else {
if (slowN >= SLOW_COUNT && connState !== "slow") {
connState = "slow";
_showConnToast(wrap, true, hideRef);
} else if (goodN >= GOOD_COUNT && connState !== "good") {
connState = "good";
_showConnToast(wrap, false, hideRef);
}
}
}
}
}
lastBufEnd = bufEnd;
lastTs2 = now;
};
const onSeeking = () => {
lastBufEnd = null;
lastTs2 = null;
sCount = 0;
sIdx = 0;
sSum = 0;
slowN = 0;
goodN = 0;
};
video.addEventListener("progress", onProgress);
video.addEventListener("seeking", onSeeking);
return { clearMonitor: () => {
video.removeEventListener("progress", onProgress);
video.removeEventListener("seeking", onSeeking);
clearTimeout(hideRef.id);
} };
}
// src/widgets/clock.js
function buildClock(wrap) {
const fmt2 = (n) => String(n).padStart(2, "0");
const _ico = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line class="hand-m" x1="12" y1="12" x2="12" y2="5.8"/><line class="hand-h" x1="12" y1="12" x2="12" y2="8"/><circle class="pin" cx="12" cy="12" r="1.1"/></svg>';
const timeEl = el("span");
const clockEl = el("div", null, { id: "aw-np-clock", html: _ico, kids: [timeEl] });
const handH = clockEl.querySelector(".hand-h");
const handM = clockEl.querySelector(".hand-m");
const tick = () => {
const d = /* @__PURE__ */ new Date(), h = d.getHours(), m = d.getMinutes();
timeEl.textContent = fmt2(h) + ":" + fmt2(m);
handH.setAttribute("transform", `rotate(${h % 12 * 30 + m * 0.5} 12 12)`);
handM.setAttribute("transform", `rotate(${m * 6} 12 12)`);
};
tick();
let timer;
const sync = () => {
tick();
timer = setInterval(tick, 6e4);
};
const initTimer = setTimeout(() => sync(), (60 - (/* @__PURE__ */ new Date()).getSeconds()) * 1e3);
wrap.appendChild(clockEl);
return { clockEl, clearTimer: () => {
clearTimeout(initTimer);
clearInterval(timer);
} };
}
// src/player/common-chrome.js
function mountCommonChrome(video, wrap, dotEl, currentColor, _play, { mobile, freezeTip }) {
const { clearMonitor } = buildConnectionMonitor(video, wrap);
applyColor(currentColor, wrap, dotEl);
const uiScale = loadUiScale();
applyUiScale(wrap, uiScale);
applyPanelScale(wrap, mobile, uiScale);
const panelScaleRO = new ResizeObserver(() => applyPanelScale(wrap, mobile, loadUiScale()));
panelScaleRO.observe(wrap);
const { clearTimer: clearClockTimer } = buildClock(wrap);
const { reloadVideo, clearTimer: clearFreezeTimer } = buildErrorHandler(video, wrap, _play, freezeTip);
return { clearMonitor, panelScaleRO, clearClockTimer, reloadVideo, clearFreezeTimer };
}
// src/player/prefs-sync.js
function runPrefsSync(ctx) {
const {
resumeToggle,
autoEpToggle,
autoPlayToggle,
connMonitorToggle,
globalToggle,
reloadSeek,
reloadSpeed,
updateSpeedInd,
showSpeedPopup,
getSpeedVal,
getColor,
setColor,
wrap,
dotEl,
syncCustomInput,
updateSwatches,
iconColorToggle,
topColorToggle,
flashToggle,
clockToggle,
speedPopupToggle,
colorGlobalToggle
} = ctx;
resumeToggle.checked = isResumeOn();
autoEpToggle.checked = isAutoEpOn();
autoPlayToggle.checked = isAutoPlayOn();
connMonitorToggle.checked = isConnMonitorOn();
if (globalToggle) globalToggle.checked = isGlobalOn();
reloadSeek();
reloadSpeed();
updateSpeedInd(getSpeedVal());
showSpeedPopup(getSpeedVal());
const newColor = loadColor();
if (newColor !== getColor()) {
setColor(newColor);
applyColor(newColor, wrap, dotEl);
syncCustomInput(newColor);
updateSwatches(newColor);
}
const iconOn = isIconColorOn();
wrap.classList.toggle("accent-icons", iconOn);
iconColorToggle.checked = iconOn;
const topOn = isTopColorOn();
wrap.classList.toggle("accent-top", topOn);
topColorToggle.checked = topOn;
flashToggle.checked = isFlashOn();
const clockOn = isClockOn();
wrap.classList.toggle("clock-hidden", !clockOn);
clockToggle.checked = clockOn;
const spOn = isSpeedPopupOn();
wrap.classList.toggle("speed-popup-hidden", !spOn);
speedPopupToggle.checked = spOn;
colorGlobalToggle.checked = isColorGlobalOn();
}
// src/player/mail-badge.js
function wireMailBadge(mailBtn, setOnUnreadChange, refreshMail) {
const mailBadgeEl = mailBtn.querySelector(".aw-np-mail-badge");
setOnUnreadChange((u) => {
if (mailBadgeEl) mailBadgeEl.style.display = u ? "block" : "none";
});
refreshMail();
}
// src/widgets/skip-popup.js
function buildSkipPopup(wrap) {
const ic = el("span", "aw-np-skip-popup-ic");
const txt = el("span", "aw-np-skip-popup-txt");
const popup = el("button", null, { id: "aw-np-skip-popup", attrs: { type: "button", tabindex: "-1" }, kids: [ic, txt] });
wrap.appendChild(popup);
let timer = null, cb = null;
function hide() {
popup.classList.remove("show");
clearTimeout(timer);
cb = null;
}
popup.addEventListener("click", (e) => {
e.stopPropagation();
const fn = cb;
hide();
if (fn) fn();
});
function show(text, iconHtml, onClick) {
txt.textContent = text;
ic.innerHTML = iconHtml || "";
cb = onClick;
popup.classList.add("show");
clearTimeout(timer);
timer = setTimeout(hide, SKIP_POPUP_MS);
}
return { show, hide };
}
// src/widgets/awake-guard.js
var MARK_MIN_MS = 1e3;
var lastTs = Date.now();
var mark = null;
var markTs = 0;
var curVideo = null;
var curWrap = null;
var wired = false;
var warnOn = false;
var warnWrap = null;
var currentToken = () => {
try {
return document.querySelector(".episode a.active")?.dataset.id || null;
} catch {
return null;
}
};
var active = () => isSkipOn() && isAutoNextOn() && isAwakeGuardOn();
var idleMs = () => Date.now() - lastTs;
function takeMark() {
if (!curVideo || !Number.isFinite(curVideo.currentTime)) return;
mark = { token: currentToken(), ep: getEpisodeNum(), time: curVideo.currentTime };
markTs = Date.now();
}
function onActivity() {
const now = Date.now();
lastTs = now;
if (now - markTs >= MARK_MIN_MS) takeMark();
if (warnOn) hideWarning();
}
function wire() {
if (wired) return;
wired = true;
const opts = { passive: true, capture: true };
for (const evt of ["pointerdown", "pointermove", "wheel", "touchstart", "keydown"]) {
document.addEventListener(evt, onActivity, opts);
}
}
function warnEl(wrap) {
let box = wrap.querySelector("#aw-np-awake-toast");
if (box) return box;
box = el("div", null, {
id: "aw-np-awake-toast",
kids: [
el("span", "awake-q", { text: "Sei ancora svegliә?" }),
el("span", "awake-hint", { text: isMobile ? "Tocca lo schermo per confermare" : "Muovi il mouse o clicca per confermare" })
]
});
wrap.appendChild(box);
return box;
}
function showWarning(wrap) {
if (warnOn && warnWrap === wrap) return;
warnEl(wrap).classList.add("show");
warnOn = true;
warnWrap = wrap;
}
function hideWarning() {
warnOn = false;
try {
warnWrap?.querySelector("#aw-np-awake-toast")?.classList.remove("show");
} catch {
}
warnWrap = null;
}
function markLabel() {
if (!mark) return null;
const t = fmt(mark.time);
return mark.ep ? `EP ${mark.ep} - ${t}` : t;
}
function showSuspended(wrap, { goTo, rearm } = {}) {
if (wrap.querySelector("#aw-np-awake-overlay")) return;
const label = markLabel();
const target = mark ? { token: mark.token, time: mark.time } : null;
const close = () => {
wrap.querySelector("#aw-np-awake-overlay")?.remove();
rearm?.();
};
const mins = Math.max(1, Math.round(idleMs() / 6e4));
const kids = [
el("div", "awake-card-head", {
kids: [
el("span", "awake-card-ic", { html: IC.moon }),
el("span", "awake-card-title", { text: "Rilevato colpo di sonno" }),
el("button", "awake-card-x", {
html: IC.close,
attrs: { type: "button", "aria-label": "Chiudi" },
on: { click: (e) => {
e.stopPropagation();
close();
} }
})
]
}),
el("div", "awake-card-body", { text: `Nessuna attività per ${mins} ${mins === 1 ? "minuto" : "minuti"},` }),
el("div", "awake-card-body", { text: "auto-next sospeso per sicurezza." })
];
if (label) {
const go = el("button", "awake-card-go", {
text: label,
attrs: { type: "button" },
on: {
click: (e) => {
e.stopPropagation();
if (!target?.token) return;
close();
if (curVideo && target.token === currentToken()) {
curVideo.currentTime = target.time;
curVideo.play?.().catch?.(() => {
});
return;
}
setPendingSeek(target.token, target.time);
goTo?.(target.token);
}
}
});
if (!target?.token) go.disabled = true;
kids.push(el("div", "awake-card-mark", { kids: [el("span", null, { text: "Ultima attività: " }), go] }));
}
const card = el("div", null, { id: "aw-np-awake-card", kids, on: { click: (e) => e.stopPropagation() } });
const overlay = el("div", null, { id: "aw-np-awake-overlay", kids: [card], on: { click: close } });
wrap.appendChild(overlay);
hideWarning();
}
function awakeTick(video, wrap) {
curVideo = video;
curWrap = wrap;
wire();
if (!mark) takeMark();
if (!active()) {
if (warnOn) hideWarning();
return;
}
if (idleMs() >= AWAKE_IDLE_MS) showWarning(wrap);
else if (warnOn) hideWarning();
}
function isAwakeBlocked() {
return active() && idleMs() >= AWAKE_IDLE_MS;
}
// src/player/skip-engine.js
var validSeg = (s) => !!s && Number.isFinite(s.start) && Number.isFinite(s.end) && s.end > s.start;
var partAt = (s, t) => {
if (!validSeg(s)) return null;
const parts = s.parts || [{ start: s.start, end: s.end }];
for (let i = 0; i < parts.length; i++) {
const p = parts[i];
if (t >= p.start && t < p.end) return { start: p.start, end: p.end, i };
}
return null;
};
var lastPartStart = (s) => {
if (!s) return null;
if (!s.parts) return s.start;
return s.parts.length ? s.parts[s.parts.length - 1].start : null;
};
function buildSkipEngine({ video, wrap, popup, ep, flash }) {
let db = { exists: false, episodes: {} };
let segs = { op: null, ed: null };
let skipped = {};
let lastSkip = null;
let curKey = null;
let autoNextDone = false;
let hasNextEp = null;
let pendingSuppress = null;
let dbFetched = false;
function ensureDb() {
if (dbFetched) return;
const malId = getMalId();
if (!malId) return;
dbFetched = true;
fetchSkipDb(malId, getAudioType()).then((d) => {
db = d;
loadSegs();
});
}
if (isSkipOn()) ensureDb();
function loadSegs() {
const e = db.episodes[String(getEpisodeNum())] || { op: null, ed: null };
segs = { op: e.op, ed: e.ed };
}
function hasNext() {
if (hasNextEp === null) hasNextEp = !!getAdjacentEpisode("next");
return hasNextEp;
}
function closeOffer() {
popup.hide();
curKey = null;
}
function doSkip(seg, p, viaAuto) {
const key = seg + "#" + p.i;
lastSkip = { start: p.start, ts: Date.now(), key };
video.currentTime = Number.isFinite(video.duration) ? Math.min(p.end, video.duration - 0.1) : p.end;
popup.hide();
if (viaAuto) {
skipped[key] = true;
flashToast(wrap, seg === "op" ? "Saltata OP" : "Saltata ED");
} else if (flash) flash(IC.skip);
}
function loadNext() {
closeOffer();
ep.next();
}
function evaluate() {
awakeTick(video, wrap);
if (!isSkipOn()) {
closeOffer();
return;
}
ensureDb();
const t = video.currentTime, dur = video.duration;
if (!Number.isFinite(t) || !Number.isFinite(dur) || dur <= 0) {
closeOffer();
return;
}
const op = segs.op, ed = segs.ed;
const edIsEnd = validSeg(ed) && dur - ed.end <= SKIP_END_GAP;
const opPart = isOpeningOn() ? partAt(op, t) : null;
const edPart = isEndingOn() && !edIsEnd ? partAt(ed, t) : null;
const inEndZone = dur > SKIP_END_GAP && t >= dur - SKIP_END_GAP;
const edTrig = edIsEnd && isEndingOn() ? lastPartStart(ed) : null;
const inFinalEd = Number.isFinite(edTrig) && t >= edTrig;
if (isAutoNextOn() && !autoNextDone && !video.seeking && dur > SKIP_END_GAP && hasNext()) {
if (inFinalEd && isAutoSkipOn() || t >= dur - AUTONEXT_TAIL) {
autoNextDone = true;
if (isAwakeBlocked()) {
closeOffer();
showSuspended(wrap, { goTo: ep.goTo, rearm: () => {
autoNextDone = false;
} });
return;
}
loadNext();
return;
}
}
const seg = opPart ? "op" : edPart ? "ed" : null;
const p = opPart || edPart;
let key = null;
if (seg) key = seg + "#" + p.i;
else if (hasNext() && (isAutoNextOn() ? inFinalEd && !isAutoSkipOn() : inEndZone)) key = "next";
if (pendingSuppress) {
const s = pendingSuppress;
pendingSuppress = null;
if (key && key === s) {
popup.hide();
curKey = key;
return;
}
}
if (key === curKey) return;
popup.hide();
curKey = key;
if (!key) return;
if (key === "next") {
popup.show("Prossimo episodio", IC.next, loadNext);
return;
}
if (isAutoSkipOn() && !skipped[key] && !video.seeking) doSkip(seg, p, true);
else popup.show(seg === "op" ? "Salta intro" : "Salta sigla", IC.skip, () => doSkip(seg, p, false));
}
function onSeeked() {
popup.hide();
curKey = null;
evaluate();
}
function enabledSegAt() {
if (!isSkipOn()) return null;
ensureDb();
const t = video.currentTime, op = segs.op, ed = segs.ed;
const opPart = isOpeningOn() ? partAt(op, t) : null;
if (opPart) return { seg: "op", p: opPart };
const edPart = isEndingOn() ? partAt(ed, t) : null;
if (edPart) return { seg: "ed", p: edPart };
return null;
}
function skip() {
const e = enabledSegAt();
if (e) doSkip(e.seg, e.p, false);
else ep.skip();
}
function undo() {
if (isSkipOn() && lastSkip && Date.now() - lastSkip.ts <= SKIP_UNDO_MS) {
if (lastSkip.key) {
skipped[lastSkip.key] = true;
pendingSuppress = lastSkip.key;
}
video.currentTime = lastSkip.start;
lastSkip = null;
if (flash) flash(IC.undo);
return;
}
ep.undo();
}
function reload() {
skipped = {};
lastSkip = null;
autoNextDone = false;
hasNextEp = null;
pendingSuppress = null;
closeOffer();
loadSegs();
}
video.addEventListener("timeupdate", evaluate);
video.addEventListener("seeked", onSeeked);
function clear() {
video.removeEventListener("timeupdate", evaluate);
video.removeEventListener("seeked", onSeeked);
popup.hide();
}
return { skip, undo, reload, clear };
}
// src/player/player-desktop.js
function buildPlayerDesktop(videoUrl) {
const { video, vol, muted, _play } = initVideo(videoUrl);
const { wrap, grad, gradTop, spinner, center, ctrls, timeEl } = buildShellElements();
const { speedIndEl, speedPopup, updateSpeedInd, showSpeedPopup, clearTimer: clearSpeedTimer } = buildSpeedIndicator(loadSpeed());
const { bufPctEl, clearTimer: clearBufTimer } = buildBufferingIndicator(video, spinner);
const { seekWrap, isSeeking } = buildSeekBar(video, true, () => {
timeEl.textContent = fmt(video.currentTime) + " / " + fmt(video.duration);
});
const { settingsPanel, resumeToggle, autoEpToggle, autoPlayToggle, connMonitorToggle, globalToggle, seekSecs: getSeekSecs, speedVal: getSpeedVal, reloadSeek, reloadSpeed } = buildSettingsPanel(video, "Secondi saltati con le freccette.", (v) => {
updateSpeedInd(v);
showSpeedPopup(v);
});
const { colorPanel, syncCustomInput, updateSwatches, topColorToggle, iconColorToggle, flashToggle, clockToggle, speedPopupToggle, colorGlobalToggle } = buildColorPanel(wrap, null);
let currentColor = loadColor();
const { topBar, dotEl, dotBtn, kbdBtn, browserBtn, reportBtn, mailBtn } = buildTopBar(wrap, colorPanel);
const { bpPanel } = buildBpHomepage();
const { reportPanel, reset: resetReport, clearReportTimer } = buildReportPanel(wrap);
const { enter: overlayPauseEnter, exit: overlayPauseExit, disconnect: disconnectOverlayPause } = buildOverlayPause(video, [settingsPanel, colorPanel, reportPanel, bpPanel]);
const { mailPanel, refresh: refreshMail, setOnUnreadChange, disconnectMail } = buildMailPanel({ onOpen: overlayPauseEnter, onClose: overlayPauseExit });
let hotkeyMap = loadHotkeys();
const hkSuffix = (a) => {
const k = hotkeyMap[a];
return k ? ` (${k.toUpperCase()})` : "";
};
const updateKbdLetter = () => {
const kl = kbdBtn.querySelector(".aw-np-kbd-letter");
if (kl) kl.textContent = (hotkeyMap.legend || "a").toUpperCase();
setTip(kbdBtn, "Hotkey" + hkSuffix("legend"));
};
updateKbdLetter();
const { overlay: hotkeyOverlay, open: openHotkey, close: closeHotkey, isOpen: isHotkeyOpen, isEditing: isEditingHotkey, setMap: setHotkeyMap, destroy: destroyHotkey } = buildHotkeyPanel(hotkeyMap, (m) => {
hotkeyMap = m;
updateKbdLetter();
applyHotkeyTips();
}, { onOpen: overlayPauseEnter, onClose: overlayPauseExit });
wrap.appendChild(hotkeyOverlay);
kbdBtn.addEventListener("click", (e) => {
e.stopPropagation();
[settingsPanel, colorPanel, reportPanel, mailPanel, bpPanel].forEach((p) => p.classList.remove("open"));
openHotkey();
});
const btnPlay = mkBtn("aw-btn-play", "", "Riproduci"), btnRestart = mkBtn("aw-btn-restart", IC.restart, "Ricomincia (R)");
const btnMute = mkBtn("aw-btn-mute", "", muted ? "Audio (M)" : "Muto (M)");
const btnUndo = mkBtn("aw-btn-undo", IC.undo, "Annulla skip (B)"), btnSkip = mkBtn("aw-btn-skip", IC.skip, "Skip OP/ED (O)");
const btnPrev = mkBtn("aw-btn-prev", IC.prev, "Precedente (P)"), btnNext = mkBtn("aw-btn-next", IC.next, "Successivo (N)");
const btnSettings = mkBtn("aw-btn-settings", IC.settings, "Impostazioni"), btnPip = mkBtn("aw-btn-pip", IC.pip, "Picture in Picture");
const btnFs = mkBtn("aw-btn-fs", "", "Fullscreen (F)");
const icoPlay = mkIcon(btnPlay, IC.play), icoMute = mkIcon(btnMute, muted ? IC.mute : IC.vol), icoFs = mkIcon(btnFs, IC.fsOn);
const volGroup = mk("div", "aw-np-vol-group"), volPopup = mk("div", "aw-np-vol-popup"), volPctEl = mk("div", "aw-np-vol-pct"), volEl = mk("input", "aw-np-vol");
volEl.type = "range";
volEl.min = 0;
volEl.max = 100;
volEl.value = muted ? 0 : Math.round(vol * 100);
volEl.tabIndex = -1;
volPctEl.textContent = (muted ? 0 : Math.round(vol * 100)) + "%";
volPopup.append(volPctEl, volEl);
volGroup.append(volPopup, btnMute);
let lastNonZeroVol = muted ? vol || 1 : vol;
const updateMuteState = () => {
const m = video.muted || video.volume === 0;
setIcon(icoMute, m ? IC.mute : IC.vol);
setTip(btnMute, (m ? "Audio" : "Muto") + hkSuffix("mute"));
};
const updateVolUi = () => {
const pct = video.muted || video.volume === 0 ? 0 : Math.round(video.volume * 100);
volPctEl.textContent = pct + "%";
volEl.style.background = `linear-gradient(to top, var(--np-accent,#fff) ${pct}%, rgba(255,255,255,.25) ${pct}%)`;
};
updateVolUi();
const { volFlash, showVolFlash, clearTimer: clearVolFlashTimer } = buildVolFlash(video);
volEl.addEventListener("input", () => {
const v = Number(volEl.value) / 100;
video.volume = v;
video.muted = v === 0;
if (v > 0) lastNonZeroVol = v;
updateMuteState();
updateVolUi();
saveVol(v === 0 ? lastNonZeroVol : v, video.muted);
});
volEl.addEventListener("pointerup", () => volEl.blur());
btnMute.addEventListener("click", () => {
if (video.muted || video.volume === 0) {
video.muted = false;
video.volume = lastNonZeroVol;
volEl.value = Math.round(video.volume * 100);
} else {
if (video.volume > 0) lastNonZeroVol = video.volume;
video.muted = true;
volEl.value = 0;
}
updateMuteState();
updateVolUi();
saveVol(video.muted ? lastNonZeroVol : video.volume, video.muted);
showVolFlash(video.muted ? "down" : "up");
});
const bar = mk("div", "aw-np-bar");
bar.append(btnPlay, btnRestart, volGroup, timeEl, speedIndEl, mk("div", "aw-np-spacer"), btnUndo, btnSkip, btnPrev, btnNext, btnSettings, btnPip, btnFs);
ctrls.append(seekWrap, bar);
bindPanelToggle(btnSettings, settingsPanel, [colorPanel, reportPanel, mailPanel, bpPanel]);
bindPanelToggle(dotBtn, colorPanel, [settingsPanel, reportPanel, mailPanel, bpPanel]);
bindPanelToggle(reportBtn, reportPanel, [settingsPanel, colorPanel, mailPanel, bpPanel], null, (o) => {
if (o) resetReport();
});
reportPanel.addEventListener("click", (e) => {
if (e.target === reportPanel) reportPanel.classList.remove("open");
});
bindPanelToggle(mailBtn, mailPanel, [settingsPanel, colorPanel, reportPanel, bpPanel]);
bindPanelToggle(browserBtn, bpPanel, [settingsPanel, colorPanel, reportPanel, mailPanel]);
mailBtn.addEventListener("click", () => {
const wasOpen = mailPanel.classList.contains("open");
if (wasOpen) refreshMail();
}, true);
wrap.addEventListener("click", () => {
settingsPanel.classList.remove("open");
colorPanel.classList.remove("open");
reportPanel.classList.remove("open");
mailPanel.classList.remove("open");
bpPanel.classList.remove("open");
});
wrap.append(video, grad, gradTop, topBar, colorPanel, reportPanel, mailPanel, bpPanel, spinner, bufPctEl, speedPopup, center, settingsPanel, ctrls, volFlash);
wireMailBadge(mailBtn, setOnUnreadChange, refreshMail);
const { clearMonitor, panelScaleRO, clearClockTimer, reloadVideo, clearFreezeTimer } = mountCommonChrome(video, wrap, dotEl, currentColor, _play, { mobile: false, freezeTip: "Sblocca video (S)" });
let hideTimer = null;
const showUi = () => {
wrap.classList.add("ui");
clearTimeout(hideTimer);
if (!video.paused) hideTimer = setTimeout(() => {
if (settingsPanel.classList.contains("open") || colorPanel.classList.contains("open") || reportPanel.classList.contains("open") || mailPanel.classList.contains("open") || bpPanel.classList.contains("open")) return;
wrap.classList.remove("ui");
}, HIDE_DELAY_MS);
};
wrap.addEventListener("pointermove", showUi);
wrap.addEventListener("pointerleave", () => {
if (!video.paused) wrap.classList.remove("ui");
});
video.addEventListener("pause", () => {
if (video.dataset.silentReload) return;
wrap.classList.add("ui");
clearTimeout(hideTimer);
});
video.addEventListener("play", () => {
if (video.dataset.silentReload) return;
showUi();
});
const { flash, flashBrief, playPauseFlash, setSkipFlash, clearTimer: clearCenterTimer } = buildCenterFlash(center);
[btnPlay, btnRestart, btnMute, btnUndo, btnSkip, btnPrev, btnNext, btnSettings, btnPip, btnFs].forEach(addRipple);
const toggle = () => video.paused ? _play() : video.pause();
video.addEventListener("click", toggle);
video.addEventListener("dblclick", () => btnFs.click());
btnPlay.addEventListener("click", toggle);
wirePlayPauseEvents({ video, onUpdate: (p) => {
setIcon(icoPlay, p ? IC.pause : IC.play);
setTip(btnPlay, p ? "Pausa" : "Riproduci");
}, playPauseFlash });
const ep = buildEpisodeActions({ video, _play, flash });
const skipPopup = buildSkipPopup(wrap);
const skipEng = buildSkipEngine({ video, wrap, popup: skipPopup, ep, flash });
btnRestart.addEventListener("click", ep.restart);
btnSkip.addEventListener("click", skipEng.skip);
btnUndo.addEventListener("click", skipEng.undo);
btnPrev.addEventListener("click", ep.prev);
btnNext.addEventListener("click", ep.next);
buildPipHandler({ video, btnPip, wrap, onEnter: () => setTip(btnPip, "Chiudi PiP"), onLeave: () => setTip(btnPip, "Picture in Picture") });
btnFs.addEventListener("click", () => fsElement() ? fsExit() : fsRequest(wrap));
const { showResumeToast, stopSaving, saveNow: saveResumeNow, setToken: setResumeToken, episodeEnded } = buildResumeLogic(video, wrap, getSpeedVal);
const onFs = () => {
const isFs = !!fsElement();
wrap.classList.toggle("fs", isFs);
if (isFs && isAutoPlayOn() && video.paused && !episodeEnded()) _play();
setTimeout(() => {
setIcon(icoFs, isFs ? IC.fsOff : IC.fsOn);
setTip(btnFs, (isFs ? "Esci" : "Fullscreen") + hkSuffix("fullscreen"));
}, 0);
};
const hkAction = { fullscreen: () => btnFs.click(), mute: () => btnMute.click(), restart: () => btnRestart.click(), skipOp: () => btnSkip.click(), undoSkip: () => btnUndo.click(), prev: () => btnPrev.click(), next: () => btnNext.click(), reload: () => reloadVideo(true), homepage: () => browserBtn.click(), report: () => reportBtn.click(), mailbox: () => mailBtn.click() };
const hkName = { restart: ["Ricomincia", btnRestart], skipOp: ["Skip OP/ED", btnSkip], undoSkip: ["Annulla skip", btnUndo], prev: ["Precedente", btnPrev], next: ["Successivo", btnNext], homepage: ["Homepage", browserBtn], report: ["Segnala", reportBtn], mailbox: ["Mail", mailBtn] };
const applyHotkeyTips = () => {
for (const a in hkName) {
const [name, btn] = hkName[a];
setTip(btn, name + hkSuffix(a));
}
updateMuteState();
setTip(btnFs, (fsElement() ? "Esci" : "Fullscreen") + hkSuffix("fullscreen"));
};
applyHotkeyTips();
const onKey = (e) => {
if (pipElement()) return;
if (e.ctrlKey || e.metaKey || e.altKey) return;
const tag = document.activeElement?.tagName ?? "", editable = document.activeElement?.isContentEditable;
if (/INPUT|TEXTAREA/.test(tag) || editable) return;
const k = (e.key || "").toLowerCase();
const legendKey = hotkeyMap.legend || "a";
if (isHotkeyOpen()) {
if (isEditingHotkey()) return;
if (e.key === "Escape" || k === legendKey) {
e.preventDefault();
closeHotkey();
}
return;
}
if (mailPanel.classList.contains("open")) {
if (e.key === "Escape") {
e.preventDefault();
mailPanel.classList.remove("open");
}
return;
}
if (k === legendKey) {
e.preventDefault();
openHotkey();
return;
}
if (e.key === " ") {
e.preventDefault();
toggle();
return;
}
if (e.key === "ArrowRight") {
e.preventDefault();
video.currentTime = Math.min(video.duration || 0, video.currentTime + getSeekSecs());
flashBrief(IC.seekFwd);
return;
}
if (e.key === "ArrowLeft") {
e.preventDefault();
video.currentTime = Math.max(0, video.currentTime - getSeekSecs());
flashBrief(IC.seekBwd);
return;
}
if (e.key === "ArrowUp") {
e.preventDefault();
video.volume = Math.min(1, video.volume + 0.1);
video.muted = false;
lastNonZeroVol = video.volume;
volEl.value = Math.round(video.volume * 100);
saveVol(video.volume, false);
updateMuteState();
updateVolUi();
showVolFlash("up");
return;
}
if (e.key === "ArrowDown") {
e.preventDefault();
video.volume = Math.max(0, video.volume - 0.1);
video.muted = video.volume === 0;
if (video.volume > 0) lastNonZeroVol = video.volume;
volEl.value = Math.round(video.volume * 100);
saveVol(video.muted ? lastNonZeroVol : video.volume, video.muted);
updateMuteState();
updateVolUi();
showVolFlash("down");
return;
}
if (e.key >= "0" && e.key <= "9" && video.duration) {
e.preventDefault();
const t = video.duration * (parseInt(e.key, 10) / 10);
flashBrief(t > video.currentTime ? IC.seekFwd : IC.seekBwd);
video.currentTime = t;
return;
}
for (const a in hkAction) {
if (hotkeyMap[a] === k) {
e.preventDefault();
hkAction[a]();
return;
}
}
};
const prefsCtx = { resumeToggle, autoEpToggle, autoPlayToggle, connMonitorToggle, globalToggle, reloadSeek, reloadSpeed, updateSpeedInd, showSpeedPopup, getSpeedVal, getColor: () => currentColor, setColor: (c) => {
currentColor = c;
}, wrap, dotEl, syncCustomInput, updateSwatches, iconColorToggle, topColorToggle, flashToggle, clockToggle, speedPopupToggle, colorGlobalToggle };
const onStorage = (e) => {
if (!e.key?.startsWith("aw-np-")) return;
runPrefsSync(prefsCtx);
if (e.key === KEY_HOTKEYS && !isEditingHotkey()) {
hotkeyMap = loadHotkeys();
updateKbdLetter();
setHotkeyMap?.(hotkeyMap);
applyHotkeyTips();
}
};
const onUnload = () => {
if (!episodeEnded()) saveResumeNow();
};
const removeFs = fsChange(onFs);
const detachExit = attachExitSavers(onUnload, onStorage);
document.addEventListener("keydown", onKey);
setCleanup(() => {
removeFs();
document.removeEventListener("keydown", onKey);
destroyHotkey?.();
detachExit();
stopSaving();
setStopSavingFn(null);
clearTimeout(hideTimer);
clearCenterTimer();
clearVolFlashTimer();
clearSpeedTimer();
clearFreezeTimer();
clearClockTimer();
clearMonitor();
clearBufTimer();
clearReportTimer();
disconnectMail();
disconnectOverlayPause();
skipEng.clear();
panelScaleRO.disconnect();
setSkipFlash(true);
teardownVideo(video);
setCleanup(null);
});
wrap._play = () => _play();
wrap._showResumeTst = (s) => showResumeToast(s);
wrap._setSkipFlash = setSkipFlash;
wrap._setResumeToken = (t) => setResumeToken(t);
wrap._skipReload = () => skipEng.reload();
return wrap;
}
// src/player/player-mobile.js
function buildPlayerMobile(videoUrl) {
const { video, vol, muted, _play } = initVideo(videoUrl);
const { wrap, grad, gradTop, spinner, center, ctrls, timeEl } = buildShellElements();
const { flash, flashBrief, setSkipFlash, clearTimer: clearCenterTimer } = buildCenterFlash(center);
const ep = buildEpisodeActions({ video, _play, flash });
const skipPopup = buildSkipPopup(wrap);
const skipEng = buildSkipEngine({ video, wrap, popup: skipPopup, ep, flash });
const { speedIndEl, speedPopup, updateSpeedInd, showSpeedPopup, clearTimer: clearSpeedTimer } = buildSpeedIndicator(loadSpeed());
const { bufPctEl, clearTimer: clearBufTimer } = buildBufferingIndicator(video, spinner);
const { seekWrap, isSeeking } = buildSeekBar(video, false, () => {
timeEl.textContent = fmt(video.currentTime) + " / " + fmt(video.duration);
});
const mkMBtn = (id, html) => {
const b = mk("button");
b.className = "np-btn";
b.id = id;
b.innerHTML = html;
b.tabIndex = -1;
return b;
};
const btnPlay = mkMBtn("aw-btn-play", IC.play), btnMute = mkMBtn("aw-btn-mute", muted ? IC.mute : IC.vol);
const btnPip = mkMBtn("aw-btn-pip", IC.pip), btnSettings = mkMBtn("aw-btn-settings", IC.settings);
const btnMenu = mkMBtn("aw-btn-menu", IC.menu), btnFs = mkMBtn("aw-btn-fs", IC.fsOn);
const btnRestart = mkMBtn("aw-btn-restart", IC.restart), btnUndo = mkMBtn("aw-btn-undo", IC.undo);
const btnSkip = mkMBtn("aw-btn-skip", IC.skip), btnPrev = mkMBtn("aw-btn-prev", IC.prev), btnNext = mkMBtn("aw-btn-next", IC.next);
const spacer = mk("div", "aw-np-spacer");
const bar = mk("div", "aw-np-bar");
[btnRestart, btnUndo, btnSkip, btnPrev, btnNext].forEach((b) => b.classList.add("np-landscape-only"));
btnMenu.classList.add("np-portrait-only");
bar.append(btnPlay, btnRestart, btnMute, timeEl, speedIndEl, spacer, btnUndo, btnSkip, btnPrev, btnNext, btnMenu, btnSettings, btnPip, btnFs);
ctrls.append(seekWrap, bar);
const menuPanel = mk("div", "aw-np-menu-panel");
const mkMenuBtn = (html, label, onClick) => {
const b = document.createElement("button");
b.className = "np-menu-btn";
b.innerHTML = html + `<span>${label}</span>`;
b.addEventListener("click", () => {
menuPanel.classList.remove("open");
onClick();
});
return b;
};
menuPanel.append(
mkMenuBtn(IC.restart, "Ricomincia", ep.restart),
mkMenuBtn(IC.undo, "Annulla skip", skipEng.undo),
mkMenuBtn(IC.skip, "Skip OP/ED", skipEng.skip),
mkMenuBtn(IC.prev, "Episodio prec.", ep.prev),
mkMenuBtn(IC.next, "Episodio succ.", ep.next)
);
const { settingsPanel, resumeToggle, autoEpToggle, autoPlayToggle, connMonitorToggle, seekSecs: getSeekSecs, speedVal: getSpeedVal, reloadSeek, reloadSpeed } = buildSettingsPanel(video, "Secondi saltati col doppio tap.", (v) => {
updateSpeedInd(v);
showSpeedPopup(v);
});
const { colorPanel, syncCustomInput, updateSwatches, topColorToggle, iconColorToggle, flashToggle, clockToggle, speedPopupToggle, colorGlobalToggle } = buildColorPanel(wrap, null);
let currentColor = loadColor();
const { topBar, dotEl, dotBtn, browserBtn, reportBtn, mailBtn } = buildTopBar(wrap, colorPanel);
const { bpPanel } = buildBpHomepage();
const { reportPanel, reset: resetReport, clearReportTimer } = buildReportPanel(wrap);
const { enter: overlayPauseEnter, exit: overlayPauseExit, disconnect: disconnectOverlayPause } = buildOverlayPause(video, [settingsPanel, colorPanel, reportPanel, bpPanel]);
const { mailPanel, refresh: refreshMail, setOnUnreadChange, disconnectMail } = buildMailPanel({ onOpen: overlayPauseEnter, onClose: overlayPauseExit });
const playCenter = mk("div", "aw-np-play-center");
playCenter.classList.add("np-ui-layer");
playCenter.innerHTML = IC.play;
const seekFlashLeft = document.createElement("div");
seekFlashLeft.className = "np-seek-flash";
seekFlashLeft.id = "aw-np-seek-flash-left";
seekFlashLeft.innerHTML = IC.seekBwd;
const seekFlashRight = document.createElement("div");
seekFlashRight.className = "np-seek-flash";
seekFlashRight.id = "aw-np-seek-flash-right";
seekFlashRight.innerHTML = IC.seekFwd;
const volBarLeft = document.createElement("div");
volBarLeft.id = "aw-np-vol-bar-left";
const volBarRight = document.createElement("div");
volBarRight.id = "aw-np-vol-bar-right";
const BAR_S = "position:absolute;top:0;bottom:0;width:25%;display:flex;align-items:center;justify-content:center;pointer-events:none;opacity:0;transition:opacity var(--np-t-slow) var(--np-ease);z-index:50;";
volBarLeft.style.cssText = BAR_S + "left:0;";
volBarRight.style.cssText = BAR_S + "right:0;";
const mkVolBarInner = () => {
const inner = document.createElement("div");
inner.style.cssText = "display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:6px;background:var(--np-surf-2);border:var(--np-line);border-radius:var(--np-r-lg);padding:10px 0 12px;width:44px;height:48%;box-shadow:var(--np-elev-2);";
const pct = document.createElement("div");
pct.style.cssText = "font-size:var(--np-fs-small);font-weight:500;color:var(--np-accent-bg-fg,rgba(255,255,255,.85));width:32px;text-align:center;font-variant-numeric:tabular-nums;letter-spacing:.02em;";
const track = document.createElement("div");
track.style.cssText = "position:relative;width:4px;flex:1;border-radius:var(--np-r-xs);background:rgba(255,255,255,.2);flex-shrink:0;";
const fill = document.createElement("div");
fill.style.cssText = "position:absolute;bottom:0;left:0;right:0;border-radius:var(--np-r-xs);background:var(--np-accent,#fff);";
track.appendChild(fill);
inner.append(pct, track);
return { inner, pct, fill };
};
const { inner: volInnerL, pct: volPctL, fill: volBarFillL } = mkVolBarInner();
const { inner: volInnerR, pct: volPctR, fill: volBarFillR } = mkVolBarInner();
volBarLeft.appendChild(volInnerL);
volBarRight.appendChild(volInnerR);
let lastNonZeroVol = muted ? vol || 1 : vol;
const { volFlash, showVolFlash, clearTimer: clearVolFlashTimer } = buildVolFlash(video);
wrap.append(video, grad, gradTop, topBar, colorPanel, reportPanel, mailPanel, bpPanel, menuPanel, settingsPanel, spinner, bufPctEl, speedPopup, center, playCenter, seekFlashLeft, seekFlashRight, volBarLeft, volBarRight, volFlash, ctrls);
const { clearMonitor, panelScaleRO, clearClockTimer, reloadVideo, clearFreezeTimer } = mountCommonChrome(video, wrap, dotEl, currentColor, _play, { mobile: true, freezeTip: "Sblocca video" });
let hideTimer = null;
const toggle = () => video.paused ? _play() : video.pause();
const closepanels = () => {
settingsPanel.classList.remove("open");
colorPanel.classList.remove("open");
menuPanel.classList.remove("open");
reportPanel.classList.remove("open");
mailPanel.classList.remove("open");
bpPanel.classList.remove("open");
};
const startHideTimer = () => {
clearTimeout(hideTimer);
if (!video.paused) hideTimer = setTimeout(() => {
if (!wrap.querySelector(".open")) wrap.classList.remove("ui");
}, HIDE_DELAY_MS);
};
const showUi = () => {
closepanels();
wrap.classList.add("ui");
startHideTimer();
};
const hideUi = () => {
wrap.classList.remove("ui");
clearTimeout(hideTimer);
};
const isOnControls = (e) => {
const t = e.target;
return t && (t.closest("#aw-np-controls") || t.closest("#aw-np-top") || t.closest("#aw-np-settings-panel") || t.closest("#aw-np-color-panel") || t.closest("#aw-np-menu-panel") || t.closest("#aw-np-report-panel") || t.closest("#aw-np-mail-panel") || t.closest("#aw-np-bp-panel"));
};
video.addEventListener("pause", () => {
if (video.dataset.silentReload) return;
wrap.classList.add("ui");
clearTimeout(hideTimer);
});
video.addEventListener("play", () => {
if (video.dataset.silentReload) return;
showUi();
});
[btnPlay, btnMute, btnPip, btnSettings, btnMenu, btnFs].forEach(addRipple);
btnPlay.addEventListener("click", toggle);
playCenter.addEventListener("click", (e) => {
e.stopPropagation();
toggle();
});
wirePlayPauseEvents({ video, onUpdate: (p) => {
const html = p ? IC.pause : IC.play;
btnPlay.innerHTML = html;
playCenter.innerHTML = html;
} });
btnRestart.addEventListener("click", ep.restart);
btnUndo.addEventListener("click", skipEng.undo);
btnSkip.addEventListener("click", skipEng.skip);
btnPrev.addEventListener("click", ep.prev);
btnNext.addEventListener("click", ep.next);
btnMute.addEventListener("click", () => {
if (video.muted || video.volume === 0) {
video.muted = false;
video.volume = lastNonZeroVol;
} else {
if (video.volume > 0) lastNonZeroVol = video.volume;
video.muted = true;
}
const m = video.muted || video.volume === 0;
btnMute.innerHTML = m ? IC.mute : IC.vol;
saveVol(video.muted ? lastNonZeroVol : video.volume, video.muted);
showVolFlash(video.muted ? "down" : "up");
});
buildPipHandler({ video, btnPip, wrap });
let mobileFs = false;
const setMobileFs = (state) => {
mobileFs = state;
btnFs.innerHTML = state ? IC.fsOff : IC.fsOn;
};
btnFs.addEventListener("click", () => {
if (mobileFs) {
document.exitFullscreen?.()?.catch?.(() => {
});
} else {
wrap.requestFullscreen?.()?.catch?.(() => {
});
}
});
const lockLandscape = () => {
try {
const p = screen.orientation?.lock?.("landscape");
if (p && p.catch) p.catch(() => {
});
} catch (e) {
}
};
const unlockOrient = () => {
try {
screen.orientation?.unlock?.();
} catch (e) {
}
};
const removeFsM = fsChange(() => {
const fs = !!document.fullscreenElement;
wrap.classList.toggle("fs", fs);
if (fs) lockLandscape();
else unlockOrient();
if (fs && isAutoPlayOn() && video.paused && !episodeEnded()) _play();
setTimeout(() => setMobileFs(fs), 0);
});
bindPanelToggle(btnSettings, settingsPanel, [menuPanel, colorPanel, reportPanel, mailPanel, bpPanel], startHideTimer);
bindPanelToggle(btnMenu, menuPanel, [settingsPanel, colorPanel, reportPanel, mailPanel, bpPanel], startHideTimer);
bindPanelToggle(dotBtn, colorPanel, [settingsPanel, menuPanel, reportPanel, mailPanel, bpPanel], startHideTimer);
bindPanelToggle(reportBtn, reportPanel, [settingsPanel, colorPanel, menuPanel, mailPanel, bpPanel], startHideTimer, (o) => {
if (o) resetReport();
});
reportPanel.addEventListener("click", (e) => {
if (e.target === reportPanel) reportPanel.classList.remove("open");
});
bindPanelToggle(mailBtn, mailPanel, [settingsPanel, colorPanel, menuPanel, reportPanel, bpPanel], startHideTimer);
bindPanelToggle(browserBtn, bpPanel, [settingsPanel, colorPanel, menuPanel, reportPanel, mailPanel], startHideTimer);
mailBtn.addEventListener("click", () => {
const wasOpen = mailPanel.classList.contains("open");
if (wasOpen) refreshMail();
}, true);
wireMailBadge(mailBtn, setOnUnreadChange, refreshMail);
wrap.addEventListener("click", (e) => {
if (isOnControls(e)) return;
closepanels();
startHideTimer();
});
let volBarTimer = null;
const updateVolBars = () => {
const pct = (video.muted ? 0 : video.volume) * 100;
const ps = Math.round(pct) + "%";
volBarFillL.style.height = pct + "%";
volBarFillR.style.height = pct + "%";
volPctL.textContent = ps;
volPctR.textContent = ps;
};
let volSliding = false, volSlideStartY = 0, volSlideStartX = 0, volSlideStartVol = 0, volSlideEndTime = 0;
const isFullscreenLandscape = () => window.innerWidth > window.innerHeight && (!!document.fullscreenElement || window.innerHeight === screen.height || window.innerWidth === screen.width);
const updateVolBarPosition = () => {
if (window.innerWidth < window.innerHeight) {
const vRect = video.getBoundingClientRect();
const wRect = wrap.getBoundingClientRect();
const bandH = vRect.top - wRect.top;
const topPct = bandH > 20 ? (bandH / 2 - 30) / wRect.height * 100 + "%" : "10%";
volBarLeft.style.alignItems = "flex-start";
volBarRight.style.alignItems = "flex-start";
volBarLeft.style.paddingTop = topPct;
volBarRight.style.paddingTop = topPct;
} else {
volBarLeft.style.alignItems = "center";
volBarRight.style.alignItems = "center";
volBarLeft.style.paddingTop = "0";
volBarRight.style.paddingTop = "0";
}
};
video.addEventListener("contextmenu", (e) => e.preventDefault());
wrap.addEventListener("touchstart", (e) => {
if (isOnControls(e)) return;
if (!isFullscreenLandscape()) return;
const touch = e.touches[0];
if (touch.clientX < 8 || touch.clientX > window.innerWidth - 8 || touch.clientY < 20) return;
const rect = wrap.getBoundingClientRect(), x = touch.clientX - rect.left, quarter = rect.width / 5;
if (x < quarter || x > rect.width - quarter) {
volSliding = false;
volSlideStartY = touch.clientY;
volSlideStartX = touch.clientX;
volSlideStartVol = video.muted ? 0 : video.volume;
}
}, { passive: true });
wrap.addEventListener("touchmove", (e) => {
if (isOnControls(e)) return;
if (!isFullscreenLandscape()) return;
const touch = e.touches[0], rect = wrap.getBoundingClientRect(), x = touch.clientX - rect.left, quarter = rect.width / 5;
if (x >= quarter && x <= rect.width - quarter) return;
const dy = volSlideStartY - touch.clientY, dx = touch.clientX - volSlideStartX;
if (!volSliding && Math.abs(dx) > Math.abs(dy)) return;
if (!volSliding && Math.abs(dy) < 10) return;
volSliding = true;
const newVol = Math.max(0, Math.min(1, volSlideStartVol + dy / rect.height));
video.volume = newVol;
video.muted = newVol === 0;
const m = video.muted || video.volume === 0;
btnMute.innerHTML = m ? IC.mute : IC.vol;
if (newVol > 0) lastNonZeroVol = newVol;
saveVol(newVol === 0 ? lastNonZeroVol : newVol, newVol === 0);
updateVolBars();
updateVolBarPosition();
const slidingLeft = x < quarter;
if (slidingLeft) {
volBarRight.style.opacity = "1";
volBarLeft.style.opacity = "0";
} else {
volBarLeft.style.opacity = "1";
volBarRight.style.opacity = "0";
}
clearTimeout(volBarTimer);
volBarTimer = setTimeout(() => {
volBarLeft.style.opacity = "0";
volBarRight.style.opacity = "0";
}, 1500);
}, { passive: true });
wrap.addEventListener("touchend", () => {
if (volSliding) volSlideEndTime = Date.now();
volSliding = false;
});
wrap.addEventListener("touchcancel", () => {
if (volSliding) volSlideEndTime = Date.now();
volSliding = false;
});
let tapTimer = null, lastTap = 0, seekFlashLTimer = null, seekFlashRTimer = null;
wrap.addEventListener("click", (e) => {
if (isOnControls(e)) return;
if (isSeeking()) return;
if (Date.now() - volSlideEndTime < 300) return;
const now = Date.now(), rect = wrap.getBoundingClientRect(), x = e.clientX - rect.left, quarter = rect.width / 5;
const inCenter = x >= quarter && x <= rect.width - quarter;
if (now - lastTap < 300) {
clearTimeout(tapTimer);
if (x < quarter) {
video.currentTime = Math.max(0, video.currentTime - getSeekSecs());
flashBrief(IC.seekBwd);
seekFlashLeft.classList.add("on");
clearTimeout(seekFlashLTimer);
seekFlashLTimer = setTimeout(() => seekFlashLeft.classList.remove("on"), 400);
} else if (x > rect.width - quarter) {
video.currentTime = Math.min(video.duration || 0, video.currentTime + getSeekSecs());
flashBrief(IC.seekFwd);
seekFlashRight.classList.add("on");
clearTimeout(seekFlashRTimer);
seekFlashRTimer = setTimeout(() => seekFlashRight.classList.remove("on"), 400);
} else if (inCenter) toggle();
lastTap = 0;
} else {
tapTimer = setTimeout(() => {
if (wrap.classList.contains("ui")) hideUi();
else showUi();
}, 200);
lastTap = now;
}
});
const { showResumeToast, stopSaving, saveNow: saveResumeNow, setToken: setResumeToken, episodeEnded } = buildResumeLogic(video, wrap, getSpeedVal);
const prefsCtx = { resumeToggle, autoEpToggle, autoPlayToggle, connMonitorToggle, reloadSeek, reloadSpeed, updateSpeedInd, showSpeedPopup, getSpeedVal, getColor: () => currentColor, setColor: (c) => {
currentColor = c;
}, wrap, dotEl, syncCustomInput, updateSwatches, iconColorToggle, topColorToggle, flashToggle, clockToggle, speedPopupToggle, colorGlobalToggle };
const onStorage = (e) => {
if (!e.key?.startsWith("aw-np-")) return;
runPrefsSync(prefsCtx);
};
const onUnload = () => {
if (!episodeEnded()) saveResumeNow();
};
const detachExit = attachExitSavers(onUnload, onStorage);
setCleanup(() => {
removeFsM();
detachExit();
stopSaving();
setStopSavingFn(null);
clearTimeout(hideTimer);
clearCenterTimer();
clearVolFlashTimer();
clearTimeout(volBarTimer);
clearTimeout(seekFlashLTimer);
clearTimeout(seekFlashRTimer);
clearTimeout(tapTimer);
clearSpeedTimer();
clearFreezeTimer();
clearClockTimer();
clearMonitor();
clearBufTimer();
clearReportTimer();
disconnectMail();
disconnectOverlayPause();
skipEng.clear();
panelScaleRO.disconnect();
setSkipFlash(true);
teardownVideo(video);
setCleanup(null);
});
wrap._play = () => _play();
wrap._showResumeTst = (s) => showResumeToast(s);
wrap._setSkipFlash = setSkipFlash;
wrap._setResumeToken = (t) => setResumeToken(t);
wrap._skipReload = () => skipEng.reload();
return wrap;
}
// src/state/episode-auto.js
var saveLastEpisode = (t) => {
const a = animeId();
if (a) lsSet(KEY_AUTOEP_PFX + a, t);
};
var loadLastEpisode = () => {
const a = animeId();
return a ? lsGet(KEY_AUTOEP_PFX + a) : null;
};
// src/player/player-controls.js
function mountPlayer(url) {
if (!url) return;
const container = document.querySelector("#player");
if (!container) return;
const existing = container.querySelector("#aw-np-video");
if (existing && existing.getAttribute("src") === url) return;
{
const _c = getCleanup();
if (_c) _c();
}
injectStyle();
container.innerHTML = "";
const wrap = buildPlayer(url);
container.appendChild(wrap);
}
function injectStyle() {
isMobile ? injectStyleMobile() : injectStyleDesktop();
}
function buildPlayer(url) {
return isMobile ? buildPlayerMobile(url) : buildPlayerDesktop(url);
}
function swapVideoSrc(url) {
const video = document.querySelector("#aw-np-video");
if (!video) {
mountPlayer(url);
return;
}
if (video.getAttribute("src") === url) return;
const wrap = document.querySelector("#aw-np");
const swapToken = getActiveToken();
const swapKey = KEY_RESUME_PFX + (swapToken || location.pathname);
const swapSaved = parseFloat(lsGet(swapKey) ?? "");
wrap?._setResumeToken(swapToken);
wrap?._setSkipFlash(true);
video.pause();
video.src = url;
video.addEventListener("loadedmetadata", () => {
const spd = loadSpeed();
if (video.playbackRate !== spd) video.playbackRate = spd;
const marked = takePendingSeek(swapToken);
if (marked != null && isFinite(video.duration)) {
video.currentTime = Math.min(marked, video.duration - 0.1);
wrap?._showResumeTst(video.currentTime);
} else if (isResumeOn()) {
if (swapSaved >= RESUME_MIN_POS && isFinite(video.duration) && video.duration - swapSaved >= RESUME_END_GAP) {
video.currentTime = swapSaved;
wrap?._showResumeTst(swapSaved);
}
}
wrap?._skipReload?.();
if (isAutoPlayOn() && !!fsElement()) {
wrap?._play();
video.addEventListener("playing", () => wrap?._setSkipFlash(false), { once: true });
} else {
wrap?._setSkipFlash(false);
}
}, { once: true });
["#aw-np-seek-fill", "#aw-np-seek-thumb", "#aw-np-seek-buf"].forEach((sel, i) => {
const el2 = document.querySelector(sel);
if (el2) el2.style[i === 1 ? "left" : "width"] = "0%";
});
const timeEl = document.querySelector("#aw-np-time");
if (timeEl) timeEl.textContent = "00:00 / 00:00";
}
function setActiveEpisode(token) {
const all = Array.from(document.querySelectorAll(".episode a"));
const idx = all.findIndex((a) => a.dataset.id === token);
all.forEach((a, i) => a.classList.toggle("active", i === idx));
const prevBtn = document.querySelector(".prevnext.prev");
if (prevBtn) prevBtn.style.display = idx > 0 ? "" : "none";
const nextBtn = document.querySelector(".prevnext.next");
if (nextBtn) nextBtn.style.display = idx < all.length - 1 ? "" : "none";
const epInfoEl = document.querySelector("#aw-np-epinfo");
if (epInfoEl && idx !== -1) {
const num = all[idx].textContent.trim() || String(idx + 1);
const maxNum = all.reduce((m, a) => {
const n = parseFloat(a.textContent.trim());
return isNaN(n) ? m : Math.max(m, n);
}, 0);
epInfoEl.textContent = `Episodio ${num}/${maxNum > 0 ? maxNum : all.length}`;
}
if (idx === -1) return;
const epData = all[idx].dataset;
const episodeId = epData.episodeId, epNum = epData.num || epData.episodeNum;
const player = document.querySelector("#player");
if (player) {
player.dataset.id = token;
if (episodeId) player.dataset.episodeId = episodeId;
if (epNum) player.dataset.num = epNum;
}
document.querySelectorAll(".episodeNum").forEach((el2) => el2.textContent = epNum || "");
const _animeId = document.querySelector("#player")?.dataset.animeId || "";
const _csrf = document.querySelector('meta[name="csrf-token"]')?.content || "";
const _updatePageMeta = (eId, num) => {
fetchWithRetry(`/api/episode/download?id=${eId}`, { credentials: "same-origin" }).then((r) => r.json()).then((data) => {
const dl = document.querySelector("#downloadLink");
if (dl && data?.url) dl.href = data.url;
}).catch(() => {
});
if (!_animeId) return;
fetchWithRetry(`/api/comments/anime/get/${_animeId}/${eId}`, {
method: "POST",
credentials: "same-origin",
headers: { "CSRF-Token": _csrf, "Content-Type": "application/x-www-form-urlencoded" }
}).then((r) => r.text()).then((html) => {
const el2 = document.querySelector("#comments-scrollover-wrapper");
if (el2) el2.innerHTML = html;
document.querySelector("#episode-comment")?.classList.add("active");
const sp = document.querySelector("#episode-comment span");
if (sp) sp.textContent = num;
document.querySelectorAll("#anime-comment,#rules").forEach((e) => e.classList.remove("active"));
pageWin.loadSideFeatures?.();
}).catch(() => {
});
};
if (episodeId) _updatePageMeta(episodeId, epNum);
}
function showLoadError(token) {
const msg = "Impossibile caricare l'episodio. Tocca per riprovare.";
const retry = () => {
document.querySelector("#aw-np #aw-np-load-error-toast")?.remove();
document.getElementById("aw-np-load-error")?.remove();
if (token) loadEpisode(token);
else location.reload();
};
const wrap = document.querySelector("#aw-np");
if (wrap) {
wrap.querySelector("#aw-np-load-error-toast")?.remove();
const t = document.createElement("div");
t.id = "aw-np-load-error-toast";
t.textContent = msg;
t.addEventListener("click", retry, { once: true });
wrap.appendChild(t);
return;
}
const container = document.querySelector("#player");
if (!container) return;
let box = document.getElementById("aw-np-load-error");
if (!box) {
box = document.createElement("div");
box.id = "aw-np-load-error";
box.style.cssText = 'display:flex;align-items:center;justify-content:center;min-height:220px;padding:24px;text-align:center;color:#fff;background:#16171a;border-radius:12px;cursor:pointer;font:500 15px "Google Sans",Roboto,"Helvetica Neue",sans-serif';
container.appendChild(box);
}
box.textContent = msg;
box.onclick = retry;
}
function loadEpisode(token) {
if (!token) return;
{
const _s = getStopSavingFn();
if (_s) {
_s();
setStopSavingFn(null);
}
}
const vid = document.querySelector("#aw-np-video");
if (vid && getActiveToken()) saveResumePos(vid.currentTime);
setActiveToken(token);
saveLastEpisode(token);
setActiveEpisode(token);
const wasFullscreen = !!fsElement();
const _preUrl = token === getPreloadedToken() && getPreloadedUrl() ? getPreloadedUrl() : null;
const _preVid = getPreloadedVideo();
const _preVidUsed = token === getPreloadedToken() && !!_preVid;
setPreloadedToken(null);
setPreloadedUrl(null);
setPreloadedVideo(null);
if (_preVid && !_preVidUsed) {
_preVid.pause();
_preVid.src = "";
_preVid.remove();
}
const _doLoad = (url) => {
if (!url) {
showLoadError(token);
return;
}
const epLink = document.querySelector(`.episode a[data-id="${token}"]`);
if (epLink?.href) history.replaceState({}, "", epLink.href);
if (wasFullscreen && !!fsElement()) swapVideoSrc(url);
else mountPlayer(url);
if (_preVid && _preVidUsed) setTimeout(() => {
_preVid.pause();
_preVid.src = "";
_preVid.remove();
}, 4e3);
};
if (_preUrl) _doLoad(_preUrl);
else getUrlForToken(token).then(_doLoad);
}
function wireControls() {
document.querySelectorAll(".episode a").forEach((a) => {
if (a.dataset.npWired) return;
a.dataset.npWired = "1";
a.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
loadEpisode(a.dataset.id);
});
});
document.querySelectorAll(".prevnext").forEach((btn) => {
if (btn.dataset.npWired) return;
btn.dataset.npWired = "1";
btn.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
const t = getAdjacentEpisode(btn.dataset.value);
if (t) loadEpisode(t.dataset.id);
});
});
}
function setupPlayerLabel() {
let labelDone = false;
const hide = () => {
['.control[data-value="original"]', '.control[data-value="alternative"]'].forEach((sel) => document.querySelectorAll(sel).forEach((el2) => {
if (el2.dataset.npBlocked) return;
el2.dataset.npBlocked = "1";
el2.style.setProperty("display", "none", "important");
el2.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
}, true);
}));
if (!document.getElementById("aw-bp-label")) {
const ref = document.querySelector('.control[data-value="original"]') || document.querySelector('.control[data-value="alternative"]');
if (!ref) return;
const label = document.createElement("div");
label.id = "aw-bp-label";
label.className = "control active";
label.style.pointerEvents = "none";
label.innerHTML = '<i style="color:#ec4f4f;" class="icon icon-random"></i> <span>Better Player</span>';
ref.insertAdjacentElement("beforebegin", label);
}
labelDone = true;
};
hide();
if (labelDone) return;
const obs = new MutationObserver(() => {
hide();
if (labelDone) obs.disconnect();
});
obs.observe(document.body, { childList: true, subtree: true });
}
function init() {
cleanupResumeStorage();
injectStyle();
const currentToken2 = document.querySelector("#player")?.dataset?.id;
if (currentToken2) setActiveToken(currentToken2);
const lastToken = isAutoEpOn() ? loadLastEpisode() : null;
const lastValid = !!lastToken && !!document.querySelector(`.episode a[data-id="${lastToken}"]`);
if (lastValid && lastToken !== currentToken2) {
loadEpisode(lastToken);
} else if (currentToken2) {
saveLastEpisode(currentToken2);
getUrlForToken(currentToken2).then((url) => {
if (url) mountPlayer(url);
else showLoadError(currentToken2);
});
} else {
const link = document.querySelector("#downloadLink");
const href = link?.getAttribute("href") || "";
const m = href.match(/[?&]id=(.+)/);
const url = m ? decodeURIComponent(m[1]) : href.startsWith("http") ? href : null;
if (url) mountPlayer(url);
else showLoadError(null);
}
wireControls();
setupPlayerLabel();
}
// src/watchlist/watchlist-api.js
var FOLDERS = [
{ num: 1, str: "watching", label: "In corso" },
{ num: 2, str: "watched", label: "Completato" },
{ num: 3, str: "onhold", label: "In pausa" },
{ num: 4, str: "dropped", label: "Droppato" },
{ num: 5, str: "planned", label: "Da guardare" }
];
var FOLDER_STR_TO_NUM = {};
var FOLDER_NUM_TO_STR = {};
var FOLDER_NUM_TO_LABEL = {};
for (const f of FOLDERS) {
FOLDER_STR_TO_NUM[f.str] = f.num;
FOLDER_NUM_TO_STR[f.num] = f.str;
FOLDER_NUM_TO_LABEL[f.num] = f.label;
}
var EDIT_FIELDS = ["folder", "episodes", "rewatches", "notes", "vote"];
var DEFAULT_BASE = "https://www.animeworld.ac";
var GLOBAL_WIN = typeof window !== "undefined" ? window : void 0;
function resolveWin(opts) {
return opts && opts.win || GLOBAL_WIN;
}
function resolveFetch(opts, win) {
if (opts && opts.fetchImpl) return opts.fetchImpl;
if (win && typeof win.fetch === "function") return win.fetch.bind(win);
return typeof fetch !== "undefined" ? fetch : null;
}
function baseUrl(win) {
return win && win.baseURL || DEFAULT_BASE;
}
function csrfToken(win = GLOBAL_WIN) {
try {
if (win && win.csrfToken) return win.csrfToken;
const doc = win && win.document || (typeof document !== "undefined" ? document : null);
return doc?.querySelector('meta[name="csrf-token"]')?.content || "";
} catch {
return "";
}
}
function encodeEditBody(fields) {
const f = fields || {};
return EDIT_FIELDS.map((k) => `${k}=${encodeURIComponent(f[k] == null ? "" : f[k])}`).join("&");
}
async function addOrEdit(animeId2, folderString, opts = {}) {
const win = resolveWin(opts);
const doFetch = resolveFetch(opts, win);
try {
const url = `${baseUrl(win)}/api/watchlist/addOrEdit/${animeId2}?folder=${folderString}`;
const res = await doFetch(url, {
method: "POST",
headers: { "CSRF-Token": csrfToken(win) },
credentials: "same-origin"
});
const data = await res.json().catch(() => null);
if (!res.ok || !data || data.error === true) {
return { ok: false, error: data && data.message || `HTTP ${res && res.status}` };
}
const d = data.data || {};
return {
ok: true,
entryId: d.id != null ? String(d.id) : null,
folderNum: d.folder != null ? Number(d.folder) : null,
did: data.did || null
};
} catch (e) {
return { ok: false, error: e && e.message || String(e) };
}
}
async function editEntry(entryId, fields = {}, opts = {}) {
const win = resolveWin(opts);
const doFetch = resolveFetch(opts, win);
try {
const url = `${baseUrl(win)}/api/watchlist/edit/${entryId}`;
const res = await doFetch(url, {
method: "POST",
headers: {
"CSRF-Token": csrfToken(win),
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
},
credentials: "same-origin",
body: encodeEditBody(fields)
});
const data = await res.json().catch(() => null);
return { ok: !!(res && res.ok) && !(data && data.error === true) };
} catch {
return { ok: false };
}
}
async function deleteEntry(animeId2, opts = {}) {
const win = resolveWin(opts);
const doFetch = resolveFetch(opts, win);
try {
const url = `${baseUrl(win)}/api/watchlist/delete/${animeId2}?anime=true`;
const res = await doFetch(url, {
method: "DELETE",
headers: { "CSRF-Token": csrfToken(win) },
credentials: "same-origin"
});
const data = await res.json().catch(() => null);
return { ok: !!(res && res.ok) && !(data && data.error === true) };
} catch {
return { ok: false };
}
}
// src/watchlist/watchlist-read.js
var GLOBAL_DOC = typeof document !== "undefined" ? document : void 0;
var GLOBAL_WIN2 = typeof window !== "undefined" ? window : void 0;
function isLoggedIn(win = GLOBAL_WIN2, doc = GLOBAL_DOC) {
try {
if (numStrId(win && win.usersId)) return true;
const d = doc || win && win.document || GLOBAL_DOC;
if (!d) return true;
if (d.querySelector('a[href*="/logout"]')) return true;
return !d.querySelector('a[href*="/login"]');
} catch {
return true;
}
}
function toInt(v) {
const n = parseInt(v, 10);
return Number.isFinite(n) ? n : 0;
}
function valueOf(doc, sel) {
try {
const el2 = doc && doc.querySelector(sel);
return el2 && el2.value != null ? el2.value : "";
} catch {
return "";
}
}
function numStrId(v) {
const s = String(v == null ? "" : v).trim();
return /^\d+$/.test(s) ? s.replace(/^0+(?=\d)/, "") : null;
}
function boolOrNull(v) {
if (typeof v === "boolean") return v;
if (v === 0 || v === 1) return !!v;
if (typeof v === "string") {
const s = v.trim().toLowerCase();
if (s === "true" || s === "1") return true;
if (s === "false" || s === "0") return false;
}
return null;
}
function currentFolderFromBookmark(doc = GLOBAL_DOC) {
try {
const li = doc && doc.querySelector(".userbookmark ul.dropdown-menu li.active");
return li && li.getAttribute("data-value") || null;
} catch {
return null;
}
}
function readCurrentEntry(doc = GLOBAL_DOC, win = GLOBAL_WIN2) {
const out = {
inList: false,
animeId: null,
entryId: null,
folderNum: null,
folderStr: null,
episodes: 0,
vote: 0,
rewatches: 0,
notes: ""
};
try {
const rawAnimeId = win && win.animeId != null ? String(win.animeId) : null;
const rawIsInList = boolOrNull(win && win.isInList);
const modal = doc && doc.querySelector(".watchlist-edit-modal");
const modalAnimeId = modal && numStrId(modal.getAttribute("data-animes-id"));
const modalEntryId = modal && (modal.getAttribute("data-id") || null);
const bookmarkId = (function() {
const ub = doc && doc.querySelector(".userbookmark ul.dropdown-menu.bookmark");
return ub && numStrId(ub.getAttribute("data-id"));
})();
const bookmarkActive = (function() {
const li = doc && doc.querySelector(".userbookmark ul.dropdown-menu li.active");
return li && li.getAttribute("data-value");
})();
out.animeId = numStrId(rawAnimeId) || modalAnimeId || bookmarkId || null;
out.inList = rawIsInList != null ? rawIsInList : !!(modal && modalEntryId || bookmarkActive);
const folderStr = currentFolderFromBookmark(doc);
const entryId = modal && modal.getAttribute("data-id");
if (modal && entryId) {
out.entryId = entryId;
out.folderNum = toInt(valueOf(doc, "#watchlist-edit-folder")) || null;
out.vote = toInt(valueOf(doc, "#watchlist-edit-score"));
out.episodes = toInt(valueOf(doc, "#watchlist-edit-episodes"));
out.rewatches = toInt(valueOf(doc, "#watchlist-edit-rewatches"));
out.notes = valueOf(doc, "#watchlist-edit-notes") || "";
}
if (folderStr) {
out.folderStr = folderStr;
if (out.folderNum == null && FOLDER_STR_TO_NUM[folderStr] != null) {
out.folderNum = FOLDER_STR_TO_NUM[folderStr];
}
}
if (out.folderNum != null && out.folderStr == null && FOLDER_NUM_TO_STR[out.folderNum] != null) {
out.folderStr = FOLDER_NUM_TO_STR[out.folderNum];
}
} catch {
}
return out;
}
function availableEpisodeLinks(doc = GLOBAL_DOC) {
try {
let links = doc && doc.querySelectorAll(".server.active .episode a") || [];
if (!links.length) {
const first = doc && doc.querySelector(".server");
links = first ? first.querySelectorAll(".episode a") : [];
}
return links;
} catch {
return [];
}
}
function readAiringInfo(doc = GLOBAL_DOC) {
const out = { stato: "", available: 0 };
try {
const dts = doc && doc.querySelectorAll(".info dt, dl dt") || [];
for (const dt of dts) {
if (/stato/i.test((dt.textContent || "").trim())) {
const dd = dt.nextElementSibling;
out.stato = dd && (dd.textContent || "").trim() || "";
break;
}
}
out.available = availableEpisodeLinks(doc).length;
} catch {
}
return out;
}
function isLastAvailableEpisode(doc = GLOBAL_DOC) {
try {
const links = availableEpisodeLinks(doc);
if (!links.length) return false;
const active2 = Array.prototype.find.call(links, (a) => a.classList && a.classList.contains("active"));
return !!active2 && active2 === links[links.length - 1];
} catch {
return false;
}
}
function currentEpisodePosition(doc = GLOBAL_DOC) {
try {
const links = availableEpisodeLinks(doc);
if (!links.length) return null;
const active2 = Array.prototype.find.call(links, (a) => a.classList && a.classList.contains("active"));
if (!active2) return null;
const idx = Array.prototype.indexOf.call(links, active2);
return idx >= 0 ? idx + 1 : null;
} catch {
return null;
}
}
// src/watchlist/watchlist-coherence.js
var WATCHING = FOLDER_NUM_TO_STR[1];
var WATCHED = FOLDER_NUM_TO_STR[2];
var ONHOLD = FOLDER_NUM_TO_STR[3];
var DROPPED = FOLDER_NUM_TO_STR[4];
var PLANNED = FOLDER_NUM_TO_STR[5];
var FOLDERS_BY_CATEGORY = {
notReleased: [PLANNED],
airing: [WATCHING, ONHOLD, DROPPED, PLANNED],
ended: [WATCHING, WATCHED, ONHOLD, DROPPED, PLANNED]
};
function toNum(v, def) {
const n = typeof v === "number" ? v : parseFloat(v);
return Number.isFinite(n) ? n : def;
}
function categorize(stato, available) {
const s = String(stato == null ? "" : stato).toLowerCase();
const avail = episodeCap(available);
if (/non rilasciat/i.test(s) || avail === 0) return "notReleased";
if (/in corso/i.test(s)) return "airing";
if (/finit|droppat/i.test(s)) return "ended";
return avail > 0 ? "ended" : "notReleased";
}
function episodeCap(available) {
const n = Math.floor(toNum(available, 0));
return n < 0 ? 0 : n;
}
function allowedFolders(category) {
const list = FOLDERS_BY_CATEGORY[category];
return list ? list.slice() : [];
}
function fieldsEnabled(state) {
const s = state || {};
const released = s.category !== "notReleased";
const started = !!s.inList && s.folder != null && s.folder !== PLANNED;
return { episodes: released, vote: released, rewatch: released && started };
}
function clampEpisodes(n, available) {
const cap = episodeCap(available);
let v = Math.floor(toNum(n, 0));
if (v < 0) v = 0;
if (v > cap) v = cap;
return v;
}
function clampVote(n) {
let v = Math.floor(toNum(n, 0));
if (v < 0) v = 0;
if (v > 10) v = 10;
return v;
}
function clampRewatch(n) {
const v = Math.floor(toNum(n, 0));
return v < 0 ? 0 : v;
}
function resolveWrite(action, state) {
const a = action || {};
const st = state || {};
const category = st.category;
const available = st.available;
const inList = !!st.inList;
const cap = episodeCap(available);
let folder = st.folder != null ? st.folder : null;
let episodes = clampEpisodes(st.episodes, available);
let vote = clampVote(st.vote);
let rewatches = clampRewatch(st.rewatches);
if (category === "notReleased") {
if (a.type === "setFolder" && a.folder === PLANNED) folder = PLANNED;
return { folder, episodes, vote, rewatches };
}
let touched = null;
switch (a.type) {
case "setFolder":
folder = a.folder != null ? a.folder : folder;
touched = "folder";
break;
case "setEpisodes":
episodes = clampEpisodes(a.value, available);
touched = "episodes";
break;
case "stepEpisodes":
episodes = clampEpisodes(episodes + toNum(a.delta, 0), available);
touched = "episodes";
break;
case "setVote":
vote = clampVote(a.value);
touched = "vote";
break;
case "stepVote":
vote = clampVote(vote + toNum(a.delta, 0));
touched = "vote";
break;
case "setRewatch":
rewatches = clampRewatch(a.value);
touched = "rewatch";
break;
case "stepRewatch":
rewatches = clampRewatch(rewatches + toNum(a.delta, 0));
touched = "rewatch";
break;
default:
touched = null;
}
if (touched === "folder") {
if (folder === WATCHED) episodes = cap;
} else if (touched === "episodes") {
if (episodes === cap && category === "ended") {
folder = WATCHED;
} else if (episodes > 0 && (!inList || folder === PLANNED || folder == null)) {
folder = WATCHING;
} else if (folder === WATCHED && episodes < cap) {
folder = WATCHING;
}
} else if (touched === "vote") {
if (!inList || folder === PLANNED || folder == null) folder = WATCHING;
}
return { folder, episodes, vote, rewatches };
}
// src/watchlist/watchlist-write.js
async function applyAction(action, state, api, ctx) {
const a = action || {};
const st = state || {};
const animeId2 = ctx && ctx.animeId != null ? ctx.animeId : st.animeId;
const callOpts = { win: ctx && ctx.win };
if (a.type === "delete") {
const r = await api.deleteEntry(animeId2, callOpts);
if (!r || !r.ok) return { ...st, ok: false };
return {
...st,
ok: true,
inList: false,
entryId: null,
folder: null,
folderNum: null,
episodes: 0,
vote: 0,
rewatches: 0
};
}
const target = resolveWrite(a, st);
let inList = !!st.inList;
let entryId = st.entryId;
if (!inList || !entryId) {
if (!target.folder) return { ...st, ok: false };
const r = await api.addOrEdit(animeId2, target.folder, callOpts);
if (!r || !r.ok) return { ...st, ok: false };
entryId = r.entryId;
inList = true;
}
const folderNum = api.FOLDER_STR_TO_NUM[target.folder];
const er = await api.editEntry(entryId, {
folder: folderNum,
episodes: target.episodes,
rewatches: target.rewatches,
notes: st.notes || "",
vote: target.vote
}, callOpts);
if (!(er && er.ok)) {
return { ...st, ok: false, inList, entryId };
}
return {
...st,
ok: true,
inList: true,
entryId,
folder: target.folder,
folderNum: folderNum != null ? folderNum : null,
episodes: target.episodes,
vote: target.vote,
rewatches: target.rewatches
};
}
// src/watchlist/watchlist-session.js
var sessionEntries = /* @__PURE__ */ new Map();
var promptsShown = /* @__PURE__ */ new Set();
function keyOf(animeId2) {
return animeId2 != null ? String(animeId2) : null;
}
function getSessionEntry(animeId2) {
const k = keyOf(animeId2);
return k != null ? sessionEntries.get(k) : void 0;
}
function setSessionEntry(animeId2, partial) {
const k = keyOf(animeId2);
if (k == null || !partial) return;
const prev = sessionEntries.get(k) || {};
sessionEntries.set(k, { ...prev, ...partial });
try {
if (typeof document !== "undefined" && typeof CustomEvent === "function") {
document.dispatchEvent(new CustomEvent("aw-wl-changed", { detail: { animeId: k } }));
}
} catch {
}
}
function readEntryMerged(doc, win) {
const entry = readCurrentEntry(doc, win);
const override = getSessionEntry(entry.animeId);
if (!override) return entry;
return { ...entry, ...override };
}
function wasPromptShown(animeId2) {
const k = keyOf(animeId2);
return k != null && promptsShown.has(k);
}
function markPromptShown(animeId2) {
const k = keyOf(animeId2);
if (k != null) promptsShown.add(k);
}
function clearPromptShown(animeId2) {
const k = keyOf(animeId2);
if (k != null) promptsShown.delete(k);
}
var removedSeries = /* @__PURE__ */ new Set();
function markSeriesRemoved(animeId2) {
const k = keyOf(animeId2);
if (k != null) removedSeries.add(k);
}
function wasSeriesRemoved(animeId2) {
const k = keyOf(animeId2);
return k != null && removedSeries.has(k);
}
function clearSeriesRemoved(animeId2) {
const k = keyOf(animeId2);
if (k != null) removedSeries.delete(k);
}
// src/watchlist/watchlist-noask.js
var KEY_WL_REMEMBER = "aw-wl-remember";
var NOASK_PREFIX = "aw-wl-noask-";
var NOASK_WATCHING = "watching";
var NOASK_REWATCH = "rewatch";
var NOASK_COMPLETE = "complete";
function noAskKey(animeId2, kind) {
return NOASK_PREFIX + animeId2 + "-" + kind;
}
function isRememberOn() {
return lsGet(KEY_WL_REMEMBER) === "1";
}
function isNoAsk(animeId2, kind) {
return isRememberOn() && animeId2 != null && !!kind && lsGet(noAskKey(animeId2, kind)) === "1";
}
function rememberDismissal(animeId2, kind) {
if (animeId2 == null || !kind) return;
if (isRememberOn()) lsSet(noAskKey(animeId2, kind), "1");
}
function clearAllNoAsk() {
try {
const toDel = [];
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (k && k.indexOf(NOASK_PREFIX) === 0) toDel.push(k);
}
for (const k of toDel) lsDel(k);
} catch {
}
}
// src/watchlist/watchlist-accent.js
var SURF_LIGHT = "#f5f8f6";
var TINT_LIGHT = 0.06;
var SURF_DARK = "#202420";
var TINT_DARK = 0.1;
var FAINT_LIGHT = 1.25;
var FAINT_DARK = 1.9;
function applyAccentVars(node, theme) {
if (!node || !node.style) return;
const raw = loadColor() || "#ffffff";
const rgb = hexToRgb(raw) || [255, 255, 255];
node.style.setProperty("--wl-acc", raw);
node.style.setProperty("--wl-acc-on", inkOnAccent(raw, SURF_DARK, TINT_DARK));
node.style.setProperty("--wl-acc-text", accentTextTone(rgb, theme));
const dark = theme === "dark";
const surf = mixRgb(hexToRgb(dark ? SURF_DARK : SURF_LIGHT), rgb, dark ? TINT_DARK : TINT_LIGHT);
const faint = contrastRatio(rgb, surf) < (dark ? FAINT_DARK : FAINT_LIGHT);
if (node.classList) node.classList.toggle("aw-wl-faint", faint);
}
// src/watchlist/watchlist-panel.js
var WL_ICON = IC.bookmark;
var FOLDER_ICON = {
watching: "fa fa-eye",
watched: "fa fa-check",
onhold: "fa fa-hand-rock",
dropped: "fa fa-eye-slash",
planned: "fa fa-bookmark"
};
var KEY_WL_INTEGRATION = "aw-wl-integration";
var KEY_WL_AUTOPROGRESS = "aw-wl-autoprogress";
var KEY_WL_REMEMBER2 = "aw-wl-remember";
var MINUS = "−";
function buildWatchlistPanel(ctx = {}) {
const doc = ctx.doc || (typeof document !== "undefined" ? document : void 0);
const win = ctx.win || (typeof window !== "undefined" ? window : void 0);
const api = ctx.api;
let theme = ctx.theme === "dark" ? "dark" : "light";
if (!isLoggedIn(win, doc)) {
const lIcon = el("span", "aw-wl-ic", { html: WL_ICON });
const lTitle = el("span", "aw-wl-title", { text: "Watchlist AW" });
const lR1 = el("div", "aw-wl-r1", { kids: [lIcon, lTitle] });
const lMsg = el("div", "aw-wl-locked-msg", { text: "Accedi ad AnimeWorld per usare questa funzione." });
const lockedPanel = el("div", "aw-wl-panel aw-wl-locked", { kids: [lR1, lMsg] });
lockedPanel.classList.add(theme === "dark" ? "aw-wl-on-dark" : "aw-wl-on-light");
applyAccentVars(lockedPanel, theme);
return {
panel: lockedPanel,
refresh() {
},
destroy() {
lockedPanel.remove();
},
getAnimeId() {
return null;
},
// pannello bloccato: niente stato di sessione da applicare
setTheme(t) {
theme = t === "dark" ? "dark" : "light";
lockedPanel.classList.toggle("aw-wl-on-dark", theme === "dark");
lockedPanel.classList.toggle("aw-wl-on-light", theme === "light");
applyAccentVars(lockedPanel, theme);
},
applySession() {
}
};
}
const entry = readCurrentEntry(doc, win);
const airing = readAiringInfo(doc);
const state = {
inList: entry.inList,
animeId: entry.animeId,
entryId: entry.entryId,
folder: entry.folderStr,
// il resto del codice ragiona sulla STRINGA folder
folderNum: entry.folderNum,
episodes: entry.episodes,
vote: entry.vote,
rewatches: entry.rewatches,
notes: entry.notes,
category: categorize(airing.stato, airing.available),
available: airing.available
};
function mkStep(labelText, groupClass) {
const minus = el("button", "aw-wl-minus", { text: MINUS, attrs: { type: "button" } });
const valEl = el("span", "aw-wl-val");
const plus = el("button", "aw-wl-plus", { text: "+", attrs: { type: "button" } });
const step = el("div", "aw-wl-step", { kids: [minus, valEl, plus] });
const lbl = el("span", "aw-wl-lbl", { text: labelText });
const group = el("div", "aw-wl-grp " + groupClass, { kids: [lbl, step] });
return { group, minus, valEl, plus };
}
function mkToggle(labelText, key, tip) {
const input = el("input", null, { attrs: { type: "checkbox" } });
input.checked = lsGet(key) === "1";
const track = el("span", "aw-wl-sw-track");
const thumb = el("span", "aw-wl-sw-thumb");
const sw = el("label", "aw-wl-sw", { kids: [input, track, thumb] });
const lbl = el("span", "aw-wl-tog-lbl", { text: labelText });
const kids = [lbl, sw];
if (tip) kids.push(el("span", "aw-wl-tip", { text: tip }));
const tog = el("div", "aw-wl-tog", { kids });
input.addEventListener("change", () => {
lsSet(key, input.checked ? "1" : "0");
try {
if (typeof document !== "undefined" && typeof CustomEvent === "function") {
document.dispatchEvent(new CustomEvent("aw-wl-integration-changed", { detail: { key, on: input.checked } }));
}
} catch {
}
});
sw.addEventListener("click", (e) => e.stopPropagation());
tog.addEventListener("click", () => {
if (input.disabled) return;
input.checked = !input.checked;
input.dispatchEvent(new Event("change"));
});
return { tog, input };
}
const icon = el("span", "aw-wl-ic", { html: WL_ICON });
const title = el("span", "aw-wl-title", { text: "Watchlist AW" });
const badgeDot = el("span", "aw-wl-dot");
const badgeTxt = el("span", "aw-wl-badge-txt");
const badge = el("span", "aw-wl-badge", { kids: [badgeDot, badgeTxt] });
const r1 = el("div", "aw-wl-r1", { kids: [icon, title, badge] });
const FOLDER_LABEL = {};
FOLDERS.forEach((f) => {
FOLDER_LABEL[f.str] = f.label;
});
const stTriggerIc = el("i", "aw-wl-status-ic");
const stTriggerTxt = el("span", "aw-wl-status-txt", { text: "Stato" });
const stCaret = el("span", "aw-wl-status-caret", { text: "▾" });
const stTrigger = el("button", "aw-wl-status", { attrs: { type: "button", "aria-haspopup": "listbox", "aria-expanded": "false", "aria-label": "Stato watchlist" }, kids: [stTriggerIc, stTriggerTxt, stCaret] });
const stMenu = el("div", "aw-wl-status-menu", { attrs: { role: "listbox" } });
const statusOptions = FOLDERS.map((f) => {
const ic = el("i", "aw-wl-status-ic " + (FOLDER_ICON[f.str] || ""));
const opt = el("button", "aw-wl-status-opt", { attrs: { type: "button", role: "option", "data-folder": f.str }, kids: [ic, el("span", null, { text: f.label })] });
opt.addEventListener("click", () => {
closeMenu();
if (!opt.disabled) run({ type: "setFolder", folder: f.str });
});
stMenu.append(opt);
return { opt, str: f.str };
});
const stWrap = el("div", "aw-wl-status-wrap", { kids: [stTrigger, stMenu] });
let stOpen = false;
function onDocClick(e) {
if (!stWrap.contains(e.target)) closeMenu();
}
function openMenu() {
if (stOpen || stTrigger.disabled) return;
stOpen = true;
stTrigger.setAttribute("aria-expanded", "true");
stMenu.classList.add("open");
if (doc) doc.addEventListener("click", onDocClick, true);
}
function closeMenu() {
if (!stOpen) return;
stOpen = false;
stTrigger.setAttribute("aria-expanded", "false");
stMenu.classList.remove("open");
if (doc) doc.removeEventListener("click", onDocClick, true);
}
stTrigger.addEventListener("click", (e) => {
e.stopPropagation();
stOpen ? closeMenu() : openMenu();
});
stTrigger.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeMenu();
});
const trash = el("button", "aw-wl-trash", { html: IC.trash, attrs: { type: "button", "aria-label": "Rimuovi dalla lista" } });
const statusGrp = el("div", "aw-wl-grp aw-wl-status-grp", { kids: [stWrap, trash] });
const epStep = mkStep("Episodi", "aw-wl-ep");
const voteStep = mkStep("Voto", "aw-wl-vote");
const reStep = mkStep("Rewatch", "aw-wl-rewatch");
const r2 = el("div", "aw-wl-r2", { kids: [statusGrp, el("div", "aw-wl-sep"), epStep.group, el("div", "aw-wl-sep"), voteStep.group, el("div", "aw-wl-sep"), reStep.group] });
const togIntegration = mkToggle("Integrazione watchlist", KEY_WL_INTEGRATION, "Un popup propone aggiornamenti di stato per la serie");
const togAutoProgress = mkToggle("Auto-progresso", KEY_WL_AUTOPROGRESS, "Aggiorna l’episodio automaticamente durante la visione");
const togRemember = mkToggle("Ricorda le scelte", KEY_WL_REMEMBER2, "Il popup rifiutato non viene più riproposto per quella serie");
togRemember.input.addEventListener("change", () => {
if (!togRemember.input.checked) clearAllNoAsk();
});
const r3 = el("div", "aw-wl-r3", { kids: [togIntegration.tog, togAutoProgress.tog, togRemember.tog] });
function syncIntegrationDeps() {
const on = togIntegration.input.checked;
for (const t of [togAutoProgress, togRemember]) {
t.tog.classList.toggle("aw-wl-tog-disabled", !on);
t.input.disabled = !on;
}
}
syncIntegrationDeps();
togIntegration.input.addEventListener("change", syncIntegrationDeps);
const panel = el("div", "aw-wl-panel", { kids: [r1, r2, r3] });
panel.classList.add(theme === "dark" ? "aw-wl-on-dark" : "aw-wl-on-light");
function refresh() {
const cat = state.category;
const fe = fieldsEnabled({ category: cat, folder: state.folder, inList: state.inList });
curFe = fe;
const allowed = allowedFolders(cat);
const cap = episodeCap(state.available);
applyAccentVars(panel, theme);
badgeTxt.textContent = state.inList ? "In lista" : "Non in lista";
badge.classList.toggle("aw-wl-in", state.inList);
badge.classList.toggle("aw-wl-out", !state.inList);
stTrigger.disabled = false;
for (const { opt, str } of statusOptions) {
opt.disabled = !allowed.includes(str);
opt.classList.toggle("aw-wl-sel", state.inList && str === state.folder);
}
if (state.inList && state.folder) {
stTriggerIc.className = "aw-wl-status-ic " + (FOLDER_ICON[state.folder] || "");
stTriggerTxt.textContent = FOLDER_LABEL[state.folder] || "Stato";
} else {
stTriggerIc.className = "aw-wl-status-ic";
stTriggerTxt.textContent = "Stato";
}
trash.disabled = !state.inList;
trash.classList.toggle("aw-wl-active", state.inList);
epStep.valEl.textContent = state.episodes + " / " + cap;
epStep.minus.disabled = !(fe.episodes && state.episodes > 0);
epStep.plus.disabled = !(fe.episodes && state.episodes < cap);
voteStep.valEl.textContent = String(state.vote);
voteStep.minus.disabled = !fe.vote;
voteStep.plus.disabled = !fe.vote;
reStep.valEl.textContent = String(state.rewatches);
reStep.minus.disabled = !fe.rewatch;
reStep.plus.disabled = !fe.rewatch;
}
let busy = false;
let curFe = { episodes: false, vote: false, rewatch: false };
function openEditor(valEl, curVal, actionType) {
if (valEl.querySelector("input")) return;
const input = el("input", "aw-wl-edit", { attrs: { type: "text", inputmode: "numeric", "aria-label": "Modifica valore" } });
input.value = String(curVal);
valEl.textContent = "";
valEl.appendChild(input);
input.focus();
input.select();
let done = false;
const finish = (commit) => {
if (done) return;
done = true;
const n = parseInt(input.value, 10);
if (commit && Number.isFinite(n) && n !== curVal) run({ type: actionType, value: n });
else refresh();
};
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
finish(true);
} else if (e.key === "Escape") {
e.preventDefault();
finish(false);
}
});
input.addEventListener("blur", () => finish(true));
input.addEventListener("click", (e) => e.stopPropagation());
}
function disableAll(d) {
for (const c of [stTrigger, trash, epStep.minus, epStep.plus, voteStep.minus, voteStep.plus, reStep.minus, reStep.plus]) c.disabled = d;
}
async function run(action) {
if (busy) return;
busy = true;
disableAll(true);
try {
const next = await applyAction(action, state, api, { animeId: state.animeId, win });
if (next && next.ok) {
const { ok, ...fields } = next;
Object.assign(state, fields);
if (action.type === "delete") markSeriesRemoved(state.animeId);
else clearSeriesRemoved(state.animeId);
setSessionEntry(state.animeId, {
inList: state.inList,
entryId: state.entryId,
folderStr: state.folder,
folderNum: state.folderNum,
episodes: state.episodes,
vote: state.vote,
rewatches: state.rewatches,
notes: state.notes
});
}
} finally {
busy = false;
refresh();
}
}
trash.addEventListener("click", () => {
if (state.inList) run({ type: "delete" });
});
epStep.minus.addEventListener("click", () => run({ type: "stepEpisodes", delta: -1 }));
epStep.plus.addEventListener("click", () => run({ type: "stepEpisodes", delta: 1 }));
voteStep.minus.addEventListener("click", () => run({ type: "stepVote", delta: -1 }));
voteStep.plus.addEventListener("click", () => run({ type: "stepVote", delta: 1 }));
reStep.minus.addEventListener("click", () => run({ type: "stepRewatch", delta: -1 }));
reStep.plus.addEventListener("click", () => run({ type: "stepRewatch", delta: 1 }));
epStep.valEl.addEventListener("click", () => {
if (!busy && curFe.episodes) openEditor(epStep.valEl, state.episodes, "setEpisodes");
});
voteStep.valEl.addEventListener("click", () => {
if (!busy && curFe.vote) openEditor(voteStep.valEl, state.vote, "setVote");
});
reStep.valEl.addEventListener("click", () => {
if (!busy && curFe.rewatch) openEditor(reStep.valEl, state.rewatches, "setRewatch");
});
refresh();
function applySession() {
try {
if (busy) return;
const ov = getSessionEntry(state.animeId);
if (!ov) return;
if ("inList" in ov) state.inList = ov.inList;
if ("entryId" in ov) state.entryId = ov.entryId;
if ("folderStr" in ov) state.folder = ov.folderStr;
if ("folderNum" in ov) state.folderNum = ov.folderNum;
if ("episodes" in ov) state.episodes = ov.episodes;
if ("vote" in ov) state.vote = ov.vote;
if ("rewatches" in ov) state.rewatches = ov.rewatches;
if ("notes" in ov) state.notes = ov.notes;
refresh();
} catch {
}
}
function setTheme(t) {
theme = t === "dark" ? "dark" : "light";
panel.classList.toggle("aw-wl-on-dark", theme === "dark");
panel.classList.toggle("aw-wl-on-light", theme === "light");
refresh();
}
function destroy() {
closeMenu();
panel.remove();
}
return { panel, refresh, destroy, setTheme, applySession, getAnimeId: () => state.animeId };
}
// src/styles/watchlist-css.js
var WL_1 = `
.aw-wl-panel{
--wl-acc:#3aa564;--wl-acc-text:#1f8f3d;--wl-acc-on:#ffffff;
--wl-surface:#f5f8f6;--wl-bd:#d9e0db;--wl-fg:#273229;--wl-muted:#66756b;--wl-line:#dbe4de;--wl-field:#ffffff;--wl-sw-off:#cdd6d0;--wl-danger:#dc3545;
box-sizing:border-box;clear:both;width:100%;container-type:inline-size;
margin:10px 0 6px;padding:9px 13px;
display:flex;flex-direction:column;gap:8px;
border-radius:8px;
background:var(--wl-surface);border:1px solid var(--wl-bd);color:var(--wl-fg);
font-family:'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;font-size:13px;line-height:1.2;
-webkit-font-smoothing:antialiased;
}
.aw-wl-panel,.aw-wl-panel *,.aw-wl-panel *::before,.aw-wl-panel *::after{box-sizing:border-box}
.aw-wl-panel button,.aw-wl-popup button{-webkit-tap-highlight-color:transparent}
.aw-wl-panel.aw-wl-on-light,.aw-wl-popup.aw-wl-on-light{
--wl-surface:color-mix(in srgb,var(--wl-acc) 6%,#f5f8f6);
--wl-bd:color-mix(in srgb,var(--wl-acc) 22%,#d9e0db);
--wl-fg:color-mix(in srgb,var(--wl-acc) 14%,#273229);
--wl-muted:color-mix(in srgb,var(--wl-acc) 30%,#66756b);
--wl-line:color-mix(in srgb,var(--wl-acc) 24%,#dbe4de);
--wl-field:color-mix(in srgb,var(--wl-acc) 4%,#ffffff);
--wl-sw-off:#cdd6d0;--wl-danger:#dc3545;
box-shadow:0 1px 4px rgba(0,0,0,.10);
}
.aw-wl-panel.aw-wl-on-dark,.aw-wl-popup.aw-wl-on-dark{
--wl-surface:color-mix(in srgb,var(--wl-acc) 10%,#202420);
--wl-bd:color-mix(in srgb,var(--wl-acc) 26%,#39433d);
--wl-fg:color-mix(in srgb,var(--wl-acc) 12%,#d5ded7);
--wl-muted:color-mix(in srgb,var(--wl-acc) 22%,#8ba394);
--wl-line:color-mix(in srgb,var(--wl-acc) 24%,#39433d);
--wl-field:color-mix(in srgb,var(--wl-acc) 10%,#23281f);
--wl-sw-off:#3c463f;--wl-danger:#e26670;
box-shadow:0 2px 8px rgba(0,0,0,.35);
}
.aw-wl-panel .aw-wl-r1{display:flex;align-items:center;gap:8px}
.aw-wl-panel .aw-wl-ic{display:flex;align-items:center;color:var(--wl-acc-text)}
.aw-wl-panel .aw-wl-ic svg{width:16px;height:16px;display:block;color:var(--wl-acc-text)}
.aw-wl-panel .aw-wl-title{font-size:13px;font-weight:600;white-space:nowrap;color:var(--wl-acc-text)}
.aw-wl-panel .aw-wl-badge{font-size:11px;font-weight:600;padding:2px 9px;border-radius:20px;white-space:nowrap;display:inline-flex;align-items:center;gap:5px;margin-left:2px}
.aw-wl-panel .aw-wl-badge.aw-wl-in{color:var(--wl-acc-on);background:var(--wl-acc)}
.aw-wl-panel .aw-wl-badge.aw-wl-out{color:var(--wl-muted);background:transparent;border:1px solid var(--wl-bd)}
.aw-wl-panel .aw-wl-dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex-shrink:0}
.aw-wl-panel .aw-wl-r2{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:nowrap}
.aw-wl-panel .aw-wl-sep{width:1px;align-self:stretch;background:var(--wl-line);margin:1px 0;flex-shrink:0}
.aw-wl-panel .aw-wl-grp{display:flex;align-items:center;gap:6px;flex-shrink:0}
.aw-wl-panel .aw-wl-lbl{font-size:11.5px;font-weight:600;color:var(--wl-muted);white-space:nowrap}
.aw-wl-panel .aw-wl-status-wrap{position:relative;display:inline-flex}
.aw-wl-panel .aw-wl-status{display:inline-flex;align-items:center;gap:6px;height:24px;width:120px;box-sizing:border-box;padding:0 8px;background:var(--wl-field);color:var(--wl-fg);border:1px solid var(--wl-acc);border-radius:6px;font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;transition:border-color .15s}
.aw-wl-panel .aw-wl-status:focus{outline:none;border-color:var(--wl-acc-text)}
.aw-wl-panel .aw-wl-status:disabled{opacity:.5;cursor:not-allowed}
.aw-wl-panel .aw-wl-status-ic{font-size:11px;width:13px;text-align:center;flex-shrink:0;color:var(--wl-acc-text)}
.aw-wl-panel .aw-wl-status-txt{flex:1;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.aw-wl-panel .aw-wl-status-caret{font-size:9px;line-height:1;opacity:.7;flex-shrink:0}
.aw-wl-panel .aw-wl-status-menu{position:absolute;top:calc(100% + 4px);left:0;min-width:100%;background:var(--wl-surface);border:1px solid var(--wl-bd);border-radius:7px;box-shadow:0 6px 20px rgba(0,0,0,.28);padding:4px;display:none;flex-direction:column;gap:1px;z-index:40}
.aw-wl-panel .aw-wl-status-menu.open{display:flex}
.aw-wl-panel .aw-wl-status-opt{display:flex;align-items:center;gap:8px;width:100%;background:none;border:none;border-radius:5px;padding:6px 9px;font-family:inherit;font-size:12px;font-weight:600;color:var(--wl-fg);cursor:pointer;text-align:left;white-space:nowrap;transition:background-color .12s}
.aw-wl-panel .aw-wl-status-opt:hover:not(:disabled){background:color-mix(in srgb,var(--wl-acc) 16%,transparent)}
.aw-wl-panel .aw-wl-status-opt.aw-wl-sel{background:color-mix(in srgb,var(--wl-acc) 26%,transparent)}
.aw-wl-panel .aw-wl-status-opt:disabled{opacity:.4;cursor:not-allowed}
.aw-wl-panel .aw-wl-trash{display:flex;align-items:center;justify-content:center;width:22px;height:22px;padding:0;background:none;border:none;border-radius:5px;color:var(--wl-danger);cursor:pointer;transition:transform .12s}
.aw-wl-panel .aw-wl-trash svg{width:14px;height:14px;display:block;fill:currentColor}
.aw-wl-panel .aw-wl-trash:hover:not(:disabled){transform:scale(1.1)}
.aw-wl-panel .aw-wl-trash:disabled{color:var(--wl-muted);opacity:.45;cursor:not-allowed}
.aw-wl-panel .aw-wl-step{display:flex;align-items:center;gap:5px}
.aw-wl-panel .aw-wl-minus,.aw-wl-panel .aw-wl-plus{display:flex;align-items:center;justify-content:center;width:22px;height:22px;box-sizing:border-box;padding:0 0 2px;background:var(--wl-acc);color:var(--wl-acc-on);border:none;border-radius:5px;font-family:inherit;font-size:15px;font-weight:700;line-height:1;cursor:pointer;transition:filter .12s,transform .1s}
.aw-wl-panel .aw-wl-minus:hover:not(:disabled),.aw-wl-panel .aw-wl-plus:hover:not(:disabled){filter:brightness(.9)}
.aw-wl-panel .aw-wl-minus:active:not(:disabled),.aw-wl-panel .aw-wl-plus:active:not(:disabled){transform:scale(.9)}
.aw-wl-panel .aw-wl-minus:disabled,.aw-wl-panel .aw-wl-plus:disabled{opacity:.35;cursor:not-allowed}
.aw-wl-panel .aw-wl-val{width:26px;text-align:center;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--wl-acc-text);cursor:pointer}
.aw-wl-panel .aw-wl-ep .aw-wl-val{width:82px}
.aw-wl-panel .aw-wl-edit{width:100%;height:20px;box-sizing:border-box;padding:0 2px;text-align:center;font-family:inherit;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--wl-fg);background:var(--wl-field);border:1px solid var(--wl-acc);border-radius:4px;outline:none}
.aw-wl-panel .aw-wl-r3{display:flex;align-items:center;gap:20px;margin-top:1px;flex-wrap:wrap}
.aw-wl-panel .aw-wl-tog{position:relative;display:flex;align-items:center;gap:8px;cursor:pointer}
.aw-wl-panel .aw-wl-tog.aw-wl-tog-disabled{cursor:not-allowed}
.aw-wl-panel .aw-wl-tog.aw-wl-tog-disabled .aw-wl-tog-lbl{opacity:.45}
.aw-wl-panel .aw-wl-tog.aw-wl-tog-disabled .aw-wl-sw{opacity:.45;cursor:not-allowed}
.aw-wl-panel .aw-wl-tog-lbl{font-size:12px;font-weight:600;color:var(--wl-fg);white-space:nowrap}
.aw-wl-panel .aw-wl-locked-msg{color:var(--wl-danger);font-size:12px;font-weight:600;line-height:1.3}
.aw-wl-panel .aw-wl-tip{position:absolute;bottom:calc(100% + 8px);left:0;width:max-content;max-width:320px;padding:7px 11px;border-radius:6px;background:var(--wl-surface);border:1px solid var(--wl-bd);color:var(--wl-fg);font-size:11px;font-weight:500;line-height:1.4;white-space:normal;box-shadow:0 4px 14px rgba(0,0,0,.28);pointer-events:none;opacity:0;transition:opacity .15s;z-index:60}
.aw-wl-panel .aw-wl-tog:hover .aw-wl-tip{opacity:1;transition-delay:.35s}
.aw-wl-panel .aw-wl-r3 .aw-wl-tog:last-child .aw-wl-tip{left:auto;right:0}
`;
var WL_2 = `
@media (hover:none){.aw-wl-panel .aw-wl-tip{display:none}}
.aw-wl-panel .aw-wl-sw{position:relative;display:inline-block;width:29px;height:17px;margin:0;flex-shrink:0;cursor:pointer}
.aw-wl-panel .aw-wl-sw input{position:absolute;opacity:0;width:0;height:0;margin:0}
.aw-wl-panel .aw-wl-sw-track{position:absolute;inset:0;border-radius:9px;background:var(--wl-sw-off);transition:background .2s}
.aw-wl-panel .aw-wl-sw input:checked~.aw-wl-sw-track{background:var(--wl-acc)}
.aw-wl-panel .aw-wl-sw-thumb{position:absolute;top:2.5px;left:2.5px;width:12px;height:12px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.35);transition:transform .2s;pointer-events:none}
.aw-wl-panel .aw-wl-sw input:checked~.aw-wl-sw-thumb{transform:translateX(12px);background:var(--wl-acc-on)}
`;
var WL_3 = `
.aw-wl-panel.aw-wl-faint .aw-wl-badge.aw-wl-in,
.aw-wl-panel.aw-wl-faint .aw-wl-minus,
.aw-wl-panel.aw-wl-faint .aw-wl-plus,
.aw-wl-panel.aw-wl-faint .aw-wl-sw input:checked~.aw-wl-sw-track{box-shadow:inset 0 0 0 1px var(--wl-acc-text)}
.aw-wl-panel.aw-wl-faint .aw-wl-status,
.aw-wl-panel.aw-wl-faint .aw-wl-edit{border-color:var(--wl-acc-text)}
.aw-wl-popup.aw-wl-faint .aw-wl-popup-confirm{border-color:var(--wl-acc-text)}
`;
var WL_4 = `
.aw-wl-panel.aw-wl-faint .aw-wl-status-opt:hover:not(:disabled){background:color-mix(in srgb,var(--wl-acc-text) 16%,transparent)}
.aw-wl-panel.aw-wl-faint .aw-wl-status-opt.aw-wl-sel{background:color-mix(in srgb,var(--wl-acc-text) 26%,transparent)}
.aw-wl-popup.aw-wl-faint .aw-wl-popup-cancel:hover{border-color:var(--wl-acc-text);background:color-mix(in srgb,var(--wl-acc-text) 8%,transparent)}
`;
var WL_5 = `
.aw-wl-panel.aw-wl-on-light.aw-wl-faint,.aw-wl-popup.aw-wl-on-light.aw-wl-faint{
--wl-bd:color-mix(in srgb,var(--wl-acc-text) 22%,#d9e0db);
--wl-fg:color-mix(in srgb,var(--wl-acc-text) 14%,#273229);
--wl-muted:color-mix(in srgb,var(--wl-acc-text) 30%,#66756b);
--wl-line:color-mix(in srgb,var(--wl-acc-text) 24%,#dbe4de);
}
.aw-wl-panel.aw-wl-on-dark.aw-wl-faint,.aw-wl-popup.aw-wl-on-dark.aw-wl-faint{
--wl-bd:color-mix(in srgb,var(--wl-acc-text) 26%,#39433d);
--wl-fg:color-mix(in srgb,var(--wl-acc-text) 12%,#d5ded7);
--wl-muted:color-mix(in srgb,var(--wl-acc-text) 22%,#8ba394);
--wl-line:color-mix(in srgb,var(--wl-acc-text) 24%,#39433d);
}
`;
var WL_6 = `
@container (max-width:600px){
.aw-wl-panel .aw-wl-r2{flex-wrap:wrap;justify-content:flex-start;gap:8px 12px}
.aw-wl-panel .aw-wl-sep{display:none}
}
`;
var WL_7 = `
.aw-wl-popup{position:fixed;bottom:56px;left:0;right:0;z-index:2147483000;display:flex;justify-content:center;padding:0 12px;pointer-events:none;font-family:'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;font-size:12px;-webkit-font-smoothing:antialiased;}
.aw-wl-popup.aw-wl-hidden{display:none}
.aw-wl-popup.aw-wl-on-light,.aw-wl-popup.aw-wl-on-dark{box-shadow:none;background:transparent;border:0}
.aw-wl-popup,.aw-wl-popup *,.aw-wl-popup *::before,.aw-wl-popup *::after{box-sizing:border-box}
.aw-wl-popup-card{pointer-events:auto;max-width:min(92vw,420px);padding:10px 16px;display:flex;flex-direction:column;align-items:center;gap:8px;border-radius:9px;background:var(--wl-surface);border:1px solid var(--wl-bd);color:var(--wl-fg);box-shadow:0 6px 22px rgba(0,0,0,.35);animation:aw-wl-pop-drop .18s ease;}
.aw-wl-popup-title{font-size:14px;font-weight:600;line-height:1.3;text-align:center;color:var(--wl-fg)}
.aw-wl-popup-func{font-weight:700;color:var(--wl-acc-text)}
.aw-wl-popup-extra:empty{display:none}
.aw-wl-popup-actions{display:flex;align-items:center;justify-content:center;gap:8px;flex-shrink:0}
.aw-wl-popup-actions button{min-width:74px;height:27px;padding:0 12px;border-radius:7px;font-family:inherit;font-size:11.5px;font-weight:600;cursor:pointer;transition:filter .12s,transform .1s,background-color .12s,border-color .12s}
.aw-wl-popup-actions button:active{transform:scale(.97)}
.aw-wl-popup-cancel{background:transparent;color:var(--wl-fg);border:1px solid var(--wl-bd)}
.aw-wl-popup-cancel:hover{border-color:var(--wl-acc);background:color-mix(in srgb,var(--wl-acc) 8%,transparent)}
.aw-wl-popup-confirm{background:var(--wl-acc);color:var(--wl-acc-on);border:1px solid var(--wl-acc)}
.aw-wl-popup-confirm:hover{filter:brightness(.92)}
@keyframes aw-wl-pop-drop{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
`;
var WL_8 = `
.aw-np-toast[data-pos="top"]{top:calc(var(--np-top-h) + 6px);bottom:auto}
`;
var WL_9 = `
.aw-wl-stars{display:inline-flex;gap:8px;cursor:pointer}
.aw-wl-star{font-size:22px;line-height:1}
.aw-wl-star .fa{background:linear-gradient(90deg,var(--wl-acc-text) var(--wl-fill,0%),var(--wl-muted) var(--wl-fill,0%));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
`;
var WATCHLIST_CSS = [WL_1, WL_2, WL_3, WL_4, WL_5, WL_6, WL_7, WL_8, WL_9].join("\n");
// src/watchlist/watchlist-init.js
function injectStyle2(doc) {
if (doc.getElementById("aw-wl-style")) return;
const s = doc.createElement("style");
s.id = "aw-wl-style";
s.textContent = WATCHLIST_CSS;
(doc.head || doc.documentElement).appendChild(s);
}
function detectTheme(doc) {
return doc && doc.body && doc.body.classList.contains("dark") ? "dark" : "light";
}
function initWatchlist(opts = {}) {
try {
const doc = opts.doc || (typeof document !== "undefined" ? document : null);
const win = opts.win || pageWin;
if (!doc) return;
const controls = doc.getElementById("controls");
if (!controls) return;
if (doc.querySelector(".aw-wl-panel")) return;
injectStyle2(doc);
const { panel, setTheme, applySession, getAnimeId } = buildWatchlistPanel({
doc,
win,
api: { addOrEdit, editEntry, deleteEntry, FOLDER_STR_TO_NUM },
theme: detectTheme(doc)
// il pannello aggiunge da se' la classe aw-wl-on-light/dark
});
const ub = controls.querySelector(".userbookmark");
if (ub) ub.style.display = "none";
if (controls.parentNode) controls.parentNode.insertBefore(panel, controls.nextSibling);
else controls.appendChild(panel);
if (doc.body && typeof MutationObserver === "function") {
const mo = new MutationObserver(() => setTheme(detectTheme(doc)));
mo.observe(doc.body, { attributes: true, attributeFilter: ["class"] });
}
doc.addEventListener("aw-np-accent-changed", () => {
try {
setTheme(detectTheme(doc));
} catch {
}
});
if (typeof applySession === "function") {
doc.addEventListener("aw-wl-changed", (e) => {
try {
const id = e && e.detail && e.detail.animeId;
const current = typeof getAnimeId === "function" ? getAnimeId() : null;
if (current != null && String(id) === String(current)) applySession();
} catch {
}
});
}
} catch {
}
}
// src/watchlist/watchlist-autoprogress.js
var KEY_WL_INTEGRATION2 = "aw-wl-integration";
var KEY_WL_AUTOPROGRESS2 = "aw-wl-autoprogress";
var PROGRESS_THRESHOLD = 85;
var WATCHING2 = "watching";
function initAutoProgress(opts = {}) {
try {
let evalProgress = function() {
const video = curVideo2;
if (done || busy || Date.now() < retryAfter || !video || !video.duration || !isFinite(video.duration)) return;
const p = video.currentTime / video.duration * 100;
if (p < PROGRESS_THRESHOLD) return;
busy = true;
pushProgress().then((acted) => {
if (acted) done = true;
}).catch(() => {
}).finally(() => {
busy = false;
});
}, attach = function(video) {
if (!video || video.dataset.awApWired) return;
video.dataset.awApWired = "1";
curVideo2 = video;
video.addEventListener("loadedmetadata", () => {
curVideo2 = video;
done = false;
busy = false;
retryAfter = 0;
failNotified = false;
});
video.addEventListener("timeupdate", evalProgress);
}, tryAttach = function() {
const v = doc.querySelector("#aw-np-video");
if (v) attach(v);
};
const doc = opts.doc || (typeof document !== "undefined" ? document : null);
const win = opts.win || pageWin;
if (!doc) return;
const api = { addOrEdit, editEntry, deleteEntry, FOLDER_STR_TO_NUM };
async function pushProgress() {
try {
if (lsGet(KEY_WL_INTEGRATION2) !== "1") return false;
if (lsGet(KEY_WL_AUTOPROGRESS2) !== "1") return false;
if (!isLoggedIn(win)) return false;
const entry = readEntryMerged(doc, win);
if (!entry.inList) return false;
if (!entry.entryId) return false;
if (entry.folderStr !== WATCHING2) return false;
const airing = readAiringInfo(doc);
const cap = episodeCap(airing.available);
if (cap <= 0) return false;
const category = categorize(airing.stato, airing.available);
if (category === "notReleased") return false;
const currentPos = currentEpisodePosition(doc);
if (currentPos == null || currentPos <= entry.episodes) return false;
if (category === "ended" && isLastAvailableEpisode(doc)) return false;
const state = {
inList: true,
animeId: entry.animeId,
entryId: entry.entryId,
folder: entry.folderStr,
// il resto del codice ragiona sulla STRINGA folder
folderNum: entry.folderNum,
episodes: entry.episodes,
vote: entry.vote,
rewatches: entry.rewatches,
notes: entry.notes,
category,
available: airing.available
};
const prevEps = entry.episodes;
const next = await applyAction({ type: "setEpisodes", value: currentPos }, state, api, { animeId: entry.animeId, win });
if (next && next.ok) {
if (next.episodes > prevEps) {
setSessionEntry(entry.animeId, { episodes: next.episodes });
const wrap = doc.querySelector("#aw-np");
if (wrap) flashToast(wrap, `Lista aggiornata: episodio ${next.episodes}`, "top");
}
return true;
}
retryAfter = Date.now() + 15e3;
if (!failNotified) {
failNotified = true;
const wrap = doc.querySelector("#aw-np");
if (wrap) flashToast(wrap, "Lista non aggiornata (errore di rete)", "top");
}
return false;
} catch {
return false;
}
}
let curVideo2 = null;
let done = false;
let busy = false;
let retryAfter = 0;
let failNotified = false;
tryAttach();
const root = doc.getElementById("player") || doc.body;
if (root && typeof MutationObserver === "function") {
const mo = new MutationObserver(() => tryAttach());
mo.observe(root, { childList: true, subtree: true });
}
if (typeof document !== "undefined") {
doc.addEventListener("aw-wl-integration-changed", (e) => {
try {
const d = e && e.detail;
if (!d || !d.on) return;
if (d.key === KEY_WL_INTEGRATION2 || d.key === KEY_WL_AUTOPROGRESS2) {
retryAfter = 0;
failNotified = false;
evalProgress();
}
} catch {
}
});
}
} catch {
}
}
// src/watchlist/watchlist-prompt.js
var KEY_WL_INTEGRATION3 = "aw-wl-integration";
var WATCHING3 = "watching";
var WATCHED2 = "watched";
function detectTheme2(doc) {
return doc && doc.body && doc.body.classList.contains("dark") ? "dark" : "light";
}
function buildWatchlistPrompt(opts = {}) {
const doc = opts.doc || (typeof document !== "undefined" ? document : null);
let theme = opts.theme === "dark" ? "dark" : "light";
const { title, funcLabel, confirmText, onConfirm, onCancel, extraSlot } = opts;
if (!doc) return { overlay: null, close() {
} };
const funcStrong = el("b", "aw-wl-popup-func", { text: funcLabel || "" });
const titleKids = title != null && title !== "" ? [String(title)] : ["Aggiornare a ", funcStrong, "?"];
const titleEl = el("div", "aw-wl-popup-title", { kids: titleKids });
const extra = extraSlot || el("div", "aw-wl-popup-extra");
const cancelBtn = el("button", "aw-wl-popup-cancel", { text: "Annulla", attrs: { type: "button" } });
const confirmBtn = el("button", "aw-wl-popup-confirm", { text: confirmText || "Conferma", attrs: { type: "button" } });
const actions = el("div", "aw-wl-popup-actions", { kids: [cancelBtn, confirmBtn] });
const card = el("div", "aw-wl-popup-card", { attrs: { role: "dialog", "aria-modal": "false" }, kids: [titleEl, extra, actions] });
const overlay = el("div", "aw-wl-popup", { kids: [card] });
overlay.classList.add(theme === "dark" ? "aw-wl-on-dark" : "aw-wl-on-light");
function applyAccent() {
applyAccentVars(overlay, theme);
}
applyAccent();
let closed = false;
let stopFs = null;
let themeMo = null;
let onAccent = null;
function close() {
if (closed) return;
closed = true;
try {
if (stopFs) stopFs();
} catch {
}
try {
if (themeMo) themeMo.disconnect();
} catch {
}
try {
if (onAccent) doc.removeEventListener("aw-np-accent-changed", onAccent);
} catch {
}
overlay.remove();
}
function cancel() {
close();
if (typeof onCancel === "function") onCancel();
}
function confirm() {
close();
if (typeof onConfirm === "function") onConfirm();
}
cancelBtn.addEventListener("click", cancel);
confirmBtn.addEventListener("click", confirm);
function setTheme(t) {
theme = t === "dark" ? "dark" : "light";
overlay.classList.toggle("aw-wl-on-dark", theme === "dark");
overlay.classList.toggle("aw-wl-on-light", theme === "light");
applyAccent();
}
if (doc.body && typeof MutationObserver === "function") {
themeMo = new MutationObserver(() => setTheme(detectTheme2(doc)));
themeMo.observe(doc.body, { attributes: true, attributeFilter: ["class"] });
}
onAccent = () => {
try {
applyAccent();
} catch {
}
};
doc.addEventListener("aw-np-accent-changed", onAccent);
function hide() {
overlay.classList.add("aw-wl-hidden");
}
function show() {
overlay.classList.remove("aw-wl-hidden");
}
doc.body.appendChild(overlay);
if (fsElement()) hide();
stopFs = fsChange(() => {
if (closed) return;
if (fsElement()) hide();
else show();
});
return { overlay, close, hide, show };
}
function initWatchlistPrompt(opts = {}) {
try {
let dismissCurrent = function() {
if (!current) return;
try {
current.close();
} catch {
}
current = null;
}, maybePrompt = function() {
try {
if (current) return;
if (lsGet(KEY_WL_INTEGRATION3) !== "1") return;
if (!isLoggedIn(win)) return;
const entry = readEntryMerged(doc, win);
const animeId2 = entry.animeId;
if (animeId2 == null) return;
if (entry.inList && !entry.entryId) return;
if (wasSeriesRemoved(animeId2)) return;
const noAskKind = entry.folderStr === WATCHED2 ? NOASK_REWATCH : NOASK_WATCHING;
if (isNoAsk(animeId2, noAskKind)) return;
if (wasPromptShown(animeId2)) return;
if (entry.folderStr === WATCHING3) return;
const airing = readAiringInfo(doc);
const category = categorize(airing.stato, airing.available);
if (category === "notReleased") return;
if (entry.folderStr !== WATCHED2 && category === "ended" && isLastAvailableEpisode(doc)) return;
showPrompt(entry, airing, category);
} catch {
}
}, showPrompt = function(entry, airing, category) {
const animeId2 = entry.animeId;
markPromptShown(animeId2);
const isRewatch = entry.folderStr === WATCHED2;
const funcLabel = isRewatch ? "Rewatch" : "In Corso";
const state = {
inList: entry.inList,
animeId: entry.animeId,
entryId: entry.entryId,
folder: entry.folderStr,
// il resto del codice ragiona sulla STRINGA folder
folderNum: entry.folderNum,
episodes: entry.episodes,
vote: entry.vote,
rewatches: entry.rewatches,
notes: entry.notes,
category,
available: airing.available
};
const ctx = { animeId: animeId2, win };
async function doWrite() {
try {
let next;
if (isRewatch) {
const s2 = await applyAction({ type: "setRewatch", value: (entry.rewatches || 0) + 1 }, state, api, ctx);
next = s2 && s2.ok ? await applyAction({ type: "setEpisodes", value: 0 }, s2, api, ctx) : s2;
} else {
next = await applyAction({ type: "setFolder", folder: WATCHING3 }, state, api, ctx);
}
if (next && next.ok && next.inList && next.folder) {
setSessionEntry(animeId2, {
inList: true,
entryId: next.entryId,
folderStr: next.folder,
folderNum: next.folder != null ? FOLDER_STR_TO_NUM[next.folder] : null,
episodes: next.episodes,
vote: next.vote,
rewatches: next.rewatches
});
} else {
clearPromptShown(animeId2);
const wrap = doc.querySelector("#aw-np");
if (wrap) flashToast(wrap, "Lista non aggiornata (errore di rete)", "top");
}
} catch {
}
}
dismissCurrent();
current = buildWatchlistPrompt({
doc,
win,
theme: detectTheme2(doc),
funcLabel,
onConfirm() {
dismissCurrent();
return doWrite();
},
onCancel() {
dismissCurrent();
rememberDismissal(animeId2, isRewatch ? NOASK_REWATCH : NOASK_WATCHING);
}
});
}, attach = function(video) {
if (!video || video.dataset.awWpWired) return;
video.dataset.awWpWired = "1";
const onEpisode = () => {
dismissCurrent();
maybePrompt();
};
if (video.readyState >= 1) maybePrompt();
video.addEventListener("loadedmetadata", onEpisode);
}, tryAttach = function() {
const v = doc.querySelector("#aw-np-video");
if (v) attach(v);
};
const doc = opts.doc || (typeof document !== "undefined" ? document : null);
const win = opts.win || pageWin;
if (!doc) return;
const api = { addOrEdit, editEntry, deleteEntry, FOLDER_STR_TO_NUM };
let current = null;
tryAttach();
const root = doc.getElementById("player") || doc.body;
if (root && typeof MutationObserver === "function") {
const mo = new MutationObserver(() => tryAttach());
mo.observe(root, { childList: true, subtree: true });
}
if (typeof document !== "undefined") {
doc.addEventListener("aw-wl-integration-changed", (e) => {
try {
const d = e && e.detail;
if (!d || d.key !== KEY_WL_INTEGRATION3) return;
if (d.on) maybePrompt();
else dismissCurrent();
} catch {
}
});
}
} catch {
}
}
// src/watchlist/watchlist-complete.js
var KEY_WL_INTEGRATION4 = "aw-wl-integration";
var WATCHED3 = "watched";
var WATCHED_THRESHOLD = 85;
function detectTheme3(doc) {
return doc && doc.body && doc.body.classList.contains("dark") ? "dark" : "light";
}
function buildVoteSlot(doc) {
let value = 0;
const block = el("div", "aw-wl-stars", { attrs: { role: "slider", "aria-label": "Voto", "aria-valuemin": "0", "aria-valuemax": "10", "aria-valuenow": "0" } });
const stars = [];
function render(v) {
for (let i = 1; i <= 5; i++) {
const frac = Math.max(0, Math.min(2, v - 2 * (i - 1))) / 2;
stars[i - 1].style.setProperty("--wl-fill", frac * 100 + "%");
}
}
for (let i = 1; i <= 5; i++) {
let valueAt = function(e) {
const rect = star.getBoundingClientRect();
return e.clientX - rect.left < rect.width / 2 ? half : full;
};
const half = 2 * i - 1;
const full = 2 * i;
const star = el("span", "aw-wl-star", { kids: [el("i", "fa fa-star")] });
star.addEventListener("click", (e) => {
const chosen = valueAt(e);
value = value === chosen ? 0 : chosen;
block.setAttribute("aria-valuenow", String(value));
render(value);
});
stars.push(star);
block.append(star);
}
render(0);
return { block, getVote: () => value };
}
function initWatchlistComplete(opts = {}) {
try {
let dismissCurrent = function() {
if (!current) return;
try {
current.close();
} catch {
}
current = null;
}, maybePromptComplete = function() {
try {
if (current) return false;
if (lsGet(KEY_WL_INTEGRATION4) !== "1") return false;
if (!isLoggedIn(win)) return false;
const video = doc.querySelector("#aw-np-video");
if (!video) return false;
const dur = video.duration;
if (!dur || !isFinite(dur)) return false;
if (video.currentTime / dur * 100 < WATCHED_THRESHOLD) return false;
const entry = readEntryMerged(doc, win);
const animeId2 = entry.animeId;
if (animeId2 == null) return false;
if (entry.inList && !entry.entryId) return false;
if (wasSeriesRemoved(animeId2)) return false;
if (isNoAsk(animeId2, NOASK_COMPLETE)) return false;
if (shownComplete.has(animeId2)) return false;
if (entry.folderStr === WATCHED3) return false;
const airing = readAiringInfo(doc);
const category = categorize(airing.stato, airing.available);
if (category !== "ended") return false;
const cap = episodeCap(airing.available);
if (cap <= 0) return false;
if (!isLastAvailableEpisode(doc)) return false;
showPrompt(entry, airing, category);
return true;
} catch {
return false;
}
}, showPrompt = function(entry, airing, category) {
const animeId2 = entry.animeId;
shownComplete.add(animeId2);
const state = {
inList: entry.inList,
animeId: entry.animeId,
entryId: entry.entryId,
folder: entry.folderStr,
// il resto del codice ragiona sulla STRINGA folder
folderNum: entry.folderNum,
episodes: entry.episodes,
vote: entry.vote,
rewatches: entry.rewatches,
notes: entry.notes,
category,
available: airing.available
};
const ctx = { animeId: animeId2, win };
const vote = buildVoteSlot(doc);
async function doWrite() {
try {
let next = await applyAction({ type: "setFolder", folder: WATCHED3 }, state, api, ctx);
const v = vote.getVote();
if (v > 0 && next && next.ok && next.inList) {
next = await applyAction({ type: "setVote", value: v }, next, api, ctx);
}
if (next && next.ok && next.inList && next.folder) {
setSessionEntry(animeId2, {
inList: true,
entryId: next.entryId,
folderStr: next.folder,
folderNum: next.folder != null ? FOLDER_STR_TO_NUM[next.folder] : null,
episodes: next.episodes,
vote: next.vote,
rewatches: next.rewatches
});
} else {
shownComplete.delete(animeId2);
const wrap = doc.querySelector("#aw-np");
if (wrap) flashToast(wrap, "Lista non aggiornata (errore di rete)", "top");
}
} catch {
}
}
dismissCurrent();
current = buildWatchlistPrompt({
doc,
win,
theme: detectTheme3(doc),
funcLabel: "Completo",
confirmText: "Salva",
extraSlot: vote.block,
onConfirm() {
dismissCurrent();
return doWrite();
},
onCancel() {
dismissCurrent();
rememberDismissal(animeId2, NOASK_COMPLETE);
}
});
}, attach = function(video) {
if (!video || video.dataset.awWcWired) return;
video.dataset.awWcWired = "1";
let fired = false;
video.addEventListener("loadedmetadata", () => {
fired = false;
if (current) {
if (!isLastAvailableEpisode(doc)) dismissCurrent();
}
});
video.addEventListener("timeupdate", () => {
if (fired || !video.duration || !isFinite(video.duration)) return;
const p = video.currentTime / video.duration * 100;
if (p < WATCHED_THRESHOLD) return;
if (maybePromptComplete()) fired = true;
});
}, tryAttach = function() {
const v = doc.querySelector("#aw-np-video");
if (v) attach(v);
};
const doc = opts.doc || (typeof document !== "undefined" ? document : null);
const win = opts.win || pageWin;
if (!doc) return;
const api = { addOrEdit, editEntry, deleteEntry, FOLDER_STR_TO_NUM };
const shownComplete = /* @__PURE__ */ new Set();
let current = null;
tryAttach();
const root = doc.getElementById("player") || doc.body;
if (root && typeof MutationObserver === "function") {
const mo = new MutationObserver(() => tryAttach());
mo.observe(root, { childList: true, subtree: true });
}
if (typeof document !== "undefined") {
doc.addEventListener("aw-wl-integration-changed", (e) => {
try {
const d = e && e.detail;
if (!d || d.key !== KEY_WL_INTEGRATION4) return;
if (!d.on) dismissCurrent();
} catch {
}
});
}
} catch {
}
}
// src/main.js
installSrcBlocker();
function bootstrap() {
setTimeout(init, 200);
setTimeout(initWatchlist, 200);
setTimeout(initAutoProgress, 200);
setTimeout(initWatchlistPrompt, 200);
setTimeout(initWatchlistComplete, 200);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", bootstrap);
} else {
bootstrap();
}
})();