myetv-player 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -143,6 +143,7 @@ const player = new MYETVvideoplayer('my-video', {
143
143
  | `brandLogoUrl` | string | `''` | Brand logo url in the controlbar (png, jpg, gif) - image height 44px - image width 120px |
144
144
  | `brandLogoLinkUrl` | string | `''` | Optional URL to open in a new page when clicking the brand logo in the controlbar
145
145
  | `brandLogoTooltipText` | string | `''` | Optional Custom tooltip of the brand logo (the default is the url of the brand logo, if present)
146
+ | `loadingLogo` | string | `''` | Optional Custom image logo url to show it inside the loading spinner
146
147
  | `watermarkUrl` | string | `''` | Optional URL of the image watermark over the video, reccomended dimension: width: 180px, height: 100px
147
148
  | `watermarkLink` | string | `''` | Optional URL to open in a new page when clicking the watermark logo in the video
148
149
  | `watermarkPosition` | string | `''` | Optional where to show the watermark logo in the video (values are: top-left, top-right, bottom-left, bottom-right)
@@ -388,6 +388,42 @@ body {
388
388
  animation: spin 1s linear infinite;
389
389
  }
390
390
 
391
+ .loading-spinner-wrap {
392
+ position: relative;
393
+ width: 56px;
394
+ height: 56px;
395
+ display: flex;
396
+ align-items: center;
397
+ justify-content: center;
398
+ }
399
+
400
+ .loading-spinner-wrap .loading-spinner {
401
+ position: absolute;
402
+ top: 0;
403
+ left: 0;
404
+ width: 100%;
405
+ height: 100%;
406
+ border-radius: 50%;
407
+ border: 3px solid rgba(255, 255, 255, 0.2);
408
+ border-top: 3px solid var(--player-primary-color);
409
+ animation: spin 1s linear infinite;
410
+ box-sizing: border-box;
411
+ }
412
+
413
+ .loading-spinner-logo {
414
+ position: relative;
415
+ z-index: 1;
416
+ max-width: 34px;
417
+ max-height: 34px;
418
+ width: auto;
419
+ height: auto;
420
+ border-radius: 0;
421
+ object-fit: contain;
422
+ display: block;
423
+ flex-shrink: 0;
424
+ pointer-events: none;
425
+ }
426
+
391
427
  @keyframes spin {
392
428
  0% {
393
429
  transform: rotate(0deg);
@@ -1 +1 @@
1
- :root{--player-primary-color: goldenrod;--player-primary-hover: #daa520;--player-primary-dark: #b8860b;--player-button-color: white;--player-button-hover: rgba(255, 255, 255, 0.1);--player-button-active: rgba(255, 255, 255, 0.2);--player-text-color: white;--player-text-secondary: rgba(255, 255, 255, 0.8);--player-bg-primary: #000;--player-bg-controls: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.8) 100%);--player-bg-title-overlay: linear-gradient(180deg, rgba(0, 0, 0, 0.8) 0%, transparent 100%);--player-bg-menu: rgba(20, 20, 20, 0.95);--player-bg-loading: rgba(0, 0, 0, 0.7);--player-border-radius: 12px;--player-controls-padding: 20px 15px 15px;--player-title-overlay-padding: 15px 20px 25px;--player-button-padding: 8px;--player-icon-size: 20px;--player-progress-height: 6px;--player-progress-bg: rgba(255, 255, 255, 0.2);--player-progress-buffer: rgba(255, 255, 255, 0.3);--player-progress-handle-size: 16px;--player-volume-height: 4px;--player-volume-bg: rgba(255, 255, 255, 0.2);--player-volume-handle-size: 14px;--player-volume-fill: 100%;--player-transition-fast: 0.2s ease;--player-transition-normal: 0.3s ease;--player-shadow-main: 0 8px 32px rgba(0, 0, 0, 0.3);--player-shadow-menu: 0 4px 16px rgba(0, 0, 0, 0.2);--player-shadow-tooltip: 0 3px 12px rgba(0, 0, 0, 0.4)}*{box-sizing:border-box}body{margin:0;padding:20px;background:#1a1a1a;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}.video-container{max-width:1200px;margin:0 auto;background:var(--player-bg-primary);border-radius:var(--player-border-radius);box-shadow:var(--player-shadow-main);position:relative}.video-container:fullscreen,.video-container:-webkit-full-screen,.video-container:-moz-full-screen{width:100vw;height:100vh;border-radius:0}.video-wrapper{position:relative;width:100%;background:var(--player-bg-primary);overflow:visible !important}.video-wrapper.player-initialized .video-player{visibility:visible;opacity:1;transition:opacity .3s ease;pointer-events:auto}.video-wrapper.player-initialized .initial-loading{opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;transition-delay:.2s;display:none;transition-delay:.5s}.video-wrapper:not(.has-controls) .video-watermark.hide-on-autohide{visibility:hidden;opacity:0}.hidden{display:none !important}.player-theme-dark{--player-bg-primary: #1a1a1a;--player-bg-controls: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.9) 100%);--player-bg-title-overlay: linear-gradient(180deg, rgba(30, 30, 30, 0.9) 0%, transparent 100%);--player-bg-menu: rgba(30, 30, 30, 0.95)}.video-player{width:100%;height:auto;display:block;min-height:300px;-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;will-change:transform;visibility:visible;opacity:1;position:relative;z-index:1;transition:none}.video-wrapper.player-initialized .video-player{visibility:visible;opacity:1;transition:opacity .3s ease;pointer-events:auto}.video-player::-webkit-media-controls-panel,.video-player::-webkit-media-controls-play-button,.video-player::-webkit-media-controls-start-playback-button,.video-player::-webkit-media-controls-timeline,.video-player::-webkit-media-controls-current-time-display,.video-player::-webkit-media-controls-time-remaining-display,.video-player::-webkit-media-controls-mute-button,.video-player::-webkit-media-controls-toggle-closed-captions-button,.video-player::-webkit-media-controls-volume-slider,.video-player::-webkit-media-controls-fullscreen-button,.video-player::-webkit-media-controls-seek-back-button,.video-player::-webkit-media-controls-seek-forward-button,.video-player::-webkit-media-controls-rewind-button,.video-player::-webkit-media-controls-return-to-realtime-button,.video-player::-webkit-media-controls-overlay-play-button{display:none !important;visibility:hidden !important;opacity:0 !important}.video-player::-moz-media-controls{display:none !important}.initial-loading{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--player-bg-primary);display:flex;align-items:center;justify-content:center;z-index:20}.video-wrapper.player-initialized .initial-loading{opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;transition-delay:.2s}.video-wrapper.player-initialized .initial-loading{display:none;transition-delay:.5s}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--player-bg-loading);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity var(--player-transition-normal);z-index:15}.loading-overlay.active{opacity:1;visibility:visible}.loading-spinner{width:50px;height:50px;border:3px solid hsla(0,0%,100%,.3);border-top:3px solid var(--player-primary-color);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.video-player::cue{background:rgba(0,0,0,.8);color:#fff;font-size:18px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-weight:500;text-shadow:2px 2px 4px rgba(0,0,0,.8);padding:8px 12px;border-radius:6px;line-height:1.4}.video-player::cue(.highlight){background:var(--player-primary-color);color:#000}.video-player::cue(b){font-weight:700}.video-player::cue(i){font-style:italic}.video-poster-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background-size:cover;background-position:center;background-repeat:no-repeat;z-index:1;cursor:pointer;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;pointer-events:none}.video-poster-overlay.visible{opacity:1;visibility:visible;pointer-events:auto}.video-poster-overlay.hidden{opacity:0;visibility:hidden;pointer-events:none}.video-poster-overlay.visible:hover{opacity:.95}.video-poster-overlay::after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:80px;height:80px;background:rgba(0,0,0,.7);border-radius:50%;border:3px solid var(--player-primary-color);opacity:0;transition:opacity .3s ease,transform .3s ease,border-color .3s ease;pointer-events:none}.video-poster-overlay.visible:hover::after{opacity:1;transform:translate(-50%, -50%) scale(1.1);border-color:var(--player-primary-hover);box-shadow:0 0 20px var(--player-primary-color)}.video-poster-overlay::before{content:"";position:absolute;top:50%;left:50%;transform:translate(-40%, -50%);width:0;height:0;border-style:solid;border-width:15px 0 15px 25px;border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) var(--player-primary-color);z-index:2;opacity:0;transition:opacity .3s ease,border-color .3s ease;pointer-events:none}.video-poster-overlay.visible:hover::before{opacity:1;border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) var(--player-primary-hover)}.initial-loading{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--player-bg-primary);display:flex;align-items:center;justify-content:center;z-index:20;flex-direction:column;gap:15px}.video-wrapper.player-initialized .initial-loading{opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;transition-delay:.2s}.video-wrapper.player-initialized .initial-loading{display:none;transition-delay:.5s}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--player-bg-loading);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity var(--player-transition-normal);z-index:15;flex-direction:column;gap:15px}.loading-overlay.active{opacity:1;visibility:visible}.loading-spinner{width:50px;height:50px;border:3px solid hsla(0,0%,100%,.3);border-top:3px solid var(--player-primary-color);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.loading-text{color:var(--player-text-color);font-size:14px;font-weight:500;text-shadow:0 2px 4px rgba(0,0,0,.5);letter-spacing:.5px;margin-top:10px;text-align:center}.title-overlay{position:absolute;top:0;left:0;right:0;background:var(--player-bg-title-overlay);padding:var(--player-title-overlay-padding);opacity:0;transform:translateY(-100%);transition:all var(--player-transition-normal);z-index:15;pointer-events:none}.title-overlay.show{opacity:1;transform:translateY(0)}.title-overlay.show.persistent{opacity:1;transform:translateY(0)}.title-text{color:var(--player-text-color);font-size:18px;font-weight:600;line-height:1.3;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-shadow:0 2px 8px rgba(0,0,0,.7);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.player-top-bar{position:absolute;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:flex-start;padding:15px 20px;background:linear-gradient(to bottom, rgba(0, 0, 0, var(--player-topbar-opacity, 0.95)) 0%, rgba(0, 0, 0, calc(var(--player-topbar-opacity, 0.95) * 0.68)) 50%, rgba(0, 0, 0, 0) 100%);backdrop-filter:blur(5px);z-index:20;opacity:0;transform:translateY(-100%);transition:all .3s ease;pointer-events:none}.player-top-bar.no-title-background{background:rgba(0,0,0,0);backdrop-filter:none}.player-top-bar .top-bar-title{flex:1;margin-right:20px;pointer-events:none;min-width:0;max-width:calc(100% - 80px)}.player-top-bar .top-bar-title .video-title{color:#fff;font-size:18px;font-weight:600;margin:0 0 4px 0;text-shadow:0 2px 4px rgba(0,0,0,.8);line-height:1.3;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}.player-top-bar .top-bar-title .video-subtitle{color:hsla(0,0%,100%,.8);font-size:14px;text-shadow:0 1px 3px rgba(0,0,0,.8);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}.player-top-bar .top-bar-spacer{flex:1}.player-top-bar .settings-control{pointer-events:all;position:relative;flex-shrink:0}.player-top-bar .settings-control .settings-btn{background:rgba(0,0,0,.6);backdrop-filter:blur(10px);padding:10px;border-radius:50%;border:1px solid hsla(0,0%,100%,.15);transition:all .3s ease;cursor:pointer}.player-top-bar .settings-control .settings-btn:hover,.player-top-bar .settings-control .settings-btn.active{background:rgba(0,0,0,.9);border-color:hsla(0,0%,100%,.3);transform:rotate(90deg)}.player-top-bar .settings-control .settings-btn .icon svg{display:block}.player-top-bar .settings-control .settings-menu{position:absolute;top:calc(100% + 10px);right:0;min-width:240px;max-width:320px;background:rgba(28,28,28,.98);backdrop-filter:blur(20px);border-radius:8px;border:1px solid hsla(0,0%,100%,.1);box-shadow:0 8px 32px rgba(0,0,0,.6);opacity:0;visibility:hidden;transform:translateY(-10px);transition:all .3s ease;max-height:600px !important;min-height:200px;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:hsla(0,0%,100%,.3) rgba(0,0,0,0);display:flex;flex-direction:column}.player-top-bar .settings-control .settings-menu::-webkit-scrollbar{width:6px}.player-top-bar .settings-control .settings-menu::-webkit-scrollbar-track{background:hsla(0,0%,100%,.05);border-radius:3px}.player-top-bar .settings-control .settings-menu::-webkit-scrollbar-thumb{background:hsla(0,0%,100%,.3);border-radius:3px}.player-top-bar .settings-control .settings-menu::-webkit-scrollbar-thumb:hover{background:hsla(0,0%,100%,.5)}.player-top-bar .settings-control .settings-menu.active{opacity:1;visibility:visible;transform:translateY(0)}.settings-option[data-action=moreinfo]{order:-1;border-bottom:1px solid hsla(0,0%,100%,.1);padding-bottom:12px;margin-bottom:8px}.settings-expandable-wrapper{border-bottom:1px solid hsla(0,0%,100%,.05)}.settings-expandable-wrapper:last-child{border-bottom:none}.settings-option{padding:12px 16px;cursor:pointer;transition:background .2s ease;display:flex;justify-content:space-between;align-items:center;color:#fff;user-select:none}.settings-option:hover{background:hsla(0,0%,100%,.1)}.settings-option .settings-option-label{flex:1;color:#fff;font-size:14px;font-weight:400}.settings-option .settings-option-label strong{font-weight:600;margin-left:4px}.settings-option .expand-arrow{color:hsla(0,0%,100%,.6);font-size:12px;transition:transform .3s ease;margin-left:10px;line-height:1}.settings-expandable-content{background:rgba(0,0,0,.3);max-height:250px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:hsla(0,0%,100%,.2) rgba(0,0,0,0)}.settings-expandable-content::-webkit-scrollbar{width:4px}.settings-expandable-content::-webkit-scrollbar-thumb{background:hsla(0,0%,100%,.2);border-radius:2px}.settings-expandable-content::-webkit-scrollbar-thumb:hover{background:hsla(0,0%,100%,.3)}.settings-suboption{padding:10px 20px 10px 32px;cursor:pointer;transition:background .2s ease;color:hsla(0,0%,100%,.8);font-size:13px;user-select:none}.settings-suboption:hover{background:hsla(0,0%,100%,.08);color:hsla(0,0%,100%,.95)}.settings-suboption.active{background:hsla(0,0%,100%,.15);color:#fff;font-weight:500;position:relative}.settings-suboption.active::before{content:"✓";position:absolute;left:12px;color:var(--player-primary-color, #ff0000);font-weight:700}body .video-wrapper:hover .player-top-bar,body .video-wrapper .player-top-bar{opacity:0 !important;transform:translateY(-100%) !important;transition:all .3s ease !important}body .video-wrapper.has-controls .player-top-bar{opacity:1 !important;transform:translateY(0) !important}body .video-wrapper .player-top-bar.persistent{opacity:1 !important;transform:translateY(0) !important}@media(max-width: 768px){.player-top-bar{padding:12px 15px}.player-top-bar .top-bar-title{margin-right:15px;max-width:calc(100% - 70px)}.player-top-bar .top-bar-title .video-title{font-size:16px}.player-top-bar .top-bar-title .video-subtitle{font-size:13px}.player-top-bar .settings-control .settings-btn{padding:8px}.player-top-bar .settings-control .settings-btn .icon svg{width:18px;height:18px}.player-top-bar .settings-control .settings-menu{min-width:200px;max-height:400px}}@media(max-width: 480px){.player-top-bar{padding:10px 12px}.player-top-bar .top-bar-title{margin-right:10px;max-width:calc(100% - 60px)}.player-top-bar .top-bar-title .video-title{font-size:14px}.player-top-bar .top-bar-title .video-subtitle{font-size:12px}.player-top-bar .settings-control .settings-btn{padding:6px}.player-top-bar .settings-control .settings-btn .icon svg{width:16px;height:16px}.player-top-bar .settings-control .settings-menu{min-width:180px;max-height:300px}}.video-wrapper:fullscreen .player-top-bar,.video-wrapper:-webkit-full-screen .player-top-bar,.video-wrapper:-moz-full-screen .player-top-bar{padding:20px 30px}.video-wrapper:fullscreen .player-top-bar .top-bar-title .video-title,.video-wrapper:-webkit-full-screen .player-top-bar .top-bar-title .video-title,.video-wrapper:-moz-full-screen .player-top-bar .top-bar-title .video-title{font-size:22px}.video-wrapper:fullscreen .player-top-bar .settings-control .settings-btn,.video-wrapper:-webkit-full-screen .player-top-bar .settings-control .settings-btn,.video-wrapper:-moz-full-screen .player-top-bar .settings-control .settings-btn{padding:12px}.title-text{color:var(--player-text-color);font-size:18px;font-weight:600;line-height:1.3;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-shadow:0 2px 8px rgba(0,0,0,.7);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.subtitle-text{color:var(--player-text-color);font-size:14px;font-weight:400;line-height:1.3;margin:5px 0 0 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-shadow:0 2px 8px rgba(0,0,0,.7);opacity:.9;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.chapter-name{font-size:13px;font-weight:500;color:hsla(0,0%,100%,.9);margin-top:6px;max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:opacity .3s}.controls{position:absolute;bottom:0;left:0;right:0;background:rgba(0, 0, 0, var(--control-bar-opacity, 0.95));padding:var(--player-controls-padding);opacity:0;transform:translateY(100%);transition:all var(--player-transition-normal);z-index:10;min-height:70px !important;box-sizing:border-box}.controls.show{opacity:1;transform:translateY(0);position:absolute !important;bottom:0 !important;z-index:20 !important}.play-icon svg,.pause-icon svg,.volume-icon svg,.mute-icon svg,.playlist-prev-btn .icon svg,.playlist-next-btn .icon svg,.subtitles-btn .icon svg,.fullscreen-icon svg,.exit-fullscreen-icon svg,.pip-icon svg,.pip-exit-icon svg{width:16px;height:16px;display:block}.controls-main{display:flex;justify-content:space-between;align-items:center;width:100%;min-height:44px !important;flex-shrink:0}.controls-left,.controls-right{display:flex;align-items:center;gap:8px;flex-shrink:1;min-width:0}.control-btn{background:none;border:none;color:var(--player-button-color);cursor:pointer;padding:var(--player-button-padding);border-radius:6px;display:flex;align-items:center;justify-content:center;gap:5px;transition:all var(--player-transition-fast);font-size:14px;font-weight:500;position:relative;flex-shrink:1;min-width:0;white-space:nowrap;vertical-align:middle}.control-btn:hover{background:var(--player-button-hover);transform:scale(1.05)}.control-btn:active{transform:scale(0.95);background:var(--player-button-active)}.subtitles-btn{position:relative}.quality-btn{min-height:36px;padding:6px 8px}.quality-btn-text{display:flex;flex-direction:column;align-items:center;line-height:1}.selected-quality{font-size:14px;font-weight:500;color:var(--player-button-color)}.current-quality{font-size:10px;font-weight:400;color:var(--player-text-secondary);opacity:.8;margin-top:2px;line-height:1}.time-display{color:var(--player-text-color);font-size:14px;font-weight:500;display:flex;flex-direction:column;align-items:center;justify-content:center;line-height:1.1;gap:0;font-variant-numeric:tabular-nums;flex-shrink:2;min-width:0;margin:0 5px}.time-display .duration{font-size:10px;color:var(--player-text-secondary);opacity:.8;font-weight:400}.icon{width:var(--player-icon-size);height:var(--player-icon-size);display:flex;align-items:center;justify-content:center;font-size:16px}.hidden{display:none !important}.controls-right .brand-logo{height:44px;max-width:120px;object-fit:contain;margin-right:10px;pointer-events:auto;opacity:.8;transition:opacity var(--player-transition-fast);order:-1;flex-shrink:1}.controls-right .brand-logo:hover{opacity:1}.controls-right .brand-logo-link{order:-1;margin-right:10px;display:inline-block;text-decoration:none}.controls-right .brand-logo-link .brand-logo{margin-right:0}.video-wrapper.hide-cursor{cursor:none !important}.video-wrapper.hide-cursor .controls{cursor:default !important}.video-wrapper.hide-cursor .control-btn{cursor:pointer !important}.video-wrapper.hide-cursor iframe{cursor:auto !important;pointer-events:auto !important}.play-from-start-btn .restart-icon{display:inline-flex;align-items:center;justify-content:center}.control-btn .icon{display:inline-flex;align-items:center;justify-content:center}.moreinfo-modal-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;z-index:10000;backdrop-filter:blur(5px)}.moreinfo-modal-content{background:linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);border-radius:12px;max-width:600px;width:90%;max-height:80%;display:flex;flex-direction:column;box-shadow:0 10px 40px rgba(0,0,0,.5);border:1px solid hsla(0,0%,100%,.1)}.moreinfo-modal-header{display:flex;justify-content:space-between;align-items:center;padding:20px 24px;border-bottom:1px solid hsla(0,0%,100%,.1)}.moreinfo-modal-title{margin:0;font-size:20px;font-weight:600;color:#fff;flex:1}.moreinfo-modal-close{background:rgba(0,0,0,0);border:none;color:#fff;font-size:32px;cursor:pointer;padding:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:background-color .2s ease;line-height:1}.moreinfo-modal-close:hover{background:hsla(0,0%,100%,.1)}.moreinfo-modal-body{padding:24px;overflow-y:auto;overflow-x:hidden;flex:1;color:#e0e0e0;font-size:14px;line-height:1.6}.moreinfo-modal-body::-webkit-scrollbar{width:8px}.moreinfo-modal-body::-webkit-scrollbar-track{background:hsla(0,0%,100%,.05);border-radius:4px}.moreinfo-modal-body::-webkit-scrollbar-thumb{background:hsla(0,0%,100%,.2);border-radius:4px}.moreinfo-modal-body::-webkit-scrollbar-thumb:hover{background:hsla(0,0%,100%,.3)}.moreinfo-modal-body{scrollbar-width:thin;scrollbar-color:hsla(0,0%,100%,.2) hsla(0,0%,100%,.05)}@media(max-width: 600px){.moreinfo-modal-content{width:95%;max-height:85%}.moreinfo-modal-header{padding:16px 20px}.moreinfo-modal-title{font-size:18px}.moreinfo-modal-body{padding:20px;font-size:13px}}.progress-container{width:100%;height:var(--player-progress-height);background:var(--player-progress-bg);border-radius:calc(var(--player-progress-height)/2);margin-bottom:15px;position:relative;cursor:pointer}.progress-bar{width:100%;height:100%;position:relative;border-radius:calc(var(--player-progress-height)/2);overflow:hidden}.progress-buffer{height:100%;background:var(--player-progress-buffer);width:0%;border-radius:calc(var(--player-progress-height)/2);transition:width var(--player-transition-fast)}.progress-filled{position:absolute;top:0;left:0;height:100%;background:var(--player-primary-color);width:0%;border-radius:calc(var(--player-progress-height)/2);transition:width .1s ease}.progress-handle{position:absolute;top:50%;transform:translate(-50%, -50%);width:var(--player-progress-handle-size);height:var(--player-progress-handle-size);background:var(--player-primary-color);border-radius:50%;opacity:1;transition:opacity var(--player-transition-fast);z-index:2;left:0%;box-shadow:0 2px 8px rgba(0,0,0,.3);pointer-events:all;touch-action:none}.progress-handle::before{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:44px;height:44px;border-radius:50%}.progress-container:hover .progress-handle{opacity:1}.progress-container:hover .progress-filled{background:var(--player-primary-hover)}.seek-tooltip{position:absolute;bottom:100%;left:0;background:rgba(0,0,0,.9);color:#fff;padding:6px 10px;border-radius:6px;font-size:12px;font-weight:500;white-space:nowrap;opacity:0;visibility:hidden;transform:translateX(-50%) translateY(-8px);transition:all .15s ease;z-index:1000;box-shadow:var(--player-shadow-tooltip);font-variant-numeric:tabular-nums;backdrop-filter:blur(8px);border:1px solid hsla(0,0%,100%,.1)}.seek-tooltip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);width:0;height:0;border-left:5px solid rgba(0,0,0,0);border-right:5px solid rgba(0,0,0,0);border-top:5px solid rgba(0,0,0,.9)}.seek-tooltip.visible{opacity:1;visibility:visible;transform:translateX(-50%) translateY(-4px)}.chapter-markers-container{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:3}.chapter-marker{position:absolute;top:0;height:100%;width:3px;background:var(--player-primary-color);opacity:.7;cursor:pointer;pointer-events:auto;transition:all var(--player-transition-fast);border-radius:2px;transform:translateX(-50%)}.chapter-marker:hover{opacity:1;width:4px;height:120%;top:-10%;box-shadow:0 0 8px var(--player-primary-color)}.chapter-marker.active{background:var(--player-primary-hover);opacity:1;width:4px}.chapter-tooltip{position:absolute;bottom:100%;left:0;background:rgba(0,0,0,.95);backdrop-filter:blur(10px);border-radius:8px;padding:0;margin-bottom:12px;opacity:0;visibility:hidden;transition:all .2s ease;transform:translateX(-50%) translateY(-8px);z-index:1000;box-shadow:var(--player-shadow-tooltip);border:1px solid hsla(0,0%,100%,.15);min-width:200px;max-width:300px;overflow:hidden;pointer-events:none}.chapter-tooltip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);width:0;height:0;border-left:6px solid rgba(0,0,0,0);border-right:6px solid rgba(0,0,0,0);border-top:6px solid rgba(0,0,0,.95)}.chapter-tooltip-image{width:100%;height:150px;background-size:cover;background-position:center;background-repeat:no-repeat;display:none;border-bottom:1px solid hsla(0,0%,100%,.1)}.chapter-tooltip-title{padding:10px 12px 6px;color:var(--player-text-color);font-size:14px;font-weight:600;line-height:1.3;word-wrap:break-word}.chapter-tooltip-time{padding:0 12px 10px;color:var(--player-text-secondary);font-size:12px;font-weight:400;font-variant-numeric:tabular-nums}.progress-handle-circle{border-radius:50%}.progress-handle-square{border-radius:2px}.progress-handle-diamond{border-radius:2px;transform:translate(-50%, -50%) rotate(45deg)}.progress-handle-arrow{border-radius:0;clip-path:polygon(0% 50%, 60% 0%, 60% 35%, 100% 35%, 100% 65%, 60% 65%, 60% 100%)}.progress-handle-triangle{border-radius:0;clip-path:polygon(50% 0%, 0% 100%, 100% 100%)}.progress-handle-heart{border-radius:0}.progress-handle-heart::before{content:"❤";font-size:12px;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%)}.progress-handle-star{border-radius:0;clip-path:polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)}.progress-handle-none{opacity:0 !important}.progress-handle::after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:44px;height:44px;border-radius:50%}.progress-container.seeking .progress-bar{height:calc(var(--player-progress-height)*2);transition:height .15s ease}.progress-container.seeking .progress-handle{transform:translate(-50%, -50%) scale(1.4);transition:transform .15s ease}@media(hover: hover)and (pointer: fine){.progress-container:hover .progress-bar{height:calc(var(--player-progress-height)*1.3);transition:height .15s ease}}.chapter-segment{box-sizing:border-box}.chapter-marker:hover{background:rgba(0,0,0,.9) !important}.chapter-tooltip{animation:fadeIn .15s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateX(-50%) translateY(-5px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}.progress-container:hover .chapter-segment{background:hsla(0,0%,100%,.4) !important}.chapter-tooltip{animation:fadeIn .15s ease-in-out}.chapter-tooltip .chapter-tooltip-content{display:flex;flex-direction:column}.chapter-tooltip .chapter-tooltip-image{background-size:cover;background-position:center;background-repeat:no-repeat;border-radius:3px}.chapter-tooltip .chapter-tooltip-title{line-height:1.3}.chapter-tooltip .chapter-tooltip-time{opacity:.8}@keyframes fadeIn{from{opacity:0;transform:translateX(-50%) translateY(-5px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}.chapter-tooltip-hover .chapter-tooltip-content{display:flex;flex-direction:column;gap:6px}.chapter-tooltip-hover .chapter-tooltip-image{width:100%;aspect-ratio:16/9;background-size:cover;background-position:center;background-repeat:no-repeat;border-radius:3px;max-width:180px}.chapter-tooltip-hover .chapter-tooltip-title{font-size:13px;font-weight:600;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chapter-tooltip-hover .chapter-tooltip-time{font-size:12px;font-weight:400;color:hsla(0,0%,100%,.8);max-width:180px}@media(max-width: 1200px){.chapter-tooltip-hover .chapter-tooltip-image{max-width:150px}.chapter-tooltip-hover .chapter-tooltip-title,.chapter-tooltip-hover .chapter-tooltip-time{max-width:150px}}@media(max-width: 768px){.chapter-tooltip-hover .chapter-tooltip-image{max-width:100px}.chapter-tooltip-hover .chapter-tooltip-title,.chapter-tooltip-hover .chapter-tooltip-time{max-width:100px}}@media(max-width: 480px){.chapter-tooltip-hover .chapter-tooltip-image{max-width:80px}.chapter-tooltip-hover .chapter-tooltip-title{font-size:11px;max-width:80px}.chapter-tooltip-hover .chapter-tooltip-time{font-size:10px;max-width:80px}}.volume-container{display:flex;align-items:center;gap:8px;position:relative;flex-shrink:2;min-width:0}.volume-slider{width:60px;height:var(--player-volume-height);background:var(--player-volume-bg);border-radius:calc(var(--player-volume-height)/2);outline:none;cursor:pointer;-webkit-appearance:none;transition:all var(--player-transition-fast)}.volume-tooltip{position:absolute;bottom:210%;transition:opacity .15s ease,transform .15s ease;left:0;transform:translateX(-50%);background:rgba(0,0,0,.9);color:#fff;padding:6px 10px;border-radius:6px;font-size:12px;font-weight:500;white-space:nowrap;opacity:0;visibility:hidden;z-index:1000;box-shadow:var(--player-shadow-tooltip);pointer-events:none;backdrop-filter:blur(8px);border:1px solid hsla(0,0%,100%,.1)}.volume-tooltip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);width:0;height:0;border-left:6px solid rgba(0,0,0,0);border-right:6px solid rgba(0,0,0,0);border-top:6px solid rgba(0,0,0,.9)}.volume-container:hover .volume-tooltip,.volume-tooltip.visible{opacity:1;visibility:visible}.volume-slider::-webkit-slider-thumb{-webkit-appearance:none;width:var(--player-volume-handle-size);height:var(--player-volume-handle-size);border-radius:50%;background:var(--player-primary-dark);cursor:pointer;transition:all var(--player-transition-fast);box-shadow:0 2px 6px rgba(0,0,0,.2);margin-top:calc((var(--player-volume-height) - var(--player-volume-handle-size))/2);transform:translateY(0)}.volume-slider::-webkit-slider-thumb:hover{transform:translateY(0) scale(1.2);background:var(--player-primary-color)}.volume-slider::-moz-range-thumb{width:var(--player-volume-handle-size);height:var(--player-volume-handle-size);border-radius:50%;background:var(--player-primary-dark);cursor:pointer;border:none;box-shadow:0 2px 6px rgba(0,0,0,.2);transition:all var(--player-transition-fast);margin-top:0;transform:translateY(calc((var(--player-volume-height) - var(--player-volume-handle-size)) / 2))}.volume-slider::-moz-range-thumb:hover{background:var(--player-primary-color);transform:translateY(calc((var(--player-volume-height) - var(--player-volume-handle-size)) / 2)) scale(1.1)}.volume-slider::-webkit-slider-runnable-track{height:var(--player-volume-height);background:linear-gradient(to right, var(--player-primary-dark) 0%, var(--player-primary-dark) var(--player-volume-fill), var(--player-volume-bg) var(--player-volume-fill), var(--player-volume-bg) 100%);border-radius:calc(var(--player-volume-height)/2);transition:background var(--player-transition-fast);margin:0;border:none}.volume-slider::-moz-range-track{height:var(--player-volume-height);background:linear-gradient(to right, var(--player-primary-dark) 0%, var(--player-primary-dark) var(--player-volume-fill), var(--player-volume-bg) var(--player-volume-fill), var(--player-volume-bg) 100%);border-radius:calc(var(--player-volume-height)/2);border:none;transition:background var(--player-transition-fast);margin:0}.quality-control{position:relative}.subtitles-control{display:none !important}.speed-control{display:none !important}.speed-menu,.quality-menu,.subtitles-menu{position:absolute;bottom:100%;right:0;background:var(--player-bg-menu);backdrop-filter:blur(10px);border-radius:8px;padding:8px 0;margin-bottom:10px;opacity:0;visibility:hidden;transition:all var(--player-transition-fast);min-width:140px;border:1px solid hsla(0,0%,100%,.1);z-index:100;box-shadow:var(--player-shadow-menu)}.speed-menu.active,.quality-menu.active,.subtitles-menu.active{opacity:1 !important;visibility:visible !important;pointer-events:all !important}.speed-option,.quality-option,.subtitles-option{padding:8px 16px;color:var(--player-text-color);cursor:pointer;transition:all var(--player-transition-fast);font-size:14px;display:flex;align-items:center;justify-content:space-between}.speed-option:hover,.quality-option:hover,.subtitles-option:hover{background:hsla(0,0%,100%,.1);color:var(--player-primary-color)}.speed-option.active,.quality-option.active,.subtitles-option.active{color:var(--player-primary-color);font-weight:600;background:hsla(0,0%,100%,.05)}.subtitles-option.selected,.subtitles-option.active{color:var(--player-primary-color);background:hsla(0,0%,100%,.1);position:relative}.subtitles-option.selected::after,.subtitles-option.active::after{content:"✓";position:absolute;right:10px;font-weight:bold}.quality-option.selected{color:var(--player-primary-color);font-weight:600}.quality-option.selected::after{content:"Selected";font-size:12px;color:var(--player-primary-color);font-weight:400;margin-left:8px}.quality-option.playing{background:hsla(0,0%,100%,.05)}.quality-option.playing::after{content:"Playing";font-size:12px;color:#4caf50;font-weight:400;margin-left:8px}.quality-option.selected.playing::after{content:"Active";font-size:12px;color:var(--player-primary-color);font-weight:500;margin-left:8px}.subtitles-option.active::after{content:"✓";font-size:12px;color:var(--player-primary-color)}.settings-control{position:relative;display:block !important}.settings-btn{background:none;border:none;color:var(--player-button-color);cursor:pointer;padding:var(--player-button-padding);border-radius:6px;display:flex;align-items:center;gap:5px;transition:all var(--player-transition-fast);font-size:14px;font-weight:500;position:relative;flex-shrink:0;min-width:0;white-space:nowrap}.settings-btn:hover{background:var(--player-button-hover);transform:scale(1.05)}.settings-btn:active{transform:scale(0.95);background:var(--player-button-active)}.settings-menu{position:absolute;bottom:100%;right:0;background:var(--player-bg-menu);backdrop-filter:blur(10px);border-radius:8px;padding:8px 0;margin-bottom:10px;opacity:0;visibility:hidden;transition:all var(--player-transition-fast);min-width:180px;border:1px solid hsla(0,0%,100%,.1);z-index:100;box-shadow:var(--player-shadow-menu);max-height:200px;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:var(--player-primary-color) hsla(0,0%,100%,.05)}.settings-menu.active{opacity:1 !important;visibility:visible !important;pointer-events:all !important}.settings-menu::-webkit-scrollbar{width:8px}.settings-menu::-webkit-scrollbar-track{background:hsla(0,0%,100%,.05);border-radius:10px}.settings-menu::-webkit-scrollbar-thumb{background:var(--player-primary-color);border-radius:10px;border:2px solid rgba(0,0,0,0);background-clip:content-box}.settings-menu::-webkit-scrollbar-thumb:hover{background:var(--player-primary-hover, var(--player-primary-color));background-clip:content-box}.settings-option{padding:8px 16px;color:var(--player-text-color);cursor:pointer;transition:all var(--player-transition-fast);font-size:14px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid hsla(0,0%,100%,.05);position:relative}.settings-option:last-child{border-bottom:none}.settings-option:hover{background:hsla(0,0%,100%,.1);color:var(--player-primary-color)}.settings-option-label{display:flex;align-items:center;gap:8px;flex:1}.settings-option-value{font-size:12px;color:var(--player-text-secondary);opacity:.8}.settings-expandable-wrapper{position:relative;display:block}.settings-option.expandable-trigger{display:flex;justify-content:space-between;align-items:center;cursor:pointer}.settings-option.expandable-trigger .settings-option-label{font-size:14px}.settings-option.expandable-trigger .expand-arrow{font-size:12px;transition:transform .2s ease;margin-left:8px}.settings-option.expandable-trigger:hover{background:hsla(0,0%,100%,.1)}.settings-expandable-content{padding-left:15px;margin-top:4px;display:none;background:rgba(0,0,0,.3);border-left:3px solid var(--player-primary-color)}.settings-expandable-content.active{display:block}.settings-suboption{padding:8px 12px;cursor:pointer;color:#fff;font-size:13px;white-space:normal;word-wrap:break-word;opacity:.8;transition:opacity .2s;display:flex;align-items:center;justify-content:space-between}.settings-suboption:hover{opacity:1;background:hsla(0,0%,100%,.1)}.settings-suboption.active{opacity:1;font-weight:bold;color:var(--player-primary-color)}.settings-suboption.active::after{content:"✓";font-size:12px}@media(max-width: 768px){.settings-menu>.settings-option{font-size:13px}.settings-suboption{font-size:12px;padding:7px 10px}}@media(max-width: 600px){.settings-menu>.settings-option{font-size:12px}.settings-suboption{font-size:11px;padding:6px 8px}}@media(max-width: 450px){.settings-menu>.settings-option{font-size:11px}.settings-suboption{font-size:10px;padding:5px 6px}}@media(max-width: 350px){.settings-control{display:block !important}.pip-btn{display:none !important}}.audio-player{width:320px;height:80px}.audio-player video{display:none !important}.audio-player .controls-wrapper{height:60px}.audio-player .audio-wave-canvas{display:block;width:100%;height:60px;background-color:#222;border-radius:4px;margin-top:5px}.player-theme-blue{--player-primary-color: #2196F3;--player-primary-hover: #1976D2;--player-primary-dark: #1565C0}.player-theme-green{--player-primary-color: #4CAF50;--player-primary-hover: #45a049;--player-primary-dark: #388e3c}.player-theme-red{--player-primary-color: #f44336;--player-primary-hover: #d32f2f;--player-primary-dark: #c62828}.video-watermark{position:absolute;z-index:15;pointer-events:auto;opacity:.7;transition:opacity .3s ease,visibility .3s ease,bottom .3s ease}.video-watermark{visibility:visible;opacity:.7}.video-wrapper:not(.has-controls) .video-watermark.hide-on-autohide{visibility:hidden;opacity:0}.video-wrapper.has-controls .video-watermark{visibility:visible;opacity:.7}.video-watermark:hover{opacity:1}.video-watermark img{display:block;max-width:150px;max-height:80px;width:auto;height:auto;object-fit:contain}.video-watermark.watermark-topleft{top:15px;left:15px}.video-watermark.watermark-topright{top:15px;right:15px}.video-watermark.watermark-bottomleft{bottom:calc(var(--player-controls-height, 70px) + 15px);left:15px}.video-watermark.watermark-bottomright{bottom:calc(var(--player-controls-height, 70px) + 15px);right:15px}.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide){bottom:15px}.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide){bottom:15px}.video-wrapper.has-controls .video-watermark.watermark-bottomleft,.video-wrapper.has-controls .video-watermark.watermark-bottomright{bottom:calc(var(--player-controls-height, 70px) + 15px)}@media(max-width: 768px){.video-watermark img{max-width:100px;max-height:50px}.video-watermark.watermark-topleft,.video-watermark.watermark-topright{top:10px}.video-watermark.watermark-topleft,.video-watermark.watermark-bottomleft{left:10px}.video-watermark.watermark-topright,.video-watermark.watermark-bottomright{right:10px}.video-watermark.watermark-bottomleft,.video-watermark.watermark-bottomright{bottom:calc(var(--player-controls-height, 60px) + 10px)}.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide),.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide){bottom:10px}}@media(max-width: 480px){.video-watermark.watermark-bottomleft,.video-watermark.watermark-bottomright{bottom:calc(var(--player-controls-height, 55px) + 10px)}.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide),.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide){bottom:8px}}.video-watermark[style*="cursor: pointer"]{cursor:pointer !important}@media(max-width: 768px){.controls-left,.controls-right{gap:8px}.volume-slider{width:50px}.time-display{font-size:12px}.icon{font-size:14px}.control-btn{padding:6px}.quality-btn{min-height:32px;padding:4px 6px}.selected-quality{font-size:12px}.current-quality{font-size:9px}.seek-tooltip{font-size:11px;padding:4px 8px}.title-overlay{padding:12px 15px 20px}.title-text{font-size:16px}.video-player::cue{font-size:16px;padding:6px 10px}.controls-right .brand-logo{height:36px;max-width:100px;margin-right:8px}}@media(max-width: 480px){.controls-left,.controls-right{gap:6px}.progress-container{margin-bottom:10px}.controls-main{padding-top:6px}.volume-container{flex-shrink:3}.volume-slider{width:35px}.quality-btn{min-height:28px;padding:3px 5px}.selected-quality{font-size:11px}.current-quality{font-size:8px}.seek-tooltip{font-size:10px;padding:3px 6px}.title-overlay{padding:10px 12px 18px}.title-text{font-size:14px}.video-player::cue{font-size:14px;padding:4px 8px}.controls-right .brand-logo{height:28px;max-width:80px;margin-right:5px}}@media(max-width: 350px){.controls-left,.controls-right{gap:4px}.control-btn{padding:4px}.icon{font-size:12px}.quality-btn{min-height:24px;padding:2px 4px}.selected-quality{font-size:10px}.current-quality{font-size:7px}.controls-right .brand-logo{height:22px;max-width:50px;margin-right:3px}.volume-slider{width:30px}.settings-menu{min-width:160px;font-size:12px}.settings-option{padding:6px 12px;font-size:12px}.settings-submenu{min-width:130px}.settings-suboption{padding:6px 12px;font-size:11px}}@media(max-width: 280px){.controls-left,.controls-right{gap:3px}.control-btn{padding:3px}.icon{font-size:10px}.quality-btn{min-height:20px;padding:1px 3px}.selected-quality{font-size:9px}.current-quality{font-size:6px}.controls-right .brand-logo{height:18px;max-width:40px;margin-right:2px}.volume-slider{width:25px}.settings-menu{min-width:140px;font-size:11px}.settings-option{padding:5px 10px;font-size:11px}.settings-submenu{min-width:120px}.settings-suboption{padding:5px 10px;font-size:10px}}@media(max-width: 600px){.controls-main{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.controls-main::-webkit-scrollbar{display:none}.controls-left,.controls-right{flex-wrap:nowrap;white-space:nowrap;flex-shrink:1;min-width:fit-content}}.controls-right .playlist-prev-btn,.controls-right .playlist-next-btn{display:none}.controls-right .playlist-prev-btn.playlist-active,.controls-right .playlist-next-btn.playlist-active{display:flex}.playlist-prev-btn .icon::before{content:"⏮"}.playlist-next-btn .icon::before{content:"⏭"}.playlist-prev-btn:disabled,.playlist-next-btn:disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.playlist-prev-btn:disabled .icon,.playlist-next-btn:disabled .icon{opacity:.5}@media(max-width: 768px){.playlist-prev-btn .icon::before,.playlist-next-btn .icon::before{font-size:16px}}@media(max-width: 480px){.playlist-prev-btn .icon::before,.playlist-next-btn .icon::before{font-size:14px}}.video-container:fullscreen,.video-container:-webkit-full-screen,.video-container:-moz-full-screen{width:100vw;height:100vh;border-radius:0}@keyframes qualityChange{0%{opacity:.7}50%{opacity:.3}100%{opacity:1}}.quality-changing{animation:qualityChange .5s ease-in-out}.control-btn:focus{outline:2px solid var(--player-primary-color);outline-offset:2px}.volume-slider:focus{outline:2px solid var(--player-primary-color);outline-offset:2px}.player-large-controls{--player-icon-size: 24px;--player-button-padding: 12px;--player-progress-height: 8px;--player-progress-handle-size: 20px;--player-title-overlay-padding: 18px 24px 30px}.player-compact-controls{--player-icon-size: 16px;--player-button-padding: 4px;--player-controls-padding: 15px 10px 10px;--player-title-overlay-padding: 12px 16px 20px}@-moz-document url-prefix(){.volume-slider::-moz-range-thumb{transform:translateY(calc((var(--player-volume-height) - var(--player-volume-handle-size)) / 2 - 2px))}.volume-slider::-moz-range-thumb:hover{transform:translateY(calc((var(--player-volume-height) - var(--player-volume-handle-size)) / 2 - 2px)) scale(1.1)}.volume-slider::-moz-range-track{height:var(--player-volume-height);background:linear-gradient(to right, var(--player-primary-dark) 0%, var(--player-primary-dark) var(--player-volume-fill), var(--player-volume-bg) var(--player-volume-fill), var(--player-volume-bg) 100%);border-radius:calc(var(--player-volume-height)/2);border:none;transition:background var(--player-transition-fast);margin:0}}@supports(-moz-appearance: none){.volume-slider{margin-top:-1px}.volume-slider::-moz-range-thumb{transform:translateY(calc((var(--player-volume-height) - var(--player-volume-handle-size)) / 2 - 1.5px))}.volume-slider::-moz-range-thumb:hover{transform:translateY(calc((var(--player-volume-height) - var(--player-volume-handle-size)) / 2 - 1.5px)) scale(1.1)}}@-moz-document url-prefix(){.volume-container{position:relative;top:2px !important}.volume-slider::-moz-range-thumb{margin-top:-6px !important;transform:translateY(-2px) !important}.volume-slider::-moz-range-thumb:hover{transform:translateY(-2px) scale(1.1) !important}}.video-player{object-position:center center}.resolution-normal .video-player{object-fit:contain;object-position:center center}.resolution-4-3 .video-player{object-fit:fill;aspect-ratio:4/3}.resolution-4-3 .video-wrapper{aspect-ratio:4/3}.resolution-16-9 .video-player{object-fit:fill;aspect-ratio:16/9}.resolution-16-9 .video-wrapper{aspect-ratio:16/9}.resolution-stretched .video-player{object-fit:fill;width:100%;height:100%}.resolution-stretched .video-wrapper{height:auto;min-height:300px}.resolution-fit-to-screen .video-player{object-fit:cover;object-position:center center;width:100%;height:100%}.resolution-fit-to-screen .video-wrapper{height:100vh;max-height:100vh}.resolution-scale-to-fit .video-player{object-fit:contain;object-position:center center;width:100%;height:100%;max-width:100vw;max-height:100vh}.resolution-scale-to-fit .video-wrapper{display:flex;align-items:center;justify-content:center;height:70vh;min-height:400px;background:var(--player-bg-primary, #000)}@media(orientation: portrait){.resolution-scale-to-fit .video-wrapper{height:50vh;min-height:350px}}@media(orientation: landscape){.resolution-scale-to-fit .video-wrapper{height:80vh;min-height:450px}}@media(max-width: 768px){.resolution-fit-to-screen .video-wrapper{height:50vh;min-height:250px}.resolution-4-3 .video-wrapper,.resolution-16-9 .video-wrapper{min-height:200px}.resolution-scale-to-fit .video-wrapper{height:45vh;min-height:300px}}@media(max-width: 480px){.resolution-fit-to-screen .video-wrapper{height:40vh;min-height:200px}.resolution-4-3 .video-wrapper,.resolution-16-9 .video-wrapper{min-height:180px}.resolution-scale-to-fit .video-wrapper{height:40vh;min-height:250px}}.video-player{transition:object-fit .3s ease,aspect-ratio .3s ease}.video-wrapper{transition:aspect-ratio .3s ease,height .3s ease}@supports not (aspect-ratio: 1){.resolution-4-3 .video-wrapper{padding-bottom:75%;height:0;position:relative}.resolution-4-3 .video-player{position:absolute;top:0;left:0;width:100%;height:100%}.resolution-16-9 .video-wrapper{padding-bottom:56.25%;height:0;position:relative}.resolution-16-9 .video-player{position:absolute;top:0;left:0;width:100%;height:100%}}.quality-changing .video-player{filter:brightness(0.7)}.resolution-debug .video-wrapper::before{content:"Resolution: " attr(data-resolution);position:absolute;top:10px;left:10px;background:rgba(0,0,0,.7);color:#fff;padding:5px 10px;border-radius:4px;font-size:12px;z-index:1000;pointer-events:none}.controls,.controls-main,.controls-left,.controls-right{overflow:visible !important}.controls-left,.controls-right{flex-wrap:nowrap !important;white-space:nowrap !important}.control-btn{min-width:0 !important;white-space:nowrap !important}video::cue{background-color:rgba(0,0,0,.8);color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:18px;font-weight:normal;line-height:1.2;text-shadow:1px 1px 1px rgba(0,0,0,.8);padding:4px 8px;border-radius:4px;white-space:pre-line}video::-webkit-media-text-track-display{color:#fff;font-family:Arial,Helvetica,sans-serif;background-color:rgba(0,0,0,.8);border-radius:4px;padding:4px 8px;font-size:18px;text-shadow:1px 1px 1px rgba(0,0,0,.8)}.custom-subtitle-overlay{font-size:clamp(12px,4vw,18px)}@media(max-width: 768px){.custom-subtitle-overlay{font-size:16px !important;bottom:70px !important;max-width:85% !important;padding:6px 12px !important;line-height:1.2 !important}}@media(max-width: 480px){.custom-subtitle-overlay{font-size:14px !important;bottom:60px !important;max-width:90% !important;padding:5px 10px !important;line-height:1.15 !important}}@media(max-width: 360px){.custom-subtitle-overlay{font-size:12px !important;bottom:50px !important;max-width:95% !important;padding:4px 8px !important}}@media(max-height: 500px)and (orientation: landscape){.custom-subtitle-overlay{font-size:13px !important;bottom:45px !important;max-width:85% !important;padding:4px 10px !important}}.speed-menu,.quality-menu,.subtitles-menu{max-height:200px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--player-primary-color) hsla(0,0%,100%,.1)}.speed-menu::-webkit-scrollbar,.quality-menu::-webkit-scrollbar,.subtitles-menu::-webkit-scrollbar{width:6px}.speed-menu::-webkit-scrollbar-track,.quality-menu::-webkit-scrollbar-track,.subtitles-menu::-webkit-scrollbar-track{background:hsla(0,0%,100%,.1);border-radius:3px}.speed-menu::-webkit-scrollbar-thumb,.quality-menu::-webkit-scrollbar-thumb,.subtitles-menu::-webkit-scrollbar-thumb{background:var(--player-primary-color);border-radius:3px}.speed-menu::-webkit-scrollbar-thumb:hover,.quality-menu::-webkit-scrollbar-thumb:hover,.subtitles-menu::-webkit-scrollbar-thumb:hover{background:var(--player-primary-hover)}@media(max-height: 400px){.speed-menu,.quality-menu,.subtitles-menu{max-height:150px}}@media(max-height: 300px){.speed-menu,.quality-menu,.subtitles-menu{max-height:120px}}.settings-submenu{max-height:180px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--player-primary-color) hsla(0,0%,100%,.1)}.settings-submenu::-webkit-scrollbar{width:6px}.settings-submenu::-webkit-scrollbar-track{background:hsla(0,0%,100%,.1);border-radius:3px}.settings-submenu::-webkit-scrollbar-thumb{background:var(--player-primary-color);border-radius:3px}.settings-submenu::-webkit-scrollbar-thumb:hover{background:var(--player-primary-hover)}@media(max-width: 350px){.settings-submenu{max-height:140px}}@media(max-height: 400px){.settings-submenu{max-height:120px}}.volume-container{position:relative;display:flex;align-items:center;gap:var(--player-controls-gap)}.volume-container[data-mobile-slider=show] .volume-slider{width:80px;height:var(--player-volume-height);background:var(--player-volume-bg);border-radius:calc(var(--player-volume-height)/2);transition:all .3s ease}@media(max-width: 1200px){.volume-container[data-mobile-slider=show] .volume-slider{width:70px}}@media(max-width: 900px){.volume-container[data-mobile-slider=show] .volume-slider{width:60px}}@media(max-width: 768px){.volume-container[data-mobile-slider=show] .volume-slider{width:50px}}@media(max-width: 600px){.volume-container[data-mobile-slider=show] .volume-slider{width:40px}}@media(max-width: 550px){.volume-container[data-mobile-slider=show] .volume-tooltip{display:none !important}.volume-container[data-mobile-slider=show]{pointer-events:auto !important;position:relative}.mute-btn{position:relative;z-index:100;pointer-events:auto !important}.volume-container[data-mobile-slider=show] .volume-slider{position:absolute;opacity:0;visibility:hidden;pointer-events:none;width:0;height:0;transform:translateX(-100%);transition:opacity 0s ease,visibility 0s ease 2s,width 0s ease 2s}.controls-left:hover .volume-container[data-mobile-slider=show] .volume-slider,.mute-btn:hover~.volume-container[data-mobile-slider=show] .volume-slider,.volume-container[data-mobile-slider=show]:hover .volume-slider,.volume-slider:hover{position:absolute;opacity:1;visibility:visible;pointer-events:auto !important;width:90px !important;height:auto;bottom:auto;top:50%;left:5px;transform:translateY(-50%);z-index:19;background:rgba(0,0,0,.92) !important;border:1px solid hsla(0,0%,100%,.15);border-radius:8px;padding:10px 14px;box-shadow:0 4px 16px rgba(0,0,0,.6);backdrop-filter:blur(10px);transition:opacity .2s ease,visibility 0s ease,width .2s ease}.controls-left:has(.volume-container[data-mobile-slider=show]):hover{pointer-events:auto !important}.controls-left:hover .volume-slider::-webkit-slider-runnable-track,.volume-container[data-mobile-slider=show]:hover .volume-slider::-webkit-slider-runnable-track,.volume-slider:hover::-webkit-slider-runnable-track{width:60px;height:4px !important;background:linear-gradient(to right, var(--player-primary-color) 0%, var(--player-primary-color) var(--player-volume-fill, 50%), rgba(255, 255, 255, 0.4) var(--player-volume-fill, 50%), rgba(255, 255, 255, 0.4) 100%) !important;border-radius:2px}.controls-left:hover .volume-slider::-webkit-slider-thumb,.volume-container[data-mobile-slider=show]:hover .volume-slider::-webkit-slider-thumb,.volume-slider:hover::-webkit-slider-thumb{opacity:1 !important;visibility:visible !important;-webkit-appearance:none;width:14px;height:14px;border-radius:50%;background:#fff;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,.5);margin-top:-5px}}.chapter-markers-container{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:3}.chapter-marker{position:absolute;top:0;height:100%;width:3px;background:var(--player-primary-color);opacity:.7;cursor:pointer;pointer-events:auto;transition:all var(--player-transition-fast);border-radius:2px;transform:translateX(-50%)}.chapter-marker:hover{opacity:1;width:4px;height:120%;top:-10%;box-shadow:0 0 8px var(--player-primary-color)}.chapter-marker.active{background:var(--player-primary-hover);opacity:1;width:4px}.chapter-tooltip{position:absolute;bottom:100%;left:0;background:rgba(0,0,0,.95);backdrop-filter:blur(10px);border-radius:8px;padding:0;margin-bottom:12px;opacity:0;visibility:hidden;transition:all .2s ease;transform:translateX(-50%) translateY(-8px);z-index:1000;box-shadow:var(--player-shadow-tooltip);border:1px solid hsla(0,0%,100%,.15);min-width:200px;max-width:300px;overflow:hidden;pointer-events:none}.chapter-tooltip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);width:0;height:0;border-left:6px solid rgba(0,0,0,0);border-right:6px solid rgba(0,0,0,0);border-top:6px solid rgba(0,0,0,.95)}.chapter-tooltip-image{width:100%;height:150px;background-size:cover;background-position:center;background-repeat:no-repeat;display:none;border-bottom:1px solid hsla(0,0%,100%,.1)}.chapter-tooltip-title{padding:10px 12px 6px;color:var(--player-text-color);font-size:14px;font-weight:600;line-height:1.3;word-wrap:break-word}.chapter-tooltip-time{padding:0 12px 10px;color:var(--player-text-secondary);font-size:12px;font-weight:400;font-variant-numeric:tabular-nums}@media(max-width: 480px){.chapter-marker{width:2px}.chapter-marker:hover{width:3px}.chapter-tooltip{min-width:160px;max-width:250px}.chapter-tooltip-image{height:100px}}.video-poster-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background-size:cover;background-position:center;background-repeat:no-repeat;z-index:1;cursor:pointer;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;pointer-events:none}.video-poster-overlay.visible{opacity:1;visibility:visible;pointer-events:auto}.video-poster-overlay.hidden{opacity:0;visibility:hidden;pointer-events:none}.video-poster-overlay.visible:hover{opacity:.95}.video-poster-overlay::after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:80px;height:80px;background:rgba(0,0,0,.7);border-radius:50%;border:3px solid var(--player-primary-color);opacity:0;transition:opacity .3s ease,transform .3s ease,border-color .3s ease;pointer-events:none}.video-poster-overlay.visible:hover::after{opacity:1;transform:translate(-50%, -50%) scale(1.1);border-color:var(--player-primary-hover);box-shadow:0 0 20px var(--player-primary-color)}.video-poster-overlay::before{content:"";position:absolute;top:50%;left:50%;transform:translate(-40%, -50%);width:0;height:0;border-style:solid;border-width:15px 0 15px 25px;border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) var(--player-primary-color);z-index:2;opacity:0;transition:opacity .3s ease,border-color .3s ease;pointer-events:none}.video-poster-overlay.visible:hover::before{opacity:1;border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) var(--player-primary-hover)}@media(max-width: 768px){.video-poster-overlay::after{width:60px;height:60px}.video-poster-overlay::before{border-width:12px 0 12px 20px}}@media(max-width: 480px){.video-poster-overlay::after{width:50px;height:50px}.video-poster-overlay::before{border-width:10px 0 10px 16px}}.video-poster-overlay.hidden{transition:opacity .5s ease,visibility 0s ease .5s}.player-theme-blue .video-poster-overlay::after{border-color:#2196f3}.player-theme-blue .video-poster-overlay.visible:hover::after{border-color:#1976d2;box-shadow:0 0 20px #2196f3}.player-theme-blue .video-poster-overlay::before{border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) #2196f3}.player-theme-blue .video-poster-overlay.visible:hover::before{border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) #1976d2}.player-theme-green .video-poster-overlay::after{border-color:#4caf50}.player-theme-green .video-poster-overlay.visible:hover::after{border-color:#45a049;box-shadow:0 0 20px #4caf50}.player-theme-green .video-poster-overlay::before{border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) #4caf50}.player-theme-green .video-poster-overlay.visible:hover::before{border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) #45a049}.player-theme-red .video-poster-overlay::after{border-color:#f44336}.player-theme-red .video-poster-overlay.visible:hover::after{border-color:#d32f2f;box-shadow:0 0 20px #f44336}.player-theme-red .video-poster-overlay::before{border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) #f44336}.player-theme-red .video-poster-overlay.visible:hover::before{border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) #d32f2f}.player-theme-dark .video-poster-overlay::after{background:rgba(0,0,0,.85)}@media(max-height: 400px){.video-player{min-height:200px}.controls{min-height:50px !important;padding:10px 10px 8px !important}.progress-container{margin-bottom:8px}.controls-main{min-height:32px !important}}@media(max-height: 330px){.video-player{min-height:150px}.controls{min-height:45px !important;padding:8px 8px 6px !important}.progress-container{margin-bottom:6px;height:4px}.controls-main{min-height:28px !important}.control-btn{padding:4px !important}.icon{font-size:14px !important}.time-display{font-size:11px !important}}@media(max-height: 250px){.video-player{min-height:120px}.controls{min-height:40px !important;padding:6px 8px 5px !important}.progress-container{margin-bottom:4px;height:3px}.controls-main{min-height:24px !important}.control-btn{padding:2px !important}.icon{font-size:12px !important}.time-display{font-size:10px !important}.quality-btn{min-height:20px !important;padding:2px 4px !important}.selected-quality{font-size:9px !important}.current-quality{display:none}.volume-slider{width:40px !important}}.video-container,.video-wrapper{overflow:visible !important}.controls.show{position:absolute !important;bottom:0 !important;overflow:visible !important}
1
+ :root{--player-primary-color: goldenrod;--player-primary-hover: #daa520;--player-primary-dark: #b8860b;--player-button-color: white;--player-button-hover: rgba(255, 255, 255, 0.1);--player-button-active: rgba(255, 255, 255, 0.2);--player-text-color: white;--player-text-secondary: rgba(255, 255, 255, 0.8);--player-bg-primary: #000;--player-bg-controls: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.8) 100%);--player-bg-title-overlay: linear-gradient(180deg, rgba(0, 0, 0, 0.8) 0%, transparent 100%);--player-bg-menu: rgba(20, 20, 20, 0.95);--player-bg-loading: rgba(0, 0, 0, 0.7);--player-border-radius: 12px;--player-controls-padding: 20px 15px 15px;--player-title-overlay-padding: 15px 20px 25px;--player-button-padding: 8px;--player-icon-size: 20px;--player-progress-height: 6px;--player-progress-bg: rgba(255, 255, 255, 0.2);--player-progress-buffer: rgba(255, 255, 255, 0.3);--player-progress-handle-size: 16px;--player-volume-height: 4px;--player-volume-bg: rgba(255, 255, 255, 0.2);--player-volume-handle-size: 14px;--player-volume-fill: 100%;--player-transition-fast: 0.2s ease;--player-transition-normal: 0.3s ease;--player-shadow-main: 0 8px 32px rgba(0, 0, 0, 0.3);--player-shadow-menu: 0 4px 16px rgba(0, 0, 0, 0.2);--player-shadow-tooltip: 0 3px 12px rgba(0, 0, 0, 0.4)}*{box-sizing:border-box}body{margin:0;padding:20px;background:#1a1a1a;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}.video-container{max-width:1200px;margin:0 auto;background:var(--player-bg-primary);border-radius:var(--player-border-radius);box-shadow:var(--player-shadow-main);position:relative}.video-container:fullscreen,.video-container:-webkit-full-screen,.video-container:-moz-full-screen{width:100vw;height:100vh;border-radius:0}.video-wrapper{position:relative;width:100%;background:var(--player-bg-primary);overflow:visible !important}.video-wrapper.player-initialized .video-player{visibility:visible;opacity:1;transition:opacity .3s ease;pointer-events:auto}.video-wrapper.player-initialized .initial-loading{opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;transition-delay:.2s;display:none;transition-delay:.5s}.video-wrapper:not(.has-controls) .video-watermark.hide-on-autohide{visibility:hidden;opacity:0}.hidden{display:none !important}.player-theme-dark{--player-bg-primary: #1a1a1a;--player-bg-controls: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.9) 100%);--player-bg-title-overlay: linear-gradient(180deg, rgba(30, 30, 30, 0.9) 0%, transparent 100%);--player-bg-menu: rgba(30, 30, 30, 0.95)}.video-player{width:100%;height:auto;display:block;min-height:300px;-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;will-change:transform;visibility:visible;opacity:1;position:relative;z-index:1;transition:none}.video-wrapper.player-initialized .video-player{visibility:visible;opacity:1;transition:opacity .3s ease;pointer-events:auto}.video-player::-webkit-media-controls-panel,.video-player::-webkit-media-controls-play-button,.video-player::-webkit-media-controls-start-playback-button,.video-player::-webkit-media-controls-timeline,.video-player::-webkit-media-controls-current-time-display,.video-player::-webkit-media-controls-time-remaining-display,.video-player::-webkit-media-controls-mute-button,.video-player::-webkit-media-controls-toggle-closed-captions-button,.video-player::-webkit-media-controls-volume-slider,.video-player::-webkit-media-controls-fullscreen-button,.video-player::-webkit-media-controls-seek-back-button,.video-player::-webkit-media-controls-seek-forward-button,.video-player::-webkit-media-controls-rewind-button,.video-player::-webkit-media-controls-return-to-realtime-button,.video-player::-webkit-media-controls-overlay-play-button{display:none !important;visibility:hidden !important;opacity:0 !important}.video-player::-moz-media-controls{display:none !important}.initial-loading{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--player-bg-primary);display:flex;align-items:center;justify-content:center;z-index:20}.video-wrapper.player-initialized .initial-loading{opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;transition-delay:.2s}.video-wrapper.player-initialized .initial-loading{display:none;transition-delay:.5s}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--player-bg-loading);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity var(--player-transition-normal);z-index:15}.loading-overlay.active{opacity:1;visibility:visible}.loading-spinner{width:50px;height:50px;border:3px solid hsla(0,0%,100%,.3);border-top:3px solid var(--player-primary-color);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.video-player::cue{background:rgba(0,0,0,.8);color:#fff;font-size:18px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-weight:500;text-shadow:2px 2px 4px rgba(0,0,0,.8);padding:8px 12px;border-radius:6px;line-height:1.4}.video-player::cue(.highlight){background:var(--player-primary-color);color:#000}.video-player::cue(b){font-weight:700}.video-player::cue(i){font-style:italic}.video-poster-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background-size:cover;background-position:center;background-repeat:no-repeat;z-index:1;cursor:pointer;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;pointer-events:none}.video-poster-overlay.visible{opacity:1;visibility:visible;pointer-events:auto}.video-poster-overlay.hidden{opacity:0;visibility:hidden;pointer-events:none}.video-poster-overlay.visible:hover{opacity:.95}.video-poster-overlay::after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:80px;height:80px;background:rgba(0,0,0,.7);border-radius:50%;border:3px solid var(--player-primary-color);opacity:0;transition:opacity .3s ease,transform .3s ease,border-color .3s ease;pointer-events:none}.video-poster-overlay.visible:hover::after{opacity:1;transform:translate(-50%, -50%) scale(1.1);border-color:var(--player-primary-hover);box-shadow:0 0 20px var(--player-primary-color)}.video-poster-overlay::before{content:"";position:absolute;top:50%;left:50%;transform:translate(-40%, -50%);width:0;height:0;border-style:solid;border-width:15px 0 15px 25px;border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) var(--player-primary-color);z-index:2;opacity:0;transition:opacity .3s ease,border-color .3s ease;pointer-events:none}.video-poster-overlay.visible:hover::before{opacity:1;border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) var(--player-primary-hover)}.initial-loading{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--player-bg-primary);display:flex;align-items:center;justify-content:center;z-index:20;flex-direction:column;gap:15px}.video-wrapper.player-initialized .initial-loading{opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;transition-delay:.2s}.video-wrapper.player-initialized .initial-loading{display:none;transition-delay:.5s}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:var(--player-bg-loading);display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity var(--player-transition-normal);z-index:15;flex-direction:column;gap:15px}.loading-overlay.active{opacity:1;visibility:visible}.loading-spinner{width:50px;height:50px;border:3px solid hsla(0,0%,100%,.3);border-top:3px solid var(--player-primary-color);border-radius:50%;animation:spin 1s linear infinite}.loading-spinner-wrap{position:relative;width:56px;height:56px;display:flex;align-items:center;justify-content:center}.loading-spinner-wrap .loading-spinner{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:50%;border:3px solid hsla(0,0%,100%,.2);border-top:3px solid var(--player-primary-color);animation:spin 1s linear infinite;box-sizing:border-box}.loading-spinner-logo{position:relative;z-index:1;max-width:34px;max-height:34px;width:auto;height:auto;border-radius:0;object-fit:contain;display:block;flex-shrink:0;pointer-events:none}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.loading-text{color:var(--player-text-color);font-size:14px;font-weight:500;text-shadow:0 2px 4px rgba(0,0,0,.5);letter-spacing:.5px;margin-top:10px;text-align:center}.title-overlay{position:absolute;top:0;left:0;right:0;background:var(--player-bg-title-overlay);padding:var(--player-title-overlay-padding);opacity:0;transform:translateY(-100%);transition:all var(--player-transition-normal);z-index:15;pointer-events:none}.title-overlay.show{opacity:1;transform:translateY(0)}.title-overlay.show.persistent{opacity:1;transform:translateY(0)}.title-text{color:var(--player-text-color);font-size:18px;font-weight:600;line-height:1.3;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-shadow:0 2px 8px rgba(0,0,0,.7);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.player-top-bar{position:absolute;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:flex-start;padding:15px 20px;background:linear-gradient(to bottom, rgba(0, 0, 0, var(--player-topbar-opacity, 0.95)) 0%, rgba(0, 0, 0, calc(var(--player-topbar-opacity, 0.95) * 0.68)) 50%, rgba(0, 0, 0, 0) 100%);backdrop-filter:blur(5px);z-index:20;opacity:0;transform:translateY(-100%);transition:all .3s ease;pointer-events:none}.player-top-bar.no-title-background{background:rgba(0,0,0,0);backdrop-filter:none}.player-top-bar .top-bar-title{flex:1;margin-right:20px;pointer-events:none;min-width:0;max-width:calc(100% - 80px)}.player-top-bar .top-bar-title .video-title{color:#fff;font-size:18px;font-weight:600;margin:0 0 4px 0;text-shadow:0 2px 4px rgba(0,0,0,.8);line-height:1.3;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}.player-top-bar .top-bar-title .video-subtitle{color:hsla(0,0%,100%,.8);font-size:14px;text-shadow:0 1px 3px rgba(0,0,0,.8);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}.player-top-bar .top-bar-spacer{flex:1}.player-top-bar .settings-control{pointer-events:all;position:relative;flex-shrink:0}.player-top-bar .settings-control .settings-btn{background:rgba(0,0,0,.6);backdrop-filter:blur(10px);padding:10px;border-radius:50%;border:1px solid hsla(0,0%,100%,.15);transition:all .3s ease;cursor:pointer}.player-top-bar .settings-control .settings-btn:hover,.player-top-bar .settings-control .settings-btn.active{background:rgba(0,0,0,.9);border-color:hsla(0,0%,100%,.3);transform:rotate(90deg)}.player-top-bar .settings-control .settings-btn .icon svg{display:block}.player-top-bar .settings-control .settings-menu{position:absolute;top:calc(100% + 10px);right:0;min-width:240px;max-width:320px;background:rgba(28,28,28,.98);backdrop-filter:blur(20px);border-radius:8px;border:1px solid hsla(0,0%,100%,.1);box-shadow:0 8px 32px rgba(0,0,0,.6);opacity:0;visibility:hidden;transform:translateY(-10px);transition:all .3s ease;max-height:600px !important;min-height:200px;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:hsla(0,0%,100%,.3) rgba(0,0,0,0);display:flex;flex-direction:column}.player-top-bar .settings-control .settings-menu::-webkit-scrollbar{width:6px}.player-top-bar .settings-control .settings-menu::-webkit-scrollbar-track{background:hsla(0,0%,100%,.05);border-radius:3px}.player-top-bar .settings-control .settings-menu::-webkit-scrollbar-thumb{background:hsla(0,0%,100%,.3);border-radius:3px}.player-top-bar .settings-control .settings-menu::-webkit-scrollbar-thumb:hover{background:hsla(0,0%,100%,.5)}.player-top-bar .settings-control .settings-menu.active{opacity:1;visibility:visible;transform:translateY(0)}.settings-option[data-action=moreinfo]{order:-1;border-bottom:1px solid hsla(0,0%,100%,.1);padding-bottom:12px;margin-bottom:8px}.settings-expandable-wrapper{border-bottom:1px solid hsla(0,0%,100%,.05)}.settings-expandable-wrapper:last-child{border-bottom:none}.settings-option{padding:12px 16px;cursor:pointer;transition:background .2s ease;display:flex;justify-content:space-between;align-items:center;color:#fff;user-select:none}.settings-option:hover{background:hsla(0,0%,100%,.1)}.settings-option .settings-option-label{flex:1;color:#fff;font-size:14px;font-weight:400}.settings-option .settings-option-label strong{font-weight:600;margin-left:4px}.settings-option .expand-arrow{color:hsla(0,0%,100%,.6);font-size:12px;transition:transform .3s ease;margin-left:10px;line-height:1}.settings-expandable-content{background:rgba(0,0,0,.3);max-height:250px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:hsla(0,0%,100%,.2) rgba(0,0,0,0)}.settings-expandable-content::-webkit-scrollbar{width:4px}.settings-expandable-content::-webkit-scrollbar-thumb{background:hsla(0,0%,100%,.2);border-radius:2px}.settings-expandable-content::-webkit-scrollbar-thumb:hover{background:hsla(0,0%,100%,.3)}.settings-suboption{padding:10px 20px 10px 32px;cursor:pointer;transition:background .2s ease;color:hsla(0,0%,100%,.8);font-size:13px;user-select:none}.settings-suboption:hover{background:hsla(0,0%,100%,.08);color:hsla(0,0%,100%,.95)}.settings-suboption.active{background:hsla(0,0%,100%,.15);color:#fff;font-weight:500;position:relative}.settings-suboption.active::before{content:"✓";position:absolute;left:12px;color:var(--player-primary-color, #ff0000);font-weight:700}body .video-wrapper:hover .player-top-bar,body .video-wrapper .player-top-bar{opacity:0 !important;transform:translateY(-100%) !important;transition:all .3s ease !important}body .video-wrapper.has-controls .player-top-bar{opacity:1 !important;transform:translateY(0) !important}body .video-wrapper .player-top-bar.persistent{opacity:1 !important;transform:translateY(0) !important}@media(max-width: 768px){.player-top-bar{padding:12px 15px}.player-top-bar .top-bar-title{margin-right:15px;max-width:calc(100% - 70px)}.player-top-bar .top-bar-title .video-title{font-size:16px}.player-top-bar .top-bar-title .video-subtitle{font-size:13px}.player-top-bar .settings-control .settings-btn{padding:8px}.player-top-bar .settings-control .settings-btn .icon svg{width:18px;height:18px}.player-top-bar .settings-control .settings-menu{min-width:200px;max-height:400px}}@media(max-width: 480px){.player-top-bar{padding:10px 12px}.player-top-bar .top-bar-title{margin-right:10px;max-width:calc(100% - 60px)}.player-top-bar .top-bar-title .video-title{font-size:14px}.player-top-bar .top-bar-title .video-subtitle{font-size:12px}.player-top-bar .settings-control .settings-btn{padding:6px}.player-top-bar .settings-control .settings-btn .icon svg{width:16px;height:16px}.player-top-bar .settings-control .settings-menu{min-width:180px;max-height:300px}}.video-wrapper:fullscreen .player-top-bar,.video-wrapper:-webkit-full-screen .player-top-bar,.video-wrapper:-moz-full-screen .player-top-bar{padding:20px 30px}.video-wrapper:fullscreen .player-top-bar .top-bar-title .video-title,.video-wrapper:-webkit-full-screen .player-top-bar .top-bar-title .video-title,.video-wrapper:-moz-full-screen .player-top-bar .top-bar-title .video-title{font-size:22px}.video-wrapper:fullscreen .player-top-bar .settings-control .settings-btn,.video-wrapper:-webkit-full-screen .player-top-bar .settings-control .settings-btn,.video-wrapper:-moz-full-screen .player-top-bar .settings-control .settings-btn{padding:12px}.title-text{color:var(--player-text-color);font-size:18px;font-weight:600;line-height:1.3;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-shadow:0 2px 8px rgba(0,0,0,.7);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.subtitle-text{color:var(--player-text-color);font-size:14px;font-weight:400;line-height:1.3;margin:5px 0 0 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-shadow:0 2px 8px rgba(0,0,0,.7);opacity:.9;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.chapter-name{font-size:13px;font-weight:500;color:hsla(0,0%,100%,.9);margin-top:6px;max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:opacity .3s}.controls{position:absolute;bottom:0;left:0;right:0;background:rgba(0, 0, 0, var(--control-bar-opacity, 0.95));padding:var(--player-controls-padding);opacity:0;transform:translateY(100%);transition:all var(--player-transition-normal);z-index:10;min-height:70px !important;box-sizing:border-box}.controls.show{opacity:1;transform:translateY(0);position:absolute !important;bottom:0 !important;z-index:20 !important}.play-icon svg,.pause-icon svg,.volume-icon svg,.mute-icon svg,.playlist-prev-btn .icon svg,.playlist-next-btn .icon svg,.subtitles-btn .icon svg,.fullscreen-icon svg,.exit-fullscreen-icon svg,.pip-icon svg,.pip-exit-icon svg{width:16px;height:16px;display:block}.controls-main{display:flex;justify-content:space-between;align-items:center;width:100%;min-height:44px !important;flex-shrink:0}.controls-left,.controls-right{display:flex;align-items:center;gap:8px;flex-shrink:1;min-width:0}.control-btn{background:none;border:none;color:var(--player-button-color);cursor:pointer;padding:var(--player-button-padding);border-radius:6px;display:flex;align-items:center;justify-content:center;gap:5px;transition:all var(--player-transition-fast);font-size:14px;font-weight:500;position:relative;flex-shrink:1;min-width:0;white-space:nowrap;vertical-align:middle}.control-btn:hover{background:var(--player-button-hover);transform:scale(1.05)}.control-btn:active{transform:scale(0.95);background:var(--player-button-active)}.subtitles-btn{position:relative}.quality-btn{min-height:36px;padding:6px 8px}.quality-btn-text{display:flex;flex-direction:column;align-items:center;line-height:1}.selected-quality{font-size:14px;font-weight:500;color:var(--player-button-color)}.current-quality{font-size:10px;font-weight:400;color:var(--player-text-secondary);opacity:.8;margin-top:2px;line-height:1}.time-display{color:var(--player-text-color);font-size:14px;font-weight:500;display:flex;flex-direction:column;align-items:center;justify-content:center;line-height:1.1;gap:0;font-variant-numeric:tabular-nums;flex-shrink:2;min-width:0;margin:0 5px}.time-display .duration{font-size:10px;color:var(--player-text-secondary);opacity:.8;font-weight:400}.icon{width:var(--player-icon-size);height:var(--player-icon-size);display:flex;align-items:center;justify-content:center;font-size:16px}.hidden{display:none !important}.controls-right .brand-logo{height:44px;max-width:120px;object-fit:contain;margin-right:10px;pointer-events:auto;opacity:.8;transition:opacity var(--player-transition-fast);order:-1;flex-shrink:1}.controls-right .brand-logo:hover{opacity:1}.controls-right .brand-logo-link{order:-1;margin-right:10px;display:inline-block;text-decoration:none}.controls-right .brand-logo-link .brand-logo{margin-right:0}.video-wrapper.hide-cursor{cursor:none !important}.video-wrapper.hide-cursor .controls{cursor:default !important}.video-wrapper.hide-cursor .control-btn{cursor:pointer !important}.video-wrapper.hide-cursor iframe{cursor:auto !important;pointer-events:auto !important}.play-from-start-btn .restart-icon{display:inline-flex;align-items:center;justify-content:center}.control-btn .icon{display:inline-flex;align-items:center;justify-content:center}.moreinfo-modal-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;z-index:10000;backdrop-filter:blur(5px)}.moreinfo-modal-content{background:linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);border-radius:12px;max-width:600px;width:90%;max-height:80%;display:flex;flex-direction:column;box-shadow:0 10px 40px rgba(0,0,0,.5);border:1px solid hsla(0,0%,100%,.1)}.moreinfo-modal-header{display:flex;justify-content:space-between;align-items:center;padding:20px 24px;border-bottom:1px solid hsla(0,0%,100%,.1)}.moreinfo-modal-title{margin:0;font-size:20px;font-weight:600;color:#fff;flex:1}.moreinfo-modal-close{background:rgba(0,0,0,0);border:none;color:#fff;font-size:32px;cursor:pointer;padding:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:background-color .2s ease;line-height:1}.moreinfo-modal-close:hover{background:hsla(0,0%,100%,.1)}.moreinfo-modal-body{padding:24px;overflow-y:auto;overflow-x:hidden;flex:1;color:#e0e0e0;font-size:14px;line-height:1.6}.moreinfo-modal-body::-webkit-scrollbar{width:8px}.moreinfo-modal-body::-webkit-scrollbar-track{background:hsla(0,0%,100%,.05);border-radius:4px}.moreinfo-modal-body::-webkit-scrollbar-thumb{background:hsla(0,0%,100%,.2);border-radius:4px}.moreinfo-modal-body::-webkit-scrollbar-thumb:hover{background:hsla(0,0%,100%,.3)}.moreinfo-modal-body{scrollbar-width:thin;scrollbar-color:hsla(0,0%,100%,.2) hsla(0,0%,100%,.05)}@media(max-width: 600px){.moreinfo-modal-content{width:95%;max-height:85%}.moreinfo-modal-header{padding:16px 20px}.moreinfo-modal-title{font-size:18px}.moreinfo-modal-body{padding:20px;font-size:13px}}.progress-container{width:100%;height:var(--player-progress-height);background:var(--player-progress-bg);border-radius:calc(var(--player-progress-height)/2);margin-bottom:15px;position:relative;cursor:pointer}.progress-bar{width:100%;height:100%;position:relative;border-radius:calc(var(--player-progress-height)/2);overflow:hidden}.progress-buffer{height:100%;background:var(--player-progress-buffer);width:0%;border-radius:calc(var(--player-progress-height)/2);transition:width var(--player-transition-fast)}.progress-filled{position:absolute;top:0;left:0;height:100%;background:var(--player-primary-color);width:0%;border-radius:calc(var(--player-progress-height)/2);transition:width .1s ease}.progress-handle{position:absolute;top:50%;transform:translate(-50%, -50%);width:var(--player-progress-handle-size);height:var(--player-progress-handle-size);background:var(--player-primary-color);border-radius:50%;opacity:1;transition:opacity var(--player-transition-fast);z-index:2;left:0%;box-shadow:0 2px 8px rgba(0,0,0,.3);pointer-events:all;touch-action:none}.progress-handle::before{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:44px;height:44px;border-radius:50%}.progress-container:hover .progress-handle{opacity:1}.progress-container:hover .progress-filled{background:var(--player-primary-hover)}.seek-tooltip{position:absolute;bottom:100%;left:0;background:rgba(0,0,0,.9);color:#fff;padding:6px 10px;border-radius:6px;font-size:12px;font-weight:500;white-space:nowrap;opacity:0;visibility:hidden;transform:translateX(-50%) translateY(-8px);transition:all .15s ease;z-index:1000;box-shadow:var(--player-shadow-tooltip);font-variant-numeric:tabular-nums;backdrop-filter:blur(8px);border:1px solid hsla(0,0%,100%,.1)}.seek-tooltip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);width:0;height:0;border-left:5px solid rgba(0,0,0,0);border-right:5px solid rgba(0,0,0,0);border-top:5px solid rgba(0,0,0,.9)}.seek-tooltip.visible{opacity:1;visibility:visible;transform:translateX(-50%) translateY(-4px)}.chapter-markers-container{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:3}.chapter-marker{position:absolute;top:0;height:100%;width:3px;background:var(--player-primary-color);opacity:.7;cursor:pointer;pointer-events:auto;transition:all var(--player-transition-fast);border-radius:2px;transform:translateX(-50%)}.chapter-marker:hover{opacity:1;width:4px;height:120%;top:-10%;box-shadow:0 0 8px var(--player-primary-color)}.chapter-marker.active{background:var(--player-primary-hover);opacity:1;width:4px}.chapter-tooltip{position:absolute;bottom:100%;left:0;background:rgba(0,0,0,.95);backdrop-filter:blur(10px);border-radius:8px;padding:0;margin-bottom:12px;opacity:0;visibility:hidden;transition:all .2s ease;transform:translateX(-50%) translateY(-8px);z-index:1000;box-shadow:var(--player-shadow-tooltip);border:1px solid hsla(0,0%,100%,.15);min-width:200px;max-width:300px;overflow:hidden;pointer-events:none}.chapter-tooltip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);width:0;height:0;border-left:6px solid rgba(0,0,0,0);border-right:6px solid rgba(0,0,0,0);border-top:6px solid rgba(0,0,0,.95)}.chapter-tooltip-image{width:100%;height:150px;background-size:cover;background-position:center;background-repeat:no-repeat;display:none;border-bottom:1px solid hsla(0,0%,100%,.1)}.chapter-tooltip-title{padding:10px 12px 6px;color:var(--player-text-color);font-size:14px;font-weight:600;line-height:1.3;word-wrap:break-word}.chapter-tooltip-time{padding:0 12px 10px;color:var(--player-text-secondary);font-size:12px;font-weight:400;font-variant-numeric:tabular-nums}.progress-handle-circle{border-radius:50%}.progress-handle-square{border-radius:2px}.progress-handle-diamond{border-radius:2px;transform:translate(-50%, -50%) rotate(45deg)}.progress-handle-arrow{border-radius:0;clip-path:polygon(0% 50%, 60% 0%, 60% 35%, 100% 35%, 100% 65%, 60% 65%, 60% 100%)}.progress-handle-triangle{border-radius:0;clip-path:polygon(50% 0%, 0% 100%, 100% 100%)}.progress-handle-heart{border-radius:0}.progress-handle-heart::before{content:"❤";font-size:12px;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%)}.progress-handle-star{border-radius:0;clip-path:polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)}.progress-handle-none{opacity:0 !important}.progress-handle::after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:44px;height:44px;border-radius:50%}.progress-container.seeking .progress-bar{height:calc(var(--player-progress-height)*2);transition:height .15s ease}.progress-container.seeking .progress-handle{transform:translate(-50%, -50%) scale(1.4);transition:transform .15s ease}@media(hover: hover)and (pointer: fine){.progress-container:hover .progress-bar{height:calc(var(--player-progress-height)*1.3);transition:height .15s ease}}.chapter-segment{box-sizing:border-box}.chapter-marker:hover{background:rgba(0,0,0,.9) !important}.chapter-tooltip{animation:fadeIn .15s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateX(-50%) translateY(-5px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}.progress-container:hover .chapter-segment{background:hsla(0,0%,100%,.4) !important}.chapter-tooltip{animation:fadeIn .15s ease-in-out}.chapter-tooltip .chapter-tooltip-content{display:flex;flex-direction:column}.chapter-tooltip .chapter-tooltip-image{background-size:cover;background-position:center;background-repeat:no-repeat;border-radius:3px}.chapter-tooltip .chapter-tooltip-title{line-height:1.3}.chapter-tooltip .chapter-tooltip-time{opacity:.8}@keyframes fadeIn{from{opacity:0;transform:translateX(-50%) translateY(-5px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}.chapter-tooltip-hover .chapter-tooltip-content{display:flex;flex-direction:column;gap:6px}.chapter-tooltip-hover .chapter-tooltip-image{width:100%;aspect-ratio:16/9;background-size:cover;background-position:center;background-repeat:no-repeat;border-radius:3px;max-width:180px}.chapter-tooltip-hover .chapter-tooltip-title{font-size:13px;font-weight:600;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chapter-tooltip-hover .chapter-tooltip-time{font-size:12px;font-weight:400;color:hsla(0,0%,100%,.8);max-width:180px}@media(max-width: 1200px){.chapter-tooltip-hover .chapter-tooltip-image{max-width:150px}.chapter-tooltip-hover .chapter-tooltip-title,.chapter-tooltip-hover .chapter-tooltip-time{max-width:150px}}@media(max-width: 768px){.chapter-tooltip-hover .chapter-tooltip-image{max-width:100px}.chapter-tooltip-hover .chapter-tooltip-title,.chapter-tooltip-hover .chapter-tooltip-time{max-width:100px}}@media(max-width: 480px){.chapter-tooltip-hover .chapter-tooltip-image{max-width:80px}.chapter-tooltip-hover .chapter-tooltip-title{font-size:11px;max-width:80px}.chapter-tooltip-hover .chapter-tooltip-time{font-size:10px;max-width:80px}}.volume-container{display:flex;align-items:center;gap:8px;position:relative;flex-shrink:2;min-width:0}.volume-slider{width:60px;height:var(--player-volume-height);background:var(--player-volume-bg);border-radius:calc(var(--player-volume-height)/2);outline:none;cursor:pointer;-webkit-appearance:none;transition:all var(--player-transition-fast)}.volume-tooltip{position:absolute;bottom:210%;transition:opacity .15s ease,transform .15s ease;left:0;transform:translateX(-50%);background:rgba(0,0,0,.9);color:#fff;padding:6px 10px;border-radius:6px;font-size:12px;font-weight:500;white-space:nowrap;opacity:0;visibility:hidden;z-index:1000;box-shadow:var(--player-shadow-tooltip);pointer-events:none;backdrop-filter:blur(8px);border:1px solid hsla(0,0%,100%,.1)}.volume-tooltip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);width:0;height:0;border-left:6px solid rgba(0,0,0,0);border-right:6px solid rgba(0,0,0,0);border-top:6px solid rgba(0,0,0,.9)}.volume-container:hover .volume-tooltip,.volume-tooltip.visible{opacity:1;visibility:visible}.volume-slider::-webkit-slider-thumb{-webkit-appearance:none;width:var(--player-volume-handle-size);height:var(--player-volume-handle-size);border-radius:50%;background:var(--player-primary-dark);cursor:pointer;transition:all var(--player-transition-fast);box-shadow:0 2px 6px rgba(0,0,0,.2);margin-top:calc((var(--player-volume-height) - var(--player-volume-handle-size))/2);transform:translateY(0)}.volume-slider::-webkit-slider-thumb:hover{transform:translateY(0) scale(1.2);background:var(--player-primary-color)}.volume-slider::-moz-range-thumb{width:var(--player-volume-handle-size);height:var(--player-volume-handle-size);border-radius:50%;background:var(--player-primary-dark);cursor:pointer;border:none;box-shadow:0 2px 6px rgba(0,0,0,.2);transition:all var(--player-transition-fast);margin-top:0;transform:translateY(calc((var(--player-volume-height) - var(--player-volume-handle-size)) / 2))}.volume-slider::-moz-range-thumb:hover{background:var(--player-primary-color);transform:translateY(calc((var(--player-volume-height) - var(--player-volume-handle-size)) / 2)) scale(1.1)}.volume-slider::-webkit-slider-runnable-track{height:var(--player-volume-height);background:linear-gradient(to right, var(--player-primary-dark) 0%, var(--player-primary-dark) var(--player-volume-fill), var(--player-volume-bg) var(--player-volume-fill), var(--player-volume-bg) 100%);border-radius:calc(var(--player-volume-height)/2);transition:background var(--player-transition-fast);margin:0;border:none}.volume-slider::-moz-range-track{height:var(--player-volume-height);background:linear-gradient(to right, var(--player-primary-dark) 0%, var(--player-primary-dark) var(--player-volume-fill), var(--player-volume-bg) var(--player-volume-fill), var(--player-volume-bg) 100%);border-radius:calc(var(--player-volume-height)/2);border:none;transition:background var(--player-transition-fast);margin:0}.quality-control{position:relative}.subtitles-control{display:none !important}.speed-control{display:none !important}.speed-menu,.quality-menu,.subtitles-menu{position:absolute;bottom:100%;right:0;background:var(--player-bg-menu);backdrop-filter:blur(10px);border-radius:8px;padding:8px 0;margin-bottom:10px;opacity:0;visibility:hidden;transition:all var(--player-transition-fast);min-width:140px;border:1px solid hsla(0,0%,100%,.1);z-index:100;box-shadow:var(--player-shadow-menu)}.speed-menu.active,.quality-menu.active,.subtitles-menu.active{opacity:1 !important;visibility:visible !important;pointer-events:all !important}.speed-option,.quality-option,.subtitles-option{padding:8px 16px;color:var(--player-text-color);cursor:pointer;transition:all var(--player-transition-fast);font-size:14px;display:flex;align-items:center;justify-content:space-between}.speed-option:hover,.quality-option:hover,.subtitles-option:hover{background:hsla(0,0%,100%,.1);color:var(--player-primary-color)}.speed-option.active,.quality-option.active,.subtitles-option.active{color:var(--player-primary-color);font-weight:600;background:hsla(0,0%,100%,.05)}.subtitles-option.selected,.subtitles-option.active{color:var(--player-primary-color);background:hsla(0,0%,100%,.1);position:relative}.subtitles-option.selected::after,.subtitles-option.active::after{content:"✓";position:absolute;right:10px;font-weight:bold}.quality-option.selected{color:var(--player-primary-color);font-weight:600}.quality-option.selected::after{content:"Selected";font-size:12px;color:var(--player-primary-color);font-weight:400;margin-left:8px}.quality-option.playing{background:hsla(0,0%,100%,.05)}.quality-option.playing::after{content:"Playing";font-size:12px;color:#4caf50;font-weight:400;margin-left:8px}.quality-option.selected.playing::after{content:"Active";font-size:12px;color:var(--player-primary-color);font-weight:500;margin-left:8px}.subtitles-option.active::after{content:"✓";font-size:12px;color:var(--player-primary-color)}.settings-control{position:relative;display:block !important}.settings-btn{background:none;border:none;color:var(--player-button-color);cursor:pointer;padding:var(--player-button-padding);border-radius:6px;display:flex;align-items:center;gap:5px;transition:all var(--player-transition-fast);font-size:14px;font-weight:500;position:relative;flex-shrink:0;min-width:0;white-space:nowrap}.settings-btn:hover{background:var(--player-button-hover);transform:scale(1.05)}.settings-btn:active{transform:scale(0.95);background:var(--player-button-active)}.settings-menu{position:absolute;bottom:100%;right:0;background:var(--player-bg-menu);backdrop-filter:blur(10px);border-radius:8px;padding:8px 0;margin-bottom:10px;opacity:0;visibility:hidden;transition:all var(--player-transition-fast);min-width:180px;border:1px solid hsla(0,0%,100%,.1);z-index:100;box-shadow:var(--player-shadow-menu);max-height:200px;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;scrollbar-color:var(--player-primary-color) hsla(0,0%,100%,.05)}.settings-menu.active{opacity:1 !important;visibility:visible !important;pointer-events:all !important}.settings-menu::-webkit-scrollbar{width:8px}.settings-menu::-webkit-scrollbar-track{background:hsla(0,0%,100%,.05);border-radius:10px}.settings-menu::-webkit-scrollbar-thumb{background:var(--player-primary-color);border-radius:10px;border:2px solid rgba(0,0,0,0);background-clip:content-box}.settings-menu::-webkit-scrollbar-thumb:hover{background:var(--player-primary-hover, var(--player-primary-color));background-clip:content-box}.settings-option{padding:8px 16px;color:var(--player-text-color);cursor:pointer;transition:all var(--player-transition-fast);font-size:14px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid hsla(0,0%,100%,.05);position:relative}.settings-option:last-child{border-bottom:none}.settings-option:hover{background:hsla(0,0%,100%,.1);color:var(--player-primary-color)}.settings-option-label{display:flex;align-items:center;gap:8px;flex:1}.settings-option-value{font-size:12px;color:var(--player-text-secondary);opacity:.8}.settings-expandable-wrapper{position:relative;display:block}.settings-option.expandable-trigger{display:flex;justify-content:space-between;align-items:center;cursor:pointer}.settings-option.expandable-trigger .settings-option-label{font-size:14px}.settings-option.expandable-trigger .expand-arrow{font-size:12px;transition:transform .2s ease;margin-left:8px}.settings-option.expandable-trigger:hover{background:hsla(0,0%,100%,.1)}.settings-expandable-content{padding-left:15px;margin-top:4px;display:none;background:rgba(0,0,0,.3);border-left:3px solid var(--player-primary-color)}.settings-expandable-content.active{display:block}.settings-suboption{padding:8px 12px;cursor:pointer;color:#fff;font-size:13px;white-space:normal;word-wrap:break-word;opacity:.8;transition:opacity .2s;display:flex;align-items:center;justify-content:space-between}.settings-suboption:hover{opacity:1;background:hsla(0,0%,100%,.1)}.settings-suboption.active{opacity:1;font-weight:bold;color:var(--player-primary-color)}.settings-suboption.active::after{content:"✓";font-size:12px}@media(max-width: 768px){.settings-menu>.settings-option{font-size:13px}.settings-suboption{font-size:12px;padding:7px 10px}}@media(max-width: 600px){.settings-menu>.settings-option{font-size:12px}.settings-suboption{font-size:11px;padding:6px 8px}}@media(max-width: 450px){.settings-menu>.settings-option{font-size:11px}.settings-suboption{font-size:10px;padding:5px 6px}}@media(max-width: 350px){.settings-control{display:block !important}.pip-btn{display:none !important}}.audio-player{width:320px;height:80px}.audio-player video{display:none !important}.audio-player .controls-wrapper{height:60px}.audio-player .audio-wave-canvas{display:block;width:100%;height:60px;background-color:#222;border-radius:4px;margin-top:5px}.player-theme-blue{--player-primary-color: #2196F3;--player-primary-hover: #1976D2;--player-primary-dark: #1565C0}.player-theme-green{--player-primary-color: #4CAF50;--player-primary-hover: #45a049;--player-primary-dark: #388e3c}.player-theme-red{--player-primary-color: #f44336;--player-primary-hover: #d32f2f;--player-primary-dark: #c62828}.video-watermark{position:absolute;z-index:15;pointer-events:auto;opacity:.7;transition:opacity .3s ease,visibility .3s ease,bottom .3s ease}.video-watermark{visibility:visible;opacity:.7}.video-wrapper:not(.has-controls) .video-watermark.hide-on-autohide{visibility:hidden;opacity:0}.video-wrapper.has-controls .video-watermark{visibility:visible;opacity:.7}.video-watermark:hover{opacity:1}.video-watermark img{display:block;max-width:150px;max-height:80px;width:auto;height:auto;object-fit:contain}.video-watermark.watermark-topleft{top:15px;left:15px}.video-watermark.watermark-topright{top:15px;right:15px}.video-watermark.watermark-bottomleft{bottom:calc(var(--player-controls-height, 70px) + 15px);left:15px}.video-watermark.watermark-bottomright{bottom:calc(var(--player-controls-height, 70px) + 15px);right:15px}.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide){bottom:15px}.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide){bottom:15px}.video-wrapper.has-controls .video-watermark.watermark-bottomleft,.video-wrapper.has-controls .video-watermark.watermark-bottomright{bottom:calc(var(--player-controls-height, 70px) + 15px)}@media(max-width: 768px){.video-watermark img{max-width:100px;max-height:50px}.video-watermark.watermark-topleft,.video-watermark.watermark-topright{top:10px}.video-watermark.watermark-topleft,.video-watermark.watermark-bottomleft{left:10px}.video-watermark.watermark-topright,.video-watermark.watermark-bottomright{right:10px}.video-watermark.watermark-bottomleft,.video-watermark.watermark-bottomright{bottom:calc(var(--player-controls-height, 60px) + 10px)}.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide),.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide){bottom:10px}}@media(max-width: 480px){.video-watermark.watermark-bottomleft,.video-watermark.watermark-bottomright{bottom:calc(var(--player-controls-height, 55px) + 10px)}.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide),.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide){bottom:8px}}.video-watermark[style*="cursor: pointer"]{cursor:pointer !important}@media(max-width: 768px){.controls-left,.controls-right{gap:8px}.volume-slider{width:50px}.time-display{font-size:12px}.icon{font-size:14px}.control-btn{padding:6px}.quality-btn{min-height:32px;padding:4px 6px}.selected-quality{font-size:12px}.current-quality{font-size:9px}.seek-tooltip{font-size:11px;padding:4px 8px}.title-overlay{padding:12px 15px 20px}.title-text{font-size:16px}.video-player::cue{font-size:16px;padding:6px 10px}.controls-right .brand-logo{height:36px;max-width:100px;margin-right:8px}}@media(max-width: 480px){.controls-left,.controls-right{gap:6px}.progress-container{margin-bottom:10px}.controls-main{padding-top:6px}.volume-container{flex-shrink:3}.volume-slider{width:35px}.quality-btn{min-height:28px;padding:3px 5px}.selected-quality{font-size:11px}.current-quality{font-size:8px}.seek-tooltip{font-size:10px;padding:3px 6px}.title-overlay{padding:10px 12px 18px}.title-text{font-size:14px}.video-player::cue{font-size:14px;padding:4px 8px}.controls-right .brand-logo{height:28px;max-width:80px;margin-right:5px}}@media(max-width: 350px){.controls-left,.controls-right{gap:4px}.control-btn{padding:4px}.icon{font-size:12px}.quality-btn{min-height:24px;padding:2px 4px}.selected-quality{font-size:10px}.current-quality{font-size:7px}.controls-right .brand-logo{height:22px;max-width:50px;margin-right:3px}.volume-slider{width:30px}.settings-menu{min-width:160px;font-size:12px}.settings-option{padding:6px 12px;font-size:12px}.settings-submenu{min-width:130px}.settings-suboption{padding:6px 12px;font-size:11px}}@media(max-width: 280px){.controls-left,.controls-right{gap:3px}.control-btn{padding:3px}.icon{font-size:10px}.quality-btn{min-height:20px;padding:1px 3px}.selected-quality{font-size:9px}.current-quality{font-size:6px}.controls-right .brand-logo{height:18px;max-width:40px;margin-right:2px}.volume-slider{width:25px}.settings-menu{min-width:140px;font-size:11px}.settings-option{padding:5px 10px;font-size:11px}.settings-submenu{min-width:120px}.settings-suboption{padding:5px 10px;font-size:10px}}@media(max-width: 600px){.controls-main{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.controls-main::-webkit-scrollbar{display:none}.controls-left,.controls-right{flex-wrap:nowrap;white-space:nowrap;flex-shrink:1;min-width:fit-content}}.controls-right .playlist-prev-btn,.controls-right .playlist-next-btn{display:none}.controls-right .playlist-prev-btn.playlist-active,.controls-right .playlist-next-btn.playlist-active{display:flex}.playlist-prev-btn .icon::before{content:"⏮"}.playlist-next-btn .icon::before{content:"⏭"}.playlist-prev-btn:disabled,.playlist-next-btn:disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.playlist-prev-btn:disabled .icon,.playlist-next-btn:disabled .icon{opacity:.5}@media(max-width: 768px){.playlist-prev-btn .icon::before,.playlist-next-btn .icon::before{font-size:16px}}@media(max-width: 480px){.playlist-prev-btn .icon::before,.playlist-next-btn .icon::before{font-size:14px}}.video-container:fullscreen,.video-container:-webkit-full-screen,.video-container:-moz-full-screen{width:100vw;height:100vh;border-radius:0}@keyframes qualityChange{0%{opacity:.7}50%{opacity:.3}100%{opacity:1}}.quality-changing{animation:qualityChange .5s ease-in-out}.control-btn:focus{outline:2px solid var(--player-primary-color);outline-offset:2px}.volume-slider:focus{outline:2px solid var(--player-primary-color);outline-offset:2px}.player-large-controls{--player-icon-size: 24px;--player-button-padding: 12px;--player-progress-height: 8px;--player-progress-handle-size: 20px;--player-title-overlay-padding: 18px 24px 30px}.player-compact-controls{--player-icon-size: 16px;--player-button-padding: 4px;--player-controls-padding: 15px 10px 10px;--player-title-overlay-padding: 12px 16px 20px}@-moz-document url-prefix(){.volume-slider::-moz-range-thumb{transform:translateY(calc((var(--player-volume-height) - var(--player-volume-handle-size)) / 2 - 2px))}.volume-slider::-moz-range-thumb:hover{transform:translateY(calc((var(--player-volume-height) - var(--player-volume-handle-size)) / 2 - 2px)) scale(1.1)}.volume-slider::-moz-range-track{height:var(--player-volume-height);background:linear-gradient(to right, var(--player-primary-dark) 0%, var(--player-primary-dark) var(--player-volume-fill), var(--player-volume-bg) var(--player-volume-fill), var(--player-volume-bg) 100%);border-radius:calc(var(--player-volume-height)/2);border:none;transition:background var(--player-transition-fast);margin:0}}@supports(-moz-appearance: none){.volume-slider{margin-top:-1px}.volume-slider::-moz-range-thumb{transform:translateY(calc((var(--player-volume-height) - var(--player-volume-handle-size)) / 2 - 1.5px))}.volume-slider::-moz-range-thumb:hover{transform:translateY(calc((var(--player-volume-height) - var(--player-volume-handle-size)) / 2 - 1.5px)) scale(1.1)}}@-moz-document url-prefix(){.volume-container{position:relative;top:2px !important}.volume-slider::-moz-range-thumb{margin-top:-6px !important;transform:translateY(-2px) !important}.volume-slider::-moz-range-thumb:hover{transform:translateY(-2px) scale(1.1) !important}}.video-player{object-position:center center}.resolution-normal .video-player{object-fit:contain;object-position:center center}.resolution-4-3 .video-player{object-fit:fill;aspect-ratio:4/3}.resolution-4-3 .video-wrapper{aspect-ratio:4/3}.resolution-16-9 .video-player{object-fit:fill;aspect-ratio:16/9}.resolution-16-9 .video-wrapper{aspect-ratio:16/9}.resolution-stretched .video-player{object-fit:fill;width:100%;height:100%}.resolution-stretched .video-wrapper{height:auto;min-height:300px}.resolution-fit-to-screen .video-player{object-fit:cover;object-position:center center;width:100%;height:100%}.resolution-fit-to-screen .video-wrapper{height:100vh;max-height:100vh}.resolution-scale-to-fit .video-player{object-fit:contain;object-position:center center;width:100%;height:100%;max-width:100vw;max-height:100vh}.resolution-scale-to-fit .video-wrapper{display:flex;align-items:center;justify-content:center;height:70vh;min-height:400px;background:var(--player-bg-primary, #000)}@media(orientation: portrait){.resolution-scale-to-fit .video-wrapper{height:50vh;min-height:350px}}@media(orientation: landscape){.resolution-scale-to-fit .video-wrapper{height:80vh;min-height:450px}}@media(max-width: 768px){.resolution-fit-to-screen .video-wrapper{height:50vh;min-height:250px}.resolution-4-3 .video-wrapper,.resolution-16-9 .video-wrapper{min-height:200px}.resolution-scale-to-fit .video-wrapper{height:45vh;min-height:300px}}@media(max-width: 480px){.resolution-fit-to-screen .video-wrapper{height:40vh;min-height:200px}.resolution-4-3 .video-wrapper,.resolution-16-9 .video-wrapper{min-height:180px}.resolution-scale-to-fit .video-wrapper{height:40vh;min-height:250px}}.video-player{transition:object-fit .3s ease,aspect-ratio .3s ease}.video-wrapper{transition:aspect-ratio .3s ease,height .3s ease}@supports not (aspect-ratio: 1){.resolution-4-3 .video-wrapper{padding-bottom:75%;height:0;position:relative}.resolution-4-3 .video-player{position:absolute;top:0;left:0;width:100%;height:100%}.resolution-16-9 .video-wrapper{padding-bottom:56.25%;height:0;position:relative}.resolution-16-9 .video-player{position:absolute;top:0;left:0;width:100%;height:100%}}.quality-changing .video-player{filter:brightness(0.7)}.resolution-debug .video-wrapper::before{content:"Resolution: " attr(data-resolution);position:absolute;top:10px;left:10px;background:rgba(0,0,0,.7);color:#fff;padding:5px 10px;border-radius:4px;font-size:12px;z-index:1000;pointer-events:none}.controls,.controls-main,.controls-left,.controls-right{overflow:visible !important}.controls-left,.controls-right{flex-wrap:nowrap !important;white-space:nowrap !important}.control-btn{min-width:0 !important;white-space:nowrap !important}video::cue{background-color:rgba(0,0,0,.8);color:#fff;font-family:Arial,Helvetica,sans-serif;font-size:18px;font-weight:normal;line-height:1.2;text-shadow:1px 1px 1px rgba(0,0,0,.8);padding:4px 8px;border-radius:4px;white-space:pre-line}video::-webkit-media-text-track-display{color:#fff;font-family:Arial,Helvetica,sans-serif;background-color:rgba(0,0,0,.8);border-radius:4px;padding:4px 8px;font-size:18px;text-shadow:1px 1px 1px rgba(0,0,0,.8)}.custom-subtitle-overlay{font-size:clamp(12px,4vw,18px)}@media(max-width: 768px){.custom-subtitle-overlay{font-size:16px !important;bottom:70px !important;max-width:85% !important;padding:6px 12px !important;line-height:1.2 !important}}@media(max-width: 480px){.custom-subtitle-overlay{font-size:14px !important;bottom:60px !important;max-width:90% !important;padding:5px 10px !important;line-height:1.15 !important}}@media(max-width: 360px){.custom-subtitle-overlay{font-size:12px !important;bottom:50px !important;max-width:95% !important;padding:4px 8px !important}}@media(max-height: 500px)and (orientation: landscape){.custom-subtitle-overlay{font-size:13px !important;bottom:45px !important;max-width:85% !important;padding:4px 10px !important}}.speed-menu,.quality-menu,.subtitles-menu{max-height:200px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--player-primary-color) hsla(0,0%,100%,.1)}.speed-menu::-webkit-scrollbar,.quality-menu::-webkit-scrollbar,.subtitles-menu::-webkit-scrollbar{width:6px}.speed-menu::-webkit-scrollbar-track,.quality-menu::-webkit-scrollbar-track,.subtitles-menu::-webkit-scrollbar-track{background:hsla(0,0%,100%,.1);border-radius:3px}.speed-menu::-webkit-scrollbar-thumb,.quality-menu::-webkit-scrollbar-thumb,.subtitles-menu::-webkit-scrollbar-thumb{background:var(--player-primary-color);border-radius:3px}.speed-menu::-webkit-scrollbar-thumb:hover,.quality-menu::-webkit-scrollbar-thumb:hover,.subtitles-menu::-webkit-scrollbar-thumb:hover{background:var(--player-primary-hover)}@media(max-height: 400px){.speed-menu,.quality-menu,.subtitles-menu{max-height:150px}}@media(max-height: 300px){.speed-menu,.quality-menu,.subtitles-menu{max-height:120px}}.settings-submenu{max-height:180px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--player-primary-color) hsla(0,0%,100%,.1)}.settings-submenu::-webkit-scrollbar{width:6px}.settings-submenu::-webkit-scrollbar-track{background:hsla(0,0%,100%,.1);border-radius:3px}.settings-submenu::-webkit-scrollbar-thumb{background:var(--player-primary-color);border-radius:3px}.settings-submenu::-webkit-scrollbar-thumb:hover{background:var(--player-primary-hover)}@media(max-width: 350px){.settings-submenu{max-height:140px}}@media(max-height: 400px){.settings-submenu{max-height:120px}}.volume-container{position:relative;display:flex;align-items:center;gap:var(--player-controls-gap)}.volume-container[data-mobile-slider=show] .volume-slider{width:80px;height:var(--player-volume-height);background:var(--player-volume-bg);border-radius:calc(var(--player-volume-height)/2);transition:all .3s ease}@media(max-width: 1200px){.volume-container[data-mobile-slider=show] .volume-slider{width:70px}}@media(max-width: 900px){.volume-container[data-mobile-slider=show] .volume-slider{width:60px}}@media(max-width: 768px){.volume-container[data-mobile-slider=show] .volume-slider{width:50px}}@media(max-width: 600px){.volume-container[data-mobile-slider=show] .volume-slider{width:40px}}@media(max-width: 550px){.volume-container[data-mobile-slider=show] .volume-tooltip{display:none !important}.volume-container[data-mobile-slider=show]{pointer-events:auto !important;position:relative}.mute-btn{position:relative;z-index:100;pointer-events:auto !important}.volume-container[data-mobile-slider=show] .volume-slider{position:absolute;opacity:0;visibility:hidden;pointer-events:none;width:0;height:0;transform:translateX(-100%);transition:opacity 0s ease,visibility 0s ease 2s,width 0s ease 2s}.controls-left:hover .volume-container[data-mobile-slider=show] .volume-slider,.mute-btn:hover~.volume-container[data-mobile-slider=show] .volume-slider,.volume-container[data-mobile-slider=show]:hover .volume-slider,.volume-slider:hover{position:absolute;opacity:1;visibility:visible;pointer-events:auto !important;width:90px !important;height:auto;bottom:auto;top:50%;left:5px;transform:translateY(-50%);z-index:19;background:rgba(0,0,0,.92) !important;border:1px solid hsla(0,0%,100%,.15);border-radius:8px;padding:10px 14px;box-shadow:0 4px 16px rgba(0,0,0,.6);backdrop-filter:blur(10px);transition:opacity .2s ease,visibility 0s ease,width .2s ease}.controls-left:has(.volume-container[data-mobile-slider=show]):hover{pointer-events:auto !important}.controls-left:hover .volume-slider::-webkit-slider-runnable-track,.volume-container[data-mobile-slider=show]:hover .volume-slider::-webkit-slider-runnable-track,.volume-slider:hover::-webkit-slider-runnable-track{width:60px;height:4px !important;background:linear-gradient(to right, var(--player-primary-color) 0%, var(--player-primary-color) var(--player-volume-fill, 50%), rgba(255, 255, 255, 0.4) var(--player-volume-fill, 50%), rgba(255, 255, 255, 0.4) 100%) !important;border-radius:2px}.controls-left:hover .volume-slider::-webkit-slider-thumb,.volume-container[data-mobile-slider=show]:hover .volume-slider::-webkit-slider-thumb,.volume-slider:hover::-webkit-slider-thumb{opacity:1 !important;visibility:visible !important;-webkit-appearance:none;width:14px;height:14px;border-radius:50%;background:#fff;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,.5);margin-top:-5px}}.chapter-markers-container{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:3}.chapter-marker{position:absolute;top:0;height:100%;width:3px;background:var(--player-primary-color);opacity:.7;cursor:pointer;pointer-events:auto;transition:all var(--player-transition-fast);border-radius:2px;transform:translateX(-50%)}.chapter-marker:hover{opacity:1;width:4px;height:120%;top:-10%;box-shadow:0 0 8px var(--player-primary-color)}.chapter-marker.active{background:var(--player-primary-hover);opacity:1;width:4px}.chapter-tooltip{position:absolute;bottom:100%;left:0;background:rgba(0,0,0,.95);backdrop-filter:blur(10px);border-radius:8px;padding:0;margin-bottom:12px;opacity:0;visibility:hidden;transition:all .2s ease;transform:translateX(-50%) translateY(-8px);z-index:1000;box-shadow:var(--player-shadow-tooltip);border:1px solid hsla(0,0%,100%,.15);min-width:200px;max-width:300px;overflow:hidden;pointer-events:none}.chapter-tooltip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);width:0;height:0;border-left:6px solid rgba(0,0,0,0);border-right:6px solid rgba(0,0,0,0);border-top:6px solid rgba(0,0,0,.95)}.chapter-tooltip-image{width:100%;height:150px;background-size:cover;background-position:center;background-repeat:no-repeat;display:none;border-bottom:1px solid hsla(0,0%,100%,.1)}.chapter-tooltip-title{padding:10px 12px 6px;color:var(--player-text-color);font-size:14px;font-weight:600;line-height:1.3;word-wrap:break-word}.chapter-tooltip-time{padding:0 12px 10px;color:var(--player-text-secondary);font-size:12px;font-weight:400;font-variant-numeric:tabular-nums}@media(max-width: 480px){.chapter-marker{width:2px}.chapter-marker:hover{width:3px}.chapter-tooltip{min-width:160px;max-width:250px}.chapter-tooltip-image{height:100px}}.video-poster-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background-size:cover;background-position:center;background-repeat:no-repeat;z-index:1;cursor:pointer;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;pointer-events:none}.video-poster-overlay.visible{opacity:1;visibility:visible;pointer-events:auto}.video-poster-overlay.hidden{opacity:0;visibility:hidden;pointer-events:none}.video-poster-overlay.visible:hover{opacity:.95}.video-poster-overlay::after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:80px;height:80px;background:rgba(0,0,0,.7);border-radius:50%;border:3px solid var(--player-primary-color);opacity:0;transition:opacity .3s ease,transform .3s ease,border-color .3s ease;pointer-events:none}.video-poster-overlay.visible:hover::after{opacity:1;transform:translate(-50%, -50%) scale(1.1);border-color:var(--player-primary-hover);box-shadow:0 0 20px var(--player-primary-color)}.video-poster-overlay::before{content:"";position:absolute;top:50%;left:50%;transform:translate(-40%, -50%);width:0;height:0;border-style:solid;border-width:15px 0 15px 25px;border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) var(--player-primary-color);z-index:2;opacity:0;transition:opacity .3s ease,border-color .3s ease;pointer-events:none}.video-poster-overlay.visible:hover::before{opacity:1;border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) var(--player-primary-hover)}@media(max-width: 768px){.video-poster-overlay::after{width:60px;height:60px}.video-poster-overlay::before{border-width:12px 0 12px 20px}}@media(max-width: 480px){.video-poster-overlay::after{width:50px;height:50px}.video-poster-overlay::before{border-width:10px 0 10px 16px}}.video-poster-overlay.hidden{transition:opacity .5s ease,visibility 0s ease .5s}.player-theme-blue .video-poster-overlay::after{border-color:#2196f3}.player-theme-blue .video-poster-overlay.visible:hover::after{border-color:#1976d2;box-shadow:0 0 20px #2196f3}.player-theme-blue .video-poster-overlay::before{border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) #2196f3}.player-theme-blue .video-poster-overlay.visible:hover::before{border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) #1976d2}.player-theme-green .video-poster-overlay::after{border-color:#4caf50}.player-theme-green .video-poster-overlay.visible:hover::after{border-color:#45a049;box-shadow:0 0 20px #4caf50}.player-theme-green .video-poster-overlay::before{border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) #4caf50}.player-theme-green .video-poster-overlay.visible:hover::before{border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) #45a049}.player-theme-red .video-poster-overlay::after{border-color:#f44336}.player-theme-red .video-poster-overlay.visible:hover::after{border-color:#d32f2f;box-shadow:0 0 20px #f44336}.player-theme-red .video-poster-overlay::before{border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) #f44336}.player-theme-red .video-poster-overlay.visible:hover::before{border-color:rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) #d32f2f}.player-theme-dark .video-poster-overlay::after{background:rgba(0,0,0,.85)}@media(max-height: 400px){.video-player{min-height:200px}.controls{min-height:50px !important;padding:10px 10px 8px !important}.progress-container{margin-bottom:8px}.controls-main{min-height:32px !important}}@media(max-height: 330px){.video-player{min-height:150px}.controls{min-height:45px !important;padding:8px 8px 6px !important}.progress-container{margin-bottom:6px;height:4px}.controls-main{min-height:28px !important}.control-btn{padding:4px !important}.icon{font-size:14px !important}.time-display{font-size:11px !important}}@media(max-height: 250px){.video-player{min-height:120px}.controls{min-height:40px !important;padding:6px 8px 5px !important}.progress-container{margin-bottom:4px;height:3px}.controls-main{min-height:24px !important}.control-btn{padding:2px !important}.icon{font-size:12px !important}.time-display{font-size:10px !important}.quality-btn{min-height:20px !important;padding:2px 4px !important}.selected-quality{font-size:9px !important}.current-quality{display:none}.volume-slider{width:40px !important}}.video-container,.video-wrapper{overflow:visible !important}.controls.show{position:absolute !important;bottom:0 !important;overflow:visible !important}
@@ -700,6 +700,7 @@ constructor(videoElement, options = {}) {
700
700
  brandLogoUrl: '', // URL for brand logo image
701
701
  brandLogoLinkUrl: '', // Optional URL to open when clicking the logo
702
702
  brandLogoTooltipText: '', // Tooltip text for brand logo
703
+ loadingLogo: null, // URL image to show inside the loading circle
703
704
  playlistEnabled: true, // Enable/disable playlist detection
704
705
  playlistAutoPlay: true, // Auto-play next video when current ends
705
706
  playlistLoop: false, // Loop playlist when reaching the end
@@ -1337,24 +1338,40 @@ createPlayerStructure() {
1337
1338
  createInitialLoading() {
1338
1339
  const initialLoader = document.createElement('div');
1339
1340
  initialLoader.className = 'initial-loading';
1340
- initialLoader.innerHTML = '<div class="loading-spinner"></div><div class="loading-text"></div>';
1341
+ initialLoader.innerHTML = `
1342
+ <div class="loading-spinner-wrap">
1343
+ <div class="loading-spinner"></div>
1344
+ ${this.options.loadingLogo
1345
+ ? `<img class="loading-spinner-logo" src="${this.options.loadingLogo}" alt="" />`
1346
+ : ''}
1347
+ </div>
1348
+ <div class="loading-text"></div>
1349
+ `;
1341
1350
  this.container.appendChild(initialLoader);
1342
1351
  this.initialLoading = initialLoader;
1343
1352
  }
1344
1353
 
1345
- collectVideoQualities() {
1346
- if (this.options.debug) console.log('📁 Video qualities will be loaded with restored sources');
1347
- }
1348
-
1349
1354
  createLoadingOverlay() {
1350
1355
  const overlay = document.createElement('div');
1351
1356
  overlay.className = 'loading-overlay';
1352
- overlay.id = 'loadingOverlay-' + this.getUniqueId();
1353
- overlay.innerHTML = '<div class="loading-spinner"></div><div class="loading-text"></div>';
1357
+ overlay.id = `loadingOverlay-${this.getUniqueId()}`;
1358
+ overlay.innerHTML = `
1359
+ <div class="loading-spinner-wrap">
1360
+ <div class="loading-spinner"></div>
1361
+ ${this.options.loadingLogo
1362
+ ? `<img class="loading-spinner-logo" src="${this.options.loadingLogo}" alt="" />`
1363
+ : ''}
1364
+ </div>
1365
+ <div class="loading-text"></div>
1366
+ `;
1354
1367
  this.container.appendChild(overlay);
1355
1368
  this.loadingOverlay = overlay;
1356
1369
  }
1357
1370
 
1371
+ collectVideoQualities() {
1372
+ if (this.options.debug) console.log('📁 Video qualities will be loaded with restored sources');
1373
+ }
1374
+
1358
1375
  updateTooltips() {
1359
1376
  if (!this.controls) return;
1360
1377
 
@@ -655,6 +655,7 @@ constructor(videoElement, options = {}) {
655
655
  brandLogoUrl: '', // URL for brand logo image
656
656
  brandLogoLinkUrl: '', // Optional URL to open when clicking the logo
657
657
  brandLogoTooltipText: '', // Tooltip text for brand logo
658
+ loadingLogo: null, // URL image to show inside the loading circle
658
659
  playlistEnabled: true, // Enable/disable playlist detection
659
660
  playlistAutoPlay: true, // Auto-play next video when current ends
660
661
  playlistLoop: false, // Loop playlist when reaching the end
@@ -1251,24 +1252,40 @@ createPlayerStructure() {
1251
1252
  createInitialLoading() {
1252
1253
  const initialLoader = document.createElement('div');
1253
1254
  initialLoader.className = 'initial-loading';
1254
- initialLoader.innerHTML = '<div class="loading-spinner"></div><div class="loading-text"></div>';
1255
+ initialLoader.innerHTML = `
1256
+ <div class="loading-spinner-wrap">
1257
+ <div class="loading-spinner"></div>
1258
+ ${this.options.loadingLogo
1259
+ ? `<img class="loading-spinner-logo" src="${this.options.loadingLogo}" alt="" />`
1260
+ : ''}
1261
+ </div>
1262
+ <div class="loading-text"></div>
1263
+ `;
1255
1264
  this.container.appendChild(initialLoader);
1256
1265
  this.initialLoading = initialLoader;
1257
1266
  }
1258
1267
 
1259
- collectVideoQualities() {
1260
- if (this.options.debug) console.log('📁 Video qualities will be loaded with restored sources');
1261
- }
1262
-
1263
1268
  createLoadingOverlay() {
1264
1269
  const overlay = document.createElement('div');
1265
1270
  overlay.className = 'loading-overlay';
1266
- overlay.id = 'loadingOverlay-' + this.getUniqueId();
1267
- overlay.innerHTML = '<div class="loading-spinner"></div><div class="loading-text"></div>';
1271
+ overlay.id = `loadingOverlay-${this.getUniqueId()}`;
1272
+ overlay.innerHTML = `
1273
+ <div class="loading-spinner-wrap">
1274
+ <div class="loading-spinner"></div>
1275
+ ${this.options.loadingLogo
1276
+ ? `<img class="loading-spinner-logo" src="${this.options.loadingLogo}" alt="" />`
1277
+ : ''}
1278
+ </div>
1279
+ <div class="loading-text"></div>
1280
+ `;
1268
1281
  this.container.appendChild(overlay);
1269
1282
  this.loadingOverlay = overlay;
1270
1283
  }
1271
1284
 
1285
+ collectVideoQualities() {
1286
+ if (this.options.debug) console.log('📁 Video qualities will be loaded with restored sources');
1287
+ }
1288
+
1272
1289
  updateTooltips() {
1273
1290
  if (!this.controls) return;
1274
1291
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myetv-player",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "MYETV Video Player - Modular HTML5 video player with plugin support for YouTube, Vimeo, Twitch, Facebook, Cloudflare Stream and streaming protocols (HLS/DASH)",
5
5
  "main": "dist/myetv-player.js",
6
6
  "files": [
@@ -71,3 +71,4 @@
71
71
 
72
72
 
73
73
 
74
+
@@ -220,7 +220,16 @@
220
220
  `;
221
221
 
222
222
  const ICON_CC = `<svg viewBox="0 0 24 24"><path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-9 8H9.5v-.5h-2v3h2V14H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V14H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z"/></svg>`;
223
- const ICON_LOADING = `<svg viewBox="0 0 24 24"><path d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z"><animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite"/></path></svg>`;
223
+ const ICON_LOADING = `<svg viewBox="0 0 24 24">
224
+ <!-- cerchio rotante -->
225
+ <path d="M12 2A10 10 0 0 1 22 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none">
226
+ <animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite"/>
227
+ </path>
228
+ <!-- icona CC fissa al centro, scala ridotta -->
229
+ <g transform="translate(4, 5) scale(0.65)">
230
+ <path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-9 8H9.5v-.5h-2v3h2V14H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V14H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z"/>
231
+ </g>
232
+ </svg>`;
224
233
 
225
234
  const WHISPER_MODELS = {
226
235
  tiny: 'Xenova/whisper-tiny',
@@ -289,6 +298,7 @@
289
298
  cacheEnabled: true,
290
299
  idCache: null,
291
300
  autoTranslation: null, // ISO 2-letter code, e.g. 'en', 'it'
301
+ translationEngine: null, // null = MyMemory (default), oppure { type: 'libretranslate', url: 'https://...', apiKey: '' }
292
302
  }, options);
293
303
 
294
304
  this.isGenerating = false;
@@ -326,26 +336,31 @@
326
336
  }
327
337
 
328
338
  _scheduleAutoTranslation() {
329
- // Light polling: waits until this.subtitles is populated (from cache or transcription),
330
- // then triggers translation automatically
331
339
  const lang = this.opts.autoTranslation.toLowerCase().trim().slice(0, 2);
332
- let tries = 0;
333
340
 
341
+ // Set translateLang immediately so chunk_done incremental translation kicks in right away
342
+ this.translateLang = lang;
343
+
344
+ let tries = 0;
334
345
  const check = setInterval(() => {
335
346
  tries++;
336
- if (this.subtitles.length > 0) {
347
+ // Wait until transcription is fully complete, then do a full translation pass
348
+ // to catch any chunks that may have been missed or arrived out of order
349
+ if (this.subtitles.length > 0 && !this.isGenerating) {
337
350
  clearInterval(check);
338
- if (this.player.options.debug)
339
- console.log('[AutoSub] autoTranslation triggering lang:', lang);
351
+ if (this.player.options.debug) console.log('[AutoSub] autoTranslation final pass, lang:', lang);
352
+ // Reset and retranslate everything cleanly at the end
353
+ this.subtitlesTrans = [];
354
+ this._transCache = {};
340
355
  this._setTranslationLang(lang);
341
356
  this._updateMenu();
342
357
  return;
343
358
  }
344
- // Stop waiting after 5 minutes (enough for any transcription, even large videos with slow models)
345
359
  if (tries > 300) clearInterval(check);
346
360
  }, 1000);
347
361
  }
348
362
 
363
+
349
364
  async handleButtonClick() {
350
365
  // If cache is enabled and no subtitles are loaded yet, try loading from cache first
351
366
  if (this.subtitles.length === 0 && !this.isGenerating) {
@@ -370,40 +385,71 @@
370
385
  async generate() {
371
386
  if (this.isGenerating) return;
372
387
 
373
- // Check cache before doing anything
388
+ let partialCache = null;
389
+
374
390
  if (this.subtitles.length === 0) {
375
391
  const cached = this._loadFromCache();
376
392
  if (cached) {
377
- this.subtitles = cached;
393
+ this.subtitles = cached.subtitles;
378
394
  this.subVisible = true;
379
395
  this._startDisplay();
380
396
  this._setBtnActive(true);
381
397
  this._updateMenu();
398
+
399
+ if (cached.chunksCompleted === null || cached.chunksCompleted >= cached.totalChunks) {
400
+ if (this.player.options.debug) console.log('[AutoSub] Full cache hit, skipping transcription');
401
+ return;
402
+ }
403
+
404
+ partialCache = cached; // Pass to transcribeChunksStreaming
382
405
  if (this.player.options.debug)
383
- console.log('[AutoSub] generate()cache hit, skipping transcription');
384
- return;
406
+ console.log('[AutoSub] Partial cache found resuming from chunk', cached.chunksCompleted, 'of', cached.totalChunks);
385
407
  }
386
408
  }
387
409
 
388
410
  this.isGenerating = true;
389
411
  this._setBtnGenerating(true);
390
412
  this._updatePanel('Extracting audio...', 5);
391
-
392
413
  try {
393
- const audioData = await this._extractAudio();
394
- this._updatePanel('Downloading Transformers.js bundle...', 30);
395
- await this._ensureBundle();
396
- this._updatePanel('Preparing Whisper worker...', 38);
397
- await this._transcribeChunksStreaming(audioData);
398
- this._saveToCache(this.subtitles);
399
- this._updatePanel('Subtitles ready!', 100);
400
- setTimeout(() => {
401
- this._closePanel();
414
+ this.isGenerating = true;
415
+ this._setBtnGenerating(true);
416
+ this._updatePanel('Extracting audio...', 5);
417
+ try {
418
+ const audioData = await this._extractAudio();
419
+ this._updatePanel('Downloading Transformers.js bundle...', 30);
420
+ await this._ensureBundle();
421
+ this._updatePanel('Preparing Whisper worker...', 38);
422
+ await this._transcribeChunksStreaming(audioData, partialCache);
423
+
424
+ // Sort subtitles by start time — partial cache + new chunks may be unsorted
425
+ this.subtitles.sort((a, b) => a.start - b.start);
426
+
427
+ // Mark cache as complete
428
+ this._saveToCache(this.subtitles, null, null, this._lastDetectedLang || null);
429
+ this._updatePanel('Subtitles ready!', 100);
402
430
  this._setBtnActive(true);
403
431
  this._updateMenu();
404
- if (this.player.options.debug)
405
- console.log('[AutoSub] Generation complete:', this.subtitles.length, 'segments');
406
- }, 800);
432
+
433
+ // Re-trigger translation if one was active, using the now-complete subtitles
434
+ if (this.translateLang && this.translateLang !== 'off') {
435
+ const lang = this.translateLang;
436
+ this.translateLang = 'off'; // reset so setTranslationLang doesn't skip
437
+ this.subtitlesTrans = [];
438
+ this._transCache = {};
439
+ await this._setTranslationLang(lang); // re-translate with full subtitles
440
+ }
441
+
442
+ setTimeout(() => { this._closePanel(); }, 800);
443
+
444
+ if (this.player.options.debug) console.log('[AutoSub] Generation complete:', this.subtitles.length, 'segments');
445
+ } catch (err) {
446
+ if (this.player.options.debug) console.error('[AutoSub] Generation error:', err);
447
+ this._updatePanel('Error: ' + (err.message || String(err)), 0);
448
+ } finally {
449
+ this.isGenerating = false;
450
+ this._setBtnGenerating(false);
451
+ }
452
+
407
453
  } catch (err) {
408
454
  if (this.player.options.debug) console.error('[AutoSub] Generation error:', err);
409
455
  this._updatePanel('Error: ' + (err.message || String(err)), 0);
@@ -742,11 +788,12 @@
742
788
 
743
789
  const genLabel = isGenerating ? '⏳ Generating...' : hasSubs ? '🔄 Regenerate transcription' : '🎙️ Generate subtitles';
744
790
  const itemGen = this._menuItem(genLabel, false, isGenerating, () => {
791
+ this._deleteCache();
745
792
  this.subtitles = [];
746
793
  this.subtitlesTrans = [];
747
- this._transCache = {};
748
- this._closeMenuNow();
749
- this.generate();
794
+ this.transCache = {};
795
+ this.closeMenuNow();
796
+ this._generate();
750
797
  });
751
798
 
752
799
  const itemPanel = this._menuItem('📊 Show progress', false, !isGenerating, () => {
@@ -831,13 +878,21 @@
831
878
 
832
879
  async _setTranslationLang(langCode) {
833
880
  this.translateLang = langCode;
834
-
835
881
  if (langCode === 'off') {
836
882
  this.subtitlesTrans = [];
837
883
  if (this.player.options.debug) console.log('[AutoSub] Translation disabled');
838
884
  return;
839
885
  }
840
886
 
887
+ // Skip if source language equals target language
888
+ const sourceLang = this._resolveLanguage(this.opts.language);
889
+ if (sourceLang && sourceLang === langCode.slice(0, 2)) {
890
+ if (this.player.options.debug) console.log('[AutoSub] Source = target lang, skipping translation');
891
+ this.subtitlesTrans = [];
892
+ this.translateLang = 'off';
893
+ return;
894
+ }
895
+
841
896
  // Already cached in memory?
842
897
  if (this._transCache[langCode]) {
843
898
  this.subtitlesTrans = this._transCache[langCode];
@@ -863,61 +918,138 @@
863
918
  }
864
919
  }
865
920
 
921
+ async _translateText(text, sourceLang, targetLang) {
922
+ const engine = this.opts.translationEngine;
923
+
924
+ // ── MyMemory (default) ──────────────────────────────────────────────
925
+ if (!engine || engine.type === 'mymemory') {
926
+ const url = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=${sourceLang}|${targetLang}`;
927
+ const resp = await fetch(url);
928
+ const json = await resp.json();
929
+ if (json.responseStatus && json.responseStatus !== 200) {
930
+ throw new Error('MyMemory error: ' + json.responseDetails + ' (status ' + json.responseStatus + ')');
931
+ }
932
+ return json.responseData?.translatedText ?? text;
933
+ }
934
+
935
+ // ── LibreTranslate ──────────────────────────────────────────────────
936
+ if (engine.type === 'libretranslate') {
937
+ const body = { q: text, source: sourceLang, target: targetLang, format: 'text' };
938
+ if (engine.apiKey) body.api_key = engine.apiKey;
939
+ const resp = await fetch(engine.url.replace(/\/$/, '') + '/translate', {
940
+ method: 'POST',
941
+ headers: { 'Content-Type': 'application/json' },
942
+ body: JSON.stringify(body)
943
+ });
944
+ if (!resp.ok) throw new Error('LibreTranslate HTTP ' + resp.status);
945
+ const json = await resp.json();
946
+ if (json.error) throw new Error('LibreTranslate: ' + json.error);
947
+ return json.translatedText ?? text;
948
+ }
949
+
950
+ // ── MarianMT (Argos Translate API / custom REST endpoint) ───────────
951
+ if (engine.type === 'marianmt') {
952
+ const resp = await fetch(engine.url.replace(/\/$/, '') + '/translate', {
953
+ method: 'POST',
954
+ headers: { 'Content-Type': 'application/json' },
955
+ body: JSON.stringify({ q: text, source: sourceLang, target: targetLang })
956
+ });
957
+ if (!resp.ok) throw new Error('MarianMT HTTP ' + resp.status);
958
+ const json = await resp.json();
959
+ return json.translatedText ?? json.translation ?? text;
960
+ }
961
+
962
+ throw new Error('Unknown translation engine: ' + engine.type);
963
+ }
964
+
866
965
  async _translateBatch(segments, targetLang) {
966
+ const SEP = ' ||| ';
867
967
  const BATCH_CHARS = 400;
868
- const SEP = '\n||||\n';
869
968
 
870
- // ── Detect source language ────────────────────────────────────────────
871
- // 1. Use the language set in plugin options (if provided)
872
- // 2. Otherwise try to detect it from the first segment via MyMemory
873
- // 3. Absolute fallback: 'en'
969
+ // Detect source language
874
970
  let sourceLang = null;
875
-
876
971
  if (this.opts.language) {
877
- // Normalize: 'italian' → 'it', 'it' → 'it'
878
972
  const lower = this.opts.language.toLowerCase().trim();
879
- // Reverse lookup in LANGUAGE_MAP (value → key)
880
973
  const found = Object.entries(LANGUAGE_MAP).find(([k, v]) => k === lower || v === lower);
881
- sourceLang = found ? found[0] : lower.slice(0, 2); // fallback: first 2 chars
974
+ sourceLang = found ? found[0] : lower.slice(0, 2);
882
975
  }
883
-
884
976
  if (!sourceLang && segments.length > 0) {
885
- // Try to detect language from the first segment via MyMemory
886
977
  try {
887
978
  const sample = segments[0].text.slice(0, 100);
888
979
  const detectUrl = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(sample)}&langpair=en|en`;
889
980
  const resp = await fetch(detectUrl);
890
981
  const json = await resp.json();
891
- // MyMemory returns the detected language in responseData
892
982
  const detected = json.responseData?.detectedLanguage;
893
- if (detected && /^[a-z]{2}(-[A-Z]{2})?$/.test(detected)) {
894
- sourceLang = detected.slice(0, 2);
895
- }
896
- } catch (_) { }
983
+ if (/[a-z]{2}-[A-Z]{2}/.test(detected)) sourceLang = detected.slice(0, 2);
984
+ } catch { }
897
985
  }
986
+ if (!sourceLang) sourceLang = 'en';
898
987
 
899
- if (!sourceLang) sourceLang = 'en'; // absolute fallback
988
+ // Normalize target to 2-letter code
989
+ const normTarget = LANGUAGE_MAP[targetLang]
990
+ ? targetLang
991
+ : (Object.entries(LANGUAGE_MAP).find(([k, v]) => v === targetLang)?.[0] ?? targetLang.slice(0, 2));
900
992
 
901
- // Skip translation if source and target language are the same
902
- if (sourceLang === targetLang) {
903
- if (this.player.options.debug)
904
- console.log('[AutoSub] Source === target lang, skipping translation');
993
+ if (sourceLang === normTarget) {
994
+ if (this.player.options.debug) console.log('[AutoSub] Source = target lang, skipping translation');
905
995
  return segments.map(s => ({ ...s }));
906
996
  }
907
997
 
908
- if (this.player.options.debug)
909
- console.log('[AutoSub] Translating', sourceLang, '→', targetLang);
998
+ if (this.player.options.debug) console.log('[AutoSub] Translating', sourceLang, '→', normTarget);
910
999
 
911
- // ── Batching ──────────────────────────────────────────────────────────
912
- const batches = [];
913
- let current = [];
914
- let charCount = 0;
1000
+ const engine = this.opts.translationEngine;
1001
+
1002
+ // ── LibreTranslate: array nativo, mapping 1:1 garantito ──────────
1003
+ if (engine?.type === 'libretranslate') {
1004
+ const BATCH_SIZE = 50;
1005
+ const result = [];
1006
+ const url = `${engine.url.replace(/\/$/, '')}/translate`;
915
1007
 
1008
+ for (let i = 0; i < segments.length; i += BATCH_SIZE) {
1009
+ const batch = segments.slice(i, i + BATCH_SIZE);
1010
+ try {
1011
+ const body = {
1012
+ q: batch.map(s => s.text), // array → risposta è array nella stessa posizione
1013
+ source: sourceLang,
1014
+ target: normTarget,
1015
+ format: 'text'
1016
+ };
1017
+ if (engine.apiKey) body.api_key = engine.apiKey;
1018
+
1019
+ const resp = await fetch(url, {
1020
+ method: 'POST',
1021
+ headers: { 'Content-Type': 'application/json' },
1022
+ body: JSON.stringify(body)
1023
+ });
1024
+ if (!resp.ok) throw new Error(`LibreTranslate HTTP ${resp.status}`);
1025
+ const json = await resp.json();
1026
+ if (json.error) throw new Error(`LibreTranslate: ${json.error}`);
1027
+
1028
+ const translations = Array.isArray(json.translatedText)
1029
+ ? json.translatedText
1030
+ : [json.translatedText];
1031
+
1032
+ batch.forEach((seg, j) => result.push({
1033
+ start: seg.start,
1034
+ end: seg.end,
1035
+ text: translations[j] ?? seg.text
1036
+ }));
1037
+ } catch (err) {
1038
+ if (this.player.options.debug) console.warn('[AutoSub] LibreTranslate batch error', err.message);
1039
+ batch.forEach(seg => result.push({ ...seg }));
1040
+ }
1041
+ if (i + BATCH_SIZE < segments.length)
1042
+ await new Promise(r => setTimeout(r, 100));
1043
+ }
1044
+ return result;
1045
+ }
1046
+
1047
+ // ── MyMemory fallback ────────────────────────────────────────────
1048
+ const batches = [];
1049
+ let current = [], charCount = 0;
916
1050
  for (const seg of segments) {
917
1051
  if (charCount + seg.text.length > BATCH_CHARS && current.length > 0) {
918
- batches.push(current);
919
- current = [];
920
- charCount = 0;
1052
+ batches.push(current); current = []; charCount = 0;
921
1053
  }
922
1054
  current.push(seg);
923
1055
  charCount += seg.text.length;
@@ -927,31 +1059,22 @@
927
1059
  const result = [];
928
1060
  for (const batch of batches) {
929
1061
  const sourceText = batch.map(s => s.text).join(SEP);
930
- const url = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(sourceText)}&langpair=${sourceLang}|${targetLang}`;
931
1062
  try {
932
- const resp = await fetch(url);
933
- const json = await resp.json();
934
-
935
- // Handle explicit API errors
936
- if (json.responseStatus && json.responseStatus !== 200) {
937
- if (this.player.options.debug) console.warn('[AutoSub] MyMemory API error:', json.responseDetails || json.responseStatus);
938
- batch.forEach(seg => result.push({ ...seg }));
939
- continue;
940
- }
941
-
942
- const translated = json.responseData?.translatedText || sourceText;
943
- const parts = translated.split('||||').map(s => s.trim().replace(/^\n+|\n+$/g, ''));
944
- batch.forEach((seg, i) => {
945
- result.push({ start: seg.start, end: seg.end, text: parts[i] || seg.text });
946
- });
947
- await new Promise(r => setTimeout(r, 120)); // rate-limit delay between batches
948
- } catch (_) {
949
- batch.forEach(seg => result.push({ ...seg })); // fallback: keep original text
1063
+ const translated = await this._translateText(sourceText, sourceLang, normTarget);
1064
+ const parts = translated.split(SEP).map(s => s.trim().replace(/&amp;/g, '&'));
1065
+ batch.forEach((seg, i) => result.push({
1066
+ start: seg.start, end: seg.end, text: parts[i] ?? seg.text
1067
+ }));
1068
+ } catch (err) {
1069
+ if (this.player.options.debug) console.warn('[AutoSub] Translation batch error', err.message);
1070
+ batch.forEach(seg => result.push({ ...seg }));
950
1071
  }
1072
+ await new Promise(r => setTimeout(r, 120));
951
1073
  }
952
1074
  return result;
953
1075
  }
954
1076
 
1077
+
955
1078
  // ─────────────────────────────────────────────────────────────────────────
956
1079
  // PRIVATE — DISPLAY
957
1080
  // ─────────────────────────────────────────────────────────────────────────
@@ -1417,19 +1540,18 @@ self.onmessage = async function(e) {
1417
1540
  }
1418
1541
  if (type === 'transcribe') {
1419
1542
  try {
1420
- const audio = new Float32Array(payload.audio);
1421
- const opts = { return_timestamps: true, task: 'transcribe', chunk_length_s: payload.chunkSec };
1422
- if (payload.lang) opts.language = payload.lang;
1423
- const result = await transcriber(audio, opts);
1424
- self.postMessage({ type: 'chunk_done', payload: {
1425
- result, timeOffset: payload.timeOffset,
1426
- chunkIndex: payload.chunkIndex, totalChunks: payload.totalChunks,
1427
- isLast: payload.isLast
1428
- }});
1543
+ const audio = new Float32Array(payload.audio);
1544
+ const opts = { return_timestamps: true, task: 'transcribe', chunk_length_s: payload.chunkSec };
1545
+ if (payload.lang) opts.language = payload.lang;
1546
+ const result = await transcriber(audio, opts);
1547
+ // Include detected language in the response so the main thread can reuse it
1548
+ const detectedLang = result.language || payload.lang || null;
1549
+ self.postMessage({ type: 'chunk_done', payload: { result, timeOffset: payload.timeOffset, chunkIndex: payload.chunkIndex, totalChunks: payload.totalChunks, isLast: payload.isLast, detectedLang } });
1429
1550
  } catch(err) {
1430
- self.postMessage({ type: 'error', payload: err.message || String(err) });
1551
+ self.postMessage({ type: 'error', payload: err.message || String(err) });
1431
1552
  }
1432
- }
1553
+ }
1554
+
1433
1555
  };
1434
1556
  `;
1435
1557
 
@@ -1455,7 +1577,7 @@ self.onmessage = async function(e) {
1455
1577
  );
1456
1578
  }
1457
1579
 
1458
- async _transcribeChunksStreaming(audioData) {
1580
+ async _transcribeChunksStreaming(audioData, partialCache = null) {
1459
1581
 
1460
1582
  // ── CAPTURE STREAM MODE (HLS or DASH fallback) ───────────────────────
1461
1583
  if (audioData?._captureMode) {
@@ -1547,13 +1669,21 @@ self.onmessage = async function(e) {
1547
1669
 
1548
1670
  if (type === 'chunk_done') {
1549
1671
  const { result, timeOffset, isLast } = payload;
1550
- const rawChunks = (result.chunks || []).map(c => {
1551
- const ts0 = c.timestamp?.[0] ?? 0;
1552
- const ts1 = c.timestamp?.[1] ?? ts0 + 3;
1553
- return { text: (c.text || '').trim(), start: ts0 + timeOffset, end: ts1 + timeOffset };
1554
- });
1672
+
1673
+ let rawChunks = (result.chunks || [])
1674
+ .filter(c => c.text?.trim())
1675
+ .map(c => {
1676
+ const ts0 = (c.timestamp?.[0] != null) ? c.timestamp[0] : 0;
1677
+ const ts1 = (c.timestamp?.[1] != null) ? c.timestamp[1] : ts0 + 5;
1678
+ return { text: c.text.trim(), start: ts0 + timeOffset, end: ts1 + timeOffset };
1679
+ });
1680
+
1681
+ if (rawChunks.length === 0 && result.text?.trim()) {
1682
+ rawChunks = [{ text: result.text.trim(), start: timeOffset, end: timeOffset + 30 }];
1683
+ }
1684
+
1555
1685
  this.subtitles.push(...this._normalizeChunks(rawChunks));
1556
- if (this.opts.cacheEnabled) this._saveToCache(this.subtitles);
1686
+ if (this.opts.cacheEnabled) this._saveToCache(this.subtitles, null, null);
1557
1687
 
1558
1688
  activeJob = false;
1559
1689
 
@@ -1593,8 +1723,17 @@ self.onmessage = async function(e) {
1593
1723
  const chunkSize = SAMPLE_RATE * CHUNK_SEC;
1594
1724
  const totalChunks = Math.ceil(float32Audio.length / chunkSize);
1595
1725
  const lang = this._resolveLanguage(this.opts.language);
1726
+ let detectedLang = lang || partialCache?.detectedLang || null;
1596
1727
  const modelId = WHISPER_MODELS[this.opts.modelSize] || WHISPER_MODELS.base;
1597
1728
 
1729
+ // Use partialCache passed from generate() — no second localStorage read
1730
+ const startChunk = (partialCache?.chunksCompleted != null && partialCache.chunksCompleted < totalChunks)
1731
+ ? partialCache.chunksCompleted
1732
+ : 0;
1733
+
1734
+ if (this.player.options.debug && startChunk > 0)
1735
+ console.log('[AutoSub] Resuming transcription from chunk', startChunk + 1, 'of', totalChunks);
1736
+
1598
1737
  if (!this.subVisible) { this.subVisible = true; this._startDisplay(); }
1599
1738
 
1600
1739
  const worker = this._createTranscriberWorker();
@@ -1619,28 +1758,73 @@ self.onmessage = async function(e) {
1619
1758
  if (type === 'ready') {
1620
1759
  URL.revokeObjectURL(worker._bundleURL);
1621
1760
  URL.revokeObjectURL(worker._workerURL);
1622
- this._updatePanel('Transcribing chunk 1 of ' + totalChunks + '...', 64);
1623
- this._sendChunk(worker, float32Audio, 0, chunkSize, totalChunks, lang, CHUNK_SEC);
1761
+ if (this.player.options.debug) console.log('[AutoSub] Worker ready sending chunk', startChunk + 1, 'of', totalChunks);
1762
+ this._updatePanel('Transcribing chunk ' + (startChunk + 1) + ' of ' + totalChunks + '...', 64);
1763
+ this._sendChunk(worker, float32Audio, startChunk, chunkSize, totalChunks, detectedLang, CHUNK_SEC);
1624
1764
  }
1625
1765
 
1626
1766
  if (type === 'chunk_done') {
1627
1767
  const { result, timeOffset, chunkIndex } = payload;
1628
- const rawChunks = (result.chunks || []).map(c => {
1629
- const ts0 = c.timestamp?.[0] ?? 0;
1630
- const ts1 = c.timestamp?.[1] ?? ts0 + 3;
1631
- return { text: (c.text || '').trim(), start: ts0 + timeOffset, end: ts1 + timeOffset };
1632
- });
1633
- this.subtitles.push(...this._normalizeChunks(rawChunks));
1634
- if (this.opts.cacheEnabled) this._saveToCache(this.subtitles);
1768
+
1769
+ // On first processed chunk, save the detected language and reuse it for all subsequent chunks
1770
+ if (chunkIndex === startChunk && payload.detectedLang && !detectedLang) {
1771
+ detectedLang = payload.detectedLang;
1772
+ if (this.player.options.debug) console.log('[AutoSub] Language auto-detected:', detectedLang);
1773
+ }
1774
+ this._saveToCache(this.subtitles, chunkIndex + 1, totalChunks, detectedLang);
1775
+
1776
+ let rawChunks = (result.chunks || [])
1777
+ .filter(c => c.text?.trim())
1778
+ .map(c => {
1779
+ const ts0 = (c.timestamp?.[0] != null) ? c.timestamp[0] : 0;
1780
+ const ts1 = (c.timestamp?.[1] != null) ? c.timestamp[1] : ts0 + 5;
1781
+ return { text: c.text.trim(), start: ts0 + timeOffset, end: ts1 + timeOffset };
1782
+ });
1783
+
1784
+ if (rawChunks.length === 0 && result.text?.trim()) {
1785
+ rawChunks = [{ text: result.text.trim(), start: timeOffset, end: timeOffset + CHUNK_SEC }];
1786
+ }
1787
+
1788
+ const normalized = this._normalizeChunks(rawChunks);
1789
+ this.subtitles.push(...normalized);
1790
+
1791
+ // Save partial progress after every chunk
1792
+ this._saveToCache(this.subtitles, chunkIndex + 1, totalChunks, detectedLang);
1793
+
1794
+ // If translation is active, translate this chunk immediately and append
1795
+ if (this.translateLang && this.translateLang !== 'off' && normalized.length > 0) {
1796
+ const currentLang = this.translateLang;
1797
+ this._translateBatch(normalized, currentLang).then(translated => {
1798
+ // Only append if translation lang hasn't changed in the meantime
1799
+ if (this.translateLang === currentLang) {
1800
+ this.subtitlesTrans.push(...translated);
1801
+ this.subtitlesTrans.sort((a, b) => a.start - b.start);
1802
+ }
1803
+ }).catch(() => {
1804
+ // On error push originals so display doesn't go blank
1805
+ if (this.translateLang === currentLang) {
1806
+ this.subtitlesTrans.push(...normalized);
1807
+ this.subtitlesTrans.sort((a, b) => a.start - b.start);
1808
+ }
1809
+ });
1810
+ }
1811
+
1635
1812
 
1636
1813
  const next = chunkIndex + 1;
1637
1814
  if (next < totalChunks) {
1638
1815
  const pct = 64 + Math.round((next / totalChunks) * 33);
1639
1816
  this._updatePanel('Transcribing chunk ' + (next + 1) + ' of ' + totalChunks + '...', pct);
1640
- this._sendChunk(worker, float32Audio, next, chunkSize, totalChunks, lang, CHUNK_SEC);
1817
+ // Use detectedLang instead of lang so all chunks use the same detected language
1818
+ this._sendChunk(worker, float32Audio, next, chunkSize, totalChunks, detectedLang, CHUNK_SEC);
1641
1819
  } else {
1642
- worker.terminate(); this._worker = null; resolve();
1820
+ if (this.player.options.debug) console.log('[AutoSub] All chunks done, subtitles:', this.subtitles.length);
1821
+ worker.terminate(); this._worker = null;
1822
+ resolve();
1823
+ // generate() chiamerà _saveToCache(subtitles, null, null, detectedLang)
1824
+ // ma detectedLang non è accessibile lì — salvalo su this
1825
+ this._lastDetectedLang = detectedLang;
1643
1826
  }
1827
+
1644
1828
  }
1645
1829
 
1646
1830
  if (type === 'error') {
@@ -1652,6 +1836,7 @@ self.onmessage = async function(e) {
1652
1836
  };
1653
1837
 
1654
1838
  worker.postMessage({ type: 'init', payload: { modelId } });
1839
+ if (this.player.options.debug) console.log('[AutoSub] Init sent (normal), modelId:', modelId, 'totalChunks:', totalChunks, 'startChunk:', startChunk);
1655
1840
  });
1656
1841
  }
1657
1842
 
@@ -1720,18 +1905,42 @@ self.onmessage = async function(e) {
1720
1905
  if (!raw) return null;
1721
1906
  const parsed = JSON.parse(raw);
1722
1907
  if (!Array.isArray(parsed.subtitles) || !parsed.subtitles.length) return null;
1723
- return parsed.subtitles;
1724
- } catch (_) { return null; }
1908
+ return {
1909
+ subtitles: parsed.subtitles,
1910
+ chunksCompleted: parsed.chunksCompleted ?? null,
1911
+ totalChunks: parsed.totalChunks ?? null,
1912
+ detectedLang: parsed.detectedLang ?? null // ← aggiunto
1913
+ };
1914
+ } catch {
1915
+ return null;
1916
+ }
1725
1917
  }
1726
1918
 
1727
- _saveToCache(subtitles) {
1919
+ _saveToCache(subtitles, chunksCompleted = null, totalChunks = null, detectedLang = null) {
1728
1920
  if (!this.opts.cacheEnabled || !subtitles.length) return;
1729
1921
  try {
1730
1922
  const key = this._getCacheKey();
1731
- const data = JSON.stringify({ subtitles, savedAt: Date.now() });
1732
- try { localStorage.setItem(key, data); }
1733
- catch (_) { this._evictOldestCache(); try { localStorage.setItem(key, data); } catch (_) { } }
1734
- } catch (_) { }
1923
+ const data = JSON.stringify({
1924
+ subtitles,
1925
+ chunksCompleted,
1926
+ totalChunks,
1927
+ detectedLang,
1928
+ savedAt: Date.now()
1929
+ });
1930
+ try {
1931
+ localStorage.setItem(key, data);
1932
+ } catch {
1933
+ this._evictOldestCache();
1934
+ try { localStorage.setItem(key, data); } catch { /* storage full */ }
1935
+ }
1936
+ } catch { /* ignore */ }
1937
+ }
1938
+
1939
+ _deleteCache() {
1940
+ if (!this.opts.cacheEnabled) return;
1941
+ try {
1942
+ localStorage.removeItem(this._getCacheKey());
1943
+ } catch { }
1735
1944
  }
1736
1945
 
1737
1946
  _evictOldestCache() {
@@ -8,7 +8,7 @@
8
8
 
9
9
  - 🧠 **100% client-side** — transcription runs in a Web Worker, never leaves the browser
10
10
  - ⚡ **Streaming transcription** — subtitles appear chunk by chunk while the model processes audio
11
- - 🌍 **Auto-translation** — translate subtitles into 17+ languages via [MyMemory API](https://mymemory.translated.net/) (free, no key)
11
+ - 🌍 **Auto-translation** — translate subtitles via [MyMemory API](https://mymemory.translated.net/) (free, no key) or self-hosted [LibreTranslate](https://libretranslate.com/) / MarianMT (no limits)
12
12
  - 🖱️ **Draggable subtitles** — reposition subtitles anywhere on the video (mouse & touch)
13
13
  - 📱 **Responsive text** — font size adapts automatically to screen size via `clamp()`
14
14
  - 💾 **localStorage cache** — transcriptions are cached and reloaded instantly on next visit
@@ -62,7 +62,7 @@ npm install myetv-autosub-plugin
62
62
 
63
63
  player.on('playerready', () => {
64
64
  player.usePlugin('autoSubtitles', {
65
- language: 'en', // source language of the video
65
+ language: 'en', // source language of the video
66
66
  modelSize: 'tiny', // whisper model size
67
67
  position: 'topbar', // button position
68
68
  idCache: 'video-123', // stable cache key (recommended)
@@ -82,6 +82,7 @@ npm install myetv-autosub-plugin
82
82
  | `modelSize` | `string` | `'base'` | Whisper model size: `'tiny'` (~39 MB), `'base'` (~74 MB), `'small'` (~244 MB). Larger = more accurate but slower. |
83
83
  | `autoGenerate` | `boolean` | `false` | Automatically start transcription and show subtitles when the video player is loaded. |
84
84
  | `autoTranslation` | `string` | `null` | Automatically translate subtitles into this language on load (ISO 2-letter code, e.g. `'en'`, `'it'`, `'fr'`). Requires subtitles to be ready first. |
85
+ | `translationEngine` | `object` | `null` | Translation engine configuration. If `null`, uses MyMemory (free, limited). See [Translation Engines](#-translation-engines) below. |
85
86
  | `showButton` | `boolean` | `true` | Show the CC button in the player interface. |
86
87
  | `position` | `string` | `'topbar'` | Button position: `'right'` or `'left'` (control bar) or `'topbar'` (next to the ⚙️ settings icon). |
87
88
  | `subtitleStyle` | `object` | `{}` | Custom inline CSS style object applied to the subtitle text element. |
@@ -89,6 +90,86 @@ npm install myetv-autosub-plugin
89
90
  | `idCache` | `string` | `null` | Stable unique key used for the localStorage cache entry (recommended: your video's database ID). Falls back to a hash of the video URL if not set. |
90
91
 
91
92
 
93
+ ---
94
+
95
+ ## 🌐 Translation Engines
96
+
97
+ The plugin supports three translation backends. By default it uses **MyMemory** (no configuration needed).
98
+
99
+ ### Comparison
100
+
101
+ | Engine | Limit | Privacy | Setup |
102
+ | :-- | :-- | :-- | :-- |
103
+ | **MyMemory** (default) | ⚠️ ~5.000 chars/day per IP | ☁️ External API | None |
104
+ | **LibreTranslate** (self-hosted) | ✅ No limits | 🔒 Your server | Easy |
105
+ | **MarianMT** (self-hosted) | ✅ No limits | 🔒 Your server | Medium |
106
+
107
+
108
+ ---
109
+
110
+ ### MyMemory (default)
111
+
112
+ Free public API, no configuration required. Limit is approximately **5.000 characters/day per IP**. Suitable for low-traffic or development use.
113
+
114
+ ```javascript
115
+ // Default — no translationEngine needed
116
+ player.usePlugin('autoSubtitles', {
117
+ autoTranslation: 'it',
118
+ });
119
+ ```
120
+
121
+
122
+ ---
123
+
124
+ ### LibreTranslate (recommended for production)
125
+
126
+ Self-hosted, no limits, supports **native array batch translation** — each subtitle segment is translated individually with guaranteed 1:1 index mapping, eliminating any desync issues.
127
+
128
+ ```javascript
129
+ player.usePlugin('autoSubtitles', {
130
+ autoTranslation: 'it',
131
+ translationEngine: {
132
+ type: 'libretranslate',
133
+ url: 'https://your-libretranslate-server.com',
134
+ apiKey: 'your-api-key', // optional, required if --api-keys is enabled
135
+ },
136
+ });
137
+ ```
138
+
139
+ **Self-hosting with Docker:**
140
+
141
+ ```bash
142
+ docker run -p 5000:5000 libretranslate/libretranslate --disable-web-ui --api-keys
143
+ ```
144
+ LibreTranslate: [https://github.com/LibreTranslate/LibreTranslate](https://github.com/LibreTranslate/LibreTranslate)
145
+ ---
146
+
147
+ ### MarianMT
148
+
149
+ Custom self-hosted REST endpoint compatible with MarianMT or Argos Translate.
150
+
151
+ ```javascript
152
+ player.usePlugin('autoSubtitles', {
153
+ autoTranslation: 'it',
154
+ translationEngine: {
155
+ type: 'marianmt',
156
+ url: 'https://your-marianmt-server.com',
157
+ },
158
+ });
159
+ ```
160
+
161
+ Expected request/response format:
162
+
163
+ ```json
164
+ // POST /translate
165
+ // Request
166
+ { "q": "Hello world", "source": "en", "target": "it" }
167
+
168
+ // Response
169
+ { "translatedText": "Ciao mondo" }
170
+ ```
171
+
172
+
92
173
  ---
93
174
 
94
175
  ## 🎛️ Subtitle Menu
@@ -101,10 +182,10 @@ Clicking the **CC button** opens a context menu with three sections:
101
182
  - **✕ Disable** — hide subtitles
102
183
 
103
184
 
104
- ### Gestione
185
+ ### Management
105
186
 
106
187
  - **🎙️ Generate subtitles** — start transcription (first time)
107
- - **🔄 Regenerate trnascription** — force re-transcription (discards cache)
188
+ - **🔄 Regenerate transcription** — force re-transcription (discards cache)
108
189
  - **📊 Show progress** — open the transcription progress panel (visible while generating)
109
190
 
110
191
 
@@ -159,9 +240,15 @@ If your site uses a Content Security Policy, add the following:
159
240
  Content-Security-Policy:
160
241
  worker-src blob:;
161
242
  script-src blob: https://cdn.jsdelivr.net;
162
- connect-src https://cdn.jsdelivr.net https://huggingface.co https://api.mymemory.translated.net;
243
+ connect-src https://cdn.jsdelivr.net
244
+ https://huggingface.co
245
+ https://api.mymemory.translated.net
246
+ https://your-libretranslate-server.com;
163
247
  ```
164
- Use crossorigin="anonymous" on the <video> element.
248
+
249
+ > If using LibreTranslate or MarianMT, replace `https://api.mymemory.translated.net` with your server URL (or add both if using MyMemory as fallback).
250
+
251
+ Use `crossorigin="anonymous"` on the `<video>` element.
165
252
 
166
253
  ---
167
254
 
@@ -190,13 +277,18 @@ Hover over the subtitle text to reveal the **⠿ sposta** drag handle. Click and
190
277
  ```javascript
191
278
  player.on('playerready', () => {
192
279
  player.usePlugin('autoSubtitles', {
193
- language: 'english', // language of the audio: "english" or "en" (if empty auto-detect)
194
- modelSize: 'base', // ai model to use: 'tiny', 'base', 'small'
195
- autoGenerate: true, // start transcription and view subtitles automatically without user interacton
196
- autoTranslation: 'en', // auto-translate to English when ready
197
- position: 'topbar', // CC button next to ⚙️ settings icon; options are: 'left', 'right', 'topbar'
198
- cacheEnabled: true, // cache client-side with localstorage
199
- idCache: `video-${videoId}`, // id of the key for the cache (if empty hased url will be used)
280
+ language: 'english', // language of the audio: "english" or "en" (if empty auto-detect)
281
+ modelSize: 'base', // AI model: 'tiny', 'base', 'small'
282
+ autoGenerate: true, // start transcription automatically
283
+ autoTranslation: 'it', // auto-translate to Italian when ready
284
+ position: 'topbar', // CC button position: 'left', 'right', 'topbar'
285
+ cacheEnabled: true,
286
+ idCache: `video-${videoId}`,
287
+ translationEngine: {
288
+ type: 'libretranslate',
289
+ url: 'https://your-libretranslate-server.com',
290
+ apiKey: 'your-api-key',
291
+ },
200
292
  subtitleStyle: {
201
293
  fontSize: '1.2em',
202
294
  color: '#ffe066',
@@ -237,6 +329,10 @@ Whisper ASR — chunks of 30s each
237
329
  │ (subtitles stream in live)
238
330
 
239
331
  Save to localStorage cache
332
+
333
+
334
+ Translate via selected engine
335
+ (MyMemory / LibreTranslate / MarianMT)
240
336
  ```
241
337
 
242
338