/**
 * Web implementation of the video player with HLS and DASH support
 */

import { BasePlayer } from '../../core/dist/BasePlayer';
import {
  VideoSource,
  PlayerConfig,
  ControlsVisibilityConfig,
  Quality,
  SubtitleTrack,
  PlayerError,
  ChapterManager as CoreChapterManager,
  Chapter,
  ChapterSegment
} from '../../core/dist/index';
import { ChapterManager } from './chapters/ChapterManager';
import {
  ChapterConfig,
  VideoChapters,
  VideoSegment,
  ChapterEvents
} from './chapters/types/ChapterTypes';
import type {
  FlashNewsTickerConfig,
  FlashNewsTickerItem,
  BroadcastStyleConfig,
  BroadcastTheme,
  TickerStyleVariant,
  TickerLayoutStyle,
  IntroAnimationType,
  TwoLineDisplayConfig,
  DecorativeShapesConfig,
  ItemCyclingConfig,
  SeparatorType
} from './react/types/FlashNewsTickerTypes';
import type {
  ThumbnailPreviewConfig,
  ThumbnailEntry,
  ThumbnailGenerationImage
} from './react/types/ThumbnailPreviewTypes';
import YouTubeExtractor from './utils/YouTubeExtractor';
import { SrtConverter } from './utils/SrtConverter';
import { DRMManager, DRMErrorHandler } from './drm';
import type { DRMInitResult, DRMError } from './drm';

// Dynamic imports for streaming libraries
declare global {
  interface Window {
    Hls: any;
    dashjs: any;
    cast?: any;
    chrome?: any;
    YT?: any;
    __onGCastApiAvailable?: (isAvailable: boolean) => void;
  }
}

export class WebPlayer extends BasePlayer {
  protected video: HTMLVideoElement | null = null;
  private hls: any = null;
  private dash: any = null;
  private qualities: Quality[] = [];
  private currentQualityIndex: number = -1;
  private autoQuality: boolean = true;
  private currentStreamType: string = '';
  private useCustomControls: boolean = true;
  private controlsContainer: HTMLElement | null = null;
  private volumeHideTimeout: NodeJS.Timeout | null = null;
  private hideControlsTimeout: NodeJS.Timeout | null = null;
  private isVolumeSliding: boolean = false;
  private availableQualities: Array<{ value: string, label: string }> = [];
  private availableSubtitles: Array<{ value: string, label: string }> = [];
  private currentQuality = 'auto';
  private currentSubtitle = 'off';
  private currentPlaybackRate = 1;
  private isDragging: boolean = false;

  // Event handler references for cleanup
  private globalMouseMoveHandler: ((e: MouseEvent) => void) | null = null;
  private globalTouchMoveHandler: ((e: TouchEvent) => void) | null = null;
  private globalMouseUpHandler: (() => void) | null = null;
  private globalTouchEndHandler: (() => void) | null = null;

  // Settings configuration
  private settingsConfig = {
    enabled: true,        // Show settings button
    speed: true,         // Show playback speed options
    quality: true,       // Show quality options
    subtitles: true      // Show subtitle options
  };

  // Controls visibility configuration
  private controlsVisibility!: ControlsVisibilityConfig;

  private watermarkCanvas: HTMLCanvasElement | null = null;
  private playerWrapper: HTMLElement | null = null;

  // Multi-instance support
  private instanceId: string = '';
  private static instanceCounter: number = 0;
  private cachedElements: {
    loading?: HTMLElement | null;
    progressBar?: HTMLElement | null;
    progressFilled?: HTMLElement | null;
    progressHandle?: HTMLElement | null;
    progressBuffered?: HTMLElement | null;
    playIcon?: HTMLElement | null;
    pauseIcon?: HTMLElement | null;
    centerPlay?: HTMLElement | null;
    timeDisplay?: HTMLElement | null;
    thumbnailPreview?: HTMLElement | null;
    thumbnailImage?: HTMLImageElement | null;
    thumbnailTime?: HTMLElement | null;
    timeTooltip?: HTMLElement | null;
    settingsMenu?: HTMLElement | null;
    fullscreenBtn?: HTMLElement | null;
  } = {};

  // Flash News Ticker
  private flashTickerContainer: HTMLDivElement | null = null;
  private flashTickerTopElement: HTMLDivElement | null = null;
  private flashTickerBottomElement: HTMLDivElement | null = null;

  // Professional Ticker State (for item cycling and intro animations)
  private tickerCurrentItemIndex: number = 0;
  private tickerCycleTimer: number | null = null;
  private tickerConfig: FlashNewsTickerConfig | null = null;
  private tickerHeadlineElement: HTMLDivElement | null = null;
  private tickerDetailElement: HTMLDivElement | null = null;
  private tickerIntroOverlay: HTMLDivElement | null = null;
  private tickerProgressBar: HTMLDivElement | null = null;
  private tickerProgressFill: HTMLDivElement | null = null;
  private tickerIsPaused: boolean = false;
  private tickerPauseStartTime: number = 0;
  private tickerRemainingTime: number = 0;

  // Free preview gate state
  private previewGateHit: boolean = false;
  private paymentSuccessTime: number = 0;
  private paymentSuccessful: boolean = false;

  // Security state to prevent paywall bypass
  private isPaywallActive: boolean = false;
  private authValidationInterval: any = null;
  private overlayRemovalAttempts: number = 0;
  private maxOverlayRemovalAttempts: number = 3;
  private lastSecurityCheck: number = 0;

  // Cast state
  private castContext: any = null;
  private remotePlayer: any = null;
  private remoteController: any = null;
  private isCasting: boolean = false;
  private _castTrackIdByKey: Record<string, number> = {};
  private selectedSubtitleKey: string = 'off';
  private _kiTo: any = null;

  // Paywall
  private paywallController: any = null;

  // Play/pause coordination to prevent race conditions
  private _playPromise: Promise<void> | null = null;
  private _deferredPause = false;
  private _lastToggleAt = 0;
  private _TOGGLE_DEBOUNCE_MS = 120;

  // Fullscreen fallback tracking
  private hasTriedButtonFallback: boolean = false;
  private lastUserInteraction: number = 0;

  // Progress bar tooltip state
  private showTimeTooltip: boolean = false;

  // Advanced tap handling state
  private tapStartTime: number = 0;
  private tapStartX: number = 0;
  private tapStartY: number = 0;
  private lastTapTime: number = 0;
  private lastTapX: number = 0;
  private tapCount: number = 0;
  private longPressTimer: NodeJS.Timeout | null = null;
  private isLongPressing: boolean = false;
  private longPressPlaybackRate: number = 1;
  private tapResetTimer: NodeJS.Timeout | null = null;
  private fastBackwardInterval: NodeJS.Timeout | null = null;
  private handleSingleTap: () => void = () => { };
  private handleDoubleTap: (tapX: number) => void = () => { };
  private handleLongPress: (tapX: number) => void = () => { };
  private handleLongPressEnd: () => void = () => { };

  // Autoplay enhancement state
  private autoplayCapabilities: {
    canAutoplay: boolean;
    canAutoplayMuted: boolean;
    canAutoplayUnmuted: boolean;
    lastCheck: number;
  } = {
      canAutoplay: false,
      canAutoplayMuted: false,
      canAutoplayUnmuted: false,
      lastCheck: 0
    };
  private autoplayRetryPending: boolean = false;
  private autoplayRetryAttempts: number = 0;
  private maxAutoplayRetries: number = 3;

  // Chapter management
  private chapterManager: ChapterManager | null = null;

  // DRM management
  private drmManager: DRMManager | null = null;
  private isDRMProtected: boolean = false;

  // Start time tracking
  private hasAppliedStartTime: boolean = false;
  private coreChapterManager: CoreChapterManager | null = null;
  private chapterConfig: ChapterConfig = { enabled: false };

  // Quality filter
  private qualityFilter: any = null;

  // Premium qualities configuration
  private premiumQualities: any = null;

  // YouTube native controls configuration
  private youtubeNativeControls: boolean = true;

  // Ad playing state (set by Google Ads Manager)
  private isAdPlaying: boolean = false;

  // Fallback source management
  private fallbackSourceIndex: number = -1;
  private fallbackErrors: Array<{ url: string; error: any }> = [];
  private isLoadingFallback: boolean = false;
  private currentRetryAttempt: number = 0;
  private lastFailedUrl: string = ''; // Track last failed URL to avoid duplicate error handling
  private isFallbackPosterMode: boolean = false; // True when showing fallback poster (no playable sources)
  private hlsErrorRetryCount: number = 0; // Track HLS error recovery attempts
  private readonly MAX_HLS_ERROR_RETRIES: number = 3; // Max HLS recovery attempts before fallback

  // Live stream detection
  private lastDuration: number = 0; // Track duration changes to detect live streams
  private isDetectedAsLive: boolean = false; // Once detected as live, stays live

  // Live stream waiting state
  private isWaitingForLiveStream: boolean = false;
  private liveRetryTimer: NodeJS.Timeout | null = null;
  private liveRetryAttempts: number = 0;

  // Bandwidth detection system
  private bandwidthDetector: {
    estimatedBandwidth: number | null;
    detectionComplete: boolean;
    detectionMethod: 'probe' | 'manifest' | 'fallback';
  } = {
    estimatedBandwidth: null,
    detectionComplete: false,
    detectionMethod: 'fallback'
  };

  private readonly BANDWIDTH_TIERS = {
    HIGH: 5_000_000,    // 5 Mbps - High quality settings
    MEDIUM: 2_500_000,  // 2.5 Mbps - Balanced settings
    LOW: 1_000_000      // 1 Mbps - Conservative settings
  };
  private liveStreamOriginalSource: VideoSource | null = null;
  private liveMessageRotationTimer: NodeJS.Timeout | null = null;
  private liveMessageIndex: number = 0;
  private liveBufferingTimeoutTimer: NodeJS.Timeout | null = null;

  // Live countdown state
  private liveCountdownTimer: NodeJS.Timeout | null = null;
  private liveCountdownRemainingSeconds: number = 0;
  private isShowingLiveCountdown: boolean = false;
  private hasCountdownCompleted: boolean = false; // Prevent showing countdown again after completion
  private isReloadingAfterCountdown: boolean = false; // Prevent race conditions during reload
  private countdownCallbackFired: boolean = false; // Prevent double callback execution

  // Thumbnail preview
  private thumbnailPreviewConfig: ThumbnailPreviewConfig | null = null;
  private thumbnailEntries: ThumbnailEntry[] = [];
  private preloadedThumbnails: Map<string, HTMLImageElement> = new Map();
  private currentThumbnailUrl: string | null = null;
  private thumbnailPreviewEnabled: boolean = false;
  private subtitleBlobUrls: string[] = [];
  private subtitleOverlay: HTMLElement | null = null;

  // Debug logging helper
  private debugLog(message: string, ...args: any[]): void {
    if (this.config.debug) {
      console.log(`[WebPlayer] ${message}`, ...args);
    }
  }

  private debugError(message: string, ...args: any[]): void {
    if (this.config.debug) {
      console.error(`[WebPlayer] ${message}`, ...args);
    }
  }

  private debugWarn(message: string, ...args: any[]): void {
    if (this.config.debug) {
      console.warn(`[WebPlayer] ${message}`, ...args);
    }
  }

  // ============================================
  // Live Stream Waiting Methods
  // ============================================

  /**
   * Check if error indicates live stream is not ready yet (vs fatal error)
   */
  private isLiveStreamNotReady(error?: any): boolean {
    if (!this.config.isLive || !this.video) return false;

    // For live streams, treat ANY error as "not ready yet"
    // The retry mechanism will handle it - if it never becomes available,
    // the max retry limit will eventually show a proper error
    if (this.video.error) {
      this.debugLog(`Live stream error detected (code: ${this.video.error.code}), treating as "not ready"`);
      return true;
    }

    // Also check for invalid duration after metadata loads
    if (this.video.readyState >= 1) { // HAVE_METADATA or higher
      if (this.video.duration === 0 || !isFinite(this.video.duration)) {
        this.debugLog('Live stream has invalid duration, treating as "not ready"');
        return true;
      }
    }

    return false;
  }

  /**
   * Enter live stream waiting mode
   */
  private startLiveStreamWaiting(): void {
    if (this.isWaitingForLiveStream) {
      this.debugLog(`⏭️ Already in waiting mode, retry count: ${this.liveRetryAttempts}`);
      // Still need to check if max retries reached
      this.scheduleLiveStreamRetry();
      return; // Already waiting
    }

    this.debugLog('🔴 Entering live stream waiting mode');
    this.isWaitingForLiveStream = true;
    this.liveRetryAttempts = 0;

    // Clear buffering timeout timer since we're now in retry mode
    if (this.liveBufferingTimeoutTimer) {
      clearTimeout(this.liveBufferingTimeoutTimer);
      this.liveBufferingTimeoutTimer = null;
    }

    // Store original source for retries
    if (this.source) {
      this.liveStreamOriginalSource = this.source as any;
    }

    // Show waiting UI with rotating messages
    this.showLiveWaitingUI();

    // Emit callback
    this.emit('onLiveStreamWaiting');

    // Schedule first retry
    this.scheduleLiveStreamRetry();
  }

  /**
   * Exit live stream waiting mode and clear all timers
   */
  private stopLiveStreamWaiting(success: boolean): void {
    if (!this.isWaitingForLiveStream) return;

    this.debugLog(`🟢 Exiting live stream waiting mode (success: ${success})`);

    // Clear rotation timer
    if (this.liveMessageRotationTimer) {
      clearInterval(this.liveMessageRotationTimer);
      this.liveMessageRotationTimer = null;
    }

    // Clear retry timer
    if (this.liveRetryTimer) {
      clearTimeout(this.liveRetryTimer);
      this.liveRetryTimer = null;
    }

    // Hide UI
    const loading = this.cachedElements.loading;
    if (loading) {
      loading.classList.remove('with-message');
      const messageEl = loading.querySelector('.uvf-loading-message') as HTMLElement;
      if (messageEl) {
        messageEl.textContent = '';
      }
    }

    // Reset state
    this.isWaitingForLiveStream = false;
    this.liveRetryAttempts = 0;
    this.liveStreamOriginalSource = null;
    this.liveMessageIndex = 0;

    // Emit callback if successful
    if (success) {
      this.emit('onLiveStreamReady');
    }
  }

  /**
   * Schedule next retry attempt
   */
  private scheduleLiveStreamRetry(): void {
    // Check max retries
    const maxRetries = this.config.liveMaxRetryAttempts;
    this.debugLog(`🔍 Checking retries: current=${this.liveRetryAttempts}, max=${maxRetries}`);

    if (maxRetries !== undefined && this.liveRetryAttempts >= maxRetries) {
      this.debugLog(`❌ Max retry attempts (${maxRetries}) reached, stopping`);
      this.stopLiveStreamWaiting(false);

      // Emit callback to notify app that stream is unavailable
      this.debugLog('📢 Emitting onLiveStreamUnavailable event');
      this.emit('onLiveStreamUnavailable', {
        reason: 'Max retry attempts exceeded',
        attempts: this.liveRetryAttempts
      });

      // Show error UI
      this.handleError({
        code: 'LIVE_STREAM_UNAVAILABLE',
        message: 'Live stream is not currently broadcasting',
        type: 'network',
        fatal: true
      });
      return;
    }

    const retryInterval = this.config.liveRetryInterval || 5000;
    this.liveRetryAttempts++;
    this.debugLog(`Scheduling live retry #${this.liveRetryAttempts} in ${retryInterval}ms`);

    this.liveRetryTimer = setTimeout(() => {
      this.retryLiveStreamLoad();
    }, retryInterval);
  }

  /**
   * Attempt to reload the live stream
   */
  private async retryLiveStreamLoad(): Promise<void> {
    if (!this.isWaitingForLiveStream || !this.liveStreamOriginalSource) return;

    this.debugLog(`🔄 Retry attempt #${this.liveRetryAttempts} for live stream`);

    try {
      // Attempt to reload the source
      await this.load(this.liveStreamOriginalSource);

      // Ensure loading UI is still visible after retry (in case cleanup removed it)
      if (this.isWaitingForLiveStream) {
        const loading = this.cachedElements.loading;
        if (loading && !loading.classList.contains('active')) {
          loading.classList.add('active', 'with-message');
        }
      }

      // If load succeeds and canplay fires, stopLiveStreamWaiting will be called automatically
    } catch (error) {
      this.debugLog('Retry failed, scheduling next attempt', error);
      // Schedule next retry
      this.scheduleLiveStreamRetry();
    }
  }

  /**
   * Display waiting UI and start message rotation
   */
  private showLiveWaitingUI(): void {
    const loading = this.cachedElements.loading;
    if (!loading) return;

    loading.classList.add('active', 'with-message');

    // Show first message
    const messages = this.getLiveWaitingMessages();
    this.liveMessageIndex = 0;
    this.updateLiveWaitingMessage(messages[0]);

    // Start rotation timer
    const rotationInterval = this.config.liveMessageRotationInterval || 2500;
    if (this.liveMessageRotationTimer) {
      clearInterval(this.liveMessageRotationTimer);
    }
    this.liveMessageRotationTimer = setInterval(() => {
      this.rotateLoadingMessage();
    }, rotationInterval);
  }

  /**
   * Update the loading message text
   */
  private updateLiveWaitingMessage(text: string): void {
    const loading = this.cachedElements.loading;
    if (!loading) return;

    const messageEl = loading.querySelector('.uvf-loading-message') as HTMLElement;
    if (messageEl) {
      messageEl.textContent = text;
    }
  }

  /**
   * Get array of messages for rotation
   */
  private getLiveWaitingMessages(): string[] {
    const messages = this.config.liveWaitingMessages || {};
    return [
      messages.waitingForStream || 'Waiting for Stream',
      messages.loading || 'Loading',
      messages.comingBack || 'Coming back',
    ];
  }

  /**
   * Rotate to next message in sequence
   */
  private rotateLoadingMessage(): void {
    if (!this.isWaitingForLiveStream) return;

    const messages = this.getLiveWaitingMessages();
    this.liveMessageIndex = (this.liveMessageIndex + 1) % messages.length;
    this.updateLiveWaitingMessage(messages[this.liveMessageIndex]);
  }

  // ============================================
  // Live Countdown Methods
  // ============================================

  /**
   * Show countdown timer for live streams that haven't started yet
   */
  private showLiveCountdown(): void {
    const countdown = this.config.liveCountdown;
    if (!countdown?.nextProgramStartTime) {
      this.debugLog('⚠️ showLiveCountdown called but no nextProgramStartTime configured');
      return;
    }

    // CRITICAL: Check if countdown has already completed - don't show again
    if (this.hasCountdownCompleted) {
      this.debugLog('⏱️ showLiveCountdown called but countdown already completed - skipping');
      return;
    }

    // Calculate remaining seconds from UTC timestamp
    const remainingMs = countdown.nextProgramStartTime - Date.now();
    this.liveCountdownRemainingSeconds = Math.max(0, Math.floor(remainingMs / 1000));

    this.debugLog(`⏱️ Starting countdown with ${this.liveCountdownRemainingSeconds} seconds remaining (hasCountdownCompleted=${this.hasCountdownCompleted})`);

    // If countdown is already at zero or negative, complete immediately
    if (this.liveCountdownRemainingSeconds <= 0) {
      this.handleCountdownComplete();
      return;
    }

    // Show countdown UI
    const loading = this.cachedElements.loading;
    if (!loading) return;

    this.isShowingLiveCountdown = true;
    loading.classList.add('active', 'uvf-countdown-mode');

    // Hide center play button during countdown
    const centerPlayBtn = this.playerWrapper?.querySelector('.uvf-center-play-btn') as HTMLElement;
    if (centerPlayBtn) {
      centerPlayBtn.style.display = 'none';
    }

    // Update display
    this.updateLiveCountdownDisplay();

    // Start countdown timer
    const updateInterval = countdown.updateInterval || 1000;
    if (this.liveCountdownTimer) {
      clearInterval(this.liveCountdownTimer);
    }
    this.liveCountdownTimer = setInterval(() => {
      this.tickCountdown();
    }, updateInterval);
  }

  /**
   * Sanitize HTML to prevent XSS attacks
   */
  private sanitizeHtml(str: string): string {
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
  }

  /**
   * Update the countdown display with current time remaining
   */
  private updateLiveCountdownDisplay(): void {
    const loading = this.cachedElements.loading;
    if (!loading) return;

    const messageEl = loading.querySelector('.uvf-loading-message') as HTMLElement;
    if (!messageEl) return;

    const countdown = this.config.liveCountdown;
    const noProgramMsg = this.sanitizeHtml(countdown?.noProgramMessage || 'There is no active program in this channel.');
    const countdownMsg = this.sanitizeHtml(countdown?.countdownMessage || 'Next program will start in:');
    const timerColor = this.sanitizeHtml(countdown?.timerColor || 'var(--uvf-accent-color, #00d4ff)');
    const formattedTime = this.sanitizeHtml(this.formatCountdownTime(this.liveCountdownRemainingSeconds));

    messageEl.innerHTML = `
      <div style="font-size: 18px; margin-bottom: 16px; font-weight: 500;">${noProgramMsg}</div>
      <div style="font-size: 16px; margin-bottom: 8px;">${countdownMsg}</div>
      <div style="font-size: 20px; font-weight: bold; color: ${timerColor};">${formattedTime}</div>
    `;
  }

  /**
   * Tick the countdown timer down by one second
   * Recalculates from timestamp to prevent drift
   */
  private tickCountdown(): void {
    // Recalculate from actual timestamp to avoid drift (browser throttling, system load, etc.)
    const nextProgramTime = this.config.liveCountdown?.nextProgramStartTime;
    if (!nextProgramTime) {
      this.handleCountdownComplete();
      return;
    }

    const remainingMs = nextProgramTime - Date.now();
    this.liveCountdownRemainingSeconds = Math.max(0, Math.floor(remainingMs / 1000));

    if (this.liveCountdownRemainingSeconds <= 0) {
      this.handleCountdownComplete();
    } else {
      this.updateLiveCountdownDisplay();
    }
  }

  /**
   * Handle countdown completion
   */
  private handleCountdownComplete(): void {
    this.debugLog('⏱️ Countdown complete!');

    // Mark countdown as completed to prevent showing again on reload
    this.hasCountdownCompleted = true;
    this.debugLog(`⏱️ Set hasCountdownCompleted = true`);

    // Stop countdown timer
    this.stopLiveCountdown();

    // Fire callback only once (prevent double execution)
    if (this.config.liveCountdown?.onCountdownComplete && !this.countdownCallbackFired) {
      this.countdownCallbackFired = true;
      this.debugLog('⏱️ Firing onCountdownComplete callback');
      this.config.liveCountdown.onCountdownComplete();
    }

    // Auto-reload stream if enabled (default: true)
    const autoReload = this.config.liveCountdown?.autoReloadOnComplete !== false;
    if (autoReload && this.source && !this.isReloadingAfterCountdown) {
      this.isReloadingAfterCountdown = true;
      this.debugLog('🔄 Auto-reloading stream after countdown');

      // Reset autoplay flag to allow autoplay on reload
      this.autoplayAttempted = false;

      // Use the proper load method to reload the stream
      // This ensures proper HLS/DASH detection and settings button visibility
      this.load(this.source)
        .catch((error) => {
          this.debugError('Failed to reload stream after countdown:', error);
        })
        .finally(() => {
          this.isReloadingAfterCountdown = false;
          this.debugLog('🔄 Stream reload after countdown complete');
        });
    }
  }

  /**
   * Stop and cleanup countdown timer
   */
  private stopLiveCountdown(): void {
    if (!this.isShowingLiveCountdown) return;

    this.debugLog('⏱️ Stopping countdown');

    // Clear timer
    if (this.liveCountdownTimer) {
      clearInterval(this.liveCountdownTimer);
      this.liveCountdownTimer = null;
    }

    // Reset state
    this.isShowingLiveCountdown = false;
    this.liveCountdownRemainingSeconds = 0;

    // Clean up UI
    const loading = this.cachedElements.loading;
    if (loading) {
      loading.classList.remove('uvf-countdown-mode', 'active');

      const messageEl = loading.querySelector('.uvf-loading-message') as HTMLElement;
      if (messageEl) {
        messageEl.innerHTML = '';
      }
    }

    // Restore center play button (with null check for disposed player)
    if (this.playerWrapper) {
      const centerPlayBtn = this.playerWrapper.querySelector('.uvf-center-play-btn') as HTMLElement;
      if (centerPlayBtn) {
        centerPlayBtn.style.display = '';
      }
    }
  }

  /**
   * Update countdown configuration dynamically
   */
  public updateLiveCountdown(config: any): void {
    // Handle undefined/null config - stop countdown if currently showing
    if (!config || !config.nextProgramStartTime) {
      this.debugLog('⏱️ updateLiveCountdown called with no config/timestamp - stopping countdown');
      if (this.isShowingLiveCountdown) {
        this.stopLiveCountdown();
      }
      this.config.liveCountdown = config;
      return;
    }

    // Check if this is a different timestamp (new countdown)
    const oldTimestamp = this.config.liveCountdown?.nextProgramStartTime;
    const newTimestamp = config.nextProgramStartTime;
    const isDifferentTimestamp = oldTimestamp !== newTimestamp;

    this.debugLog(`⏱️ updateLiveCountdown called: oldTimestamp=${oldTimestamp}, newTimestamp=${newTimestamp}, isDifferent=${isDifferentTimestamp}, hasCountdownCompleted=${this.hasCountdownCompleted}`);

    this.config.liveCountdown = config;

    // Only reset flags if setting a NEW countdown timestamp
    // Don't reset if it's the same timestamp (prevents re-showing after completion)
    if (isDifferentTimestamp) {
      this.debugLog(`⏱️ Different timestamp detected - resetting flags`);
      this.hasCountdownCompleted = false;
      this.countdownCallbackFired = false; // Reset callback flag for new countdown
    } else {
      this.debugLog(`⏱️ Same timestamp - keeping hasCountdownCompleted=${this.hasCountdownCompleted}`);
    }

    // Stop existing countdown
    if (this.isShowingLiveCountdown) {
      this.stopLiveCountdown();
    }

    // Start new countdown if configured, not yet completed, and not currently reloading
    if (!this.hasCountdownCompleted && !this.isReloadingAfterCountdown) {
      const remainingMs = newTimestamp - Date.now();
      if (remainingMs > 0) {
        this.showLiveCountdown();
      }
    }
  }

  // ============================================
  // Multi-Instance Helper Methods
  // ============================================

  /**
   * Get a unique element ID for this player instance
   * @param baseName - The base element name (e.g., 'loading', 'progress-bar')
   * @returns Full element ID with instance prefix (e.g., 'uvf-player-1-loading')
   */
  private getElementId(baseName: string): string {
    return `uvf-${this.instanceId}-${baseName}`;
  }

  /**
   * Get an element by its base name (scoped to this player instance)
   * @param baseName - The base element name (e.g., 'loading', 'progress-bar')
   * @returns The HTML element or null if not found
   */
  private getElement(baseName: string): HTMLElement | null {
    return document.getElementById(this.getElementId(baseName));
  }

  /**
   * Cache frequently-accessed element references for performance
   * Should be called after controls are created and appended to document
   */
  private cacheElementReferences(): void {
    this.cachedElements = {
      loading: this.getElement('loading'),
      progressBar: this.getElement('progress-bar'),
      progressFilled: this.getElement('progress-filled'),
      progressHandle: this.getElement('progress-handle'),
      progressBuffered: this.getElement('progress-buffered'),
      playIcon: this.getElement('play-icon'),
      pauseIcon: this.getElement('pause-icon'),
      centerPlay: this.getElement('center-play'),
      timeDisplay: this.getElement('time-display'),
      thumbnailPreview: this.getElement('thumbnail-preview'),
      thumbnailImage: this.getElement('thumbnail-image') as HTMLImageElement,
      thumbnailTime: this.getElement('thumbnail-time'),
      timeTooltip: this.getElement('time-tooltip'),
      settingsMenu: this.getElement('settings-menu'),
      fullscreenBtn: this.getElement('fullscreen-btn'),
    };
  }

  // ============================================
  // Controls Visibility Methods
  // ============================================

  /**
   * Deep merge partial controls visibility config with existing config
   */
  private deepMergeControlsConfig(
    existing: ControlsVisibilityConfig,
    partial: Partial<ControlsVisibilityConfig>
  ): ControlsVisibilityConfig {
    return {
      playback: { ...existing.playback, ...partial.playback },
      audio: { ...existing.audio, ...partial.audio },
      progress: { ...existing.progress, ...partial.progress },
      quality: { ...existing.quality, ...partial.quality },
      display: { ...existing.display, ...partial.display },
      features: { ...existing.features, ...partial.features },
      chrome: { ...existing.chrome, ...partial.chrome },
    };
  }

  /**
   * Set element visibility by base name
   * @param elementBaseName - The base element name without instance prefix (e.g., 'center-play', not 'uvf-player-1-center-play')
   */
  private setElementVisibility(elementBaseName: string, visible: boolean | undefined): void {
    if (visible === undefined) return; // No change
    const element = this.getElement(elementBaseName);
    if (element) {
      if (visible) {
        // Show: remove hidden class and clear inline style
        element.classList.remove('uvf-hidden');
        element.style.display = '';
      } else {
        // Hide: add hidden class AND set inline style for double protection
        element.classList.add('uvf-hidden');
        element.style.setProperty('display', 'none', 'important');
      }
    }
  }

  /**
   * Apply controlsVisibility configuration to DOM elements
   */
  private applyControlsVisibility(): void {
    if (!this.container) return;
    const cv = this.controlsVisibility;

    // Playback controls
    this.setElementVisibility('center-play', cv.playback?.centerPlayButton);
    this.setElementVisibility('play-pause', cv.playback?.playPauseButton);
    this.setElementVisibility('skip-back', cv.playback?.skipButtons);
    this.setElementVisibility('skip-forward', cv.playback?.skipButtons);
    this.setElementVisibility('btn-prev', cv.playback?.previousButton);
    this.setElementVisibility('btn-next', cv.playback?.nextButton);

    // Audio controls
    const volumeControl = this.container.querySelector('.uvf-volume-control');
    if (volumeControl) {
      (volumeControl as HTMLElement).style.display = cv.audio?.volumeButton ? '' : 'none';
    }
    this.setElementVisibility('volume-panel', cv.audio?.volumeSlider);

    // Progress controls
    this.setElementVisibility('progress-bar', cv.progress?.progressBar);
    this.setElementVisibility('time-display', cv.progress?.timeDisplay);

    // Quality controls
    this.setElementVisibility('quality-badge', cv.quality?.badge);
    const settingsContainer = this.container.querySelector('.uvf-settings-container');
    if (settingsContainer) {
      (settingsContainer as HTMLElement).style.display = cv.quality?.settingsButton ? '' : 'none';
    }

    // Display controls
    this.setElementVisibility('fullscreen-btn', cv.display?.fullscreenButton);
    this.setElementVisibility('pip-btn', cv.display?.pipButton);

    // Feature controls
    this.setElementVisibility('epg-btn', cv.features?.epgButton);
    this.setElementVisibility('playlist-btn', cv.features?.playlistButton);
    this.setElementVisibility('cast-btn', cv.features?.castButton);
    // NOTE: Stop cast button visibility is controlled by _syncCastButtons() based on runtime casting state
    // It should only show when actively casting, not be user-configurable
    this.setElementVisibility('share-btn', cv.features?.shareButton);

    // Chrome controls
    const topBar = this.container.querySelector('.uvf-top-bar');
    if (topBar) {
      (topBar as HTMLElement).style.display = cv.chrome?.navigationButtons ? '' : 'none';
    }
    const brandingContainer = this.container.querySelector('.uvf-framework-branding');
    if (brandingContainer) {
      (brandingContainer as HTMLElement).style.display = cv.chrome?.frameworkBranding ? '' : 'none';
    }
  }

  /**
   * Enable or disable all custom player controls
   * @param enabled - true to show all controls, false to hide all controls
   * @throws Error if player is not initialized
   */
  public setControlsEnabled(enabled: boolean): void {
    if (!this.container || !this.playerWrapper) {
      throw new Error('Player not initialized. Call initialize() first.');
    }

    this.useCustomControls = enabled;

    if (enabled) {
      this.playerWrapper.classList.remove('controls-disabled');
      this.playerWrapper.classList.add('controls-visible');
    } else {
      this.playerWrapper.classList.add('controls-disabled');
      this.playerWrapper.classList.remove('controls-visible');
    }

    this.debugLog(`Controls ${enabled ? 'enabled' : 'disabled'}`);
  }

  /**
   * Update visibility of individual control elements
   * @param config - Partial controls visibility configuration (deep merged with existing)
   * @throws Error if player is not initialized
   */
  public setControlsVisibility(config: Partial<ControlsVisibilityConfig>): void {
    if (!this.container || !this.playerWrapper) {
      throw new Error('Player not initialized. Call initialize() first.');
    }

    // Deep merge partial config into existing controlsVisibility
    this.controlsVisibility = this.deepMergeControlsConfig(this.controlsVisibility, config);

    // Apply visibility changes to DOM elements
    this.applyControlsVisibility();

    this.debugLog('Controls visibility updated:', this.controlsVisibility);
  }

  // ============================================
  // Thumbnail Preview Methods
  // ============================================

  /**
   * Initialize thumbnail preview with config
   */
  public initializeThumbnailPreview(config: ThumbnailPreviewConfig): void {
    if (!config || !config.generationImage) {
      this.thumbnailPreviewEnabled = false;
      return;
    }

    this.thumbnailPreviewConfig = config;
    this.thumbnailPreviewEnabled = config.enabled !== false;

    // Transform generation image data to sorted array for efficient lookup
    this.thumbnailEntries = this.transformThumbnailData(config.generationImage);

    this.debugLog('Thumbnail preview initialized:', {
      enabled: this.thumbnailPreviewEnabled,
      entries: this.thumbnailEntries.length
    });

    // Apply custom styles if provided
    if (config.style) {
      this.applyThumbnailStyles(config.style);
    }

    // Preload images if enabled (default: true)
    if (config.preloadImages !== false && this.thumbnailEntries.length > 0) {
      this.preloadThumbnailImages();
    }
  }

  /**
   * Transform generation image JSON to sorted array of ThumbnailEntry
   */
  private transformThumbnailData(generationImage: ThumbnailGenerationImage): ThumbnailEntry[] {
    const entries: ThumbnailEntry[] = [];

    for (const [url, timeRange] of Object.entries(generationImage)) {
      const parts = timeRange.split('-');
      if (parts.length === 2) {
        const startTime = parseFloat(parts[0]);
        const endTime = parseFloat(parts[1]);
        if (!isNaN(startTime) && !isNaN(endTime)) {
          entries.push({ url, startTime, endTime });
        }
      }
    }

    // Sort by startTime for efficient binary search
    entries.sort((a, b) => a.startTime - b.startTime);

    return entries;
  }

  /**
   * Find thumbnail for a given time using binary search (O(log n))
   */
  private findThumbnailForTime(time: number): ThumbnailEntry | null {
    if (this.thumbnailEntries.length === 0) return null;

    let left = 0;
    let right = this.thumbnailEntries.length - 1;
    let result: ThumbnailEntry | null = null;

    while (left <= right) {
      const mid = Math.floor((left + right) / 2);
      const entry = this.thumbnailEntries[mid];

      if (time >= entry.startTime && time < entry.endTime) {
        return entry;
      } else if (time < entry.startTime) {
        right = mid - 1;
      } else {
        left = mid + 1;
      }
    }

    // If no exact match, find the closest entry
    if (left > 0 && left <= this.thumbnailEntries.length) {
      const prevEntry = this.thumbnailEntries[left - 1];
      if (time >= prevEntry.startTime && time < prevEntry.endTime) {
        return prevEntry;
      }
    }

    return result;
  }

  /**
   * Preload all thumbnail images for instant switching
   */
  private preloadThumbnailImages(): void {
    this.debugLog('Preloading', this.thumbnailEntries.length, 'thumbnail images');

    for (const entry of this.thumbnailEntries) {
      if (this.preloadedThumbnails.has(entry.url)) continue;

      const img = new Image();
      img.onload = () => {
        this.preloadedThumbnails.set(entry.url, img);
        this.debugLog('Preloaded thumbnail:', entry.url);
      };
      img.onerror = () => {
        this.debugWarn('Failed to preload thumbnail:', entry.url);
      };
      img.src = entry.url;
    }
  }

  /**
   * Apply custom thumbnail styles
   */
  private applyThumbnailStyles(style: ThumbnailPreviewConfig['style']): void {
    if (!style) return;

    const wrapper = this.cachedElements.thumbnailPreview;
    const imageWrapper = wrapper?.querySelector('.uvf-thumbnail-preview-image-wrapper') as HTMLElement;

    if (imageWrapper) {
      if (style.width) {
        imageWrapper.style.width = `${style.width}px`;
      }
      if (style.height) {
        imageWrapper.style.height = `${style.height}px`;
      }
      if (style.borderRadius !== undefined) {
        imageWrapper.style.borderRadius = `${style.borderRadius}px`;
      }
    }
  }

  /**
   * Update thumbnail preview based on mouse position
   */
  private updateThumbnailPreview(e: MouseEvent): void {
    if (!this.thumbnailPreviewEnabled || this.thumbnailEntries.length === 0) {
      return;
    }

    const progressBar = this.cachedElements.progressBar;
    const thumbnailPreview = this.cachedElements.thumbnailPreview;
    const thumbnailImage = this.cachedElements.thumbnailImage;
    const thumbnailTime = this.cachedElements.thumbnailTime;

    if (!progressBar || !thumbnailPreview || !thumbnailImage || !this.video) {
      return;
    }

    const rect = progressBar.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const percent = Math.max(0, Math.min(1, x / rect.width));
    const time = percent * this.video.duration;

    // Find thumbnail for this time
    const entry = this.findThumbnailForTime(time);

    if (entry) {
      // Show thumbnail preview
      thumbnailPreview.classList.add('visible');

      // Calculate position (clamp to prevent overflow)
      const thumbnailWidth = 160; // Default width
      const halfWidth = thumbnailWidth / 2;
      const minX = halfWidth;
      const maxX = rect.width - halfWidth;
      const clampedX = Math.max(minX, Math.min(maxX, x));

      thumbnailPreview.style.left = `${clampedX}px`;

      // Update image only if URL changed
      if (this.currentThumbnailUrl !== entry.url) {
        this.currentThumbnailUrl = entry.url;
        thumbnailImage.classList.remove('loaded');

        // Check if image is preloaded
        const preloaded = this.preloadedThumbnails.get(entry.url);
        if (preloaded) {
          thumbnailImage.src = preloaded.src;
          thumbnailImage.classList.add('loaded');
        } else {
          thumbnailImage.onload = () => {
            thumbnailImage.classList.add('loaded');
          };
          thumbnailImage.src = entry.url;
        }
      }

      // Update time display
      if (thumbnailTime && this.thumbnailPreviewConfig?.showTimeInThumbnail !== false) {
        thumbnailTime.textContent = this.formatTime(time);
        thumbnailTime.style.display = 'block';
      } else if (thumbnailTime) {
        thumbnailTime.style.display = 'none';
      }
    } else {
      // No thumbnail for this time - hide preview and let regular tooltip show
      this.hideThumbnailPreview();
    }
  }

  /**
   * Hide thumbnail preview
   */
  private hideThumbnailPreview(): void {
    const thumbnailPreview = this.cachedElements.thumbnailPreview;
    if (thumbnailPreview) {
      thumbnailPreview.classList.remove('visible');
    }
    this.currentThumbnailUrl = null;
  }

  /**
   * Set thumbnail preview config at runtime
   */
  public setThumbnailPreview(config: ThumbnailPreviewConfig | null): void {
    if (!config) {
      this.thumbnailPreviewEnabled = false;
      this.thumbnailPreviewConfig = null;
      this.thumbnailEntries = [];
      this.preloadedThumbnails.clear();
      this.hideThumbnailPreview();
      return;
    }

    this.initializeThumbnailPreview(config);
  }

  // Note: Uses existing formatTime method defined elsewhere in this class

  // ============================================
  // End Thumbnail Preview Methods
  // ============================================

  async initialize(container: HTMLElement | string, config?: any): Promise<void> {
    // Generate unique instance ID for this player
    this.instanceId = `player-${++WebPlayer.instanceCounter}`;
    console.log(`[WebPlayer] Instance ${this.instanceId} initializing`);

    // Debug log the config being passed
    console.log('WebPlayer.initialize called with config:', config);

    // Set useCustomControls based on controls config
    if (config && config.controls !== undefined) {
      this.useCustomControls = config.controls;
      console.log('[WebPlayer] Controls set to:', this.useCustomControls);
    }

    // Configure settings menu options
    if (config && config.settings) {
      console.log('Settings config found:', config.settings);
      this.settingsConfig = {
        enabled: config.settings.enabled !== undefined ? config.settings.enabled : true,
        speed: config.settings.speed !== undefined ? config.settings.speed : true,
        quality: config.settings.quality !== undefined ? config.settings.quality : true,
        subtitles: config.settings.subtitles !== undefined ? config.settings.subtitles : true
      };
      console.log('Settings config applied:', this.settingsConfig);
    } else {
      console.log('No settings config found, using defaults:', this.settingsConfig);
    }

    // Store config for later use (needed by resolveControlVisibility and other methods)
    this.config = config || {};

    // Resolve control visibility with backward compatibility
    this.controlsVisibility = this.resolveControlVisibility();
    console.log('Controls visibility resolved:', this.controlsVisibility);

    // Configure chapters if provided
    if (config && config.chapters) {
      console.log('Chapter config found:', config.chapters);
      this.chapterConfig = {
        enabled: config.chapters.enabled || false,
        data: config.chapters.data,
        dataUrl: config.chapters.dataUrl,
        autoHide: config.chapters.autoHide !== undefined ? config.chapters.autoHide : true,
        autoHideDelay: config.chapters.autoHideDelay || 5000,
        showChapterMarkers: config.chapters.showChapterMarkers !== undefined ? config.chapters.showChapterMarkers : true,
        skipButtonPosition: config.chapters.skipButtonPosition || 'bottom-right',
        customStyles: config.chapters.customStyles || {},
        userPreferences: config.chapters.userPreferences || {
          autoSkipIntro: false,
          autoSkipRecap: false,
          autoSkipCredits: false,
          showSkipButtons: true,
          skipButtonTimeout: 5000,
          rememberChoices: true
        }
      };
      console.log('Chapter config applied:', this.chapterConfig);
    } else {
      console.log('No chapter config found, chapters disabled');
    }

    // Configure quality filter if provided
    if (config && config.qualityFilter) {
      console.log('Quality filter config found:', config.qualityFilter);
      this.qualityFilter = config.qualityFilter;
    }

    // Configure premium qualities if provided
    if (config && config.premiumQualities) {
      console.log('Premium qualities config found:', config.premiumQualities);
      this.premiumQualities = config.premiumQualities;
    }

    // Configure YouTube native controls if provided
    if (config && config.youtubeNativeControls !== undefined) {
      console.log('YouTube native controls config found:', config.youtubeNativeControls);
      this.youtubeNativeControls = config.youtubeNativeControls;
    } else if (this.useCustomControls) {
      // Fallback: If custom controls are enabled and youtubeNativeControls is not explicitly set,
      // default to hiding native controls so our custom controls can show.
      // Note: WebPlayerView.tsx defaults youtubeNativeControls to false, so this is a safety fallback.
      console.log('Custom controls enabled, defaulting YouTube native controls to false');
      this.youtubeNativeControls = false;
    }

    // Configure thumbnail preview if provided
    if (config && config.thumbnailPreview) {
      console.log('Thumbnail preview config found:', config.thumbnailPreview);
      this.initializeThumbnailPreview(config.thumbnailPreview);
    }

    // Call parent initialize
    await super.initialize(container, config);
  }

  protected async setupPlayer(): Promise<void> {
    if (!this.container) {
      throw new Error('Container element is required');
    }

    // Inject styles
    this.injectStyles();

    // Create wrapper
    const wrapper = document.createElement('div');
    wrapper.className = 'uvf-player-wrapper';
    this.playerWrapper = wrapper;

    // Create video container
    const videoContainer = document.createElement('div');
    videoContainer.className = 'uvf-video-container';

    // Create video element
    this.video = document.createElement('video');
    this.video.className = 'uvf-video';
    this.video.controls = false; // We'll use custom controls
    // Don't set autoplay attribute - we'll handle it programmatically with intelligent detection
    this.video.autoplay = false;
    // Respect user's muted preference, intelligent autoplay will handle browser policies
    this.video.muted = this.config.muted ?? false;
    this.state.isMuted = this.video.muted;
    // NEVER loop live streams - they should either keep playing or stop when ended
    this.video.loop = this.config.isLive ? false : (this.config.loop ?? false);
    this.video.playsInline = this.config.playsInline ?? true;
    this.video.preload = this.config.preload ?? 'metadata';

    // Enable AirPlay for iOS devices
    (this.video as any).webkitAllowsAirPlay = true;
    this.video.setAttribute('x-webkit-airplay', 'allow');

    if (this.config.crossOrigin) {
      this.video.crossOrigin = this.config.crossOrigin;
    }

    // Create subtitle overlay
    this.subtitleOverlay = document.createElement('div');
    this.subtitleOverlay.className = 'uvf-subtitle-overlay';
    videoContainer.appendChild(this.subtitleOverlay);

    // Add watermark canvas
    this.watermarkCanvas = document.createElement('canvas');
    this.watermarkCanvas.className = 'uvf-watermark-layer';

    // Create flash news ticker container
    this.flashTickerContainer = document.createElement('div');
    this.flashTickerContainer.className = 'uvf-flash-ticker-container';
    this.flashTickerContainer.style.cssText = `
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      z-index: 0;
      pointer-events: none;
    `;

    // Add video to container
    videoContainer.appendChild(this.video);
    videoContainer.appendChild(this.watermarkCanvas);
    videoContainer.appendChild(this.flashTickerContainer);

    // Assemble the player - add video container first
    wrapper.appendChild(videoContainer);

    // Always create custom controls - append to wrapper (not videoContainer)
    // This ensures controls are not constrained by the video's aspect-ratio
    this.createCustomControls(wrapper);

    // Apply controls-disabled class if controls={false}
    if (!this.useCustomControls) {
      wrapper.classList.add('controls-disabled');
    } else {
      // Make controls visible by default when controls={true}
      wrapper.classList.add('controls-visible');
    }

    // Add to container
    this.container.innerHTML = '';
    this.container.appendChild(wrapper);

    // NOW apply control visibility - elements are in the document and can be found by getElementById()
    this.applyControlsVisibility();

    // Cache frequently-accessed element references for performance
    this.cacheElementReferences();

    // Measure the controls bar height on the first rendered frame and expose it as
    // --uvf-ctrl-height so the subtitle overlay is positioned correctly right from
    // startup (controls-visible class is already added above, so without this the
    // subtitle would use the 0px fallback until the next showControls() call).
    requestAnimationFrame(() => {
      if (this.controlsContainer && this.playerWrapper) {
        this.playerWrapper.style.setProperty(
          '--uvf-ctrl-height',
          `${this.controlsContainer.getBoundingClientRect().height}px`
        );
      }
    });

    // Apply scrollbar preferences from data attributes, if any
    this.applyScrollbarPreferencesFromDataset();

    // Setup event listeners
    this.setupVideoEventListeners();
    this.setupControlsEventListeners();
    this.setupKeyboardShortcuts();
    this.setupWatermark();
    this.setupFullscreenListeners();
    this.setupUserInteractionTracking();

    // Initialize chapter manager if enabled
    if (this.chapterConfig.enabled && this.video) {
      this.setupChapterManager();
    }

    // Initialize paywall controller if provided
    try {
      const pw: any = (this.config as any).paywall || null;
      if (pw && pw.enabled) {
        const { PaywallController } = await import('./paywall/PaywallController');
        this.paywallController = new (PaywallController as any)(pw, {
          getOverlayContainer: () => this.playerWrapper,
          onResume: (accessInfo?: { accessGranted?: boolean; paymentSuccessful?: boolean; freeDuration?: number }) => {
            try {
              const isFullAccess = accessInfo?.paymentSuccessful === true || accessInfo?.accessGranted === true;
              const isFreePreview = !isFullAccess;

              this.debugLog('onResume callback triggered', {
                isFullAccess,
                isFreePreview,
                accessInfo
              });

              if (isFullAccess) {
                // Full access granted - payment successful or subscription active
                this.debugLog('Full access granted - lifting all restrictions');

                // Reset all security state after successful payment/auth
                this.previewGateHit = false;
                this.paymentSuccessTime = Date.now();
                this.paymentSuccessful = true;
                this.isPaywallActive = false;
                this.overlayRemovalAttempts = 0;

                // Clear security monitoring immediately
                if (this.authValidationInterval) {
                  this.debugLog('Clearing security monitoring interval');
                  clearInterval(this.authValidationInterval);
                  this.authValidationInterval = null;
                }

                // Force cleanup of any remaining overlays
                this.forceCleanupOverlays();

                this.debugLog('Payment successful - all security restrictions lifted, resuming playback');
              } else {
                // Free preview mode
                this.debugLog('Free preview mode - maintaining restrictions');

                // Set free duration from server if provided
                if (accessInfo?.freeDuration && accessInfo.freeDuration > 0) {
                  this.debugLog(`Setting free duration from server: ${accessInfo.freeDuration}s`);
                  this.setFreeDuration(accessInfo.freeDuration);
                }

                // Don't set paymentSuccessful - keep restrictions active
                this.previewGateHit = false;
                this.isPaywallActive = false;
                this.overlayRemovalAttempts = 0;

                // Clear security monitoring for now (will restart if needed)
                if (this.authValidationInterval) {
                  clearInterval(this.authValidationInterval);
                  this.authValidationInterval = null;
                }

                // Close auth/paywall overlays
                this.forceCleanupOverlays();
              }

              // Give a small delay to ensure overlay is properly closed before resuming
              setTimeout(() => {
                this.play();
              }, 150);
            } catch (error) {
              this.debugError('Error in onResume callback:', error);
            }
          },
          onShow: () => {
            // Activate security monitoring when paywall is shown
            this.isPaywallActive = true;
            this.startOverlayMonitoring();

            // Use safe pause method to avoid race conditions
            try { this.requestPause(); } catch (_) { }
          },
          onClose: () => {
            this.debugLog('onClose callback triggered - paywall closing');

            // Deactivate security monitoring when paywall is closed
            this.isPaywallActive = false;

            // Clear monitoring interval
            if (this.authValidationInterval) {
              this.debugLog('Clearing security monitoring interval on close');
              clearInterval(this.authValidationInterval);
              this.authValidationInterval = null;
            }

            // Reset overlay removal attempts counter
            this.overlayRemovalAttempts = 0;
          }
        });
        // When free preview ends, open overlay
        this.on('onFreePreviewEnded' as any, () => {
          this.debugLog('onFreePreviewEnded event triggered, calling paywallController.openOverlay()');
          try {
            this.paywallController?.openOverlay();
          } catch (error) {
            this.debugError('Error calling paywallController.openOverlay():', error);
          }
        });
      }
    } catch (_) { }

    // Attempt to bind Cast context if available
    this.setupCastContextSafe();

    // Initialize metadata UI to hidden/empty by default
    this.updateMetadataUI();
  }

  private autoplayAttempted: boolean = false;

  private setupVideoEventListeners(): void {
    if (!this.video) return;

    this.video.addEventListener('play', () => {
      // Don't enforce preview if payment was successful
      if (!this.paymentSuccessful && this.config.freeDuration && this.config.freeDuration > 0) {
        const lim = Number(this.config.freeDuration);
        const cur = (this.video?.currentTime || 0);
        if (!this.previewGateHit && cur >= lim) {
          try { this.video?.pause(); } catch (_) { }
          this.showNotification('Free preview ended. Please rent to continue.');
          return;
        }
      }
      this.state.isPlaying = true;
      this.state.isPaused = false;
      this.emit('onPlay');
    });

    this.video.addEventListener('playing', () => {
      this.debugLog('▶️ playing event fired');

      // Handle deferred pause requests
      if (this._deferredPause) {
        this._deferredPause = false;
        try { this.video?.pause(); } catch (_) { }
      }

      // Stop buffering state
      this.setBuffering(false);
      const loading = this.cachedElements.loading;
      if (loading) {
        loading.classList.remove('active');

        // For live streams, clean up custom messages when buffering completes
        if (this.config.isLive && this.config.liveWaitingMessages && !this.isWaitingForLiveStream) {
          this.debugLog('✅ Live stream buffering complete, hiding messages');
          loading.classList.remove('with-message');

          // Stop message rotation
          if (this.liveMessageRotationTimer) {
            clearInterval(this.liveMessageRotationTimer);
            this.liveMessageRotationTimer = null;
          }

          // Clear buffering timeout timer
          if (this.liveBufferingTimeoutTimer) {
            this.debugLog('⏱️ Clearing buffering timeout timer');
            clearTimeout(this.liveBufferingTimeoutTimer);
            this.liveBufferingTimeoutTimer = null;
          }

          // Clear message text
          const messageEl = loading.querySelector('.uvf-loading-message') as HTMLElement;
          if (messageEl) {
            messageEl.textContent = '';
          }
          this.liveMessageIndex = 0;
        }
      }
    });

    this.video.addEventListener('pause', () => {
      this.state.isPlaying = false;
      this.state.isPaused = true;
      this.emit('onPause');
    });

    this.video.addEventListener('ended', () => {
      this.state.isEnded = true;
      this.state.isPlaying = false;

      // For live streams, the 'ended' event means the stream has finished
      if (this.config.isLive) {
        this.debugLog('🔴 Live stream ended');
        // Clear any buffering timeout
        if (this.liveBufferingTimeoutTimer) {
          clearTimeout(this.liveBufferingTimeoutTimer);
          this.liveBufferingTimeoutTimer = null;
        }
        // Show loading and enter waiting mode to check if stream comes back
        const loading = this.cachedElements.loading;
        if (loading) loading.classList.add('active');
        // Optionally enter retry mode to check if stream comes back
        // Uncomment the line below if you want automatic retry when live stream ends
        // this.startLiveStreamWaiting();
      }

      this.emit('onEnded');
    });

    this.video.addEventListener('timeupdate', () => {
      if (!this.video) return;
      const t = this.video.currentTime || 0;
      this.updateTime(t);
      // Emit time update event for React components (e.g., commerce sync)
      this.emit('onTimeUpdate', t);
      // Debug: Log time update every ~5 seconds
      if (Math.floor(t) % 5 === 0 && Math.floor(t) !== Math.floor((this.video?.currentTime || 0) - 0.1)) {
        console.log(`[DEBUG] onTimeUpdate emitted: ${t.toFixed(2)}s`);
      }
      // Enforce free preview gate on local playback
      this.enforceFreePreviewGate(t);
      // Process chapter time updates
      if (this.coreChapterManager) {
        this.coreChapterManager.processTimeUpdate(t);
      }
    });

    this.video.addEventListener('progress', () => {
      this.updateBufferProgress();
    });

    this.video.addEventListener('waiting', () => {
      this.debugLog('⏸️ waiting event fired (buffering)');

      // Don't mark as buffering while an ad is playing — video is intentionally paused
      if (this.isAdPlaying) return;
      this.setBuffering(true);
      const loading = this.cachedElements.loading;
      if (loading) {
        loading.classList.add('active');

        // For live streams with custom waiting messages, show them during buffering
        if (this.config.isLive && this.config.liveWaitingMessages && !this.isWaitingForLiveStream) {
          this.debugLog('🔄 Live stream buffering, showing custom messages');
          loading.classList.add('with-message');

          // Start message rotation if not already running
          if (!this.liveMessageRotationTimer) {
            const messages = this.getLiveWaitingMessages();
            this.liveMessageIndex = 0;
            this.updateLiveWaitingMessage(messages[0]);

            const rotationInterval = this.config.liveMessageRotationInterval || 2500;
            this.liveMessageRotationTimer = setInterval(() => {
              this.rotateLoadingMessage();
            }, rotationInterval);
          }

          // Start buffering timeout timer to enter retry mode if buffering takes too long
          if (!this.liveBufferingTimeoutTimer) {
            const timeout = (this.config as any).liveBufferingTimeout ?? 30000; // Default 30 seconds
            this.debugLog(`⏱️ Starting buffering timeout timer (${timeout}ms)`);
            this.liveBufferingTimeoutTimer = setTimeout(() => {
              this.debugLog('⚠️ Buffering timeout exceeded, entering retry mode');
              this.startLiveStreamWaiting();
            }, timeout);
          }
        }
      }
    });

    this.video.addEventListener('canplay', () => {
      this.debugLog('📡 canplay event fired');

      // Reset fallback tracking on successful load
      if (this.isLoadingFallback) {
        this.debugLog('✅ Fallback source loaded successfully!');
        this.isLoadingFallback = false;
        this.lastFailedUrl = '';
      }

      // Reset fallback poster mode when video successfully loads
      if (this.isFallbackPosterMode) {
        this.debugLog('✅ Exiting fallback poster mode - video source loaded');
        this.isFallbackPosterMode = false;
        // Remove fallback poster overlay if it exists
        const posterOverlay = this.playerWrapper?.querySelector('#uvf-fallback-poster');
        if (posterOverlay) {
          posterOverlay.remove();
        }
        // Show video element again
        if (this.video) {
          this.video.style.display = '';
        }
      }

      this.setBuffering(false);
      const loading = this.cachedElements.loading;
      if (loading) loading.classList.remove('active');

      // If waiting for live stream and canplay fires, stream is ready!
      if (this.isWaitingForLiveStream) {
        this.debugLog('✅ Live stream is now ready!');
        this.stopLiveStreamWaiting(true);
      }

      // If showing countdown and stream becomes available early, hide countdown
      if (this.isShowingLiveCountdown) {
        this.debugLog('✅ Stream became available during countdown');
        this.stopLiveCountdown();
      }

      // Apply startTime if configured and not yet applied
      // Done here (at canplay) instead of loadedmetadata for faster loading
      // because video has buffered enough data to play from this position
      if (this.config.startTime !== undefined &&
        this.config.startTime > 0 &&
        !this.hasAppliedStartTime &&
        this.video &&
        this.video.duration > 0) {
        const startTime = Math.min(this.config.startTime, this.video.duration);
        this.debugLog(`⏩ Applying startTime at canplay: ${startTime}s (continue watching)`);
        this.video.currentTime = startTime;
        this.hasAppliedStartTime = true;
      }

      this.emit('onReady');

      // Update time display when video is ready to play
      this.updateTimeDisplay();

      // Handle deferred pause requests
      if (this._deferredPause) {
        this._deferredPause = false;
        try { this.video?.pause(); } catch (_) { }
      }

      // Attempt autoplay once when video is ready to play
      this.debugLog(`🎬 Autoplay check: config.autoPlay=${this.config.autoPlay}, autoplayAttempted=${this.autoplayAttempted}`);
      if (this.config.autoPlay && !this.autoplayAttempted) {
        this.debugLog('🎬 Starting intelligent autoplay attempt');
        this.autoplayAttempted = true;
        this.attemptIntelligentAutoplay().then(success => {
          if (!success) {
            this.debugWarn('❌ Intelligent autoplay failed - will retry on user interaction');
            this.setupAutoplayRetry();
          } else {
            this.debugLog('✅ Intelligent autoplay succeeded');
          }
        }).catch(error => {
          this.debugError('Autoplay failed:', error);
          this.setupAutoplayRetry();
        });
      } else {
        this.debugLog(`⛔ Skipping autoplay: autoPlay=${this.config.autoPlay}, attempted=${this.autoplayAttempted}`);
      }
    });

    this.video.addEventListener('loadedmetadata', () => {
      if (!this.video) return;
      this.state.duration = this.video.duration || 0;
      this.debugLog('Metadata loaded - duration:', this.video.duration);

      // Check if live stream has invalid duration (not ready yet)
      if (this.config.isLive && (this.video.duration === 0 || !isFinite(this.video.duration))) {
        this.debugLog('Live stream metadata loaded but duration invalid, entering waiting mode');
        this.startLiveStreamWaiting();
        return;
      }

      // Update time display immediately when metadata loads
      this.updateTimeDisplay();

      // Hide spinner — video is ready to accept play commands.
      // With preload:'metadata' (the default), canplay does not fire until
      // playback starts, so this is the earliest safe point to unblock the UI.
      const loading = this.cachedElements.loading;
      if (loading) loading.classList.remove('active');

      // Note: startTime is now applied at 'canplay' event for faster loading
      // (moved from here to avoid premature seeking before buffering)

      this.emit('onLoadedMetadata', {
        duration: this.video.duration || 0,
        width: this.video.videoWidth || 0,
        height: this.video.videoHeight || 0
      });
    });

    this.video.addEventListener('volumechange', () => {
      if (!this.video) return;
      this.state.volume = this.video.volume;
      this.state.isMuted = this.video.muted;
      this.emit('onVolumeChanged', this.video.volume);
    });

    this.video.addEventListener('error', async (e) => {
      if (!this.video || !this.video.src) return;

      const error = this.video.error;
      if (error) {
        const currentSrc = this.video.src || this.video.currentSrc || '';

        // Avoid processing the same error multiple times
        if (this.lastFailedUrl === currentSrc && this.isLoadingFallback) {
          this.debugLog(`⚠️ Duplicate error for same URL while loading fallback, ignoring: ${currentSrc}`);
          return;
        }

        this.lastFailedUrl = currentSrc;
        this.debugLog(`Video error detected (code: ${error.code}):`, error.message);
        this.debugLog(`Failed source: ${currentSrc}`);

        // Check if this is a live stream waiting for availability FIRST (before fallbacks)
        if (this.isLiveStreamNotReady(error)) {
          this.debugLog('Detected live stream not ready, entering waiting mode');
          this.startLiveStreamWaiting();
          return; // Don't process as fatal error or try fallbacks
        }

        // Hide spinner for non-live errors
        const loading = this.cachedElements.loading;
        if (loading) loading.classList.remove('active');

        this.debugLog(`Fallback check - isLoadingFallback: ${this.isLoadingFallback}`);
        this.debugLog(`Fallback check - fallbackSources:`, this.source?.fallbackSources);
        this.debugLog(`Fallback check - fallbackPoster:`, this.source?.fallbackPoster);
        this.debugLog(`Fallback check - has fallbackSources: ${!!this.source?.fallbackSources?.length}`);
        this.debugLog(`Fallback check - has fallbackPoster: ${!!this.source?.fallbackPoster}`);

        // Try fallback sources if available and not already loading one
        if (!this.isLoadingFallback && (this.source?.fallbackSources?.length || this.source?.fallbackPoster)) {
          this.debugLog('✅ Attempting to load fallback sources...');
          const fallbackLoaded = await this.tryFallbackSource(error);
          if (fallbackLoaded) {
            this.debugLog('✅ Fallback loaded successfully!');
            // Successfully loaded fallback, don't call handleError
            return;
          }
          this.debugLog('❌ All fallbacks failed');
        } else {
          this.debugLog('❌ No fallback sources available or already loading');
        }

        // No fallback available or all fallbacks failed - handle error normally
        if (!this.isLoadingFallback) {
          this.handleError({
            code: `MEDIA_ERR_${error.code}`,
            message: error.message || this.getMediaErrorMessage(error.code),
            type: 'media',
            fatal: true,
            details: error
          });
        }
      }
    });

    this.video.addEventListener('seeking', () => {
      this.debugLog('🔍 seeking event fired');
      // Show loading during seek (will be hidden by 'playing' or 'seeked' if no buffering)
      const loading = this.cachedElements.loading;
      if (loading) loading.classList.add('active');
      this.emit('onSeeking');
    });

    this.video.addEventListener('seeked', () => {
      this.debugLog('✓ seeked event fired');
      // Apply gate if user seeks beyond free preview
      if (!this.video) return;
      const t = this.video.currentTime || 0;
      this.enforceFreePreviewGate(t, true);

      // Hide loading if video is not waiting (seeked to buffered position)
      // If it's still buffering, 'waiting' event will keep it active
      if (this.video.readyState >= 3) { // HAVE_FUTURE_DATA or higher
        const loading = this.cachedElements.loading;
        if (loading) loading.classList.remove('active');
      }

      this.emit('onSeeked');
    });

    // Handle stalled event (data fetch stalled)
    this.video.addEventListener('stalled', () => {
      this.debugLog('⚠️ stalled event fired (data fetch stalled)');
      // Show loading when data fetch stalls
      const loading = this.cachedElements.loading;
      if (loading) loading.classList.add('active');
    });

    // Handle loadstart event (new source starts loading)
    this.video.addEventListener('loadstart', () => {
      this.debugLog('📥 loadstart event fired (new source loading)');
      // Show loading when new source starts loading
      const loading = this.cachedElements.loading;
      if (loading) loading.classList.add('active');
    });
  }

  private getMediaErrorMessage(code: number): string {
    switch (code) {
      case 1: return 'Media loading aborted';
      case 2: return 'Network error';
      case 3: return 'Media decoding failed';
      case 4: return 'Media format not supported';
      default: return 'Unknown media error';
    }
  }

  private updateBufferProgress(): void {
    if (!this.video) return;

    const buffered = this.video.buffered;
    if (buffered.length > 0) {
      const bufferedEnd = buffered.end(buffered.length - 1);
      const duration = this.video.duration;
      const percentage = duration > 0 ? (bufferedEnd / duration) * 100 : 0;
      this.updateBuffered(percentage);
    }
  }

  async load(source: any): Promise<void> {
    this.source = source as any;
    this.subtitles = (source.subtitles || []) as any;

    // Check if countdown should be shown for live streams
    // Don't show countdown again if it has already completed once
    if (this.config.isLive && this.config.liveCountdown?.nextProgramStartTime && !this.hasCountdownCompleted) {
      const remainingMs = this.config.liveCountdown.nextProgramStartTime - Date.now();
      if (remainingMs > 0) {
        this.debugLog('⏱️ Countdown configured, showing countdown timer instead of loading stream');
        this.showLiveCountdown();
        return; // Don't proceed with normal loading yet
      } else {
        this.debugLog('⏱️ Countdown time has passed, proceeding with normal stream loading');
      }
    }

    // Show loading overlay immediately when loading new source
    const loading = this.cachedElements.loading;
    if (loading) {
      loading.classList.add('active');
      this.debugLog('🔄 Loading overlay shown for new source');
    }

    // Check if this is DRM-protected content
    this.isDRMProtected = !!(source.drm && source.drm.licenseUrl);
    this.debugLog('DRM config provided:', source.drm);

    this.debugLog('Loading video source:', source.url);
    this.debugLog('Fallback sources provided:', source.fallbackSources);
    this.debugLog('Fallback poster provided:', source.fallbackPoster);

    // Reset autoplay flag for new source
    this.autoplayAttempted = false;

    // Reset startTime flag for new source
    this.hasAppliedStartTime = false;

    // Reset fallback state for new source
    this.fallbackSourceIndex = -1;
    this.fallbackErrors = [];
    this.isLoadingFallback = false;
    this.currentRetryAttempt = 0;
    this.lastFailedUrl = '';
    this.isFallbackPosterMode = false; // Reset fallback poster mode
    this.hlsErrorRetryCount = 0; // Reset HLS error retry count

    // Reset live waiting state ONLY if it's a different source (not a retry)
    if (this.isWaitingForLiveStream) {
      const isDifferentSource = this.liveStreamOriginalSource?.url !== source.url;
      if (isDifferentSource) {
        this.debugLog('Different source detected, stopping live waiting mode');
        this.stopLiveStreamWaiting(false);
      } else {
        this.debugLog('Same source retry, keeping live waiting mode active');
      }
    }

    // Clean up previous instances
    await this.cleanup();

    // Reset progress bar UI to prevent showing stale progress from previous video
    this.resetProgressBar();

    if (!this.video) {
      throw new Error('Video element not initialized');
    }

    // Detect source type
    const sourceType = this.detectSourceType(source);

    try {
      await this.loadVideoSource(source.url, sourceType, source);
    } catch (error) {
      // Try fallback sources if available
      const fallbackLoaded = await this.tryFallbackSource(error);
      if (!fallbackLoaded) {
        this.handleError({
          code: 'LOAD_ERROR',
          message: `Failed to load video: ${error}`,
          type: 'network',
          fatal: true,
          details: error
        });
        throw error;
      }
    }
  }

  /**
   * Load a video source (main or fallback)
   */
  private async loadVideoSource(url: string, sourceType: string, source: any): Promise<void> {
    // Store the stream type for visibility checks
    this.currentStreamType = sourceType;

    // Update settings button visibility based on stream type
    // Only show settings for HLS/DASH streams (which have quality options)
    if (this.useCustomControls && this.controlsVisibility.quality) {
      const shouldShowSettings = this.settingsConfig.enabled &&
        (sourceType === 'hls' || sourceType === 'dash');

      // Update the settings button visibility
      this.controlsVisibility.quality.settingsButton = shouldShowSettings;

      // Apply the visibility change to DOM
      const settingsButton = this.getElement('settings-btn');
      if (settingsButton) {
        this.setElementVisibility('settings-btn', shouldShowSettings);
      }
    }

    // Initialize DRM BEFORE loading the video if DRM config exists
    if (this.isDRMProtected && source.drm) {
      try {
        await this.initializeDRM(source.drm);
      } catch (drmError) {
        this.handleDRMError(drmError);
        throw drmError; // Prevent video loading
      }
    }

    switch (sourceType) {
      case 'hls':
        await this.loadHLS(url);
        break;
      case 'dash':
        await this.loadDASH(url);
        break;
      case 'youtube':
        await this.loadYouTube(url, source);
        break;
      default:
        await this.loadNative(url);
    }

    // Load subtitles if provided — must be awaited so all <track> elements are in
    // the DOM before loadedmetadata fires and updateSettingsMenu() runs.
    if (source.subtitles && source.subtitles.length > 0) {
      await this.loadSubtitles(source.subtitles);
    }

    // Apply metadata
    if (source.metadata) {
      if (source.metadata.posterUrl && this.video) {
        this.video.poster = source.metadata.posterUrl;
      }
      // Update player UI with metadata (title, description, thumbnail)
      this.updateMetadataUI();
    } else {
      // Clear to defaults if no metadata
      this.updateMetadataUI();
    }
  }

  /**
   * Initialize DRM system for protected content
   */
  private async initializeDRM(drmConfig: any): Promise<void> {
    if (!this.video) {
      throw new Error('Video element not initialized');
    }

    this.debugLog('Initializing DRM protection...');
    this.debugLog('DRM Config:', {
      type: drmConfig.type,
      licenseUrl: drmConfig.licenseUrl,
      certificateUrl: drmConfig.certificateUrl
    });

    try {
      // Create DRMManager instance
      this.drmManager = new DRMManager(
        this.video,
        drmConfig,
        this.config.debug
      );

      // Initialize DRM system
      const initResult: DRMInitResult = await this.drmManager.initialize();

      if (!initResult.success) {
        const error = initResult.error!;
        this.debugError('DRM initialization failed:', error);
        throw error;
      }

      this.debugLog('DRM initialized successfully:', {
        drmType: initResult.drmType,
        keySystem: initResult.keySystem
      });

    } catch (error) {
      this.debugError('DRM initialization error:', error);
      if (this.drmManager) {
        await this.drmManager.destroy();
        this.drmManager = null;
      }
      throw error;
    }
  }

  /**
   * Handle DRM-specific errors with user-friendly messages
   */
  private handleDRMError(error: any): void {
    const userMessage = DRMErrorHandler.getUserFriendlyMessage(error);
    const technicalMessage = DRMErrorHandler.getTechnicalMessage(error);

    this.showNotification(userMessage);
    this.debugError('DRM Error:', technicalMessage);

    this.handleError({
      code: error.code || 'DRM_ERROR',
      message: userMessage,
      type: 'drm',
      fatal: true,
      details: {
        technical: technicalMessage,
        original: error
      }
    });
  }

  /**
   * Try loading the next fallback source
   */
  private async tryFallbackSource(error: any): Promise<boolean> {
    this.debugLog('🔄 tryFallbackSource called');
    this.debugLog('🔄 isLoadingFallback:', this.isLoadingFallback);
    this.debugLog('🔄 fallbackSources:', this.source?.fallbackSources);
    this.debugLog('🔄 fallbackSources length:', this.source?.fallbackSources?.length);
    this.debugLog('🔄 Current fallbackSourceIndex:', this.fallbackSourceIndex);
    this.debugLog('🔄 Current retry attempt:', this.currentRetryAttempt);

    if (this.isLoadingFallback) {
      this.debugLog('⚠️ Already loading a fallback, skipping');
      return false;
    }

    if (!this.source?.fallbackSources || this.source.fallbackSources.length === 0) {
      this.debugLog('⚠️ No fallback sources available, trying fallback poster');
      return this.showFallbackPoster();
    }

    this.debugLog('✅ Starting fallback loading process');
    this.isLoadingFallback = true;

    // Record current error
    const currentUrl = this.fallbackSourceIndex === -1
      ? this.source.url
      : this.source.fallbackSources[this.fallbackSourceIndex]?.url;

    this.fallbackErrors.push({ url: currentUrl, error });
    this.debugLog(`Source failed: ${currentUrl}`, error);

    // Don't retry the main URL - go straight to first fallback
    // Only retry actual fallback sources
    const isMainUrl = this.fallbackSourceIndex === -1;
    const maxRetries = this.source.fallbackRetryAttempts || 1;

    if (!isMainUrl && this.currentRetryAttempt < maxRetries) {
      // Only retry if this is a fallback source (not the main URL)
      this.currentRetryAttempt++;
      this.debugLog(`Retrying fallback source (attempt ${this.currentRetryAttempt}/${maxRetries}): ${currentUrl}`);

      const retryDelay = this.source.fallbackRetryDelay || 1000;
      await new Promise(resolve => setTimeout(resolve, retryDelay));

      try {
        const sourceType = this.detectSourceType({ url: currentUrl, type: this.source.type });
        await this.loadVideoSource(currentUrl, sourceType, this.source);
        // Don't mark as successful immediately - let it load and see if error happens
        // Just continue and see what happens
        this.debugLog(`Retry initiated for: ${currentUrl} - waiting for load confirmation...`);
        // Return false to continue the fallback chain if this fails again
      } catch (retryError) {
        this.debugLog(`Retry failed for: ${currentUrl}`, retryError);
        // Continue to next fallback
      }
    } else {
      if (isMainUrl) {
        this.debugLog(`⏭️ Skipping retry of main URL, moving to first fallback source`);
      } else {
        this.debugLog(`⏭️ Max retries (${maxRetries}) reached for ${currentUrl}, moving to next fallback`);
      }
    }

    // Move to next fallback source
    this.currentRetryAttempt = 0;
    this.fallbackSourceIndex++;

    if (this.fallbackSourceIndex >= this.source.fallbackSources.length) {
      // All sources exhausted
      this.isLoadingFallback = false;
      this.debugLog('All video sources failed. Attempting to show fallback poster.');

      // Trigger callback if provided
      if (this.source.onAllSourcesFailed) {
        try {
          this.source.onAllSourcesFailed(this.fallbackErrors);
        } catch (callbackError) {
          console.error('Error in onAllSourcesFailed callback:', callbackError);
        }
      }

      return this.showFallbackPoster();
    }

    // Try next fallback source
    const fallbackSource = this.source.fallbackSources[this.fallbackSourceIndex];
    this.debugLog(`Trying fallback source ${this.fallbackSourceIndex + 1}/${this.source.fallbackSources.length}: ${fallbackSource.url}`);

    const retryDelay = this.source.fallbackRetryDelay || 1000;
    await new Promise(resolve => setTimeout(resolve, retryDelay));

    try {
      const sourceType = this.detectSourceType(fallbackSource);
      await this.loadVideoSource(fallbackSource.url, sourceType, this.source);
      this.isLoadingFallback = false;
      this.debugLog(`Successfully loaded fallback source: ${fallbackSource.url}`);
      this.showNotification(`Switched to backup source ${this.fallbackSourceIndex + 1}`);
      return true;
    } catch (fallbackError) {
      this.debugLog(`Fallback source failed: ${fallbackSource.url}`, fallbackError);
      this.isLoadingFallback = false;
      // Recursively try next fallback
      return this.tryFallbackSource(fallbackError);
    }
  }

  /**
   * Show fallback poster image when all video sources fail
   */
  private showFallbackPoster(): boolean {
    // Don't show fallback poster for live streams - they use the waiting mechanism
    if (this.config.isLive) {
      this.debugLog('Skipping fallback poster for live stream');
      return false;
    }

    if (!this.source?.fallbackPoster) {
      this.debugLog('No fallback poster available');
      return false;
    }

    this.debugLog('Showing fallback poster:', this.source.fallbackPoster);

    // Set flag to indicate we're in fallback poster mode (no playable sources)
    this.isFallbackPosterMode = true;
    this.debugLog('✅ Fallback poster mode activated - playback disabled');

    if (this.video) {
      // Hide video element, show poster
      this.video.style.display = 'none';
      this.video.poster = this.source.fallbackPoster;
      // Remove src to prevent play attempts
      this.video.removeAttribute('src');
      this.video.load(); // Reset video element
    }

    // Create poster overlay
    const posterOverlay = document.createElement('div');
    posterOverlay.id = this.getElementId('fallback-poster');
    posterOverlay.style.cssText = `
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-image: url('${this.source.fallbackPoster}');
      background-size: cover;
      background-position: center;
      background-repeat: no-repeat;
      z-index: 10;
      display: flex;
      align-items: center;
      justify-content: center;
    `;

    // Add error message overlay (if enabled)
    const showErrorMessage = this.source.fallbackShowErrorMessage !== false; // Default to true
    if (showErrorMessage) {
      const errorMessage = document.createElement('div');
      errorMessage.style.cssText = `
        background: rgba(0, 0, 0, 0.8);
        color: white;
        padding: 20px 30px;
        border-radius: 8px;
        text-align: center;
        max-width: 400px;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
      `;
      errorMessage.innerHTML = `
        <svg width="48" height="48" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" xmlns="http://www.w3.org/2000/svg">
          <rect x="10" y="16" width="20" height="16" rx="2" />
          <polygon points="34,20 42,16 42,32 34,28" />
          <line x1="5" y1="8" x2="38" y2="40" stroke="currentColor" stroke-width="2"/>
        </svg>
        <div style="font-size: 18px; font-weight: 600; margin-bottom: 8px;">Video Unavailable</div>
        <div style="font-size: 14px; opacity: 0.9;">This video cannot be played at the moment.</div>
      `;

      posterOverlay.appendChild(errorMessage);
    }

    // Remove existing fallback poster if any
    const existingPoster = this.playerWrapper?.querySelector('#uvf-fallback-poster');
    if (existingPoster) {
      existingPoster.remove();
    }

    // Add to player
    if (this.playerWrapper) {
      this.playerWrapper.appendChild(posterOverlay);
    }

    this.showNotification('Video unavailable');
    return true;
  }

  private detectSourceType(source: VideoSource): string {
    if (source.type && source.type !== 'auto') {
      return source.type;
    }

    const url = source.url.toLowerCase();

    // Check for YouTube URLs
    if (YouTubeExtractor.isYouTubeUrl(url)) {
      return 'youtube';
    }

    // Extract the path without query parameters for better extension detection
    const urlPath = url.split('?')[0].split('#')[0];

    // Check file extension - order matters! Check streaming formats first
    // Use endsWith for exact extension match, fallback to includes for edge cases
    if (urlPath.endsWith('.m3u8') || url.includes('.m3u8')) return 'hls';
    if (urlPath.endsWith('.mpd') || url.includes('.mpd')) return 'dash';
    if (urlPath.endsWith('.mp4')) return 'mp4';
    if (urlPath.endsWith('.webm')) return 'webm';

    return 'mp4'; // default
  }

  private async loadHLS(url: string): Promise<void> {
    // Detect bandwidth before configuring HLS
    if (!this.bandwidthDetector.detectionComplete) {
      await this.detectBandwidth();
    }

    const tier = this.getBandwidthTier(
      this.bandwidthDetector.estimatedBandwidth || this.BANDWIDTH_TIERS.MEDIUM
    );

    this.debugLog(`[UVF] Initializing HLS with ${tier} bandwidth tier`);

    // Check if HLS.js is available
    if (!window.Hls) {
      await this.loadScript('https://cdn.jsdelivr.net/npm/hls.js@latest');
    }

    if (window.Hls.isSupported()) {
      // Get optimized config based on detected bandwidth
      const hlsConfig: any = this.getOptimizedHLSConfig(tier);

      // Set startPosition for HLS to begin loading from the correct fragment
      // This avoids downloading from beginning and seeking later
      if (this.config.startTime !== undefined && this.config.startTime > 0) {
        hlsConfig.startPosition = this.config.startTime;
        this.debugLog(`⏩ HLS startPosition set to: ${this.config.startTime}s (continue watching)`);
      }

      // Apply user-provided startBitrate if specified (override detection)
      if (this.config.adaptiveBitrate?.startBitrate) {
        hlsConfig.abrEwmaDefaultEstimate = this.config.adaptiveBitrate.startBitrate;
        this.debugLog(`🎯 HLS starting bitrate set to: ${this.config.adaptiveBitrate.startBitrate} bps (user override)`);
      }

      // Apply DRM configuration if available
      if (this.isDRMProtected && this.drmManager) {
        try {
          const drmHlsConfig = this.drmManager.getHLSConfig();
          Object.assign(hlsConfig, drmHlsConfig);
          this.debugLog('Applied DRM config to HLS.js');
        } catch (error) {
          this.debugError('Failed to get HLS DRM config:', error);
          throw error;
        }
      }

      this.hls = new window.Hls(hlsConfig);

      // Emit bandwidth detection event
      this.emit('onBandwidthDetected', {
        bandwidth: this.bandwidthDetector.estimatedBandwidth,
        tier: tier,
        method: this.bandwidthDetector.detectionMethod
      });

      this.hls.loadSource(url);
      this.hls.attachMedia(this.video);

      this.hls.on(window.Hls.Events.MANIFEST_PARSED, (event: any, data: any) => {
        // Reset HLS error retry count on successful manifest load
        this.hlsErrorRetryCount = 0;

        // Extract quality levels
        this.qualities = data.levels.map((level: any, index: number) => ({
          height: level.height,
          width: level.width || 0,
          bitrate: level.bitrate,
          label: `${level.height}p`,
          index: index
        }));

        // Update settings menu with detected qualities
        this.updateSettingsMenu();

        // Update quality badge with initial state
        this.updateQualityBadgeText();

        // Apply quality filter automatically if configured (for auto quality mode)
        if (this.qualityFilter || (this.premiumQualities && this.premiumQualities.enabled)) {
          this.debugLog('Applying quality filter on HLS manifest load');
          this.applyHLSQualityFilter();
        }

        // Note: Autoplay is now handled in the 'canplay' event when video is ready
      });

      this.hls.on(window.Hls.Events.LEVEL_SWITCHED, (event: any, data: any) => {
        if (this.qualities[data.level]) {
          this.currentQualityIndex = data.level;
          this.state.currentQuality = this.qualities[data.level] as any;
          this.emit('onQualityChanged', this.qualities[data.level]);

          // Update UI if in auto mode (show current quality in real-time)
          if (this.autoQuality) {
            this.updateQualityBadgeText();
            this.updateQualityLabel(); // Update only the quality label without rebuilding menu
          }
        }
      });

      this.hls.on(window.Hls.Events.ERROR, (event: any, data: any) => {
        if (data.fatal) {
          this.handleHLSError(data);
        }
      });

      // Setup bandwidth monitoring for continuous optimization
      this.setupBandwidthMonitoring();
    } else if (this.video!.canPlayType('application/vnd.apple.mpegurl')) {
      // Native HLS support (Safari)
      this.video!.src = url;
    } else {
      throw new Error('HLS is not supported in this browser');
    }
  }

  private async handleHLSError(data: any): Promise<void> {
    const Hls = window.Hls;

    this.debugLog(`🔴 HLS Error: type=${data.type}, details=${data.details}, fatal=${data.fatal}`);

    // For live streams, manifest errors likely mean stream not ready yet
    if (this.config.isLive && data.fatal) {
      if (data.details === 'manifestLoadError' ||
        data.details === 'manifestParsingError' ||
        data.type === Hls?.ErrorTypes.NETWORK_ERROR) {
        this.debugLog('Live stream HLS error detected, entering waiting mode');
        this.startLiveStreamWaiting();
        return;
      }
    }

    // Check if we've exceeded max retry attempts
    if (this.hlsErrorRetryCount >= this.MAX_HLS_ERROR_RETRIES) {
      this.debugLog(`🔴 HLS max retries (${this.MAX_HLS_ERROR_RETRIES}) exceeded, triggering fallback`);
      this.hls?.destroy();
      this.hls = null;

      // Try fallback sources
      const fallbackLoaded = await this.tryFallbackSource({
        code: 'HLS_ERROR',
        message: data.details,
        type: data.type,
        fatal: true,
        details: data
      });

      if (!fallbackLoaded) {
        this.handleError({
          code: 'HLS_ERROR',
          message: `HLS stream failed after ${this.MAX_HLS_ERROR_RETRIES} retries: ${data.details}`,
          type: 'media',
          fatal: true,
          details: data
        });
      }
      return;
    }

    switch (data.type) {
      case Hls.ErrorTypes.NETWORK_ERROR:
        this.hlsErrorRetryCount++;
        this.debugLog(`🔴 Fatal network error (attempt ${this.hlsErrorRetryCount}/${this.MAX_HLS_ERROR_RETRIES}), trying to recover`);

        // For manifest errors (like 404), recovery won't help - go straight to fallback
        if (data.details === 'manifestLoadError' || data.details === 'manifestParsingError') {
          this.debugLog(`🔴 Manifest error detected (${data.details}), skipping recovery - triggering fallback`);
          this.hls?.destroy();
          this.hls = null;

          const fallbackLoaded = await this.tryFallbackSource({
            code: 'HLS_MANIFEST_ERROR',
            message: data.details,
            type: 'network',
            fatal: true,
            details: data
          });

          if (!fallbackLoaded) {
            this.handleError({
              code: 'HLS_MANIFEST_ERROR',
              message: `Failed to load HLS manifest: ${data.details}`,
              type: 'media',
              fatal: true,
              details: data
            });
          }
        } else {
          // For other network errors, try recovery
          this.hls?.startLoad();
        }
        break;

      case Hls.ErrorTypes.MEDIA_ERROR:
        this.hlsErrorRetryCount++;
        this.debugLog(`🔴 Fatal media error (attempt ${this.hlsErrorRetryCount}/${this.MAX_HLS_ERROR_RETRIES}), trying to recover`);
        this.hls?.recoverMediaError();
        break;

      default:
        this.debugLog(`🔴 Fatal unrecoverable HLS error: ${data.details}`);
        this.hls?.destroy();
        this.hls = null;

        // Try fallback sources for unrecoverable errors
        const fallbackLoaded = await this.tryFallbackSource({
          code: 'HLS_ERROR',
          message: data.details,
          type: 'media',
          fatal: true,
          details: data
        });

        if (!fallbackLoaded) {
          this.handleError({
            code: 'HLS_ERROR',
            message: data.details,
            type: 'media',
            fatal: true,
            details: data
          });
        }
        break;
    }
  }

  private async loadDASH(url: string): Promise<void> {
    // Detect bandwidth before configuring DASH
    if (!this.bandwidthDetector.detectionComplete) {
      await this.detectBandwidth();
    }

    const tier = this.getBandwidthTier(
      this.bandwidthDetector.estimatedBandwidth || this.BANDWIDTH_TIERS.MEDIUM
    );

    this.debugLog(`[UVF] Initializing DASH with ${tier} bandwidth tier`);

    // Check if dash.js is available
    if (!window.dashjs) {
      await this.loadScript('https://cdn.dashjs.org/latest/dash.all.min.js');
    }

    this.dash = window.dashjs.MediaPlayer().create();

    // Set initial seek time for DASH if startTime is configured (continue watching)
    // This allows DASH to start downloading from the correct segment
    if (this.config.startTime !== undefined && this.config.startTime > 0) {
      this.debugLog(`⏩ DASH will seek to: ${this.config.startTime}s on ready (continue watching)`);
      // Note: DASH doesn't support startPosition in config like HLS
      // The seek will be applied at canplay event when stream is ready
    }

    this.dash.initialize(this.video, url, this.config.autoPlay);

    // Apply DRM protection data if available
    if (this.isDRMProtected && this.drmManager) {
      try {
        const protectionData = this.drmManager.getDashProtectionData();
        this.dash.setProtectionData(protectionData);
        this.debugLog('Applied DRM protection data to dash.js');
      } catch (error) {
        this.debugError('Failed to apply DASH DRM protection:', error);
        throw error;
      }
    }

    // Get optimized settings based on detected bandwidth
    const dashSettings = this.getOptimizedDASHSettings(tier);

    // Apply user-provided startBitrate if specified (override detection)
    if (this.config.adaptiveBitrate?.startBitrate) {
      dashSettings.streaming.abr.initialBitrate = {
        video: Math.round(this.config.adaptiveBitrate.startBitrate / 1000)
      };
      this.debugLog(`🎯 DASH starting bitrate set to: ${Math.round(this.config.adaptiveBitrate.startBitrate / 1000)} kbps (user override)`);
    }

    // Apply adaptive bitrate enable/disable setting
    if (this.config.enableAdaptiveBitrate !== undefined) {
      dashSettings.streaming.abr.autoSwitchBitrate.video = this.config.enableAdaptiveBitrate;
    }

    this.dash.updateSettings(dashSettings);

    // Emit bandwidth detection event
    this.emit('onBandwidthDetected', {
      bandwidth: this.bandwidthDetector.estimatedBandwidth,
      tier: tier,
      method: this.bandwidthDetector.detectionMethod
    });

    // Listen for quality changes
    this.dash.on(window.dashjs.MediaPlayer.events.QUALITY_CHANGE_RENDERED, (e: any) => {
      if (e.mediaType === 'video') {
        this.updateDASHQuality(e.newQuality);
      }
    });

    // Extract available qualities
    this.dash.on(window.dashjs.MediaPlayer.events.STREAM_INITIALIZED, () => {
      const bitrateList = this.dash.getBitrateInfoListFor('video');
      if (bitrateList && bitrateList.length > 0) {
        this.qualities = bitrateList.map((info: any, index: number) => ({
          height: info.height || 0,
          width: info.width || 0,
          bitrate: info.bitrate,
          label: `${info.height}p`,
          index: index
        }));

        // Update settings menu with detected qualities
        this.updateSettingsMenu();

        // Update quality badge with initial state
        this.updateQualityBadgeText();

        // Apply quality filter automatically if configured (for auto quality mode)
        if (this.qualityFilter || (this.premiumQualities && this.premiumQualities.enabled)) {
          this.debugLog('Applying quality filter on DASH stream initialization');
          this.applyDASHQualityFilter();
        }
      }
    });

    // Handle errors
    this.dash.on(window.dashjs.MediaPlayer.events.ERROR, (e: any) => {
      this.handleError({
        code: 'DASH_ERROR',
        message: e.error.message,
        type: 'media',
        fatal: true,
        details: e
      });
    });

    // Setup bandwidth monitoring for continuous optimization
    this.setupBandwidthMonitoring();
  }

  private updateDASHQuality(qualityIndex: number): void {
    if (this.qualities[qualityIndex]) {
      this.currentQualityIndex = qualityIndex;
      this.state.currentQuality = this.qualities[qualityIndex] as any;
      this.emit('onQualityChanged', this.qualities[qualityIndex]);

      // Update UI if in auto mode (show current quality in real-time)
      if (this.autoQuality) {
        this.updateQualityBadgeText();
        this.updateQualityLabel(); // Update only the quality label without rebuilding menu
      }
    }
  }

  /**
   * Detect user's bandwidth using intelligent probing
   * Returns estimated bandwidth in bits per second
   */
  private async detectBandwidth(): Promise<number> {
    try {
      // Method 1: Use Network Information API if available
      if ('connection' in navigator) {
        const connection = (navigator as any).connection;
        if (connection && connection.downlink) {
          // downlink is in Mbps, convert to bps
          const bandwidthBps = connection.downlink * 1_000_000;
          this.bandwidthDetector.detectionMethod = 'probe';
          this.bandwidthDetector.estimatedBandwidth = bandwidthBps;
          this.debugLog(`[UVF Bandwidth] Network API detected: ${(bandwidthBps / 1_000_000).toFixed(2)} Mbps`);
          return bandwidthBps;
        }
      }

      // Method 2: Quick probe with small file download
      const probeUrl = 'https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js';
      const startTime = performance.now();
      const response = await fetch(probeUrl, { cache: 'no-store' });
      const blob = await response.blob();
      const endTime = performance.now();

      const durationSeconds = (endTime - startTime) / 1000;
      const fileSizeBytes = blob.size;
      const bandwidthBps = (fileSizeBytes * 8) / durationSeconds;

      this.bandwidthDetector.detectionMethod = 'probe';
      this.bandwidthDetector.estimatedBandwidth = bandwidthBps;
      this.debugLog(`[UVF Bandwidth] Probe detected: ${(bandwidthBps / 1_000_000).toFixed(2)} Mbps`);

      return bandwidthBps;
    } catch (error) {
      // Fallback: Assume medium bandwidth
      this.debugLog('[UVF Bandwidth] Detection failed, using medium tier fallback');
      this.bandwidthDetector.detectionMethod = 'fallback';
      this.bandwidthDetector.estimatedBandwidth = this.BANDWIDTH_TIERS.MEDIUM;
      return this.BANDWIDTH_TIERS.MEDIUM;
    } finally {
      this.bandwidthDetector.detectionComplete = true;
    }
  }

  /**
   * Determine optimal settings tier based on detected bandwidth
   */
  private getBandwidthTier(bandwidth: number): 'high' | 'medium' | 'low' {
    if (bandwidth >= this.BANDWIDTH_TIERS.HIGH) return 'high';
    if (bandwidth >= this.BANDWIDTH_TIERS.MEDIUM) return 'medium';
    return 'low';
  }

  /**
   * Get optimized HLS config based on bandwidth tier
   */
  private getOptimizedHLSConfig(tier: 'high' | 'medium' | 'low'): any {
    const baseConfig = {
      debug: this.config.debug,
      enableWorker: true,
      lowLatencyMode: false,
      backBufferLength: 90,
      startLevel: -1,  // Auto-select best level
    };

    switch (tier) {
      case 'high':
        return {
          ...baseConfig,
          abrEwmaDefaultEstimate: this.bandwidthDetector.estimatedBandwidth || this.BANDWIDTH_TIERS.HIGH,
          capLevelToPlayerSize: false,  // Allow full resolution even on small players
          maxBufferLength: 30,
          maxMaxBufferLength: 60,
          abrBandWidthFactor: 0.75,  // Very conservative - only drop if bandwidth < 75%
          abrBandWidthUpFactor: 0.85,
          abrEwmaFastLive: 3.0,  // Fast adaptation
          abrEwmaSlowLive: 9.0,
        };

      case 'medium':
        return {
          ...baseConfig,
          abrEwmaDefaultEstimate: this.bandwidthDetector.estimatedBandwidth || this.BANDWIDTH_TIERS.MEDIUM,
          capLevelToPlayerSize: true,
          maxBufferLength: 30,
          maxMaxBufferLength: 50,
          abrBandWidthFactor: 0.85,  // Moderate
          abrBandWidthUpFactor: 0.9,
        };

      case 'low':
        return {
          ...baseConfig,
          abrEwmaDefaultEstimate: this.bandwidthDetector.estimatedBandwidth || this.BANDWIDTH_TIERS.LOW,
          capLevelToPlayerSize: true,
          maxBufferLength: 20,
          maxMaxBufferLength: 40,
          abrBandWidthFactor: 0.9,  // Aggressive - drop early to prevent buffering
          abrBandWidthUpFactor: 0.95,
        };
    }
  }

  /**
   * Get optimized DASH settings based on bandwidth tier
   */
  private getOptimizedDASHSettings(tier: 'high' | 'medium' | 'low'): any {
    const bandwidth = this.bandwidthDetector.estimatedBandwidth || this.BANDWIDTH_TIERS.MEDIUM;

    switch (tier) {
      case 'high':
        return {
          streaming: {
            abr: {
              autoSwitchBitrate: { video: true, audio: true },
              initialBitrate: { video: Math.round(bandwidth / 1000) },
              ABRStrategy: 'abrBola',  // BOLA optimizes for quality
              limitBitrateByPortal: false,
              bandwidthSafetyFactor: 0.75,
              useDefaultABRRules: false,
            },
            buffer: {
              fastSwitchEnabled: true,
              stableBufferTime: 20,
              bufferTimeAtTopQuality: 30,
              bufferTimeAtTopQualityLongForm: 40,
            }
          }
        };

      case 'medium':
        return {
          streaming: {
            abr: {
              autoSwitchBitrate: { video: true, audio: true },
              initialBitrate: { video: Math.round(bandwidth / 1000) },
              bandwidthSafetyFactor: 0.85,
            },
            buffer: {
              fastSwitchEnabled: true,
              stableBufferTime: 15,
              bufferTimeAtTopQuality: 20,
            }
          }
        };

      case 'low':
        return {
          streaming: {
            abr: {
              autoSwitchBitrate: { video: true, audio: true },
              initialBitrate: { video: Math.round(bandwidth / 1000) },
              bandwidthSafetyFactor: 0.9,
            },
            buffer: {
              fastSwitchEnabled: true,
              stableBufferTime: 10,
              bufferTimeAtTopQuality: 15,
            }
          }
        };
    }
  }

  /**
   * Monitor bandwidth changes during playback and re-optimize if needed
   */
  private setupBandwidthMonitoring(): void {
    if (this.hls) {
      // Monitor HLS bandwidth estimates
      this.hls.on(window.Hls.Events.FRAG_LOADED, (event: any, data: any) => {
        const currentBandwidth = this.hls.bandwidthEstimate;

        if (currentBandwidth && this.bandwidthDetector.estimatedBandwidth) {
          const currentTier = this.getBandwidthTier(this.bandwidthDetector.estimatedBandwidth);
          const newTier = this.getBandwidthTier(currentBandwidth);

          // If bandwidth tier changed significantly, re-optimize
          if (currentTier !== newTier) {
            this.debugLog(`[UVF] Bandwidth tier changed: ${currentTier} → ${newTier}`);
            this.bandwidthDetector.estimatedBandwidth = currentBandwidth;
            this.reoptimizePlayback(newTier);
          }
        }
      });
    }

    if (this.dash) {
      // Monitor DASH bandwidth estimates
      this.dash.on(window.dashjs.MediaPlayer.events.METRIC_CHANGED, (e: any) => {
        if (e.metric === 'bandwidth') {
          const currentBandwidth = e.value;

          if (currentBandwidth && this.bandwidthDetector.estimatedBandwidth) {
            const currentTier = this.getBandwidthTier(this.bandwidthDetector.estimatedBandwidth);
            const newTier = this.getBandwidthTier(currentBandwidth);

            if (currentTier !== newTier) {
              this.debugLog(`[UVF] Bandwidth tier changed: ${currentTier} → ${newTier}`);
              this.bandwidthDetector.estimatedBandwidth = currentBandwidth;
              this.reoptimizePlayback(newTier);
            }
          }
        }
      });
    }
  }

  /**
   * Re-optimize playback settings when bandwidth tier changes
   */
  private reoptimizePlayback(newTier: 'high' | 'medium' | 'low'): void {
    if (this.hls) {
      const newConfig = this.getOptimizedHLSConfig(newTier);
      // Apply updated settings
      this.hls.config.abrBandWidthFactor = newConfig.abrBandWidthFactor;
      this.hls.config.abrBandWidthUpFactor = newConfig.abrBandWidthUpFactor;
      this.hls.config.maxBufferLength = newConfig.maxBufferLength;
      this.hls.capLevelToPlayerSize = newConfig.capLevelToPlayerSize;
    }

    if (this.dash) {
      const newSettings = this.getOptimizedDASHSettings(newTier);
      this.dash.updateSettings(newSettings);
    }

    this.emit('onBandwidthTierChanged', {
      tier: newTier,
      bandwidth: this.bandwidthDetector.estimatedBandwidth
    });
  }

  /**
   * Get detected bandwidth information
   */
  public getBandwidthInfo(): {
    estimated: number | null;
    tier: 'high' | 'medium' | 'low' | null;
    method: 'probe' | 'manifest' | 'fallback' | null;
  } {
    if (!this.bandwidthDetector.estimatedBandwidth) {
      return { estimated: null, tier: null, method: null };
    }

    return {
      estimated: this.bandwidthDetector.estimatedBandwidth,
      tier: this.getBandwidthTier(this.bandwidthDetector.estimatedBandwidth),
      method: this.bandwidthDetector.detectionMethod
    };
  }

  /**
   * Force bandwidth re-detection and re-optimization
   */
  public async redetectBandwidth(): Promise<void> {
    this.bandwidthDetector.detectionComplete = false;
    const newBandwidth = await this.detectBandwidth();
    const newTier = this.getBandwidthTier(newBandwidth);
    this.reoptimizePlayback(newTier);
  }

  private async loadNative(url: string): Promise<void> {
    if (!this.video) return;
    this.video.src = url;
    this.video.load();
  }

  private async loadYouTube(url: string, source: any): Promise<void> {
    try {
      this.debugLog('Loading YouTube video:', url);

      // Extract video ID and fetch metadata
      const videoId = YouTubeExtractor.extractVideoId(url);
      if (!videoId) {
        throw new Error('Invalid YouTube URL');
      }

      // Fetch YouTube metadata (title, thumbnail)
      const metadata = await YouTubeExtractor.getVideoMetadata(url);

      // Store metadata for later use
      // User-provided metadata takes priority, but fall back to YouTube metadata
      const userMeta = source.metadata || {};
      this.source = {
        url: source.url || url,
        ...this.source,
        metadata: {
          title: userMeta.title || metadata.title,
          thumbnailUrl: userMeta.thumbnailUrl || metadata.thumbnail,
          duration: userMeta.duration || metadata.duration,
          videoId: videoId,
          posterUrl: userMeta.posterUrl || metadata.thumbnail,
          description: userMeta.description || '',
        }
      };

      // Update player poster with thumbnail
      if (this.video && metadata.thumbnail) {
        this.video.poster = metadata.thumbnail;
      }

      // Create YouTube iframe player with custom controls integration
      await this.createYouTubePlayer(videoId);

      this.debugLog('✅ YouTube video loaded successfully');
    } catch (error) {
      this.debugError('Failed to load YouTube video:', error);
      throw new Error(`YouTube video loading failed: ${error}`);
    }
  }

  private youtubePlayer: any = null;
  private youtubePlayerReady: boolean = false;
  private youtubeIframe: HTMLIFrameElement | null = null;

  private async createYouTubePlayer(videoId: string): Promise<void> {
    const container = this.playerWrapper || this.video?.parentElement;
    if (!container) {
      throw new Error('No container found for YouTube player');
    }

    // Hide the regular video element
    if (this.video) {
      this.video.style.display = 'none';
    }

    // Create iframe container
    const iframeContainer = document.createElement('div');
    iframeContainer.id = `youtube-player-${videoId}`;
    iframeContainer.style.cssText = `
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 1;
    `;

    // Remove existing YouTube player if any
    const existingPlayer = container.querySelector(`#youtube-player-${videoId}`);
    if (existingPlayer) {
      existingPlayer.remove();
    }

    // Remove existing overlay if any
    const existingOverlay = container.querySelector('.uvf-youtube-overlay');
    if (existingOverlay) {
      existingOverlay.remove();
    }

    // Prepend to ensure it sits behind any controls that might be appended later
    if (container.firstChild) {
      container.insertBefore(iframeContainer, container.firstChild);
    } else {
      container.appendChild(iframeContainer);
    }

    // Create transparent overlay for YouTube videos
    // When youtubeNativeControls=false: blocks YouTube controls and handles play/pause
    // When youtubeNativeControls=true with controls=true: allows hover detection for our controls
    if (!this.youtubeNativeControls || this.useCustomControls) {
      const overlay = document.createElement('div');
      overlay.className = 'uvf-youtube-overlay';

      // Different behavior based on mode
      if (!this.youtubeNativeControls) {
        // Custom controls mode - overlay doesn't cover controls area
        overlay.style.cssText = `
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: calc(100% - 120px);
          z-index: 2;
          background: transparent;
          pointer-events: auto;
          cursor: pointer;
        `;

        // Add click handler directly to the overlay since pointer-events is none
        // This allows video clicks while controls remain interactive
        overlay.addEventListener('click', () => {
          this.showControls();
          this.scheduleHideControls();
          if (this.youtubePlayer && this.youtubePlayerReady) {
            const state = this.youtubePlayer.getPlayerState();
            if (state === 1) { // Playing
              this.youtubePlayer.pauseVideo();
            } else {
              this.youtubePlayer.playVideo();
            }
          }
        });

        // Handle double-click for fullscreen
        overlay.addEventListener('dblclick', () => {
          if (this.isFullscreen()) {
            this.exitFullscreen();
          } else {
            this.enterFullscreen();
          }
        });
      } else {
        // Hybrid mode - transparent overlay just for hover detection, allow YouTube controls through
        overlay.style.cssText = `
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          z-index: 2;
          pointer-events: none;
        `;
      }

      // Add mouse event listeners for control visibility
      overlay.addEventListener('mouseenter', () => {
        this.playerWrapper?.classList.add('controls-visible');
      });

      overlay.addEventListener('mouseleave', () => {
        // Only hide controls if video is playing, otherwise keep them visible
        if (this.state.isPlaying) {
          this.scheduleHideControls();
        }
      });

      // Insert overlay after the iframe container but before controls
      if (iframeContainer.nextSibling) {
        container.insertBefore(overlay, iframeContainer.nextSibling);
      } else {
        container.appendChild(overlay);
      }

      // Also add listeners to the container for fallback hover detection
      container.addEventListener('mouseenter', () => {
        this.playerWrapper?.classList.add('controls-visible');
        if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
      });

      container.addEventListener('mouseleave', () => {
        // Only hide controls if video is playing, otherwise keep them visible
        if (this.state.isPlaying) {
          this.scheduleHideControls();
        }
      });
    }

    // Load YouTube IFrame API if not loaded
    if (!window.YT) {
      await this.loadYouTubeAPI();
    }

    // Wait for API to be ready
    await this.waitForYouTubeAPI();

    // Create YouTube player
    this.youtubePlayer = new window.YT.Player(iframeContainer.id, {
      videoId: videoId,
      width: '100%',
      height: '100%',
      playerVars: {
        controls: this.youtubeNativeControls ? 1 : 0,        // Show/hide YouTube native controls based on config
        disablekb: 0,      // Allow keyboard controls
        fs: this.youtubeNativeControls ? 1 : 0,             // Show/hide fullscreen button based on config
        iv_load_policy: 3, // Hide annotations
        modestbranding: 1, // Minimal YouTube branding
        rel: 0,            // Don't show related videos
        showinfo: 0,       // Hide video info (deprecated but keep for older players)
        autohide: 1,       // Auto-hide controls after delay
        cc_load_policy: 0, // Don't show captions by default
        playsinline: 1,    // Play inline on mobile
        autoplay: this.config.autoPlay ? 1 : 0,
        mute: this.config.muted ? 1 : 0,
        loop: this.config.loop ? 1 : 0,                      // Enable/disable loop
        playlist: this.config.loop ? videoId : undefined,    // Required for loop to work with single video
        widget_referrer: window.location.href,               // Hide YouTube recommendations
        origin: window.location.origin,                      // Required for security
        enablejsapi: 1                                        // Enable JavaScript API
      },
      events: {
        onReady: () => this.onYouTubePlayerReady(),
        onStateChange: (event: any) => this.onYouTubePlayerStateChange(event),
        onError: (event: any) => this.onYouTubePlayerError(event)
      }
    });

    this.debugLog('YouTube player created');
  }

  private async loadYouTubeAPI(): Promise<void> {
    return new Promise((resolve) => {
      if (window.YT) {
        resolve();
        return;
      }

      // Set up the callback for when API loads
      (window as any).onYouTubeIframeAPIReady = () => {
        this.debugLog('YouTube IFrame API loaded');
        resolve();
      };

      // Load the API script
      const script = document.createElement('script');
      script.src = 'https://www.youtube.com/iframe_api';
      script.async = true;
      document.body.appendChild(script);
    });
  }

  private async waitForYouTubeAPI(): Promise<void> {
    return new Promise((resolve) => {
      const checkAPI = () => {
        if (window.YT && window.YT.Player) {
          resolve();
        } else {
          setTimeout(checkAPI, 100);
        }
      };
      checkAPI();
    });
  }

  private onYouTubePlayerReady(): void {
    this.youtubePlayerReady = true;
    this.debugLog('YouTube player ready');

    // Hide loading spinner when YouTube player is ready
    const loading = this.cachedElements.loading;
    if (loading) {
      loading.classList.remove('active');
    } else {
      // Spinner DOM may not exist yet (race with UI setup) — retry once after a tick
      setTimeout(() => {
        const l = this.cachedElements.loading;
        if (l) l.classList.remove('active');
      }, 50);
    }

    // If YouTube native controls are enabled AND custom controls are disabled, hide custom controls
    // This allows hybrid mode where both YouTube native controls and custom controls can be shown
    if (this.youtubeNativeControls && !this.useCustomControls && this.playerWrapper) {
      this.debugLog('[YouTube] Native controls enabled & custom controls disabled - hiding custom controls');
      this.playerWrapper.classList.add('youtube-native-controls-mode');
      const controlsBar = this.getElement('controls');
      if (controlsBar) controlsBar.style.display = 'none';
      const topBar = document.querySelector('.uvf-top-bar') as HTMLElement;
      if (topBar) topBar.style.display = 'none';
    } else if (this.youtubeNativeControls && this.useCustomControls && this.playerWrapper) {
      // Hybrid mode - both YouTube native controls and custom controls should be visible
      this.debugLog('[YouTube] Hybrid mode - both YouTube native controls and custom controls enabled');
      this.playerWrapper.classList.remove('youtube-native-controls-mode');

      this.showControls();

      // Force controls to stay visible
      setTimeout(() => {
        if (this.playerWrapper) {
          this.playerWrapper.classList.add('controls-visible');
          this.playerWrapper.classList.remove('no-cursor');
          this.debugLog('[YouTube] Hybrid mode - Force-applied controls-visible class');
        }
      }, 100);

      this.debugLog('[YouTube] Hybrid mode ready, custom controls visible');
    } else if (!this.youtubeNativeControls && this.useCustomControls && this.playerWrapper) {
      // Custom controls only mode - ensure custom controls are visible
      this.debugLog('[YouTube] Custom controls only mode - ensuring controls are visible');
      this.playerWrapper.classList.remove('youtube-native-controls-mode');

      this.showControls();

      // Force controls to stay visible and remove any hiding classes
      setTimeout(() => {
        if (this.playerWrapper) {
          this.playerWrapper.classList.add('controls-visible');
          this.playerWrapper.classList.remove('no-cursor');
          this.debugLog('[YouTube] Force-applied controls-visible class');

          // Ensure controls bar is not hidden by inline styles AND has correct z-index
          const controlsBar = this.getElement('controls');
          if (controlsBar) {
            if (controlsBar.style.display === 'none') {
              controlsBar.style.display = '';
              this.debugLog('[YouTube] Removed inline display:none from controls');
            }
            // Force z-index to ensure controls are above overlay
            controlsBar.style.zIndex = '100';
            this.debugLog('[YouTube] Set controls z-index to 100');
          }
        }
      }, 100);

      this.debugLog('[YouTube] Custom controls ready, controls will auto-hide on interaction');
    } else if (!this.youtubeNativeControls && !this.useCustomControls && this.playerWrapper) {
      // No controls mode - both YouTube native and custom controls are hidden
      this.debugLog('[YouTube] No controls mode - all controls hidden');
      this.playerWrapper.classList.add('youtube-native-controls-mode');
      const controlsBar = this.getElement('controls');
      if (controlsBar) controlsBar.style.display = 'none';
    }

    // Debug: Log control visibility state after YouTube loads
    setTimeout(() => {
      const controlsBar = this.getElement('controls');
      const wrapper = this.playerWrapper;
      const overlay = wrapper?.querySelector('.uvf-youtube-overlay') as HTMLElement;

      if (this.config.debug) {
        console.log('[YouTube Debug] Control visibility check:');
        console.log('- youtubeNativeControls:', this.youtubeNativeControls);
        console.log('- useCustomControls:', this.useCustomControls);

        if (wrapper) {
          console.log('- Wrapper classes:', wrapper.className);
        }

        if (controlsBar) {
          const rect = controlsBar.getBoundingClientRect();
          const style = window.getComputedStyle(controlsBar);
          console.log('- Controls bar:');
          console.log('  - Display:', style.display);
          console.log('  - Visibility:', style.visibility);
          console.log('  - Opacity:', style.opacity);
          console.log('  - Z-index:', style.zIndex);
          console.log('  - Pointer-events:', style.pointerEvents);
          console.log('  - Position:', style.position);
          console.log('  - Dimensions:', { width: rect.width, height: rect.height });
          console.log('  - Position:', { top: rect.top, left: rect.left, bottom: rect.bottom });
        }

        if (overlay) {
          const overlayRect = overlay.getBoundingClientRect();
          const overlayStyle = window.getComputedStyle(overlay);
          console.log('- YouTube overlay:');
          console.log('  - Height:', overlayStyle.height);
          console.log('  - Pointer-events:', overlayStyle.pointerEvents);
          console.log('  - Z-index:', overlayStyle.zIndex);
          console.log('  - Bottom position:', overlayRect.bottom);
        }
      }
    }, 500);

    // Set initial volume
    if (this.youtubePlayer) {
      const volume = this.config.volume ? this.config.volume * 100 : 100;
      this.youtubePlayer.setVolume(volume);

      if (this.config.muted) {
        this.youtubePlayer.mute();
      }

      // Apply startTime if configured and not yet applied
      if (this.config.startTime !== undefined &&
        this.config.startTime > 0 &&
        !this.hasAppliedStartTime) {
        this.debugLog(`⏩ Seeking YouTube player to startTime: ${this.config.startTime}s`);
        this.youtubePlayer.seekTo(this.config.startTime, true);
        this.hasAppliedStartTime = true;
      }

      // Handle autoplay - YouTube's autoplay parameter alone isn't reliable
      if (this.config.autoPlay && !this.autoplayAttempted) {
        this.debugLog('🎬 Attempting YouTube autoplay');
        this.autoplayAttempted = true;
        try {
          this.youtubePlayer.playVideo();
          this.debugLog('✅ YouTube autoplay initiated');
        } catch (error) {
          this.debugWarn('❌ YouTube autoplay failed:', error);
        }
      }
    }

    // Start time tracking
    this.startYouTubeTimeTracking();

    this.emit('onReady');
  }

  private onYouTubePlayerStateChange(event: any): void {
    const state = event.data;

    switch (state) {
      case window.YT.PlayerState.UNSTARTED: // -1: Video is loaded but not started
        this.updateYouTubeUI('unstarted');
        this.showControls();
        break;

      case window.YT.PlayerState.PLAYING:
        this.state.isPlaying = true;
        this.state.isPaused = false;
        this.state.isBuffering = false;
        this.updateYouTubeUI('playing');
        this.scheduleHideControls();
        this.emit('onPlay');
        break;

      case window.YT.PlayerState.PAUSED:
        this.state.isPlaying = false;
        this.state.isPaused = true;
        this.state.isBuffering = false;
        this.updateYouTubeUI('paused');
        this.showControls();
        this.emit('onPause');
        break;

      case window.YT.PlayerState.BUFFERING:
        this.state.isBuffering = true;
        this.updateYouTubeUI('buffering');
        this.emit('onBuffering', true);
        break;

      case window.YT.PlayerState.ENDED:
        this.state.isPlaying = false;
        this.state.isPaused = true;
        this.state.isEnded = true;
        this.updateYouTubeUI('ended');
        this.emit('onEnded');

        // Manual loop fallback for YouTube (in case loop parameter doesn't work)
        if (this.config.loop && this.youtubePlayer) {
          this.debugLog('🔁 Loop enabled - restarting YouTube video');
          setTimeout(() => {
            if (this.youtubePlayer && this.youtubePlayerReady) {
              this.youtubePlayer.seekTo(0);
              this.youtubePlayer.playVideo();
            }
          }, 100);
        }
        break;

      case window.YT.PlayerState.CUED:
        this.state.duration = this.youtubePlayer.getDuration();
        this.updateYouTubeUI('cued');
        break;
    }
  }

  private updateYouTubeUI(state: string): void {
    const playIcon = this.cachedElements.playIcon;
    const pauseIcon = this.cachedElements.pauseIcon;
    const centerPlay = this.cachedElements.centerPlay;
    const loading = this.cachedElements.loading;

    // Handle loading spinner for YouTube
    if (state === 'buffering') {
      if (loading) loading.classList.add('active');
    } else if (state === 'playing' || state === 'paused' || state === 'cued' || state === 'ended' || state === 'unstarted') {
      if (loading) loading.classList.remove('active');
    }

    if (state === 'playing' || state === 'buffering') {
      if (playIcon) playIcon.style.display = 'none';
      if (pauseIcon) pauseIcon.style.display = 'block';
      if (centerPlay) centerPlay.classList.add('hidden');
    } else if (state === 'paused' || state === 'cued' || state === 'ended' || state === 'unstarted') {
      if (playIcon) playIcon.style.display = 'block';
      if (pauseIcon) pauseIcon.style.display = 'none';
      if (centerPlay) centerPlay.classList.remove('hidden');
    }
  }

  private onYouTubePlayerError(event: any): void {
    const errorCode = event.data;
    let errorMessage = 'YouTube player error';

    switch (errorCode) {
      case 2:
        errorMessage = 'Invalid video ID';
        break;
      case 5:
        errorMessage = 'HTML5 player error';
        break;
      case 100:
        errorMessage = 'Video not found or private';
        break;
      case 101:
      case 150:
        errorMessage = 'Video cannot be embedded';
        break;
    }

    this.handleError({
      code: 'YOUTUBE_ERROR',
      message: errorMessage,
      type: 'media',
      fatal: true,
      details: { errorCode }
    });
  }



  private youtubeTimeTrackingInterval: NodeJS.Timeout | null = null;

  private startYouTubeTimeTracking(): void {
    if (this.youtubeTimeTrackingInterval) {
      clearInterval(this.youtubeTimeTrackingInterval);
    }

    this.youtubeTimeTrackingInterval = setInterval(() => {
      if (this.youtubePlayer && this.youtubePlayerReady) {
        try {
          const currentTime = this.youtubePlayer.getCurrentTime();
          const duration = this.youtubePlayer.getDuration();
          const buffered = this.youtubePlayer.getVideoLoadedFraction() * 100;

          this.state.currentTime = currentTime || 0;
          this.state.duration = duration || 0;
          this.state.bufferedPercentage = buffered || 0;

          // Update UI progress bar
          this.updateYouTubeProgressBar(currentTime, duration, buffered);

          this.emit('onTimeUpdate', this.state.currentTime);
          this.emit('onProgress', this.state.bufferedPercentage);
        } catch (error) {
          // Ignore errors during tracking
        }
      }
    }, 250); // Update every 250ms
  }

  private updateYouTubeProgressBar(currentTime: number, duration: number, buffered: number): void {
    if (!duration || duration === 0) return;

    const percent = (currentTime / duration) * 100;

    // Update progress filled
    const progressFilled = this.cachedElements.progressFilled;
    if (progressFilled && !this.isDragging) {
      progressFilled.style.width = percent + '%';
    }

    // Update progress handle
    const progressHandle = this.cachedElements.progressHandle;
    if (progressHandle && !this.isDragging) {
      progressHandle.style.left = percent + '%';
    }

    // Update buffered progress
    const progressBuffered = this.cachedElements.progressBuffered;
    if (progressBuffered) {
      progressBuffered.style.width = buffered + '%';
    }

    // Update time display
    this.updateTimeDisplay();
  }


  protected loadScript(src: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = src;
      script.onload = () => resolve();
      script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
      document.head.appendChild(script);
    });
  }

  private async loadSubtitles(subtitles: SubtitleTrack[]): Promise<void> {
    if (!this.video) return;

    // 1. Revoke and clear previously created Blob URLs
    this.subtitleBlobUrls.forEach(url => {
      try {
        URL.revokeObjectURL(url);
      } catch (e) {
        this.debugError('Failed to revoke subtitle Blob URL:', e);
      }
    });
    this.subtitleBlobUrls = [];

    // 2. Remove existing tracks
    const existingTracks = this.video.querySelectorAll('track');
    existingTracks.forEach(track => track.remove());

    // Resolve a subtitle URL to a browser-ready VTT blob URL.
    // All URLs (including .vtt) are fetched and served as blob URLs to avoid
    // cross-origin CORS issues when the <track> element loads a remote URL.
    //
    // Auto-detection logic (after fetch):
    //   • content starts with "WEBVTT" → already VTT, serve as blob
    //   • otherwise                    → treat as SRT, convert to VTT, serve as blob
    const resolveSubtitleUrl = async (rawUrl: string): Promise<string> => {
      try {
        const response = await fetch(rawUrl);
        const text = await response.text();

        let vttText: string;
        if (text.trimStart().startsWith('WEBVTT')) {
          // Content is already valid VTT (no-extension URL, or misnamed .srt file)
          this.debugLog(`Auto-detected VTT content for: ${rawUrl}`);
          vttText = text;
        } else {
          // Treat as SRT and convert
          this.debugLog(`Converting SRT to VTT: ${rawUrl}`);
          vttText = SrtConverter.convertToVtt(text);
        }

        const blob = new Blob([vttText], { type: 'text/vtt' });
        const blobUrl = URL.createObjectURL(blob);
        this.subtitleBlobUrls.push(blobUrl);
        return blobUrl;
      } catch (error) {
        this.debugError(`Failed to load subtitle at ${rawUrl}:`, error);
        return rawUrl; // Fallback to original URL
      }
    };

    // 3. Resolve all subtitle URLs in parallel while preserving original array order.
    //    Promise.all guarantees that every track is appended in the same order as the
    //    input array, keeping textTracks indices in sync with the subtitles array.
    const resolvedUrls = await Promise.all(subtitles.map(s => resolveSubtitleUrl(s.url || '')));

    // 4. Append all tracks in the original array order
    let hasDefault = false;
    subtitles.forEach((subtitle, index) => {
      const track = document.createElement('track');
      track.kind = subtitle.kind || 'subtitles';
      track.label = subtitle.label;
      track.srclang = subtitle.language;
      track.src = resolvedUrls[index];

      // First subtitle marked default wins; ignore subsequent defaults
      if (subtitle.default && !hasDefault) {
        track.default = true;
        hasDefault = true;
      }

      this.video!.appendChild(track);

      // Listen for cue changes to update the custom overlay
      track.addEventListener('load', () => {
        if (track.track) {
          track.track.addEventListener('cuechange', () => this.updateSubtitleOverlay());
        }
      });

      // Some browsers already expose the track object before 'load' fires
      if (track.track) {
        track.track.addEventListener('cuechange', () => this.updateSubtitleOverlay());
      }
    });

    // Override browser auto-show: when a <track default> is appended, the browser
    // automatically sets its mode to 'showing' (native rendering). We must suppress
    // that and route all rendering through our custom overlay instead.
    //
    // Strategy: set every track to 'disabled', then if one is marked default,
    // re-activate it via our overlay system (mode='hidden' + update state).
    let defaultIndex = -1;
    subtitles.forEach((subtitle, i) => {
      if (subtitle.default && defaultIndex === -1) defaultIndex = i;
    });

    const textTracks = this.video.textTracks;
    for (let i = 0; i < textTracks.length; i++) {
      textTracks[i].mode = (i === defaultIndex) ? 'hidden' : 'disabled';
    }

    if (defaultIndex >= 0) {
      this.currentSubtitleIndex = defaultIndex;
      this.currentSubtitle = String(defaultIndex);
      this.updateSubtitleOverlay();
    }

    // Refresh the settings menu so subtitle options appear immediately after
    // tracks are in the DOM — guards against the race with loadedmetadata.
    this.updateSettingsMenu();
  }

  private updateSubtitleOverlay(): void {
    if (!this.subtitleOverlay || !this.video) return;

    const tracks = this.video.textTracks;
    let activeCues: VTTCue[] = [];

    // Find the currently active track's cues
    // We use the currentSubtitleIndex from BasePlayer to know which one we want
    if (this.currentSubtitleIndex >= 0 && this.currentSubtitleIndex < tracks.length) {
      const activeTrack = tracks[this.currentSubtitleIndex];
      if (activeTrack && activeTrack.activeCues) {
        activeCues = Array.from(activeTrack.activeCues) as VTTCue[];
      }
    }

    if (activeCues.length > 0) {
      // Join multiple cues, converting newlines within each cue to <br>
      // Sanitize: only allow safe VTT inline tags (b, i, u, ruby, rt) to prevent XSS
      const sanitize = (raw: string): string =>
        raw
          .replace(/&/g, '&amp;')
          .replace(/</g, '\x00LT\x00')
          .replace(/>/g, '\x00GT\x00')
          .replace(/\x00LT\x00(\/?(b|i|u|ruby|rt))\x00GT\x00/gi, '<$1>')
          .replace(/\x00LT\x00/g, '&lt;')
          .replace(/\x00GT\x00/g, '&gt;')
          .replace(/\n/g, '<br>');
      const text = activeCues.map(cue => sanitize(cue.text)).join('<br>');
      this.subtitleOverlay.innerHTML = `<span>${text}</span>`;
      this.subtitleOverlay.style.display = 'block';
    } else {
      this.subtitleOverlay.innerHTML = '';
      this.subtitleOverlay.style.display = 'none';
    }
  }

  private isAbortPlayError(err: any): boolean {
    return !!err && (
      (err.name === 'AbortError') ||
      (typeof err.message === 'string' && /interrupted by a call to pause\(\)/i.test(err.message))
    );
  }

  private isAutoplayRestrictionError(err: any): boolean {
    if (!err) return false;

    const message = (err.message || '').toLowerCase();
    const name = (err.name || '').toLowerCase();

    // Common autoplay restriction error patterns
    return (
      name === 'notallowederror' ||
      message.includes('user didn\'t interact') ||
      message.includes('autoplay') ||
      message.includes('gesture') ||
      message.includes('user activation') ||
      message.includes('play() failed') ||
      message.includes('user interaction')
    );
  }

  /**
   * Detect browser autoplay capabilities
   * Tests both muted and unmuted autoplay support
   */
  private async detectAutoplayCapabilities(): Promise<void> {
    // Cache for 5 minutes to avoid repeated checks
    const now = Date.now();
    if (this.autoplayCapabilities.lastCheck && (now - this.autoplayCapabilities.lastCheck) < 300000) {
      return;
    }

    try {
      // Create a temporary video element for testing
      const testVideo = document.createElement('video');
      testVideo.muted = true;
      testVideo.playsInline = true;
      testVideo.style.position = 'absolute';
      testVideo.style.opacity = '0';
      testVideo.style.pointerEvents = 'none';
      testVideo.style.width = '1px';
      testVideo.style.height = '1px';

      // Use a minimal data URL video
      testVideo.src = 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMQAAAAhmcmVlAAAA70D=';

      document.body.appendChild(testVideo);

      try {
        // Test muted autoplay
        await testVideo.play();
        this.autoplayCapabilities.canAutoplayMuted = true;
        this.autoplayCapabilities.canAutoplay = true;
        this.debugLog('✅ Muted autoplay is supported');

        // Test unmuted autoplay
        testVideo.pause();
        testVideo.currentTime = 0;
        testVideo.muted = false;
        testVideo.volume = 0.5;

        try {
          await testVideo.play();
          this.autoplayCapabilities.canAutoplayUnmuted = true;
          this.debugLog('✅ Unmuted autoplay is supported');
        } catch (unmutedError) {
          this.autoplayCapabilities.canAutoplayUnmuted = false;
          this.debugLog('⚠️ Unmuted autoplay is blocked');
        }

        testVideo.pause();
      } catch (error) {
        this.autoplayCapabilities.canAutoplay = false;
        this.autoplayCapabilities.canAutoplayMuted = false;
        this.autoplayCapabilities.canAutoplayUnmuted = false;
        this.debugLog('❌ All autoplay is blocked');
      } finally {
        document.body.removeChild(testVideo);
      }

      this.autoplayCapabilities.lastCheck = now;
    } catch (error) {
      this.debugError('Failed to detect autoplay capabilities:', error);
      // Assume muted autoplay works as fallback
      this.autoplayCapabilities.canAutoplayMuted = true;
      this.autoplayCapabilities.canAutoplay = true;
    }
  }

  /**
   * Check if page has user activation (from navigation or interaction)
   */
  private hasUserActivation(): boolean {
    // Check if browser supports userActivation API
    if (typeof navigator !== 'undefined' && (navigator as any).userActivation) {
      const hasActivation = (navigator as any).userActivation.hasBeenActive;
      this.debugLog(`🎯 User activation detected: ${hasActivation}`);
      return hasActivation;
    }

    // Fallback: Check if user has interacted with the page
    const hasInteracted = this.lastUserInteraction > 0 &&
      (Date.now() - this.lastUserInteraction) < 5000;

    this.debugLog(`🎯 Recent user interaction: ${hasInteracted}`);
    return hasInteracted;
  }

  /**
   * Attempt intelligent autoplay based on detected capabilities
   */
  private async attemptIntelligentAutoplay(): Promise<boolean> {
    this.debugLog('🎬 attemptIntelligentAutoplay called');
    if (!this.config.autoPlay || !this.video) {
      this.debugLog(`⛔ Early return: autoPlay=${this.config.autoPlay}, video=${!!this.video}`);
      return false;
    }

    // Detect capabilities first
    this.debugLog('🔍 Detecting autoplay capabilities...');
    await this.detectAutoplayCapabilities();
    this.debugLog(`📦 Capabilities detected: canAutoplayMuted=${this.autoplayCapabilities.canAutoplayMuted}, canAutoplayUnmuted=${this.autoplayCapabilities.canAutoplayUnmuted}`);

    // Check if user has activated the page (navigation counts as activation)
    const hasActivation = this.hasUserActivation();
    this.debugLog(`👤 User activation: ${hasActivation}`);

    // Try unmuted autoplay if:
    // 1. Browser supports unmuted autoplay OR user has activated the page
    // 2. User hasn't explicitly set muted=true
    const shouldTryUnmuted = (this.autoplayCapabilities.canAutoplayUnmuted || hasActivation)
      && this.config.muted !== true;

    if (shouldTryUnmuted) {
      this.video.muted = false;
      this.state.isMuted = false;
      this.video.volume = this.config.volume ?? 1.0;
      this.debugLog(`🔊 Attempting unmuted autoplay (activation: ${hasActivation})`);

      try {
        this.debugLog('▶️ Calling play() for unmuted autoplay...');
        await this.play();
        // Verify video is actually playing
        this.debugLog(`📊 Play returned, video.paused=${this.video.paused}`);
        if (!this.video.paused) {
          this.debugLog('✅ Unmuted autoplay successful');
          return true;
        } else {
          this.debugLog('⚠️ Unmuted play() succeeded but video is paused, trying muted');
        }
      } catch (error) {
        this.debugLog('⚠️ Unmuted autoplay failed:', error);
      }
    }

    // Always try muted autoplay as fallback (browsers allow this)
    // Ignore capability detection - it can give false negatives
    this.video.muted = true;
    this.state.isMuted = true;
    this.debugLog('🔇 Attempting muted autoplay (always try this)');

    try {
      this.debugLog('▶️ Calling play() for muted autoplay...');
      await this.play();
      // Verify video is actually playing
      this.debugLog(`📊 Play returned, video.paused=${this.video.paused}`);
      if (!this.video.paused) {
        this.debugLog('✅ Muted autoplay successful');
        // Show YouTube-style unmute button instead of blocking overlay
        this.showUnmuteButton();
        return true;
      } else {
        this.debugLog('❌ Muted play() succeeded but video is paused');
      }
    } catch (error) {
      this.debugLog('❌ Muted autoplay failed:', error);
    }

    return false;
  }

  /**
   * Set up intelligent autoplay retry on user interaction
   */
  private setupAutoplayRetry(): void {
    if (!this.config.autoPlay || this.autoplayRetryAttempts >= this.maxAutoplayRetries) {
      return;
    }

    const interactionEvents = ['click', 'mousedown', 'keydown', 'touchstart'];

    const retryAutoplay = async () => {
      if (this.autoplayRetryPending || this.state.isPlaying) {
        return;
      }

      this.autoplayRetryPending = true;
      this.autoplayRetryAttempts++;
      this.debugLog(`🔄 Attempting autoplay retry #${this.autoplayRetryAttempts}`);

      try {
        const success = await this.attemptIntelligentAutoplay();
        if (success) {
          this.debugLog('✅ Autoplay retry successful');
          this.autoplayRetryPending = false;
          // Remove event listeners after success
          interactionEvents.forEach(eventType => {
            document.removeEventListener(eventType, retryAutoplay);
          });
        } else {
          this.autoplayRetryPending = false;
        }
      } catch (error) {
        this.autoplayRetryPending = false;
        this.debugError('Autoplay retry failed:', error);
      }
    };

    interactionEvents.forEach(eventType => {
      document.addEventListener(eventType, retryAutoplay, { once: true, passive: true });
    });

    this.debugLog('🎯 Autoplay retry armed - waiting for user interaction');
  }


  /**
   * Show YouTube-style unmute button when video autoplays muted
   */
  private showUnmuteButton(): void {
    // Remove existing unmute button
    this.hideUnmuteButton();

    this.debugLog('🔇 Showing unmute button - video autoplaying muted');

    const unmuteBtn = document.createElement('button');
    unmuteBtn.id = this.getElementId('unmute-btn');
    unmuteBtn.className = 'uvf-unmute-btn';
    unmuteBtn.setAttribute('aria-label', 'Tap to unmute');
    unmuteBtn.innerHTML = `
      <svg viewBox="0 0 24 24" class="uvf-unmute-icon">
        <path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>
      </svg>
      <span class="uvf-unmute-text">Tap to unmute</span>
    `;

    // Click handler to unmute
    unmuteBtn.addEventListener('click', (e) => {
      e.stopPropagation();
      this.unmute();
      this.debugLog('🔊 Video unmuted by user');
      this.hideUnmuteButton();
    });

    // Add enhanced styles
    const style = document.createElement('style');
    style.textContent = `
      .uvf-unmute-btn {
        position: absolute !important;
        bottom: 80px !important;
        left: 20px !important;
        z-index: 1000 !important;
        display: flex !important;
        align-items: center !important;
        gap: 8px !important;
        padding: 12px 16px !important;
        background: rgba(0, 0, 0, 0.8) !important;
        border: none !important;
        border-radius: 4px !important;
        color: white !important;
        font-size: 14px !important;
        font-weight: 500 !important;
        cursor: pointer !important;
        transition: all 0.2s ease !important;
        backdrop-filter: blur(10px) !important;
        -webkit-backdrop-filter: blur(10px) !important;
        animation: uvf-unmute-pulse 2s ease-in-out infinite !important;
      }
      
      .uvf-unmute-btn:hover {
        background: rgba(0, 0, 0, 0.9) !important;
        transform: scale(1.05) !important;
      }
      
      .uvf-unmute-btn:active {
        transform: scale(0.95) !important;
      }
      
      .uvf-unmute-icon {
        width: 20px !important;
        height: 20px !important;
        fill: white !important;
      }
      
      .uvf-unmute-text {
        white-space: nowrap !important;
      }
      
      @keyframes uvf-unmute-pulse {
        0%, 100% {
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
        }
        50% {
          box-shadow: 0 2px 16px rgba(255, 255, 255, 0.2) !important;
        }
      }
      
      /* Mobile responsive */
      @media (max-width: 767px) {
        .uvf-unmute-btn {
          bottom: 70px !important;
          left: 50% !important;
          transform: translateX(-50%) !important;
          padding: 10px 14px !important;
          font-size: 13px !important;
        }
        
        .uvf-unmute-btn:hover {
          transform: translateX(-50%) scale(1.05) !important;
        }
      }
    `;

    // Add to page if not already added
    if (!this.getElement('unmute-styles')) {
      style.id = this.getElementId('unmute-styles');
      document.head.appendChild(style);
    }

    // Add to player
    if (this.playerWrapper) {
      this.playerWrapper.appendChild(unmuteBtn);
      this.debugLog('✅ Unmute button added to player');

      // Enable clicking anywhere on video to unmute
      this.setupClickToUnmute();
    }
  }

  /**
   * Set up click anywhere on video to unmute when unmute button is visible
   */
  private setupClickToUnmute(): void {
    // Remove any existing listener first
    if (this.clickToUnmuteHandler) {
      this.playerWrapper?.removeEventListener('click', this.clickToUnmuteHandler, true);
    }

    this.clickToUnmuteHandler = (e: MouseEvent) => {
      const unmuteBtn = this.getElement('unmute-btn');

      // Only handle if unmute button is visible (video is muted)
      if (!unmuteBtn || !this.video) return;

      // Don't unmute if clicking on controls or buttons
      const target = e.target as HTMLElement;
      if (target.closest('.uvf-controls-container') ||
        target.closest('button') ||
        target.closest('.uvf-settings-menu')) {
        return;
      }

      // Stop the event from triggering play/pause
      e.stopPropagation();
      e.preventDefault();

      // Unmute the video
      this.unmute();
      this.debugLog('🔊 Video unmuted by clicking on player');
      this.hideUnmuteButton();

      // Clean up the handler
      if (this.clickToUnmuteHandler) {
        this.playerWrapper?.removeEventListener('click', this.clickToUnmuteHandler, true);
        this.clickToUnmuteHandler = null;
      }
    };

    // Use capture phase to intercept clicks before they reach the video element
    this.playerWrapper?.addEventListener('click', this.clickToUnmuteHandler, true);
    this.debugLog('👆 Click anywhere to unmute enabled');
  }

  /**
   * Hide unmute button
   */
  private clickToUnmuteHandler: ((e: MouseEvent) => void) | null = null;

  private hideUnmuteButton(): void {
    const unmuteBtn = this.getElement('unmute-btn');
    if (unmuteBtn) {
      unmuteBtn.remove();
      this.debugLog('Unmute button removed');
    }

    // Remove click to unmute handler when button is hidden
    if (this.clickToUnmuteHandler) {
      this.playerWrapper?.removeEventListener('click', this.clickToUnmuteHandler, true);
      this.clickToUnmuteHandler = null;
    }
  }

  private updateTimeTooltip(e: MouseEvent): void {
    const progressBar = this.cachedElements.progressBar;
    const tooltip = this.cachedElements.timeTooltip;
    if (!progressBar || !tooltip) return;

    // Get duration from YouTube player or regular video
    let duration: number;
    if (this.youtubePlayer && this.youtubePlayerReady) {
      duration = this.youtubePlayer.getDuration();
    } else if (this.video) {
      duration = this.video.duration;
    } else {
      return; // No video source available
    }

    // Validate duration
    if (!isFinite(duration) || isNaN(duration) || duration <= 0) {
      return;
    }

    const rect = progressBar.getBoundingClientRect();
    const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
    const percent = (x / rect.width);
    const time = percent * duration;

    // Update tooltip content and position
    tooltip.textContent = this.formatTime(time);
    tooltip.style.left = `${x}px`;
    tooltip.classList.add('visible');
  }

  private hideTimeTooltip(): void {
    const tooltip = this.cachedElements.timeTooltip;
    if (tooltip) {
      tooltip.classList.remove('visible');
    }
  }

  private setupUserInteractionTracking(): void {
    // Track various user interactions to enable autoplay
    const interactionEvents = ['click', 'mousedown', 'keydown', 'touchstart'];

    const updateLastInteraction = () => {
      this.lastUserInteraction = Date.now();
      this.debugLog('User interaction detected at:', this.lastUserInteraction);
    };

    // Listen on document for global interactions
    interactionEvents.forEach(eventType => {
      document.addEventListener(eventType, updateLastInteraction, { passive: true });
    });

    // Also listen on player wrapper for more specific interactions
    if (this.playerWrapper) {
      interactionEvents.forEach(eventType => {
        this.playerWrapper!.addEventListener(eventType, updateLastInteraction, { passive: true });
      });
    }
  }

  async play(): Promise<void> {
    // Handle YouTube player
    if (this.youtubePlayer && this.youtubePlayerReady) {
      this.youtubePlayer.playVideo();
      return;
    }

    if (!this.video) throw new Error('Video element not initialized');

    // Prevent playback when in fallback poster mode (no valid sources)
    if (this.isFallbackPosterMode) {
      this.debugLog('⚠️ Play blocked: In fallback poster mode (no playable sources)');
      return;
    }

    // Security check: Prevent play if paywall is active and user not authenticated
    if (!this.canPlayVideo()) {
      this.debugWarn('Playbook blocked by security check');
      this.enforcePaywallSecurity();
      return;
    }

    const now = Date.now();
    if (now - this._lastToggleAt < this._TOGGLE_DEBOUNCE_MS) return;
    this._lastToggleAt = now;

    // If already playing or a play is in-flight, no-op
    if (!this.video.paused || this._playPromise) return;

    try {
      this._deferredPause = false; // a new play cancels any prior deferred pause
      this._playPromise = this.video.play();
      await this._playPromise; // await to sequence future actions
      this._playPromise = null;

      // If someone asked to pause while we were starting playbook, do it now
      if (this._deferredPause) {
        this._deferredPause = false;
        this.video.pause();
      }

      // Hide unmute button when video starts playing with sound
      if (!this.video.muted) {
        this.hideUnmuteButton();
      }
      await super.play();
    } catch (err) {
      this._playPromise = null;
      if (this.isAbortPlayError(err)) {
        // Benign: pause() raced play(); ignore the error.
        return;
      }

      // Check if this is an autoplay restriction error
      if (this.isAutoplayRestrictionError(err)) {
        this.debugWarn('Autoplay blocked by browser policy');
        // Throw error so intelligent autoplay can handle fallback
        throw err;
      }

      this.handleError({
        code: 'PLAY_ERROR',
        message: `Failed to start playbook: ${err}`,
        type: 'media',
        fatal: false,
        details: err
      });
      throw err;
    }
  }

  pause(): void {
    // Handle YouTube player
    if (this.youtubePlayer && this.youtubePlayerReady) {
      this.youtubePlayer.pauseVideo();
      return;
    }

    if (!this.video) return;

    const now = Date.now();
    if (now - this._lastToggleAt < this._TOGGLE_DEBOUNCE_MS) return;
    this._lastToggleAt = now;

    // If a play is still pending, defer the pause to avoid interrupt error
    if (this._playPromise || this.video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
      this._deferredPause = true;
      return;
    }

    this.video.pause();
    super.pause();
  }

  // Safe method for external components like PaywallController to request pause
  public requestPause(): void {
    this._deferredPause = true;
    if (!this._playPromise && this.video && this.video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
      try { this.video.pause(); } catch (_) { }
    }
  }

  /**
   * Safely set video currentTime with validation to prevent non-finite value errors
   */
  private safeSetCurrentTime(time: number): boolean {
    if (!this.video) {
      this.debugWarn('Cannot set currentTime: video element not available');
      return false;
    }

    // Validate the time value is finite
    if (!isFinite(time) || isNaN(time)) {
      this.debugWarn('Attempted to set invalid currentTime value:', time);
      return false;
    }

    // Ensure time is non-negative
    const safeTime = Math.max(0, time);

    // Additional validation: check if video duration is available and valid
    const duration = this.video.duration;
    if (isFinite(duration) && duration > 0) {
      // Clamp time to valid range [0, duration]
      const clampedTime = Math.min(safeTime, duration);
      try {
        this.video.currentTime = clampedTime;
        return true;
      } catch (error) {
        this.debugError('Error setting currentTime:', error);
        return false;
      }
    } else {
      // Duration not available yet, but still set time if it's valid
      try {
        this.video.currentTime = safeTime;
        return true;
      } catch (error) {
        this.debugError('Error setting currentTime:', error);
        return false;
      }
    }
  }

  seek(time: number): void {
    // Handle YouTube player
    if (this.youtubePlayer && this.youtubePlayerReady) {
      this.youtubePlayer.seekTo(time, true);
      this.emit('onSeeking');
      setTimeout(() => this.emit('onSeeked'), 500);
      return;
    }

    if (!this.video) return;

    // Validate input time
    if (!isFinite(time) || isNaN(time)) {
      this.debugWarn('Invalid seek time:', time);
      return;
    }

    // Security check: Prevent seeking beyond free preview limit
    const freeDuration = Number(this.config.freeDuration || 0);
    if (freeDuration > 0 && !this.paymentSuccessful) {
      const requestedTime = Math.max(0, Math.min(time, this.video.duration || time));
      if (requestedTime >= freeDuration) {
        this.debugWarn('Seek blocked - beyond free preview limit');
        this.enforcePaywallSecurity();
        // Reset to safe position
        this.safeSetCurrentTime(freeDuration - 1);
        return;
      }
    }

    // Use safe setter with validated time
    this.safeSetCurrentTime(time);
  }

  setVolume(level: number): void {
    // Handle YouTube player
    if (this.youtubePlayer && this.youtubePlayerReady) {
      const volumePercent = level * 100;
      this.youtubePlayer.setVolume(volumePercent);
    }

    if (!this.video) return;
    this.video.volume = Math.max(0, Math.min(1, level));
    super.setVolume(level);
  }

  mute(): void {
    // Handle YouTube player
    if (this.youtubePlayer && this.youtubePlayerReady) {
      this.youtubePlayer.mute();
    }

    if (!this.video) return;

    // Update video element and state synchronously
    this.video.muted = true;
    this.state.isMuted = true;

    // Call parent to emit events
    super.mute();
  }

  unmute(): void {
    // Handle YouTube player
    if (this.youtubePlayer && this.youtubePlayerReady) {
      this.youtubePlayer.unMute();
    }

    if (!this.video) return;

    // Update video element and state synchronously
    this.video.muted = false;
    this.state.isMuted = false;

    // Call parent to emit events
    super.unmute();
  }


  getCurrentTime(): number {
    // Handle YouTube player
    if (this.youtubePlayer && this.youtubePlayerReady) {
      return this.youtubePlayer.getCurrentTime();
    }

    if (this.video && typeof this.video.currentTime === 'number') {
      return this.video.currentTime;
    }
    return super.getCurrentTime();
  }

  getQualities(): any[] {
    return this.qualities as any;
  }

  getCurrentQuality(): any {
    // If we have a manually set quality, return it
    if (this.currentQualityIndex >= 0 && this.qualities[this.currentQualityIndex]) {
      this.debugLog(`getCurrentQuality: returning manual quality at index ${this.currentQualityIndex}`);
      return this.qualities[this.currentQualityIndex] as any;
    }

    // In auto mode, get the currently playing quality from HLS/DASH
    if (this.autoQuality) {
      if (this.hls) {
        // Check currentLevel first (actively playing level)
        let levelIndex = this.hls.currentLevel;

        // If currentLevel is -1 (auto), check nextLevel or loadLevel
        if (levelIndex < 0) {
          // nextLevel is the level that will be loaded next
          if (this.hls.nextLevel >= 0) {
            levelIndex = this.hls.nextLevel;
            this.debugLog(`getCurrentQuality: Using nextLevel=${levelIndex} (currentLevel was -1)`);
          }
          // loadLevel is the level being loaded
          else if (this.hls.loadLevel >= 0) {
            levelIndex = this.hls.loadLevel;
            this.debugLog(`getCurrentQuality: Using loadLevel=${levelIndex} (currentLevel was -1)`);
          }
        }

        // Return the quality if we have a valid level
        if (levelIndex >= 0 && this.qualities[levelIndex]) {
          this.debugLog(`getCurrentQuality: HLS auto mode, level=${levelIndex}, quality=${this.qualities[levelIndex]?.label || 'unknown'}`);
          return this.qualities[levelIndex] as any;
        } else {
          // Fallback: If no level determined yet, use the highest quality as a hint
          if (this.qualities.length > 0) {
            const highestQuality = this.qualities[this.qualities.length - 1];
            this.debugLog(`getCurrentQuality: Using highest quality as fallback: ${highestQuality?.label || 'unknown'}`);
            return highestQuality;
          }
          this.debugLog(`getCurrentQuality: HLS auto mode but no valid level yet (currentLevel=${this.hls.currentLevel}, nextLevel=${this.hls.nextLevel}, loadLevel=${this.hls.loadLevel})`);
        }
      } else if (this.dash) {
        try {
          const currentQualityIndex = this.dash.getQualityFor('video');
          if (currentQualityIndex >= 0 && this.qualities[currentQualityIndex]) {
            this.debugLog(`getCurrentQuality: DASH auto mode, currentLevel=${currentQualityIndex}, quality=${this.qualities[currentQualityIndex]?.label || 'unknown'}`);
            return this.qualities[currentQualityIndex] as any;
          }
        } catch (e) {
          // DASH might not have quality info yet
        }
      } else {
        this.debugLog(`getCurrentQuality: Auto mode but HLS/DASH not ready. hls=${!!this.hls}, qualities.length=${this.qualities.length}`);
      }
    }

    this.debugLog(`getCurrentQuality: returning null (autoQuality=${this.autoQuality}, currentQualityIndex=${this.currentQualityIndex})`);
    return null;
  }

  setQuality(index: number): void {
    if (this.hls) {
      this.hls.currentLevel = index;
    } else if (this.dash) {
      this.dash.setQualityFor('video', index);
    }

    this.currentQualityIndex = index;
    this.autoQuality = false;
  }

  setPlaybackRate(rate: number): void {
    if (!this.video) return;
    this.video.playbackRate = rate;
    super.setPlaybackRate(rate);
  }

  /**
   * Configure Flash News Ticker
   */
  setFlashNewsTicker(config: FlashNewsTickerConfig): void {
    if (!this.flashTickerContainer) return;

    // Clear existing tickers
    this.flashTickerContainer.innerHTML = '';
    this.flashTickerTopElement = null;
    this.flashTickerBottomElement = null;

    if (!config.enabled) {
      return;
    }

    // Handle 'both' position with separate configs
    if (config.position === 'both') {
      // Create top ticker with topConfig or fallback to main config
      if (config.topConfig && config.topConfig.items && config.topConfig.items.length > 0) {
        const topConfig = { ...config, ...config.topConfig };
        this.flashTickerTopElement = this.createTickerElement(topConfig, 'top');
        this.flashTickerContainer.appendChild(this.flashTickerTopElement);
      } else if (config.items && config.items.length > 0) {
        this.flashTickerTopElement = this.createTickerElement(config, 'top');
        this.flashTickerContainer.appendChild(this.flashTickerTopElement);
      }

      // Create bottom ticker with bottomConfig or fallback to main config
      if (config.bottomConfig && config.bottomConfig.items && config.bottomConfig.items.length > 0) {
        const bottomConfig = { ...config, ...config.bottomConfig };
        this.flashTickerBottomElement = this.createTickerElement(bottomConfig, 'bottom');
        this.flashTickerContainer.appendChild(this.flashTickerBottomElement);
      } else if (config.items && config.items.length > 0) {
        this.flashTickerBottomElement = this.createTickerElement(config, 'bottom');
        this.flashTickerContainer.appendChild(this.flashTickerBottomElement);
      }
    }
    // Handle single position (top or bottom)
    else {
      if (!config.items || config.items.length === 0) {
        return;
      }

      if (config.position === 'top') {
        this.flashTickerTopElement = this.createTickerElement(config, 'top');
        this.flashTickerContainer.appendChild(this.flashTickerTopElement);
      } else {
        // Default to bottom
        this.flashTickerBottomElement = this.createTickerElement(config, 'bottom');
        this.flashTickerContainer.appendChild(this.flashTickerBottomElement);
      }
    }
  }

  private createTickerElement(config: FlashNewsTickerConfig, position: 'top' | 'bottom'): HTMLDivElement {
    const variant = config.variant || 'standard';

    // Route to specific broadcast layouts
    if (variant === 'broadcast1') return this.createBroadcast1Element(config, position);

    // Route to specific broadcast style if configured (older style)
    if (config.styleVariant === 'broadcast' || variant === 'breaking' || variant === 'live') {
      const layoutStyle = config.broadcastStyle?.layoutStyle || 'broadcast';
      if (layoutStyle === 'two-line' || layoutStyle === 'professional') {
        return this.createProfessionalTickerElement(config, position);
      }
      return this.createBroadcastTickerElement(config, position);
    }

    // Simple style (original implementation)
    return this.createSimpleTickerElement(config, position);
  }

  private createSimpleTickerElement(config: FlashNewsTickerConfig, position: 'top' | 'bottom'): HTMLDivElement {
    const ticker = document.createElement('div');
    ticker.className = `uvf-flash-ticker ticker-${position} ticker-simple`;

    const bottomOffset = config.bottomOffset || 0;
    const topOffset = config.topOffset || 0;
    const height = config.height || 40;

    ticker.style.cssText = `
      position: absolute;
      left: 0;
      right: 0;
      height: ${height}px;
      ${position === 'top' ? `top: ${topOffset}px;` : `bottom: ${bottomOffset}px;`}
      background-color: ${config.backgroundColor || 'rgba(0,0,0,0.7)'};
      overflow: hidden;
      pointer-events: none;
      display: flex;
      align-items: center;
    `;

    // Create scrolling track
    const track = document.createElement('div');
    track.className = 'uvf-ticker-track';

    const duration = this.calculateTickerDuration(config);
    track.style.cssText = `
      display: flex;
      white-space: nowrap;
      animation: ticker-scroll ${duration}s linear infinite;
      will-change: transform;
      padding-left: 100%;
    `;

    // Calculate responsive font size based on container width
    const calculateResponsiveFontSize = (): number => {
      const containerWidth = this.container?.offsetWidth || 1920;
      const baseFontSize = config.fontSize || 14;

      // Scale font size based on container width
      // Base: 14px at 1920px width
      if (containerWidth < 768) {
        // Mobile: smaller font
        return Math.max(baseFontSize * 0.8, 12);
      } else if (containerWidth < 1280) {
        // Tablet: slightly smaller
        return Math.max(baseFontSize * 0.9, 13);
      }
      // Desktop: use configured size
      return baseFontSize;
    };

    const responsiveFontSize = calculateResponsiveFontSize();

    // Render items multiple times for continuous seamless loop
    const renderItems = () => {
      if (!config.items) return;
      config.items.forEach((item) => {
        const span = document.createElement('span');

        // Support HTML content for rich formatting (e.g., colored "Breaking:")
        if (item.html) {
          span.innerHTML = item.html;
        } else {
          span.textContent = item.text;
        }

        span.style.cssText = `
          color: ${config.textColor || '#ffffff'};
          font-size: ${responsiveFontSize}px;
          font-weight: ${config.fontWeight || 600};
          margin-right: ${config.gap || 100}px;
          display: inline-flex;
          align-items: center;
        `;
        track.appendChild(span);

        // Add separator after each item (including last one for seamless loop)
        if (config.separator) {
          const sep = document.createElement('span');
          sep.textContent = config.separator;
          sep.style.cssText = `
            color: ${config.textColor || '#ffffff'};
            font-size: ${responsiveFontSize}px;
            opacity: 0.5;
            margin: 0 8px;
          `;
          track.appendChild(sep);
        }
      });
    };

    // Render multiple times for continuous seamless scrolling
    for (let i = 0; i < 10; i++) {
      renderItems();
    }

    ticker.appendChild(track);

    // Add animation keyframes to document if not exists
    this.ensureTickerAnimations();

    return ticker;
  }

  private createBroadcastTickerElement(config: FlashNewsTickerConfig, position: 'top' | 'bottom'): HTMLDivElement {
    const broadcastStyle = config.broadcastStyle || {};
    const theme = broadcastStyle.theme || 'breaking-red';

    // Get theme colors
    const themeColors = this.getBroadcastThemeColors(theme, broadcastStyle);

    const ticker = document.createElement('div');
    ticker.className = `uvf-flash-ticker ticker-${position} ticker-broadcast`;

    const bottomOffset = config.bottomOffset || 0;
    const topOffset = config.topOffset || 0;
    const headerHeight = broadcastStyle.headerHeight || 28;
    const bodyHeight = config.height || 36;
    const totalHeight = headerHeight + bodyHeight;

    ticker.style.cssText = `
      position: absolute;
      left: 0;
      right: 0;
      height: ${totalHeight}px;
      ${position === 'top' ? `top: ${topOffset}px;` : `bottom: ${bottomOffset}px;`}
      overflow: hidden;
      pointer-events: none;
      display: flex;
      flex-direction: column;
    `;

    // Create header row (BREAKING NEWS with globe and LIVE badge)
    const header = document.createElement('div');
    header.className = 'uvf-ticker-header';
    header.style.cssText = `
      display: flex;
      align-items: center;
      height: ${headerHeight}px;
      background: ${themeColors.headerBg};
      padding: 0 12px;
      position: relative;
    `;

    // Add globe graphic if enabled
    if (broadcastStyle.showGlobe !== false) {
      const globe = this.createGlobeElement(broadcastStyle.animateGlobe !== false);
      header.appendChild(globe);
    }

    // Add header text (BREAKING NEWS)
    const headerText = document.createElement('span');
    headerText.className = 'uvf-ticker-header-text';
    headerText.textContent = broadcastStyle.headerText || 'BREAKING NEWS';
    headerText.style.cssText = `
      color: ${broadcastStyle.headerTextColor || '#ffffff'};
      font-size: ${broadcastStyle.headerFontSize || 16}px;
      font-weight: 800;
      text-transform: uppercase;
      letter-spacing: 1px;
      margin-left: ${broadcastStyle.showGlobe !== false ? '8px' : '0'};
      text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
    `;
    header.appendChild(headerText);

    // Add LIVE badge if enabled
    if (broadcastStyle.showLiveBadge !== false) {
      const liveBadge = this.createLiveBadgeElement(broadcastStyle.pulseLiveBadge !== false);
      header.appendChild(liveBadge);
    }

    // Create body row (scrolling ticker)
    const body = document.createElement('div');
    body.className = 'uvf-ticker-body';
    body.style.cssText = `
      display: flex;
      align-items: center;
      height: ${bodyHeight}px;
      background: ${themeColors.bodyBg};
      overflow: hidden;
      position: relative;
    `;

    // Create scrolling track
    const track = document.createElement('div');
    track.className = 'uvf-ticker-track';

    const duration = this.calculateTickerDuration(config);
    track.style.cssText = `
      display: flex;
      white-space: nowrap;
      animation: ticker-scroll ${duration}s linear infinite;
      will-change: transform;
      padding-left: 100%;
    `;

    // Calculate responsive font size
    const containerWidth = this.container?.offsetWidth || 1920;
    const baseFontSize = config.fontSize || 14;
    let responsiveFontSize = baseFontSize;
    if (containerWidth < 768) {
      responsiveFontSize = Math.max(baseFontSize * 0.8, 12);
    } else if (containerWidth < 1280) {
      responsiveFontSize = Math.max(baseFontSize * 0.9, 13);
    }

    // Render items
    const renderItems = () => {
      if (!config.items) return;
      config.items.forEach((item) => {
        const span = document.createElement('span');
        if (item.html) {
          span.innerHTML = item.html;
        } else {
          span.textContent = item.text;
        }
        span.style.cssText = `
          color: ${config.textColor || '#ffffff'};
          font-size: ${responsiveFontSize}px;
          font-weight: ${config.fontWeight || 600};
          margin-right: ${config.gap || 100}px;
          display: inline-flex;
          align-items: center;
        `;
        track.appendChild(span);

        if (config.separator) {
          const sep = document.createElement('span');
          sep.textContent = config.separator;
          sep.style.cssText = `
            color: ${config.textColor || '#ffffff'};
            font-size: ${responsiveFontSize}px;
            opacity: 0.5;
            margin: 0 8px;
          `;
          track.appendChild(sep);
        }
      });
    };

    for (let i = 0; i < 10; i++) {
      renderItems();
    }

    body.appendChild(track);
    ticker.appendChild(header);
    ticker.appendChild(body);

    // Add all animation keyframes
    this.ensureTickerAnimations();

    return ticker;
  }

  private getBroadcastThemeColors(theme: BroadcastTheme, broadcastStyle: BroadcastStyleConfig): { headerBg: string; bodyBg: string } {
    switch (theme) {
      case 'breaking-red':
        return {
          headerBg: 'linear-gradient(90deg, #cc0000 0%, #ff0000 50%, #cc0000 100%)',
          bodyBg: 'linear-gradient(90deg, #1a237e 0%, #283593 50%, #1a237e 100%)'
        };
      case 'breaking-blue':
        return {
          headerBg: 'linear-gradient(90deg, #0d47a1 0%, #1565c0 50%, #0d47a1 100%)',
          bodyBg: 'linear-gradient(90deg, #1a1a2e 0%, #16213e 50%, #1a1a2e 100%)'
        };
      case 'alert-red':
        return {
          headerBg: 'linear-gradient(90deg, #b71c1c 0%, #d32f2f 50%, #b71c1c 100%)',
          bodyBg: 'linear-gradient(90deg, #7f0000 0%, #9a0000 50%, #7f0000 100%)'
        };
      case 'news-blue':
        return {
          headerBg: 'linear-gradient(90deg, #01579b 0%, #0277bd 50%, #01579b 100%)',
          bodyBg: 'linear-gradient(90deg, #002171 0%, #003c8f 50%, #002171 100%)'
        };
      case 'custom':
        return {
          headerBg: broadcastStyle.headerColor || '#cc0000',
          bodyBg: broadcastStyle.bodyColor || '#1a237e'
        };
      default:
        return {
          headerBg: 'linear-gradient(90deg, #cc0000 0%, #ff0000 50%, #cc0000 100%)',
          bodyBg: 'linear-gradient(90deg, #1a237e 0%, #283593 50%, #1a237e 100%)'
        };
    }
  }

  private createBroadcast1Element(config: FlashNewsTickerConfig, position: 'top' | 'bottom'): HTMLDivElement {
    const ticker = document.createElement('div');
    ticker.className = `uvf-ticker-broadcast1 ticker-${position}`;

    const height = config.height || 90;
    const bottomOffset = config.bottomOffset || 0;
    const topOffset = config.topOffset || 10;

    // Store state for cycling
    this.tickerConfig = config;
    this.tickerCurrentItemIndex = 0;

    ticker.style.cssText = `
      position: absolute;
      left: 10%;
      right: 10%;
      height: ${height}px;
      ${position === 'top' ? `top: ${topOffset}px;` : `bottom: ${bottomOffset}px;`}
      display: flex;
      flex-direction: column;
      font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
      z-index: 100;
      pointer-events: none;
      filter: drop-shadow(0 8px 15px rgba(0,0,0,0.4));
    `;

    // Row 1: Split Red/Blue
    const row1 = document.createElement('div');
    row1.style.cssText = `display: flex; height: 62%; position: relative; overflow: hidden;`;

    const b1 = document.createElement('div');
    b1.textContent = 'BREAKING NEWS';
    b1.style.cssText = `
      background: #c0392b;
      color: white;
      font-weight: 900;
      padding: 0 25px;
      display: flex;
      align-items: center;
      clip-path: polygon(0 0, 92% 0, 100% 100%, 0 100%);
      font-size: ${height * 0.18}px;
      z-index: 2;
    `;
    row1.appendChild(b1);

    const b2 = document.createElement('div');
    b2.className = 'uvf-ticker-headline-line';
    this.tickerHeadlineElement = b2;
    b2.style.cssText = `
      background: #2980b9;
      color: white;
      padding: 0 40px;
      display: flex;
      align-items: center;
      flex-grow: 1;
      margin-left: -15px;
      clip-path: polygon(3% 0, 100% 0, 97% 100%, 0 100%);
      position: relative;
    `;

    const headlineText = document.createElement('div');
    headlineText.className = 'uvf-ticker-headline-text';
    headlineText.style.cssText = `
      font-weight: 800;
      font-size: ${height * 0.22}px;
      line-height: 1.2;
      width: 100%;
      display: -webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow: hidden;
      text-overflow: ellipsis;
      text-shadow: 1px 1px 3px rgba(0,0,0,0.5);
    `;

    const firstItem = config.items?.[0];
    if (firstItem) {
      headlineText.textContent = firstItem.headline || firstItem.text;
    }

    b2.appendChild(headlineText);
    row1.appendChild(b2);
    ticker.appendChild(row1);

    // Row 2: Black scrolling
    const row2 = document.createElement('div');
    row2.className = 'uvf-ticker-detail-line';
    this.tickerDetailElement = row2;
    row2.style.cssText = `
      height: 33%;
      background: #111;
      display: flex;
      align-items: center;
      margin-top: 3px;
      clip-path: polygon(1% 0, 99% 0, 100% 100%, 0 100%);
      position: relative;
      overflow: hidden;
    `;

    const lbadge = document.createElement('div');
    lbadge.textContent = 'LIVE';
    lbadge.style.cssText = `
      background: #f1c40f;
      color: black;
      font-weight: 950;
      padding: 0 15px;
      margin: 0 20px;
      font-size: ${height * 0.14}px;
      clip-path: polygon(0 0, 85% 0, 100% 100%, 0 100%);
      z-index: 2;
    `;
    row2.appendChild(lbadge);

    const tCont = document.createElement('div');
    tCont.style.cssText = `flex-grow: 1; overflow: hidden; height: 100%; display: flex; align-items: center; color: white;`;

    // Use standard scrolling track class for compatibility
    const track = this.createSeamlessScrollingTrack(config.items || [], config);
    track.className = 'uvf-ticker-track'; // Change class for compatibility with updateItemContent
    track.style.fontSize = `${height * 0.15}px`;
    track.style.fontWeight = '600';
    tCont.appendChild(track);
    row2.appendChild(tCont);
    ticker.appendChild(row2);

    this.ensureTickerAnimations();

    // Start cycling if enabled or if we want the "flow"
    if (config.items && config.items.length > 1) {
      this.startItemCycling();
    }

    return ticker;
  }


  private createSeamlessScrollingTrack(items: FlashNewsTickerItem[], config: FlashNewsTickerConfig): HTMLDivElement {
    const track = document.createElement('div');
    track.className = 'uvf-ticker-track-seamless';

    const duration = this.calculateTickerDuration(config);
    track.style.cssText = `
      display: flex;
      white-space: nowrap;
      animation: ticker-scroll ${duration}s linear infinite;
      will-change: transform;
      padding-left: 50%;
    `;

    const content = items.map(item => item.html || item.text).join(config.separator || ' • ');

    // Duplicate content for horizontal seamless loop
    const span1 = document.createElement('span');
    span1.innerHTML = content;
    span1.style.paddingRight = `${config.gap || 100}px`;

    const span2 = document.createElement('span');
    span2.innerHTML = content;
    span2.style.paddingRight = `${config.gap || 100}px`;

    track.appendChild(span1);
    track.appendChild(span2);

    return track;
  }

  private createGlobeElement(animate: boolean): HTMLDivElement {
    const globeContainer = document.createElement('div');
    globeContainer.className = 'uvf-ticker-globe';
    globeContainer.style.cssText = `
      width: 24px;
      height: 24px;
      position: relative;
      flex-shrink: 0;
    `;

    // SVG Globe icon
    globeContainer.innerHTML = `
      <svg viewBox="0 0 24 24" fill="none" style="width: 100%; height: 100%; ${animate ? 'animation: globe-rotate 8s linear infinite;' : ''}">
        <circle cx="12" cy="12" r="10" stroke="#ffffff" stroke-width="1.5" fill="none"/>
        <ellipse cx="12" cy="12" rx="10" ry="4" stroke="#ffffff" stroke-width="1" fill="none"/>
        <ellipse cx="12" cy="12" rx="4" ry="10" stroke="#ffffff" stroke-width="1" fill="none"/>
        <line x1="2" y1="12" x2="22" y2="12" stroke="#ffffff" stroke-width="0.5"/>
        <line x1="12" y1="2" x2="12" y2="22" stroke="#ffffff" stroke-width="0.5"/>
        <path d="M4 8 Q12 6 20 8" stroke="#ffffff" stroke-width="0.5" fill="none"/>
        <path d="M4 16 Q12 18 20 16" stroke="#ffffff" stroke-width="0.5" fill="none"/>
      </svg>
    `;

    return globeContainer;
  }

  private createLiveBadgeElement(pulse: boolean): HTMLDivElement {
    const badge = document.createElement('div');
    badge.className = 'uvf-ticker-live-badge';
    badge.style.cssText = `
      display: flex;
      align-items: center;
      margin-left: auto;
      padding: 2px 8px;
      background: #ff0000;
      border-radius: 3px;
      ${pulse ? 'animation: live-pulse 1.5s ease-in-out infinite;' : ''}
    `;

    // Red dot
    const dot = document.createElement('span');
    dot.style.cssText = `
      width: 8px;
      height: 8px;
      background: #ffffff;
      border-radius: 50%;
      margin-right: 4px;
      ${pulse ? 'animation: dot-blink 1s ease-in-out infinite;' : ''}
    `;
    badge.appendChild(dot);

    // LIVE text
    const text = document.createElement('span');
    text.textContent = 'LIVE';
    text.style.cssText = `
      color: #ffffff;
      font-size: 11px;
      font-weight: 800;
      letter-spacing: 0.5px;
    `;
    badge.appendChild(text);

    return badge;
  }

  private ensureTickerAnimations(): void {
    if (!document.querySelector('#uvf-ticker-animation')) {
      const style = document.createElement('style');
      style.id = this.getElementId('ticker-animation');
      style.textContent = `
        /* Basic ticker scroll */
        @keyframes ticker-scroll {
          0% { transform: translateX(0%); }
          100% { transform: translateX(-100%); }
        }

        /* Globe rotation */
        @keyframes globe-rotate {
          0% { transform: rotate(0deg); }
          100% { transform: rotate(360deg); }
        }

        /* LIVE badge animations */
        @keyframes live-pulse {
          0%, 100% { opacity: 1; transform: scale(1); }
          50% { opacity: 0.85; transform: scale(1.02); }
        }
        @keyframes dot-blink {
          0%, 100% { opacity: 1; }
          50% { opacity: 0.3; }
        }

        /* Intro Animations */
        @keyframes intro-slide-in {
          0% { transform: translateX(-100%); opacity: 0; }
          20% { transform: translateX(0); opacity: 1; }
          80% { transform: translateX(0); opacity: 1; }
          100% { transform: translateX(100%); opacity: 0; }
        }

        @keyframes intro-flash {
          0%, 100% { opacity: 0; }
          10%, 30%, 50%, 70%, 90% { opacity: 1; background: rgba(255,255,255,0.3); }
          20%, 40%, 60%, 80% { opacity: 1; background: transparent; }
        }

        @keyframes intro-scale {
          0% { transform: scale(0); opacity: 0; }
          20% { transform: scale(1.2); opacity: 1; }
          30% { transform: scale(1); }
          80% { transform: scale(1); opacity: 1; }
          100% { transform: scale(0); opacity: 0; }
        }

        @keyframes intro-pulse {
          0% { transform: scale(0.8); opacity: 0; }
          25% { transform: scale(1.1); opacity: 1; }
          50% { transform: scale(1); opacity: 1; }
          75% { transform: scale(1); opacity: 1; }
          100% { transform: scale(0.8); opacity: 0; }
        }

        @keyframes intro-shake {
          0% { transform: translateX(0); opacity: 0; }
          10% { opacity: 1; }
          15%, 35%, 55%, 75% { transform: translateX(-5px); }
          25%, 45%, 65%, 85% { transform: translateX(5px); }
          90% { transform: translateX(0); opacity: 1; }
          100% { transform: translateX(0); opacity: 0; }
        }

        @keyframes intro-none {
          0% { opacity: 1; }
          80% { opacity: 1; }
          100% { opacity: 0; }
        }

        /* Item Transitions */
        @keyframes item-fade-out {
          from { opacity: 1; }
          to { opacity: 0; }
        }
        @keyframes item-fade-in {
          from { opacity: 0; }
          to { opacity: 1; }
        }
        @keyframes item-slide-out {
          from { transform: translateY(0); opacity: 1; }
          to { transform: translateY(-100%); opacity: 0; }
        }
        @keyframes item-slide-in {
          from { transform: translateY(100%); opacity: 0; }
          to { transform: translateY(0); opacity: 1; }
        }

        /* Decorative Separator Animations */
        @keyframes separator-pulse {
          0%, 100% { opacity: 0.8; transform: scaleY(1); }
          50% { opacity: 1; transform: scaleY(1.1); }
        }

        @keyframes chevron-pulse {
          0%, 100% { transform: translateX(0); opacity: 0.8; }
          50% { transform: translateX(3px); opacity: 1; }
        }

        @keyframes diamond-spin {
          from { transform: rotate(0deg); }
          to { transform: rotate(360deg); }
        }
      `;
      document.head.appendChild(style);
    }
  }

  private calculateTickerDuration(config: FlashNewsTickerConfig): number {
    const speed = config.speed || 50;
    if (!config.items || config.items.length === 0) {
      return 15; // Default minimum duration
    }
    const itemsLength = config.items.reduce((acc, item) => acc + item.text.length, 0);
    const avgCharWidth = 10; // Approximate pixel width per character
    const gap = config.gap || 100;
    const separatorWidth = config.separator ? 30 : 0; // Approximate separator width
    const totalWidth = (itemsLength * avgCharWidth) + (config.items.length * gap) + (config.items.length * separatorWidth);
    return Math.max(totalWidth / speed, 15); // Minimum 15s for smooth scrolling
  }

  /**
   * Create professional TV-style ticker with two-line display and item cycling
   */
  private createProfessionalTickerElement(config: FlashNewsTickerConfig, position: 'top' | 'bottom'): HTMLDivElement {
    const broadcastStyle = config.broadcastStyle || {};
    const theme = broadcastStyle.theme || 'breaking-red';
    const twoLineConfig = broadcastStyle.twoLineDisplay || {};
    const itemCycling = config.itemCycling || {};

    // Store config for item cycling
    this.tickerConfig = config;
    this.tickerCurrentItemIndex = 0;

    // Get theme colors
    const themeColors = this.getBroadcastThemeColors(theme, broadcastStyle);

    const ticker = document.createElement('div');
    ticker.className = `uvf-flash-ticker ticker-${position} ticker-professional`;

    const bottomOffset = config.bottomOffset || 0;
    const topOffset = config.topOffset || 0;
    const headerHeight = broadcastStyle.headerHeight || 28;

    // Calculate body height based on two-line config
    const topLineConfig = twoLineConfig.topLine || {};
    const bottomLineConfig = twoLineConfig.bottomLine || {};
    const topLineHeight = topLineConfig.minHeight || 32;
    const bottomLineHeight = bottomLineConfig.height || 28;
    const bodyHeight = twoLineConfig.enabled !== false ? topLineHeight + bottomLineHeight : (config.height || 36);
    const progressHeight = itemCycling.showProgress ? (itemCycling.progressHeight || 3) : 0;
    const totalHeight = headerHeight + bodyHeight + progressHeight;

    ticker.style.cssText = `
      position: absolute;
      left: 0;
      right: 0;
      height: ${totalHeight}px;
      ${position === 'top' ? `top: ${topOffset}px;` : `bottom: ${bottomOffset}px;`}
      overflow: hidden;
      pointer-events: ${itemCycling.pauseOnHover !== false ? 'auto' : 'none'};
      display: flex;
      flex-direction: column;
    `;

    // Create header row
    const header = this.createProfessionalHeader(broadcastStyle, themeColors, headerHeight);
    ticker.appendChild(header);

    // Create body with two-line display
    const body = this.createTwoLineBody(config, twoLineConfig, themeColors.bodyBg, bodyHeight);
    ticker.appendChild(body);

    // Create progress bar if enabled
    if (itemCycling.showProgress) {
      const progressBar = this.createProgressBar(itemCycling);
      ticker.appendChild(progressBar);
    }

    // Create intro overlay (hidden by default)
    const introOverlay = this.createIntroOverlay(broadcastStyle);
    ticker.appendChild(introOverlay);
    this.tickerIntroOverlay = introOverlay;

    // Add hover pause functionality
    if (itemCycling.pauseOnHover !== false && itemCycling.enabled) {
      ticker.addEventListener('mouseenter', () => this.pauseItemCycling());
      ticker.addEventListener('mouseleave', () => this.resumeItemCycling());
    }

    // Add all animation keyframes
    this.ensureTickerAnimations();

    // Start item cycling if enabled
    if (itemCycling.enabled && config.items && config.items.length > 1) {
      // Initial display with intro animation if configured
      const firstItem = config.items[0];
      if (firstItem.showIntro) {
        this.playIntroAnimation(firstItem).then(() => {
          this.startItemCycling();
        });
      } else {
        this.startItemCycling();
      }
    }

    return ticker;
  }

  /**
   * Create professional header row with globe, separator, text, and LIVE badge
   */
  private createProfessionalHeader(
    broadcastStyle: BroadcastStyleConfig,
    themeColors: { headerBg: string; bodyBg: string },
    headerHeight: number
  ): HTMLDivElement {
    const header = document.createElement('div');
    header.className = 'uvf-ticker-header-row';
    header.style.cssText = `
      display: flex;
      align-items: center;
      height: ${headerHeight}px;
      background: ${themeColors.headerBg};
      padding: 0 12px;
      position: relative;
    `;

    // Add globe graphic if enabled
    if (broadcastStyle.showGlobe !== false) {
      const globe = this.createGlobeElement(broadcastStyle.animateGlobe !== false);
      header.appendChild(globe);
    }

    // Add decorative separator between globe and text
    if (broadcastStyle.decorativeShapes?.headerSeparator) {
      const separator = this.createDecorativeSeparator(broadcastStyle.decorativeShapes.headerSeparator);
      header.appendChild(separator);
    } else if (broadcastStyle.showGlobe !== false) {
      // Default simple separator
      const defaultSeparator = document.createElement('div');
      defaultSeparator.className = 'uvf-ticker-separator';
      defaultSeparator.style.cssText = `
        width: 2px;
        height: 18px;
        background: rgba(255,255,255,0.6);
        margin: 0 10px;
      `;
      header.appendChild(defaultSeparator);
    }

    // Add header text (BREAKING NEWS)
    const headerText = document.createElement('span');
    headerText.className = 'uvf-ticker-header-text';
    headerText.textContent = broadcastStyle.headerText || 'BREAKING NEWS';
    headerText.style.cssText = `
      color: ${broadcastStyle.headerTextColor || '#ffffff'};
      font-size: ${broadcastStyle.headerFontSize || 16}px;
      font-weight: 800;
      text-transform: uppercase;
      letter-spacing: 1px;
      text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
    `;
    header.appendChild(headerText);

    // Add LIVE badge if enabled
    if (broadcastStyle.showLiveBadge !== false) {
      const liveBadge = this.createLiveBadgeElement(broadcastStyle.pulseLiveBadge !== false);
      header.appendChild(liveBadge);
    }

    return header;
  }

  /**
   * Create decorative separator element
   */
  private createDecorativeSeparator(config: NonNullable<DecorativeShapesConfig['headerSeparator']>): HTMLDivElement {
    const separator = document.createElement('div');
    separator.className = 'uvf-ticker-separator';

    const width = config.width || 2;
    const height = config.height || 20;
    const color = config.color || '#ffffff';
    const animated = config.animated !== false;
    const type = config.type || 'line';

    let animationStyle = '';
    let content = '';

    switch (type) {
      case 'pulse-line':
        animationStyle = animated ? 'animation: separator-pulse 1.5s ease-in-out infinite;' : '';
        break;
      case 'chevron':
        content = '›';
        animationStyle = animated ? 'animation: chevron-pulse 1s ease-in-out infinite;' : '';
        break;
      case 'diamond':
        content = '◆';
        animationStyle = animated ? 'animation: diamond-spin 3s linear infinite;' : '';
        break;
      case 'dot':
        content = '•';
        animationStyle = animated ? 'animation: dot-blink 1s ease-in-out infinite;' : '';
        break;
      default:
        // Simple line
        break;
    }

    if (type === 'line' || type === 'pulse-line') {
      separator.style.cssText = `
        width: ${width}px;
        height: ${height}px;
        background: ${color};
        margin: 0 10px;
        opacity: 0.8;
        ${animationStyle}
      `;
    } else {
      separator.textContent = content;
      separator.style.cssText = `
        color: ${color};
        font-size: ${height}px;
        margin: 0 10px;
        opacity: 0.8;
        display: flex;
        align-items: center;
        justify-content: center;
        ${animationStyle}
      `;
    }

    return separator;
  }

  /**
   * Create two-line body with static headline and scrolling detail
   */
  private createTwoLineBody(
    config: FlashNewsTickerConfig,
    twoLineConfig: TwoLineDisplayConfig,
    bodyBg: string,
    bodyHeight: number
  ): HTMLDivElement {
    const body = document.createElement('div');
    body.className = 'uvf-ticker-body-row';
    body.style.cssText = `
      display: flex;
      flex-direction: column;
      height: ${bodyHeight}px;
      background: ${bodyBg};
      overflow: hidden;
      position: relative;
    `;

    const topLineConfig = twoLineConfig.topLine || {};
    const bottomLineConfig = twoLineConfig.bottomLine || {};

    // Get first item for initial display
    const firstItem = config.items?.[0];

    // Create headline line (top - static)
    const headlineLine = document.createElement('div');
    headlineLine.className = 'uvf-ticker-headline-line';
    this.tickerHeadlineElement = headlineLine;

    const topLineHeight = topLineConfig.minHeight || 32;
    const topLineFontSize = topLineConfig.fontSize || 16;
    const topLineLineHeight = topLineConfig.lineHeight || 1.3;
    const topLineMultiLine = topLineConfig.multiLine !== false;
    const topLineMaxLines = topLineConfig.maxLines || 2;

    headlineLine.style.cssText = `
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: ${topLineHeight}px;
      padding: ${topLineConfig.padding || 8}px 12px;
      background: ${topLineConfig.backgroundColor || 'transparent'};
      overflow: hidden;
      text-align: center;
    `;

    const headlineText = document.createElement('span');
    headlineText.className = 'uvf-ticker-headline-text';

    // Support multi-line text with CSS
    headlineText.style.cssText = `
      color: ${topLineConfig.textColor || config.textColor || '#ffffff'};
      font-size: ${topLineFontSize}px;
      font-weight: 700;
      line-height: ${topLineLineHeight};
      text-align: center;
      width: 100%;
      ${topLineMultiLine ? `
        display: -webkit-box;
        -webkit-line-clamp: ${topLineMaxLines};
        -webkit-box-orient: vertical;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: normal;
        word-wrap: break-word;
      ` : `
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      `}
    `;

    // Set initial headline content
    if (firstItem) {
      const headlineContent = firstItem.headline || firstItem.text;
      if (firstItem.headlineHtml) {
        headlineText.innerHTML = this.formatHeadlineText(firstItem.headlineHtml, topLineConfig, true);
      } else if (headlineContent) {
        headlineText.innerHTML = this.formatHeadlineText(headlineContent, topLineConfig, false);
      }
    }

    headlineLine.appendChild(headlineText);
    body.appendChild(headlineLine);

    // Add separator line between headline and detail
    if (twoLineConfig.showSeparator !== false) {
      const separatorLine = document.createElement('div');
      separatorLine.style.cssText = `
        height: 1px;
        background: ${twoLineConfig.separatorColor || 'rgba(255,255,255,0.2)'};
        margin: 0 12px;
      `;
      body.appendChild(separatorLine);
    }

    // Create detail line (bottom - scrolling)
    const detailLine = document.createElement('div');
    detailLine.className = 'uvf-ticker-detail-line';
    this.tickerDetailElement = detailLine;

    const bottomLineHeight = bottomLineConfig.height || 28;
    const bottomLineFontSize = bottomLineConfig.fontSize || 14;
    const bottomLineSpeed = bottomLineConfig.speed || 80;

    detailLine.style.cssText = `
      display: flex;
      align-items: center;
      height: ${bottomLineHeight}px;
      background: ${bottomLineConfig.backgroundColor || 'transparent'};
      overflow: hidden;
      position: relative;
    `;

    // Create scrolling track for detail
    const track = document.createElement('div');
    track.className = 'uvf-ticker-track';

    // Calculate scroll duration based on content
    const detailText = firstItem?.text || '';
    const textWidth = detailText.length * 10; // Approximate
    const duration = Math.max(textWidth / bottomLineSpeed, 10);

    track.style.cssText = `
      display: flex;
      white-space: nowrap;
      animation: ticker-scroll ${duration}s linear infinite;
      will-change: transform;
      padding-left: 100%;
    `;

    // Render detail text multiple times for seamless loop
    const renderDetail = (text: string, html?: string) => {
      for (let i = 0; i < 10; i++) {
        const span = document.createElement('span');
        if (html) {
          span.innerHTML = html;
        } else {
          span.textContent = text;
        }
        span.style.cssText = `
          color: ${bottomLineConfig.textColor || config.textColor || '#ffffff'};
          font-size: ${bottomLineFontSize}px;
          font-weight: 500;
          margin-right: 100px;
          display: inline-flex;
          align-items: center;
        `;
        track.appendChild(span);
      }
    };

    if (firstItem) {
      renderDetail(firstItem.text, firstItem.html);
    }

    detailLine.appendChild(track);
    body.appendChild(detailLine);

    return body;
  }

  /**
   * Create progress bar for item cycling
   */
  private createProgressBar(itemCycling: ItemCyclingConfig): HTMLDivElement {
    const progressBar = document.createElement('div');
    progressBar.className = 'uvf-ticker-progress';
    this.tickerProgressBar = progressBar;

    const height = itemCycling.progressHeight || 3;
    progressBar.style.cssText = `
      height: ${height}px;
      background: rgba(0,0,0,0.3);
      position: relative;
      overflow: hidden;
    `;

    const progressFill = document.createElement('div');
    progressFill.className = 'uvf-progress-fill';
    this.tickerProgressFill = progressFill;

    progressFill.style.cssText = `
      height: 100%;
      width: 0%;
      background: ${itemCycling.progressColor || '#ffffff'};
      transition: width 0.1s linear;
    `;

    progressBar.appendChild(progressFill);
    return progressBar;
  }

  /**
   * Create intro overlay for "JUST IN" / "BREAKING" animations
   */
  private createIntroOverlay(broadcastStyle: BroadcastStyleConfig): HTMLDivElement {
    const overlay = document.createElement('div');
    overlay.className = 'uvf-ticker-intro-overlay';
    overlay.style.cssText = `
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      display: none;
      align-items: center;
      justify-content: center;
      background: rgba(200, 0, 0, 0.95);
      z-index: 10;
    `;

    const introText = document.createElement('span');
    introText.className = 'uvf-intro-text';
    introText.style.cssText = `
      color: #ffffff;
      font-size: 24px;
      font-weight: 900;
      text-transform: uppercase;
      letter-spacing: 3px;
      text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
    `;
    introText.textContent = 'BREAKING';

    overlay.appendChild(introText);
    return overlay;
  }

  /**
   * Play intro animation for a news item
   */
  private playIntroAnimation(item: FlashNewsTickerItem): Promise<void> {
    return new Promise((resolve) => {
      if (!this.tickerIntroOverlay || !item.showIntro) {
        resolve();
        return;
      }

      const broadcastStyle = this.tickerConfig?.broadcastStyle || {};
      const animation = item.introAnimation || broadcastStyle.defaultIntroAnimation || 'slide-in';
      const duration = item.introDuration || broadcastStyle.defaultIntroDuration || 2000;
      const introText = item.introText || 'BREAKING';

      // Update intro text
      const textElement = this.tickerIntroOverlay.querySelector('.uvf-intro-text') as HTMLSpanElement;
      if (textElement) {
        textElement.textContent = introText;
      }

      // Apply animation
      this.tickerIntroOverlay.style.display = 'flex';
      this.tickerIntroOverlay.style.animation = `intro-${animation} ${duration}ms ease-out forwards`;

      // Hide after animation completes
      setTimeout(() => {
        if (this.tickerIntroOverlay) {
          this.tickerIntroOverlay.style.display = 'none';
          this.tickerIntroOverlay.style.animation = '';
        }
        resolve();
      }, duration);
    });
  }

  /**
   * Start automatic item cycling
   */
  private startItemCycling(): void {
    if (!this.tickerConfig?.items || this.tickerConfig.items.length <= 1) return;

    const itemCycling = this.tickerConfig.itemCycling || {};
    if (!itemCycling.enabled) return;

    const currentItem = this.tickerConfig.items[this.tickerCurrentItemIndex];
    const duration = (currentItem.duration || itemCycling.defaultDuration || 10) * 1000;

    // Start progress animation
    this.animateProgress(duration);

    // Set timer for next item
    this.tickerCycleTimer = window.setTimeout(() => {
      this.transitionToNextItem();
    }, duration);
  }

  /**
   * Stop item cycling
   */
  private stopItemCycling(): void {
    if (this.tickerCycleTimer) {
      clearTimeout(this.tickerCycleTimer);
      this.tickerCycleTimer = null;
    }
  }

  /**
   * Pause item cycling (for hover)
   */
  private pauseItemCycling(): void {
    if (!this.tickerCycleTimer || this.tickerIsPaused) return;

    this.tickerIsPaused = true;
    this.tickerPauseStartTime = Date.now();

    // Calculate remaining time
    const itemCycling = this.tickerConfig?.itemCycling || {};
    const currentItem = this.tickerConfig?.items?.[this.tickerCurrentItemIndex];
    const totalDuration = (currentItem?.duration || itemCycling.defaultDuration || 10) * 1000;

    // Stop the timer
    clearTimeout(this.tickerCycleTimer);
    this.tickerCycleTimer = null;

    // Pause progress bar animation
    if (this.tickerProgressFill) {
      const computedWidth = window.getComputedStyle(this.tickerProgressFill).width;
      this.tickerProgressFill.style.transition = 'none';
      this.tickerProgressFill.style.width = computedWidth;
    }
  }

  /**
   * Resume item cycling (after hover)
   */
  private resumeItemCycling(): void {
    if (!this.tickerIsPaused) return;

    this.tickerIsPaused = false;

    // Calculate remaining time based on progress
    const itemCycling = this.tickerConfig?.itemCycling || {};
    const currentItem = this.tickerConfig?.items?.[this.tickerCurrentItemIndex];
    const totalDuration = (currentItem?.duration || itemCycling.defaultDuration || 10) * 1000;

    // Get current progress percentage
    let remainingTime = totalDuration;
    if (this.tickerProgressFill) {
      const currentWidth = parseFloat(this.tickerProgressFill.style.width) || 0;
      remainingTime = totalDuration * (1 - currentWidth / 100);
    }

    // Resume progress animation
    this.animateProgress(remainingTime);

    // Set timer for remaining time
    this.tickerCycleTimer = window.setTimeout(() => {
      this.transitionToNextItem();
    }, remainingTime);
  }

  /**
   * Animate progress bar
   */
  private animateProgress(duration: number): void {
    if (!this.tickerProgressFill) return;

    // Reset to start
    this.tickerProgressFill.style.transition = 'none';
    this.tickerProgressFill.style.width = '0%';

    // Force reflow
    this.tickerProgressFill.offsetHeight;

    // Animate to 100%
    this.tickerProgressFill.style.transition = `width ${duration}ms linear`;
    this.tickerProgressFill.style.width = '100%';
  }

  /**
   * Transition to the next news item
   */
  private async transitionToNextItem(): Promise<void> {
    if (!this.tickerConfig?.items || this.tickerConfig.items.length <= 1) return;

    const itemCycling = this.tickerConfig.itemCycling || {};
    const transitionType = itemCycling.transitionType || 'fade';
    const transitionDuration = itemCycling.transitionDuration || 500;

    // Move to next item
    this.tickerCurrentItemIndex = (this.tickerCurrentItemIndex + 1) % this.tickerConfig.items.length;
    const nextItem = this.tickerConfig.items[this.tickerCurrentItemIndex];

    // Play intro animation if configured
    if (nextItem.showIntro) {
      await this.playIntroAnimation(nextItem);
    }

    // Apply transition animation
    await this.applyItemTransition(transitionType, transitionDuration, nextItem);

    // Continue cycling
    this.startItemCycling();
  }

  /**
   * Apply transition animation between items
   */
  private applyItemTransition(
    transitionType: string,
    duration: number,
    item: FlashNewsTickerItem
  ): Promise<void> {
    return new Promise((resolve) => {
      const headline = this.tickerHeadlineElement?.querySelector('.uvf-ticker-headline-text') as HTMLSpanElement;
      const detailTrack = this.tickerDetailElement?.querySelector('.uvf-ticker-track') as HTMLDivElement;

      if (!headline || !detailTrack) {
        this.updateItemContent(item);
        resolve();
        return;
      }

      if (transitionType === 'none') {
        this.updateItemContent(item);
        resolve();
        return;
      }

      // Apply fade or slide out animation
      const outAnimation = transitionType === 'slide' ? 'item-slide-out' : 'item-fade-out';
      const inAnimation = transitionType === 'slide' ? 'item-slide-in' : 'item-fade-in';

      headline.style.animation = `${outAnimation} ${duration / 2}ms ease-out forwards`;

      setTimeout(() => {
        // Update content
        this.updateItemContent(item);

        // Apply fade or slide in animation
        headline.style.animation = `${inAnimation} ${duration / 2}ms ease-out forwards`;

        setTimeout(() => {
          headline.style.animation = '';
          resolve();
        }, duration / 2);
      }, duration / 2);
    });
  }

  /**
   * Format headline text with optional forced line breaks
   */
  private formatHeadlineText(text: string, topLineConfig: { forceMultiLine?: boolean; breakAt?: number | string }, isHtml: boolean = false): string {
    if (!topLineConfig.forceMultiLine) {
      // If it's HTML, return as-is; otherwise escape for safety
      return isHtml ? text : this.escapeHtml(text);
    }

    // Calculate break position
    let breakPosition: number;
    const breakAt = topLineConfig.breakAt || '50%';

    if (typeof breakAt === 'string' && breakAt.endsWith('%')) {
      const percentage = parseFloat(breakAt) / 100;
      breakPosition = Math.floor(text.length * percentage);
    } else {
      breakPosition = typeof breakAt === 'number' ? breakAt : Math.floor(text.length / 2);
    }

    // Find the nearest space to break at (for better readability)
    let actualBreakPos = breakPosition;

    // Look for a space near the break position
    const searchRange = Math.min(15, Math.floor(text.length / 4)); // Search within ~15 chars or 25% of text
    for (let i = 0; i <= searchRange; i++) {
      // Check forward first, then backward
      if (breakPosition + i < text.length && text[breakPosition + i] === ' ') {
        actualBreakPos = breakPosition + i;
        break;
      }
      if (breakPosition - i >= 0 && text[breakPosition - i] === ' ') {
        actualBreakPos = breakPosition - i;
        break;
      }
    }

    // Split and join with <br>
    const line1 = text.substring(0, actualBreakPos).trim();
    const line2 = text.substring(actualBreakPos).trim();

    if (isHtml) {
      // For HTML content, try to insert <br> at a reasonable position
      return `${line1}<br>${line2}`;
    }

    return `${this.escapeHtml(line1)}<br>${this.escapeHtml(line2)}`;
  }

  /**
   * Escape HTML special characters
   */
  private escapeHtml(text: string): string {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
  }

  /**
   * Update headline and detail content for current item
   */
  private updateItemContent(item: FlashNewsTickerItem): void {
    // Update headline
    const headline = this.tickerHeadlineElement?.querySelector('.uvf-ticker-headline-text') as HTMLSpanElement;
    if (headline) {
      const topLineConfig = this.tickerConfig?.broadcastStyle?.twoLineDisplay?.topLine || {};
      const headlineContent = item.headline || item.text;

      if (item.headlineHtml) {
        headline.innerHTML = this.formatHeadlineText(item.headlineHtml, topLineConfig, true);
      } else if (headlineContent) {
        headline.innerHTML = this.formatHeadlineText(headlineContent, topLineConfig, false);
      }
    }

    // Update detail track
    const detailTrack = this.tickerDetailElement?.querySelector('.uvf-ticker-track') as HTMLDivElement;
    if (detailTrack) {
      // Clear existing content
      detailTrack.innerHTML = '';

      // Get style config
      const bottomLineConfig = this.tickerConfig?.broadcastStyle?.twoLineDisplay?.bottomLine || {};
      const bottomLineFontSize = bottomLineConfig.fontSize || 14;

      // Re-render detail text
      for (let i = 0; i < 10; i++) {
        const span = document.createElement('span');
        if (item.html) {
          span.innerHTML = item.html;
        } else {
          span.textContent = item.text;
        }
        span.style.cssText = `
          color: ${bottomLineConfig.textColor || this.tickerConfig?.textColor || '#ffffff'};
          font-size: ${bottomLineFontSize}px;
          font-weight: 500;
          margin-right: 100px;
          display: inline-flex;
          align-items: center;
        `;
        detailTrack.appendChild(span);
      }

      // Update scroll speed
      const bottomLineSpeed = bottomLineConfig.speed || 80;
      const textWidth = item.text.length * 10;
      const duration = Math.max(textWidth / bottomLineSpeed, 10);
      detailTrack.style.animation = `ticker-scroll ${duration}s linear infinite`;
    }
  }

  setAutoQuality(enabled: boolean): void {
    this.autoQuality = enabled;

    if (this.hls) {
      if (enabled) {
        // Apply quality filter constraints when enabling auto
        if (this.qualityFilter || (this.premiumQualities && this.premiumQualities.enabled)) {
          this.applyHLSQualityFilter();
        } else {
          this.hls.currentLevel = -1;
        }
      } else {
        this.hls.currentLevel = this.currentQualityIndex;
      }
    } else if (this.dash) {
      this.dash.updateSettings({
        streaming: {
          abr: {
            autoSwitchBitrate: {
              video: enabled
            }
          }
        }
      });

      // Apply quality filter constraints when enabling auto
      if (enabled && (this.qualityFilter || (this.premiumQualities && this.premiumQualities.enabled))) {
        this.applyDASHQualityFilter();
      }
    }

    // Update badge to reflect auto quality state
    this.updateQualityBadgeText();
  }

  async enterFullscreen(): Promise<void> {
    if (!this.playerWrapper) return;

    try {
      // iOS Safari special handling - use video element fullscreen
      if (this.isIOSDevice() && this.video) {
        this.debugLog('iOS device detected - using video element fullscreen');

        try {
          // iOS Safari supports video fullscreen but not element fullscreen
          if ((this.video as any).webkitEnterFullscreen) {
            await (this.video as any).webkitEnterFullscreen();
            this.playerWrapper.classList.add('uvf-fullscreen');
            this.emit('onFullscreenChanged', true);
            // Lock to landscape orientation
            await this.lockOrientationLandscape();
            return;
          } else if ((this.video as any).webkitRequestFullscreen) {
            await (this.video as any).webkitRequestFullscreen();
            this.playerWrapper.classList.add('uvf-fullscreen');
            this.emit('onFullscreenChanged', true);
            // Lock to landscape orientation
            await this.lockOrientationLandscape();
            return;
          }
        } catch (iosError) {
          this.debugWarn('iOS video fullscreen failed:', (iosError as Error).message);
          // Fall through to try standard fullscreen
        }
      }

      // Check if fullscreen is supported for non-iOS devices
      if (!this.isFullscreenSupported()) {
        this.debugWarn('Fullscreen not supported by browser');
        // On mobile devices that don't support fullscreen, show a helpful message
        if (this.isMobileDevice()) {
          this.showShortcutIndicator('Rotate device for fullscreen experience');
        }
        return;
      }

      // Check if already in fullscreen
      if (this.isFullscreen()) {
        this.debugLog('Already in fullscreen mode');
        return;
      }

      // Use custom fullscreenElement if provided (e.g. outer React wrapper so PlaylistPanel is included),
      // otherwise fall back to the player wrapper.
      const element: HTMLElement = (this.config as any).fullscreenElement || this.playerWrapper;

      // Try different fullscreen APIs with better error handling
      let fullscreenSuccess = false;

      if (element.requestFullscreen) {
        try {
          await element.requestFullscreen();
          fullscreenSuccess = true;
        } catch (err) {
          this.debugWarn('Standard fullscreen request failed:', (err as Error).message);
        }
      } else if ((element as any).webkitRequestFullscreen) {
        try {
          await (element as any).webkitRequestFullscreen();
          fullscreenSuccess = true;
        } catch (err) {
          this.debugWarn('WebKit fullscreen request failed:', (err as Error).message);
        }
      } else if ((element as any).mozRequestFullScreen) {
        try {
          await (element as any).mozRequestFullScreen();
          fullscreenSuccess = true;
        } catch (err) {
          this.debugWarn('Mozilla fullscreen request failed:', (err as Error).message);
        }
      } else if ((element as any).msRequestFullscreen) {
        try {
          await (element as any).msRequestFullscreen();
          fullscreenSuccess = true;
        } catch (err) {
          this.debugWarn('MS fullscreen request failed:', (err as Error).message);
        }
      }

      if (fullscreenSuccess) {
        // Add fullscreen class for styling
        this.playerWrapper.classList.add('uvf-fullscreen');
        this.emit('onFullscreenChanged', true);

        // Force YouTube custom controls to be visible in fullscreen
        // Lock to landscape orientation on mobile devices
        await this.lockOrientationLandscape();
      } else {
        this.debugWarn('All fullscreen methods failed');

        // Provide helpful feedback based on device
        if (this.isIOSDevice()) {
          this.showShortcutIndicator('Fullscreen not available - use device controls');
        } else if (this.isAndroidDevice()) {
          this.showShortcutIndicator('Try rotating device to landscape');
        } else {
          this.showShortcutIndicator('Fullscreen not supported in this browser');
        }
      }

    } catch (error) {
      this.debugWarn('Failed to enter fullscreen:', (error as Error).message);
      // Don't re-throw the error to prevent breaking the user experience
    }
  }

  async exitFullscreen(): Promise<void> {
    try {
      // iOS Safari special handling
      if (this.isIOSDevice() && this.video) {
        try {
          if ((this.video as any).webkitExitFullscreen) {
            await (this.video as any).webkitExitFullscreen();
            if (this.playerWrapper) {
              this.playerWrapper.classList.remove('uvf-fullscreen');
            }
            this.emit('onFullscreenChanged', false);
            // Unlock orientation
            await this.unlockOrientation();
            return;
          }
        } catch (iosError) {
          this.debugWarn('iOS video exit fullscreen failed:', (iosError as Error).message);
          // Fall through to try standard methods
        }
      }

      // Check if we're actually in fullscreen
      if (!this.isFullscreen()) {
        this.debugLog('Not in fullscreen mode');
        return;
      }

      // Try different exit fullscreen methods
      let exitSuccess = false;

      if (document.exitFullscreen) {
        try {
          await document.exitFullscreen();
          exitSuccess = true;
        } catch (err) {
          this.debugWarn('Standard exit fullscreen failed:', (err as Error).message);
        }
      } else if ((document as any).webkitExitFullscreen) {
        try {
          await (document as any).webkitExitFullscreen();
          exitSuccess = true;
        } catch (err) {
          this.debugWarn('WebKit exit fullscreen failed:', (err as Error).message);
        }
      } else if ((document as any).mozCancelFullScreen) {
        try {
          await (document as any).mozCancelFullScreen();
          exitSuccess = true;
        } catch (err) {
          this.debugWarn('Mozilla exit fullscreen failed:', (err as Error).message);
        }
      } else if ((document as any).msExitFullscreen) {
        try {
          await (document as any).msExitFullscreen();
          exitSuccess = true;
        } catch (err) {
          this.debugWarn('MS exit fullscreen failed:', (err as Error).message);
        }
      }

      if (exitSuccess || !this.isFullscreen()) {
        // Remove fullscreen class
        if (this.playerWrapper) {
          this.playerWrapper.classList.remove('uvf-fullscreen');
        }
        this.emit('onFullscreenChanged', false);
        // Unlock orientation
        await this.unlockOrientation();
      } else {
        this.debugWarn('All exit fullscreen methods failed');
        // Still remove the class to keep UI consistent
        if (this.playerWrapper) {
          this.playerWrapper.classList.remove('uvf-fullscreen');
        }
      }

    } catch (error) {
      this.debugWarn('Failed to exit fullscreen:', (error as Error).message);
      // Don't re-throw the error to prevent breaking the user experience
    }
  }

  async enterPictureInPicture(): Promise<void> {
    if (!this.video) return;

    try {
      if ((this.video as any).requestPictureInPicture) {
        await (this.video as any).requestPictureInPicture();
      } else {
        throw new Error('Picture-in-Picture not supported');
      }
    } catch (error) {
      console.error('Failed to enter PiP:', error);
      throw error;
    }
  }

  async exitPictureInPicture(): Promise<void> {
    try {
      if ((document as any).exitPictureInPicture) {
        await (document as any).exitPictureInPicture();
      }
    } catch (error) {
      this.debugWarn('Failed to exit PiP:', (error as Error).message);
      // Don't re-throw the error to prevent breaking the user experience
    }
  }

  /**
   * Focuses the player wrapper to enable keyboard shortcuts
   */
  focusPlayer(): void {
    if (this.playerWrapper) {
      this.playerWrapper.focus();
      this.debugLog('Player focused programmatically');
    }
  }

  /**
   * Shows a helpful tip to the user about fullscreen options
   */
  showFullscreenTip(): void {
    this.showShortcutIndicator('💡 Double-click or use ⌨️ F key for fullscreen');

    // Also show in debug log
    this.debugLog('Tip: Double-click the video area or press F key for fullscreen, or use the fullscreen button in controls');
  }

  /**
   * Detects if we're running in Brave browser
   */
  private isBraveBrowser(): boolean {
    // Multiple ways to detect Brave browser
    const userAgent = navigator.userAgent.toLowerCase();
    const isBrave = (
      // Check for Brave-specific user agent
      userAgent.includes('brave') ||
      // Check for Brave's navigator.brave object
      !!(navigator as any).brave ||
      // Check for Brave's specific properties
      (window as any).chrome && (window as any).chrome.app && (window as any).chrome.app.isInstalled === false
    );

    this.debugLog('Browser detection - Is Brave:', isBrave, 'User Agent:', userAgent);
    return isBrave;
  }

  /**
   * Checks fullscreen permissions and site settings
   */
  private async checkFullscreenPermissions(): Promise<void> {
    try {
      // Check if fullscreen is enabled
      const fullscreenEnabled = document.fullscreenEnabled ||
        (document as any).webkitFullscreenEnabled ||
        (document as any).mozFullScreenEnabled ||
        (document as any).msFullscreenEnabled;

      this.debugLog('Fullscreen permissions check:', {
        fullscreenEnabled,
        documentFullscreenEnabled: document.fullscreenEnabled,
        webkitEnabled: (document as any).webkitFullscreenEnabled,
        mozEnabled: (document as any).mozFullScreenEnabled,
        msEnabled: (document as any).msFullscreenEnabled,
        currentOrigin: window.location.origin,
        currentHref: window.location.href,
        isSecureContext: window.isSecureContext,
        protocol: window.location.protocol,
        isBrave: this.isBraveBrowser(),
        isPrivate: this.isPrivateWindow()
      });

      // Check permissions API if available
      if ('permissions' in navigator) {
        try {
          const permission = await (navigator as any).permissions.query({ name: 'fullscreen' });
          this.debugLog('Fullscreen permission state:', permission.state);
        } catch (err) {
          this.debugLog('Permissions API check failed:', (err as Error).message);
        }
      }

    } catch (error) {
      this.debugWarn('Permission check failed:', (error as Error).message);
    }
  }

  /**
   * Detects if running in private/incognito mode
   */
  private isPrivateWindow(): boolean {
    try {
      // Different methods for different browsers
      if ('webkitRequestFileSystem' in window) {
        // Chrome/Edge detection
        return new Promise<boolean>((resolve) => {
          (window as any).webkitRequestFileSystem(
            (window as any).TEMPORARY,
            1,
            () => resolve(false), // Not private
            () => resolve(true)   // Private
          );
        }) as any;
      }

      // Firefox detection
      if ('MozAppearance' in document.documentElement.style) {
        if (window.indexedDB === null) return true;
        if (window.indexedDB === undefined) return true;
      }

      // Safari detection
      try {
        window.localStorage.setItem('test', '1');
        window.localStorage.removeItem('test');
        return false;
      } catch {
        return true;
      }

    } catch {
      return false;
    }

    return false;
  }

  /**
   * Triggers the fullscreen button reliably with Brave-specific enhancements
   */
  triggerFullscreenButton(): void {
    const fullscreenBtn = this.cachedElements.fullscreenBtn;

    // Enhanced debugging for Brave browser
    const isBrave = this.isBraveBrowser();
    const isPrivate = this.isPrivateWindow();

    this.debugLog('Fullscreen trigger attempt:', {
      buttonExists: !!fullscreenBtn,
      isBrave,
      isPrivate,
      currentFullscreenElement: document.fullscreenElement,
      timestamp: Date.now(),
      lastUserInteraction: this.lastUserInteraction,
      timeSinceInteraction: Date.now() - this.lastUserInteraction
    });

    // Run permissions check
    this.checkFullscreenPermissions();

    if (fullscreenBtn) {
      this.debugLog('Triggering fullscreen button click');

      // Special handling for Brave browser
      if (isBrave) {
        this.debugLog('Applying Brave browser specific fullscreen handling');

        // For Brave, we need to ensure the gesture is absolutely fresh
        if (Date.now() - this.lastUserInteraction > 1000) {
          this.debugWarn('User gesture may be stale for Brave browser');
          this.showTemporaryMessage('Click the fullscreen button directly in Brave browser');
          return;
        }

        // Request permissions first in Brave if needed
        this.requestFullscreenPermissionBrave().then(() => {
          this.performFullscreenButtonClick(fullscreenBtn);
        }).catch(() => {
          this.performFullscreenButtonClick(fullscreenBtn);
        });
      } else {
        this.performFullscreenButtonClick(fullscreenBtn);
      }

    } else {
      this.debugWarn('Fullscreen button not found');
      this.showShortcutIndicator('Fullscreen Button Missing');

      // Enhanced guidance for Brave
      if (isBrave) {
        this.showTemporaryMessage('Brave: Please use fullscreen button in controls');
      } else {
        this.showTemporaryMessage('Press F key when player controls are visible');
      }
    }
  }

  /**
   * Performs the actual fullscreen button click with multiple event types
   */
  private performFullscreenButtonClick(fullscreenBtn: HTMLElement): void {
    // Create multiple types of events to ensure maximum compatibility
    const events = [
      new MouseEvent('mousedown', {
        bubbles: true,
        cancelable: true,
        view: window,
        detail: 1,
        button: 0,
        buttons: 1,
        isTrusted: true
      } as any),
      new MouseEvent('mouseup', {
        bubbles: true,
        cancelable: true,
        view: window,
        detail: 1,
        button: 0,
        buttons: 0,
        isTrusted: true
      } as any),
      new MouseEvent('click', {
        bubbles: true,
        cancelable: true,
        view: window,
        detail: 1,
        button: 0,
        buttons: 0,
        isTrusted: true
      } as any)
    ];

    // Log each event dispatch
    this.debugLog('Dispatching mouse events:', events.length);

    // Dispatch all events in sequence
    events.forEach((event, index) => {
      try {
        fullscreenBtn.dispatchEvent(event);
        this.debugLog(`Event ${index + 1} dispatched:`, event.type);
      } catch (error) {
        this.debugWarn(`Event ${index + 1} dispatch failed:`, (error as Error).message);
      }
    });

    // Also try direct click method with enhanced error handling
    try {
      fullscreenBtn.click();
      this.debugLog('Direct button click executed');
    } catch (error) {
      this.debugWarn('Direct button click failed:', (error as Error).message);
    }

    // Focus the button to ensure gesture context
    try {
      fullscreenBtn.focus();
      setTimeout(() => fullscreenBtn.blur(), 100);
    } catch (error) {
      this.debugLog('Button focus failed:', (error as Error).message);
    }

    // Show that we're attempting fullscreen via button
    this.showShortcutIndicator('Fullscreen');
  }

  /**
   * Requests fullscreen permission specifically for Brave browser
   */
  private async requestFullscreenPermissionBrave(): Promise<void> {
    try {
      // Check if we can request permission
      if ('permissions' in navigator && 'request' in (navigator as any).permissions) {
        await (navigator as any).permissions.request({ name: 'fullscreen' });
        this.debugLog('Brave fullscreen permission requested');
      }
    } catch (error) {
      this.debugLog('Brave permission request failed:', (error as Error).message);
      // Don't throw, continue with normal flow
    }
  }

  /**
   * Enhanced fullscreen method with specific Brave browser support
   */
  async enterFullscreenWithBraveSupport(): Promise<void> {
    if (!this.playerWrapper) {
      throw new Error('Player wrapper not available');
    }

    this.debugLog('Attempting Brave-specific fullscreen entry');

    // First, check if we can request permissions
    await this.requestFullscreenPermissionBrave();

    // Check current fullscreen state
    if (document.fullscreenElement ||
      (document as any).webkitFullscreenElement ||
      (document as any).mozFullScreenElement ||
      (document as any).msFullscreenElement) {
      this.debugLog('Already in fullscreen mode');
      return;
    }

    // Enhanced permission and capability checking for Brave
    const fullscreenEnabled = document.fullscreenEnabled ||
      (document as any).webkitFullscreenEnabled ||
      (document as any).mozFullScreenEnabled ||
      (document as any).msFullscreenEnabled;

    if (!fullscreenEnabled) {
      throw new Error('Fullscreen not supported or disabled in Brave settings');
    }

    // Check if the site has fullscreen blocked
    if ('permissions' in navigator) {
      try {
        const permission = await (navigator as any).permissions.query({ name: 'fullscreen' });
        this.debugLog('Brave fullscreen permission state:', permission.state);

        if (permission.state === 'denied') {
          throw new Error('Fullscreen permission denied in Brave site settings');
        }
      } catch (permError) {
        this.debugLog('Permission check failed:', (permError as Error).message);
      }
    }

    // Try multiple fullscreen methods with proper error handling
    // Use custom fullscreenElement if provided (e.g. outer React wrapper) so playlist panel
    // and other sibling elements are included in the fullscreen viewport.
    const fsElement: HTMLElement = (this.config as any).fullscreenElement || this.playerWrapper;
    let fullscreenError: Error | null = null;

    try {
      // Try standard fullscreen API first
      if (fsElement.requestFullscreen) {
        this.debugLog('Attempting standard requestFullscreen()');
        await fsElement.requestFullscreen({
          navigationUI: 'hide'
        } as any);
      }
      // Webkit (Safari/Chrome-based)
      else if ((fsElement as any).webkitRequestFullscreen) {
        this.debugLog('Attempting webkitRequestFullscreen()');
        await (fsElement as any).webkitRequestFullscreen();
      }
      // Firefox
      else if ((fsElement as any).mozRequestFullScreen) {
        this.debugLog('Attempting mozRequestFullScreen()');
        await (fsElement as any).mozRequestFullScreen();
      }
      // IE/Edge
      else if ((fsElement as any).msRequestFullscreen) {
        this.debugLog('Attempting msRequestFullscreen()');
        await (fsElement as any).msRequestFullscreen();
      }
      else {
        throw new Error('No fullscreen API available');
      }

      // If we get here, fullscreen was successful
      // Keep adding the class on playerWrapper so CSS rules (.uvf-player-wrapper.uvf-fullscreen) still apply
      this.playerWrapper.classList.add('uvf-fullscreen');
      this.emit('onFullscreenChanged', true);
      this.debugLog('Brave fullscreen entry successful');

    } catch (error) {
      fullscreenError = error as Error;
      this.debugWarn('Brave fullscreen attempt failed:', fullscreenError.message);

      // Provide specific guidance for common Brave issues
      if (fullscreenError.message.includes('denied') ||
        fullscreenError.message.includes('not allowed')) {
        throw new Error('Brave Browser: Fullscreen blocked. Check site permissions in Settings > Site and Shields Settings');
      } else if (fullscreenError.message.includes('gesture') ||
        fullscreenError.message.includes('user activation')) {
        throw new Error('Brave Browser: User interaction required. Click the fullscreen button directly');
      } else {
        throw new Error(`Brave Browser: ${fullscreenError.message}`);
      }
    }
  }

  /**
   * Shows a temporary message to the user
   */
  showTemporaryMessage(message: string): void {
    const existingMsg = this.getElement('temp-message');
    if (existingMsg) {
      existingMsg.remove();
    }

    const msgDiv = document.createElement('div');
    msgDiv.id = this.getElementId('temp-message');
    msgDiv.textContent = message;
    msgDiv.style.cssText = `
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background: rgba(0, 0, 0, 0.8);
      color: white;
      padding: 12px 24px;
      border-radius: 6px;
      font-size: 14px;
      z-index: 10001;
      pointer-events: none;
    `;

    document.body.appendChild(msgDiv);

    setTimeout(() => {
      if (msgDiv.parentElement) {
        msgDiv.remove();
      }
    }, 3000);
  }

  /**
   * Enters fullscreen synchronously to preserve user gesture context
   */
  enterFullscreenSynchronously(): void {
    if (!this.playerWrapper) {
      throw new Error('Player wrapper not available');
    }

    // Check if fullscreen is supported
    if (!document.fullscreenEnabled &&
      !(document as any).webkitFullscreenEnabled &&
      !(document as any).mozFullScreenEnabled &&
      !(document as any).msFullscreenEnabled) {
      throw new Error('Fullscreen not supported by browser');
    }

    // Check if already in fullscreen
    if (document.fullscreenElement ||
      (document as any).webkitFullscreenElement ||
      (document as any).mozFullScreenElement ||
      (document as any).msFullscreenElement) {
      this.debugLog('Already in fullscreen mode');
      return;
    }

    this.debugLog('Attempting synchronous fullscreen');

    // Use custom fullscreenElement if provided, so React siblings (like PlaylistPanel) are included
    const element: HTMLElement = (this.config as any).fullscreenElement || this.playerWrapper;

    // Call fullscreen API synchronously (no await) to preserve user gesture
    if (element.requestFullscreen) {
      element.requestFullscreen().then(() => {
        this.debugLog('Successfully entered fullscreen via requestFullscreen');
        this.playerWrapper?.classList.add('uvf-fullscreen');
        this.emit('onFullscreenChanged', true);
      }).catch((error) => {
        this.debugWarn('requestFullscreen failed:', error.message);
        throw error;
      });
    } else if ((element as any).webkitRequestFullscreen) {
      (element as any).webkitRequestFullscreen();
      this.debugLog('Successfully requested fullscreen via webkitRequestFullscreen');
      this.playerWrapper?.classList.add('uvf-fullscreen');
      this.emit('onFullscreenChanged', true);
    } else if ((element as any).mozRequestFullScreen) {
      (element as any).mozRequestFullScreen();
      this.debugLog('Successfully requested fullscreen via mozRequestFullScreen');
      this.playerWrapper?.classList.add('uvf-fullscreen');
      this.emit('onFullscreenChanged', true);
    } else if ((element as any).msRequestFullscreen) {
      (element as any).msRequestFullscreen();
      this.debugLog('Successfully requested fullscreen via msRequestFullscreen');
      this.playerWrapper?.classList.add('uvf-fullscreen');
      this.emit('onFullscreenChanged', true);
    } else {
      throw new Error('Fullscreen API not supported by this browser');
    }
  }

  /**
   * Requests fullscreen with proper user gesture context
   */
  async requestFullscreenWithUserGesture(event: Event): Promise<boolean> {
    if (!this.playerWrapper) return false;

    try {
      // Check if fullscreen is supported
      if (!document.fullscreenEnabled &&
        !(document as any).webkitFullscreenEnabled &&
        !(document as any).mozFullScreenEnabled &&
        !(document as any).msFullscreenEnabled) {
        this.debugWarn('Fullscreen not supported by browser');
        return false;
      }

      // Check if already in fullscreen
      if (document.fullscreenElement ||
        (document as any).webkitFullscreenElement ||
        (document as any).mozFullScreenElement ||
        (document as any).msFullscreenElement) {
        this.debugLog('Already in fullscreen mode');
        return false;
      }

      // Check if this is within a reasonable time of user interaction
      const timeSinceInteraction = Date.now() - this.lastUserInteraction;
      this.debugLog('Attempting fullscreen within user gesture context', {
        eventType: event.type,
        timeSinceInteraction,
        isTrusted: event.isTrusted
      });

      // Use custom fullscreenElement if provided (e.g. outer React wrapper so PlaylistPanel is included),
      // otherwise fall back to the player wrapper.
      const element: HTMLElement = (this.config as any).fullscreenElement || this.playerWrapper;

      // Try fullscreen immediately while in the event context
      if (element.requestFullscreen) {
        await element.requestFullscreen();
      } else if ((element as any).webkitRequestFullscreen) {
        await (element as any).webkitRequestFullscreen();
      } else if ((element as any).mozRequestFullScreen) {
        await (element as any).mozRequestFullScreen();
      } else if ((element as any).msRequestFullscreen) {
        await (element as any).msRequestFullscreen();
      } else {
        this.debugWarn('Fullscreen API not supported by this browser');
        return false;
      }

      // Add fullscreen class for styling
      this.playerWrapper.classList.add('uvf-fullscreen');
      this.emit('onFullscreenChanged', true);
      this.debugLog('Successfully entered fullscreen');
      return true;

    } catch (error) {
      this.debugWarn('Failed to enter fullscreen:', (error as Error).message);
      return false;
    }
  }

  /**
   * Shows clear fullscreen instructions overlay
   */
  showFullscreenInstructions(): void {
    // Remove any existing instruction overlay
    const existingOverlay = this.getElement('fullscreen-instructions');
    if (existingOverlay) {
      existingOverlay.remove();
    }

    // Create instruction overlay
    const overlay = document.createElement('div');
    overlay.id = this.getElementId('fullscreen-instructions');
    overlay.innerHTML = `
      <div class="uvf-fullscreen-instruction-content">
        <div class="uvf-fullscreen-icon">⛶</div>
        <h3>Enter Fullscreen</h3>
        <p>Click the fullscreen button in the player controls</p>
        <div class="uvf-fullscreen-pointer">👆 Look for this icon in the bottom right</div>
        <button class="uvf-instruction-close" onclick="this.parentElement.parentElement.remove()">Got it</button>
      </div>
    `;

    overlay.style.cssText = `
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.9);
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 10000;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      color: white;
      text-align: center;
    `;

    const content = overlay.querySelector('.uvf-fullscreen-instruction-content') as HTMLElement;
    if (content) {
      content.style.cssText = `
        background: rgba(255, 255, 255, 0.1);
        padding: 40px;
        border-radius: 12px;
        border: 2px solid var(--uvf-accent-1, #ff0000);
        backdrop-filter: blur(10px);
        max-width: 400px;
        animation: fadeIn 0.3s ease-out;
      `;
    }

    const icon = overlay.querySelector('.uvf-fullscreen-icon') as HTMLElement;
    if (icon) {
      icon.style.cssText = `
        font-size: 48px;
        margin-bottom: 16px;
      `;
    }

    const title = overlay.querySelector('h3') as HTMLElement;
    if (title) {
      title.style.cssText = `
        margin: 0 0 16px 0;
        font-size: 24px;
        font-weight: 600;
      `;
    }

    const text = overlay.querySelector('p') as HTMLElement;
    if (text) {
      text.style.cssText = `
        margin: 0 0 16px 0;
        font-size: 16px;
        opacity: 0.9;
      `;
    }

    const pointer = overlay.querySelector('.uvf-fullscreen-pointer') as HTMLElement;
    if (pointer) {
      pointer.style.cssText = `
        font-size: 14px;
        opacity: 0.8;
        margin-bottom: 24px;
      `;
    }

    const button = overlay.querySelector('.uvf-instruction-close') as HTMLElement;
    if (button) {
      button.style.cssText = `
        background: var(--uvf-accent-1, #ff0000);
        color: white;
        border: none;
        padding: 12px 24px;
        border-radius: 6px;
        font-size: 14px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.2s ease;
      `;

      button.addEventListener('mouseenter', () => {
        button.style.transform = 'scale(1.05)';
        button.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.3)';
      });

      button.addEventListener('mouseleave', () => {
        button.style.transform = 'scale(1)';
        button.style.boxShadow = 'none';
      });
    }

    // Add CSS animation
    if (!this.getElement('fullscreen-animation-styles')) {
      const style = document.createElement('style');
      style.id = this.getElementId('fullscreen-animation-styles');
      style.textContent = `
        @keyframes fadeIn {
          from { opacity: 0; transform: scale(0.9); }
          to { opacity: 1; transform: scale(1); }
        }
      `;
      document.head.appendChild(style);
    }

    // Add to player wrapper
    if (this.playerWrapper) {
      this.playerWrapper.appendChild(overlay);
    }

    // Auto-remove after 5 seconds
    setTimeout(() => {
      if (overlay.parentElement) {
        overlay.remove();
      }
    }, 5000);

    // Also highlight the fullscreen button if it exists
    const fullscreenBtn = this.cachedElements.fullscreenBtn;
    if (fullscreenBtn) {
      fullscreenBtn.style.animation = 'pulse 2s infinite';

      // Add pulse animation if not already added
      if (!this.getElement('pulse-animation-styles')) {
        const style = document.createElement('style');
        style.id = this.getElementId('pulse-animation-styles');
        style.textContent = `
          @keyframes pulse {
            0% { box-shadow: 0 0 0 0 var(--uvf-accent-1, #ff0000); }
            70% { box-shadow: 0 0 0 10px rgba(255, 0, 0, 0); }
            100% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0); }
          }
        `;
        document.head.appendChild(style);
      }

      // Remove pulse after 3 seconds
      setTimeout(() => {
        fullscreenBtn.style.animation = '';
      }, 3000);
    }

    this.debugLog('Showing fullscreen instructions overlay');
  }

  /**
   * Attempts fullscreen with better user guidance
   */
  async attemptFullscreen(): Promise<boolean> {
    try {
      await this.enterFullscreen();
      return true;
    } catch (error) {
      const errorMessage = (error as Error).message;

      if (errorMessage.includes('user gesture') ||
        errorMessage.includes('user activation') ||
        errorMessage.includes('Permissions check failed')) {

        // Try using the fullscreen button as a last resort
        const fullscreenBtn = this.cachedElements.fullscreenBtn;
        if (fullscreenBtn && !this.hasTriedButtonFallback) {
          this.hasTriedButtonFallback = true;
          this.debugLog('Attempting fullscreen via button as fallback');

          // Reset flag after a short delay
          setTimeout(() => {
            this.hasTriedButtonFallback = false;
          }, 1000);

          // Try clicking the button programmatically
          const clickEvent = new MouseEvent('click', {
            bubbles: true,
            cancelable: true,
            view: window,
            detail: 1
          });
          fullscreenBtn.dispatchEvent(clickEvent);
          return false; // We don't know if it succeeded immediately
        } else {
          // Show user-friendly guidance
          this.showShortcutIndicator('Click Fullscreen Button');
          this.debugWarn('Fullscreen requires direct user interaction. Please click the fullscreen button in the player controls.');
          return false;
        }
      } else {
        this.debugWarn('Fullscreen failed:', errorMessage);
        return false;
      }
    }
  }

  protected applySubtitleTrack(track: any): void {
    if (!this.video) return;

    const tracks = this.video.textTracks;
    for (let i = 0; i < tracks.length; i++) {
      const textTrack = tracks[i];
      // Use 'hidden' for the active track so we get cuechange events but hide native rendering
      // Use 'disabled' for others to avoid unnecessary event processing
      if (i === this.currentSubtitleIndex) {
        textTrack.mode = 'hidden';
      } else {
        textTrack.mode = 'disabled';
      }
    }
    this.updateSubtitleOverlay();
  }

  protected removeSubtitles(): void {
    if (!this.video) return;

    const tracks = this.video.textTracks;
    for (let i = 0; i < tracks.length; i++) {
      tracks[i].mode = 'disabled';
    }
    // Keep HLS internal state in sync — without this, HLS.js keeps its subtitle
    // track active even though the HTML5 textTracks are disabled.
    if (this.hls) {
      this.hls.subtitleTrack = -1;
    }
    if (this.subtitleOverlay) {
      this.subtitleOverlay.innerHTML = '';
      this.subtitleOverlay.style.display = 'none';
    }
  }

  private injectStyles(): void {
    if (this.getElement('player-styles')) return;

    const style = document.createElement('style');
    style.id = this.getElementId('player-styles');
    style.textContent = this.getPlayerStyles();
    document.head.appendChild(style);
  }

  private getPlayerStyles(): string {
    return `
      .uvf-player-wrapper {
        position: relative;
        width: 100%;
        background: #000;
        overflow: visible; /* Allow ads to render on top - changed from hidden */
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        /* Theme variables (can be overridden at runtime) */
        --uvf-accent-1: #ff0000;
        --uvf-accent-2: #ff4d4f;
        --uvf-accent-1-20: rgba(255,0,0,0.2);
        --uvf-icon-color: #ffffff;
        --uvf-text-primary: #ffffff;
        --uvf-text-secondary: rgba(255,255,255,0.75);
        /* Button theme variables */
        --uvf-button-bg: rgba(0,0,0,0.3);
        --uvf-button-border: rgba(255,255,255,0.15);
        --uvf-button-shadow: rgba(255,255,255,0.15);
        --uvf-button-shadow-hover: rgba(255,255,255,0.25);
        /* Overlay variables (can be overridden by theme) */
        --uvf-overlay-strong: rgba(0,0,0,0.95);
        --uvf-overlay-medium: rgba(0,0,0,0.7);
        --uvf-overlay-transparent: rgba(0,0,0,0);
        /* Scrollbar design variables */
        --uvf-scrollbar-width: 8px;
        --uvf-scrollbar-thumb-start: rgba(255,0,0,0.35);
        --uvf-scrollbar-thumb-end: rgba(255,0,0,0.45);
        --uvf-scrollbar-thumb-hover-start: rgba(255,0,0,0.5);
        --uvf-scrollbar-thumb-hover-end: rgba(255,0,0,0.6);
        --uvf-firefox-scrollbar-color: rgba(255,255,255,0.25);
      }
      
      /* Responsive Container Styles */
      .uvf-responsive-container {
        width: 100%;
        margin: 0 auto;
        box-sizing: border-box;
        display: block;
        position: relative;
      }
      
      .uvf-responsive-container .uvf-player-wrapper {
        width: 100%;
        height: 100%;
        max-width: inherit;
        max-height: inherit;
        box-sizing: border-box;
      }
      
      .uvf-responsive-container .uvf-video-container {
        width: 100%;
        height: 100%;
        position: relative;
        background: radial-gradient(ellipse at center, #1a1a2e 0%, #000 100%);
        overflow: visible;
        box-sizing: border-box;
      }
      
      /* Gradient border effect */
      .uvf-player-wrapper::before {
        content: '';
        position: absolute;
        top: -2px;
        left: -2px;
        right: -2px;
        bottom: -2px;
        background: linear-gradient(45deg, var(--uvf-accent-1), var(--uvf-accent-2), var(--uvf-accent-1));
        background-size: 400% 400%;
        animation: uvf-gradientBorder 10s ease infinite;
        z-index: -1;
        opacity: 0;
        transition: opacity 0.3s ease;
      }
      .uvf-player-wrapper:hover::before {
        opacity: 0.3;
      }
      @keyframes uvf-gradientBorder {
        0% { background-position: 0% 50%; }
        50% { background-position: 100% 50%; }
        100% { background-position: 0% 50%; }
      }
      
      .uvf-video-container {
        position: relative;
        width: 100%;
        aspect-ratio: 16 / 9;
        background: radial-gradient(ellipse at center, #1a1a2e 0%, #000 100%);
        overflow: visible;
      }
      
      .uvf-video {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: #000;
        object-fit: contain;
      }

      .uvf-subtitle-overlay {
        position: absolute;
        bottom: 25px;
        left: 50%;
        transform: translateX(-50%);
        width: 100%;
        max-width: 85%;
        text-align: center;
        z-index: 10;
        pointer-events: none;
        transition: bottom 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        display: none;
      }

      .uvf-player-wrapper.controls-visible .uvf-subtitle-overlay {
        /* --uvf-ctrl-height is set in JS by measuring the real controls bar height,
           so this stays pixel-perfect at every breakpoint / orientation / fullscreen state */
        bottom: calc(var(--uvf-ctrl-height, 0px) + -50px);
      }

      .uvf-subtitle-overlay span {
        background-color: rgba(0, 0, 0, 0.6);
        color: #ffffff;
        padding: 3px 8px;
        border-radius: 2px;
        font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        font-size: clamp(16px, 2.5vw, 22px);
        font-weight: 400;
        line-height: 1.6;
        text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9), -1px -1px 2px rgba(0, 0, 0, 0.9);
        display: inline;
        white-space: pre-wrap;
        box-decoration-break: clone;
        -webkit-box-decoration-break: clone;
      }
      
      .uvf-watermark-layer {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        pointer-events: none;
        z-index: 5;
        mix-blend-mode: screen;
      }
      
      /* Gradients */
      .uvf-top-gradient, .uvf-controls-gradient {
        position: absolute;
        left: 0;
        right: 0;
        pointer-events: none;
        opacity: 0;
        transition: opacity 0.3s ease;
      }
      
      .uvf-top-gradient {
        top: 0;
        height: 120px;
        background: linear-gradient(to bottom, var(--uvf-overlay-medium), var(--uvf-overlay-transparent));
        z-index: 96;
      }

      .uvf-controls-gradient {
        bottom: 0;
        height: 150px;
        background: linear-gradient(to top, var(--uvf-overlay-strong), var(--uvf-overlay-transparent));
        z-index: 97;
      }
      
      .uvf-player-wrapper:hover .uvf-top-gradient,
      .uvf-player-wrapper:hover .uvf-controls-gradient,
      .uvf-player-wrapper.controls-visible .uvf-top-gradient,
      .uvf-player-wrapper.controls-visible .uvf-controls-gradient {
        opacity: 1;
      }
      
      /* Loading Spinner */
      .uvf-loading-container {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        z-index: 10;
        display: none;
        flex-direction: column;
        align-items: center;
        justify-content: center;
      }

      .uvf-loading-container.active {
        display: flex;
      }
      
      .uvf-loading-spinner {
        width: 60px;
        height: 60px;
        border: 3px solid rgba(255,255,255,0.2);
        border-top-color: var(--uvf-accent-1);
        border-radius: 50%;
        animation: uvf-spin 1s linear infinite;
      }

      .uvf-loading-message {
        margin-top: 16px;
        color: white;
        font-size: 16px;
        font-weight: 500;
        text-align: center;
        opacity: 0;
        transition: opacity 0.3s ease;
      }

      .uvf-loading-container.with-message .uvf-loading-message {
        opacity: 1;
      }

      /* Countdown-specific styling */
      .uvf-loading-container.uvf-countdown-mode {
        width: 90%;
        max-width: 600px;
      }

      .uvf-loading-container.uvf-countdown-mode .uvf-loading-spinner {
        display: none; /* Hide spinner during countdown */
      }

      .uvf-loading-container.uvf-countdown-mode .uvf-loading-message {
        opacity: 1;
        margin-top: 0;
        font-size: 16px;
        line-height: 1.6;
      }

      .uvf-loading-container.uvf-countdown-mode.active {
        display: flex;
        pointer-events: auto;
      }

      @keyframes uvf-spin {
        to { transform: rotate(360deg); }
      }
      
      /* Hide center play button when loading is active */
      .uvf-loading-container.active ~ .uvf-center-play-container {
        opacity: 0;
        pointer-events: none;
      }
      
      /* Center Play Button Container */
      .uvf-center-play-container {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        pointer-events: none;
        z-index: 99;
      }
      
      /* Center Play Button */
      .uvf-center-play-btn {
        width: clamp(40px, 8vw, 60px);
        height: clamp(40px, 8vw, 60px);
        background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
        border: 0;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        pointer-events: auto;
        transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
        opacity: 1;
        visibility: visible;
        box-shadow: 0 4px 16px var(--uvf-accent-1-20);
      }
      
      .uvf-center-play-btn:hover {
        transform: scale(1.08);
        filter: saturate(1.08) brightness(1.05);
        box-shadow: 0 6px 20px var(--uvf-accent-1-20);
      }
      
      .uvf-center-play-btn.hidden {
        opacity: 0 !important;
        visibility: hidden !important;
        transform: scale(0.8) !important;
        pointer-events: none !important;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      
      .uvf-center-play-btn svg {
        width: clamp(18px, 3vw, 24px);
        height: clamp(18px, 3vw, 24px);
        fill: #fff;
        margin-left: 2px;
        filter: drop-shadow(0 1px 3px rgba(0,0,0,0.35));
      }
      
      /* Pulse animation for center play button when paused */
      .uvf-center-play-btn:not(.hidden) {
        animation: uvf-centerPlayPulse 3s ease-in-out infinite;
      }
      
      @keyframes uvf-centerPlayPulse {
        0% { 
          box-shadow: 0 4px 16px var(--uvf-accent-1-20);
          filter: saturate(1) brightness(1);
        }
        50% { 
          box-shadow: 0 6px 20px var(--uvf-accent-1-20), 0 0 20px rgba(255,0,0,0.1);
          filter: saturate(1.05) brightness(1.02);
        }
        100% { 
          box-shadow: 0 4px 16px var(--uvf-accent-1-20);
          filter: saturate(1) brightness(1);
        }
      }
      
      /* Controls Bar */
      .uvf-controls-bar {
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        padding: 20px;
        z-index: 100;
        opacity: 0;
        transform: translateY(10px);
        transition: all 0.3s ease;
        user-select: none;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
      }
      
      .uvf-player-wrapper:hover .uvf-controls-bar,
      .uvf-player-wrapper.controls-visible .uvf-controls-bar {
        opacity: 1 !important;
        transform: translateY(0) !important;
      }
      
      .uvf-player-wrapper.no-cursor {
        cursor: none;
      }
      
      .uvf-player-wrapper.no-cursor .uvf-controls-bar,
      .uvf-player-wrapper.no-cursor .uvf-top-gradient,
      .uvf-player-wrapper.no-cursor .uvf-controls-gradient {
        opacity: 0 !important;
        transform: translateY(10px) !important;
        pointer-events: none;
      }
      
      /* Progress Bar */
      .uvf-progress-section {
        width: 100%;
        margin-bottom: 15px;
      }
      
      .uvf-progress-bar-wrapper {
        width: 100%;
        position: relative;
        cursor: pointer;
        padding: 6px 0;
        overflow: visible;
      }
      
      /* Extended touch area for better mobile UX without affecting visual spacing */
      .uvf-progress-bar-wrapper::before {
        content: '';
        position: absolute;
        top: -8px;
        bottom: -8px;
        left: 0;
        right: 0;
        cursor: pointer;
        z-index: 10;
      }
      
      .uvf-progress-bar {
        width: 100%;
        height: 2px;
        position: relative;
        background: rgba(255, 255, 255, 0.15);
        border-radius: 4px;
        transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
        backdrop-filter: blur(4px);
      }
      
      .uvf-progress-bar-wrapper:hover .uvf-progress-bar {
        height: 4px;
        background: rgba(255, 255, 255, 0.2);
        border-radius: 6px;
        transform: scaleY(1.1);
      }
      
      .uvf-progress-buffered {
        position: absolute;
        top: 0;
        left: 0;
        height: 100%;
        background: linear-gradient(90deg, 
          rgba(255, 255, 255, 0.25) 0%,
          rgba(255, 255, 255, 0.35) 30%,
          rgba(255, 255, 255, 0.4) 50%,
          rgba(255, 255, 255, 0.35) 70%,
          rgba(255, 255, 255, 0.3) 100%
        );
        border-radius: 4px;
        pointer-events: none;
        transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
        z-index: 1;
        overflow: hidden;
      }
      
      /* Buffered progress loading shimmer effect */
      .uvf-progress-buffered::before {
        content: '';
        position: absolute;
        top: 0;
        left: -100%;
        width: 100%;
        height: 100%;
        background: linear-gradient(90deg, 
          transparent 0%,
          rgba(255, 255, 255, 0.15) 50%,
          transparent 100%
        );
        animation: bufferShimmer 2s infinite;
        border-radius: 6px;
      }
      
      @keyframes bufferShimmer {
        0% { left: -100%; }
        100% { left: 100%; }
      }
      
      .uvf-progress-bar-wrapper:hover .uvf-progress-buffered {
        border-radius: 6px;
        background: linear-gradient(90deg, 
          rgba(255, 255, 255, 0.3) 0%,
          rgba(255, 255, 255, 0.4) 30%,
          rgba(255, 255, 255, 0.5) 50%,
          rgba(255, 255, 255, 0.4) 70%,
          rgba(255, 255, 255, 0.35) 100%
        );
        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
      }
      
      .uvf-progress-bar-wrapper:hover .uvf-progress-buffered::before {
        border-radius: 6px;
      }
      
      .uvf-progress-filled {
        position: absolute;
        top: 0;
        left: 0;
        height: 100%;
        background: linear-gradient(90deg, 
          var(--uvf-accent-1, #ff4500) 0%,
          var(--uvf-accent-1, #ff5722) 25%,
          var(--uvf-accent-2, #ff6b35) 50%,
          var(--uvf-accent-2, #ff7043) 75%,
          var(--uvf-accent-2, #ff8c69) 100%
        );
        border-radius: 4px;
        pointer-events: none;
        transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
        z-index: 2;
        box-shadow: 0 0 12px var(--uvf-accent-1-20, rgba(255, 87, 34, 0.3));
      }
      
      .uvf-progress-bar-wrapper:hover .uvf-progress-filled {
        border-radius: 6px;
        background: linear-gradient(90deg, 
          var(--uvf-accent-1, #ff4500) 0%,
          var(--uvf-accent-1, #ff5722) 20%,
          var(--uvf-accent-2, #ff6b35) 40%,
          var(--uvf-accent-2, #ff7043) 60%,
          var(--uvf-accent-2, #ff8c69) 80%,
          var(--uvf-accent-2, #ffa500) 100%
        );
        box-shadow: 0 0 20px var(--uvf-accent-1-20, rgba(255, 87, 34, 0.5));
      }
      
      /* Progress Bar Handle/Thumb */
      .uvf-progress-handle {
        position: absolute;
        top: 1px; /* Center on the 2px progress bar (1px from top) */
        left: 0;
        width: 14px;
        height: 14px;
        background: #fff;
        border: 2px solid var(--uvf-accent-1, #ff5722);
        border-radius: 50%;
        transform: translate(-50%, -50%);
        cursor: grab;
        opacity: 0;
        transition: all 0.2s cubic-bezier(0.25, 0.8, 0.25, 1);
        z-index: 3;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
      }
      
      .uvf-progress-bar-wrapper:hover .uvf-progress-handle {
        opacity: 1;
        top: 2px; /* Center on the 4px hover progress bar (2px from top) */
        transform: translate(-50%, -50%) scale(1);
      }

      /* Prevent text selection during seekbar drag */
      .uvf-player-wrapper.seeking {
        user-select: none;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
      }

      /* Maintain expanded seekbar state during drag (same as hover) */
      .uvf-progress-bar-wrapper.dragging .uvf-progress-bar {
        height: 4px;
        background: rgba(255, 255, 255, 0.2);
        border-radius: 6px;
        transform: scaleY(1.1);
      }

      .uvf-progress-bar-wrapper.dragging .uvf-progress-handle {
        opacity: 1;
        top: 2px;
        transform: translate(-50%, -50%) scale(1);
      }

      .uvf-progress-bar-wrapper.dragging .uvf-progress-buffered {
        border-radius: 6px;
      }

      .uvf-progress-bar-wrapper.dragging .uvf-progress-filled {
        border-radius: 6px;
      }

      .uvf-progress-handle:hover {
        transform: translate(-50%, -50%) scale(1.2);
        box-shadow: 0 3px 12px rgba(0, 0, 0, 0.4);
      }
      
      .uvf-progress-handle:active,
      .uvf-progress-handle.dragging {
        cursor: grabbing;
        transform: translate(-50%, -50%) scale(1.3);
        box-shadow: 0 4px 16px rgba(255, 87, 34, 0.4);
      }
      
      /* Time Tooltip */
      .uvf-time-tooltip {
        position: absolute;
        bottom: 100%;
        left: 0;
        margin-bottom: 8px;
        padding: 4px 8px;
        background: rgba(0, 0, 0, 0.8);
        color: #fff;
        font-size: 12px;
        font-weight: 500;
        border-radius: 4px;
        white-space: nowrap;
        opacity: 0;
        transform: translateX(-50%) translateY(4px);
        transition: all 0.2s ease;
        pointer-events: none;
        z-index: 20;
        backdrop-filter: blur(4px);
        border: 1px solid rgba(255, 255, 255, 0.1);
      }
      
      .uvf-time-tooltip::after {
        content: '';
        position: absolute;
        top: 100%;
        left: 50%;
        transform: translateX(-50%);
        border: 4px solid transparent;
        border-top-color: rgba(0, 0, 0, 0.8);
      }
      
      .uvf-time-tooltip.visible {
        opacity: 1;
        transform: translateX(-50%) translateY(0);
      }
      
      /* Show tooltip when dragging */
      .uvf-progress-handle.dragging + .uvf-time-tooltip {
        opacity: 1;
        transform: translateX(-50%) translateY(0);
      }

      /* Thumbnail Preview */
      .uvf-thumbnail-preview {
        position: absolute;
        bottom: 100%;
        left: 0;
        margin-bottom: 12px;
        display: flex;
        flex-direction: column;
        align-items: center;
        pointer-events: none;
        z-index: 25;
        opacity: 0;
        transform: translateX(-50%) translateY(8px);
        transition: opacity 0.15s ease, transform 0.15s ease, left 0.05s ease-out;
        will-change: left, transform, opacity;
      }

      .uvf-thumbnail-preview.visible {
        opacity: 1;
        transform: translateX(-50%) translateY(0);
      }

      .uvf-thumbnail-preview-container {
        position: relative;
        background: rgba(0, 0, 0, 0.9);
        border-radius: 8px;
        padding: 4px;
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1);
        backdrop-filter: blur(12px);
        -webkit-backdrop-filter: blur(12px);
        /* Prevent container size changes */
        flex-shrink: 0;
      }

      .uvf-thumbnail-preview-image-wrapper {
        position: relative;
        width: 160px;
        height: 90px;
        border-radius: 6px;
        overflow: hidden;
        background: rgba(30, 30, 30, 0.95);
        /* Prevent layout shifts during image loading */
        flex-shrink: 0;
      }

      .uvf-thumbnail-preview-image {
        width: 100%;
        height: 100%;
        object-fit: cover;
        display: block;
        opacity: 0;
        transition: opacity 0.2s ease;
        /* Prevent reflow/repaint jitter during load */
        will-change: opacity;
        backface-visibility: hidden;
        -webkit-backface-visibility: hidden;
      }

      .uvf-thumbnail-preview-image.loaded {
        opacity: 1;
      }

      .uvf-thumbnail-preview-loading {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 24px;
        height: 24px;
        border: 2px solid rgba(255, 255, 255, 0.2);
        border-top-color: var(--uvf-accent-1, #ff5722);
        border-radius: 50%;
        animation: uvf-spin 0.8s linear infinite;
      }

      .uvf-thumbnail-preview-image.loaded + .uvf-thumbnail-preview-loading {
        display: none;
      }

      .uvf-thumbnail-preview-time {
        position: absolute;
        bottom: 6px;
        left: 50%;
        transform: translateX(-50%);
        background: rgba(0, 0, 0, 0.85);
        color: #fff;
        font-size: 12px;
        font-weight: 600;
        padding: 3px 8px;
        border-radius: 4px;
        white-space: nowrap;
        text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
        backdrop-filter: blur(4px);
        -webkit-backdrop-filter: blur(4px);
      }

      .uvf-thumbnail-preview-arrow {
        width: 0;
        height: 0;
        border-left: 8px solid transparent;
        border-right: 8px solid transparent;
        border-top: 8px solid rgba(0, 0, 0, 0.9);
        margin-top: -1px;
      }

      /* Hide regular time tooltip when thumbnail preview is visible */
      .uvf-thumbnail-preview.visible ~ .uvf-time-tooltip {
        opacity: 0 !important;
        pointer-events: none;
      }

      /* Responsive thumbnail sizes */
      @media (max-width: 768px) {
        .uvf-thumbnail-preview-image-wrapper {
          width: 120px;
          height: 68px;
        }

        .uvf-thumbnail-preview-time {
          font-size: 11px;
          padding: 2px 6px;
        }
      }

      /* Chapter Markers */
      .uvf-chapter-marker {
        position: absolute;
        top: 0;
        width: 2px;
        height: 100%;
        background: rgba(255, 255, 255, 0.6);
        z-index: 4;
        cursor: pointer;
        transition: all 0.2s ease;
      }
      
      .uvf-chapter-marker:hover {
        width: 3px;
        box-shadow: 0 0 8px rgba(255, 255, 255, 0.8);
      }
      
      .uvf-chapter-marker-intro {
        background: var(--uvf-accent-1, #ff5722);
      }
      
      .uvf-chapter-marker-recap {
        background: #ffc107;
      }
      
      .uvf-chapter-marker-credits {
        background: #9c27b0;
      }
      
      .uvf-chapter-marker-ad {
        background: #FFFF00;  /* Yellow like YouTube ads */
      }
      
      /* Skip Button Styles */
      .uvf-skip-button {
        position: absolute;
        background: rgba(0, 0, 0, 0.8);
        color: white;
        border: 2px solid rgba(255, 255, 255, 0.3);
        border-radius: 8px;
        padding: 12px 24px;
        font-size: 16px;
        font-weight: 600;
        cursor: pointer;
        backdrop-filter: blur(10px);
        transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
        z-index: 1000;
        user-select: none;
        
        /* Default hidden state */
        opacity: 0;
        transform: translateX(100px) scale(0.9);
        pointer-events: none;
      }
      
      .uvf-skip-button.visible {
        opacity: 1;
        transform: translateX(0) scale(1);
        pointer-events: auto;
      }
      
      .uvf-skip-button:hover {
        background: var(--uvf-accent-1, #ff5722);
        border-color: var(--uvf-accent-1, #ff5722);
        transform: translateX(0) scale(1.05);
        box-shadow: 0 4px 20px rgba(255, 87, 34, 0.4);
      }
      
      .uvf-skip-button:active {
        transform: translateX(0) scale(0.95);
        transition: all 0.1s ease;
      }
      
      /* Skip button positioning */
      .uvf-skip-button-bottom-right {
        bottom: 100px;
        right: 30px;
      }
      
      .uvf-skip-button-bottom-left {
        bottom: 100px;
        left: 30px;
        transform: translateX(-100px) scale(0.9);
      }
      
      .uvf-skip-button-bottom-left.visible {
        transform: translateX(0) scale(1);
      }
      
      .uvf-skip-button-bottom-left:hover {
        transform: translateX(0) scale(1.05);
      }
      
      .uvf-skip-button-bottom-left:active {
        transform: translateX(0) scale(0.95);
      }
      
      .uvf-skip-button-top-right {
        top: 30px;
        right: 30px;
      }
      
      .uvf-skip-button-top-left {
        top: 30px;
        left: 30px;
        transform: translateX(-100px) scale(0.9);
      }
      
      .uvf-skip-button-top-left.visible {
        transform: translateX(0) scale(1);
      }
      
      .uvf-skip-button-top-left:hover {
        transform: translateX(0) scale(1.05);
      }
      
      .uvf-skip-button-top-left:active {
        transform: translateX(0) scale(0.95);
      }
      
      /* Skip button segment type styling */
      .uvf-skip-intro {
        border-color: var(--uvf-accent-1, #ff5722);
      }
      
      .uvf-skip-intro:hover {
        background: var(--uvf-accent-1, #ff5722);
        border-color: var(--uvf-accent-1, #ff5722);
      }
      
      .uvf-skip-recap {
        border-color: #ffc107;
      }
      
      .uvf-skip-recap:hover {
        background: #ffc107;
        border-color: #ffc107;
        color: #000;
      }
      
      .uvf-skip-credits {
        border-color: #9c27b0;
      }
      
      .uvf-skip-credits:hover {
        background: #9c27b0;
        border-color: #9c27b0;
      }
      
      .uvf-skip-ad {
        border-color: #f44336;
      }
      
      .uvf-skip-ad:hover {
        background: #f44336;
        border-color: #f44336;
      }
      
      /* Auto-skip countdown styling */
      .uvf-skip-button.auto-skip {
        position: relative;
        overflow: hidden;
        border-color: var(--uvf-accent-1, #ff5722);
        animation: uvf-skip-pulse 2s infinite;
      }
      
      .uvf-skip-button.auto-skip::before {
        content: '';
        position: absolute;
        bottom: 0;
        left: 0;
        height: 3px;
        background: var(--uvf-accent-1, #ff5722);
        width: 0%;
        transition: none;
        z-index: -1;
      }
      
      .uvf-skip-button.auto-skip.countdown::before {
        width: 100%;
        transition: width linear;
      }
      
      @keyframes uvf-skip-pulse {
        0% { 
          box-shadow: 0 0 0 0 rgba(255, 87, 34, 0.4);
        }
        50% { 
          box-shadow: 0 0 0 8px rgba(255, 87, 34, 0.1);
        }
        100% { 
          box-shadow: 0 0 0 0 rgba(255, 87, 34, 0);
        }
      }
      
      
      
      /* Mobile responsive design with enhanced touch targets */
      @media (max-width: 768px) {
        .uvf-progress-bar-wrapper {
          padding: 8px 0; /* Optimized touch area */
        }
        
        .uvf-progress-bar {
          height: 3px; /* Slightly thicker on mobile */
        }
        
        .uvf-progress-bar-wrapper:hover .uvf-progress-bar {
          height: 5px;
        }
        
        .uvf-progress-handle {
          top: 1.5px; /* Center on the 3px mobile progress bar */
        }
        
        .uvf-progress-bar-wrapper:hover .uvf-progress-handle {
          top: 2.5px; /* Center on the 5px mobile hover progress bar */
        }
        
        /* Mobile skip button adjustments */
        .uvf-skip-button {
          padding: 10px 20px;
          font-size: 14px;
          border-radius: 6px;
        }
        
        .uvf-skip-button-bottom-right {
          bottom: 80px;
          right: 20px;
        }
        
        .uvf-skip-button-bottom-left {
          bottom: 80px;
          left: 20px;
        }
        
        .uvf-skip-button-top-right {
          top: 20px;
          right: 20px;
        }
        
        .uvf-skip-button-top-left {
          top: 20px;
          left: 20px;
        }
        
        /* Mobile chapter markers */
        .uvf-chapter-marker {
          width: 3px; /* Thicker on mobile for better touch */
        }
        
        .uvf-chapter-marker:hover {
          width: 4px;
        }
        
      }
      
      /* Controls Row */
      .uvf-controls-row {
        display: flex;
        align-items: center;
        gap: 15px;
      }
      
      /* Hidden element utility class */
      .uvf-hidden {
        display: none !important;
      }

      /* Control Buttons */
      .uvf-control-btn {
        background: rgba(255,255,255,0.1);
        backdrop-filter: blur(10px);
        border: none;
        width: 40px;
        height: 40px;
        border-radius: 50%;
        color: #fff;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: all 0.3s ease;
        position: relative;
        overflow: hidden;
      }
      
      .uvf-control-btn:hover {
        background: rgba(255,255,255,0.2);
        transform: scale(1.1);
      }
      
      .uvf-control-btn:active {
        transform: scale(0.95);
      }
      
      .uvf-control-btn svg {
        width: 20px;
        height: 20px;
        fill: var(--uvf-icon-color);
        pointer-events: none;
      }
      
      /* Settings Button Specific Styling */
      #uvf-settings-btn {
        background: var(--uvf-button-bg);
        border: 1px solid var(--uvf-button-border);
        position: relative;
        z-index: 10;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      
      #uvf-settings-btn:hover {
        transform: scale(1.08);
        box-shadow: 0 4px 12px var(--uvf-button-shadow);
      }
      
      #uvf-settings-btn:active {
        transform: scale(0.95);
        transition: all 0.1s ease;
      }
      
      #uvf-settings-btn svg {
        opacity: 0.9;
        transition: all 0.3s ease;
        filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
      }
      
      #uvf-settings-btn:hover svg {
        opacity: 1;
        transform: scale(1.05);
        filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
      }
      
      /* PiP Button Specific Styling */
      #uvf-pip-btn {
        background: var(--uvf-button-bg);
        border: 1px solid var(--uvf-button-border);
        position: relative;
        z-index: 10;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      
      #uvf-pip-btn:hover {
        transform: scale(1.08);
        box-shadow: 0 4px 12px var(--uvf-button-shadow);
      }
      
      #uvf-pip-btn:active {
        transform: scale(0.95);
        transition: all 0.1s ease;
      }
      
      #uvf-pip-btn svg {
        opacity: 0.9;
        transition: all 0.3s ease;
        filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
      }
      
      #uvf-pip-btn:hover svg {
        opacity: 1;
        transform: scale(1.05);
        filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
      }
      
      /* Fullscreen Button Specific Styling */
      #uvf-fullscreen-btn {
        background: var(--uvf-button-bg);
        border: 1px solid var(--uvf-button-border);
        position: relative;
        z-index: 10;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      
      #uvf-fullscreen-btn:hover {
        transform: scale(1.08);
        box-shadow: 0 4px 12px var(--uvf-button-shadow);
      }
      
      #uvf-fullscreen-btn:active {
        transform: scale(0.95);
        transition: all 0.1s ease;
      }
      
      #uvf-fullscreen-btn svg {
        opacity: 0.9;
        transition: all 0.3s ease;
        filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
      }
      
      #uvf-fullscreen-btn:hover svg {
        opacity: 1;
        transform: scale(1.05);
        filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
      }
      
      /* Fullscreen button state changes */
      #uvf-fullscreen-btn.fullscreen-active {
        background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
        border: 1px solid var(--uvf-accent-1);
        box-shadow: 0 0 20px rgba(var(--uvf-accent-1), 0.3);
      }
      
      #uvf-fullscreen-btn.fullscreen-active:hover {
        background: linear-gradient(135deg, var(--uvf-accent-2), var(--uvf-accent-1));
        transform: scale(1.1);
        box-shadow: 0 0 25px rgba(var(--uvf-accent-1), 0.5);
      }
      
      #uvf-fullscreen-btn.fullscreen-active svg {
        opacity: 1;
        color: white;
        filter: drop-shadow(0 2px 6px rgba(0,0,0,0.5));
      }
      
      /* Playlist toggle button — mirrors fullscreen button style */
      #uvf-playlist-btn {
        background: var(--uvf-button-bg);
        border: 1px solid var(--uvf-button-border);
        position: relative;
        z-index: 10;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }

      #uvf-playlist-btn:hover {
        transform: scale(1.08);
        box-shadow: 0 4px 12px var(--uvf-button-shadow);
      }

      #uvf-playlist-btn:active {
        transform: scale(0.95);
        transition: all 0.1s ease;
      }

      #uvf-playlist-btn svg {
        width: 18px;
        height: 18px;
        opacity: 0.9;
        transition: all 0.3s ease;
        filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
      }

      #uvf-playlist-btn:hover svg {
        opacity: 1;
        transform: scale(1.05);
        filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
      }

      /* Highlighted state when playlist panel is open */
      #uvf-playlist-btn.playlist-active {
        background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
        border: 1px solid var(--uvf-accent-1);
      }

      #uvf-playlist-btn.playlist-active svg {
        opacity: 1;
        color: white;
      }

      /* Settings Container */
      .uvf-settings-container {
        position: relative;
        display: flex;
        align-items: center;
        justify-content: center;
        min-width: 40px;
        min-height: 40px;
      }
      
      /* Skip Buttons Specific Styling */
      #uvf-skip-back,
      #uvf-skip-forward {
        background: var(--uvf-button-bg);
        border: 1px solid var(--uvf-button-border);
        position: relative;
        z-index: 10;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      
      #uvf-skip-back:hover,
      #uvf-skip-forward:hover {
        transform: scale(1.08);
        box-shadow: 0 4px 12px var(--uvf-button-shadow);
      }
      
      #uvf-skip-back:active,
      #uvf-skip-forward:active {
        transform: scale(0.95);
        transition: all 0.1s ease;
      }
      
      #uvf-skip-back svg,
      #uvf-skip-forward svg {
        width: 22px;
        height: 22px;
        stroke-width: 0;
        transform: scale(1);
        opacity: 0.9;
        transition: all 0.3s ease;
        filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
      }
      
      #uvf-skip-back:hover svg,
      #uvf-skip-forward:hover svg {
        opacity: 1;
        transform: scale(1.05);
        filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
      }
      
      #uvf-skip-forward svg {
        transform: scale(1) scaleX(-1); /* Mirror the icon for forward */
      }
      
      #uvf-skip-forward:hover svg {
        transform: scale(1.05) scaleX(-1); /* Keep mirror on hover */
      }
      
      /* Volume Button Specific Styling */
      #uvf-volume-btn {
        background: var(--uvf-button-bg);
        border: 1px solid var(--uvf-button-border);
        position: relative;
        z-index: 10;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      
      #uvf-volume-btn:hover {
        transform: scale(1.08);
        box-shadow: 0 4px 12px var(--uvf-button-shadow);
      }
      
      #uvf-volume-btn:active {
        transform: scale(0.95);
        transition: all 0.1s ease;
      }
      
      #uvf-volume-btn svg {
        opacity: 0.9;
        transition: all 0.3s ease;
        filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
      }
      
      #uvf-volume-btn:hover svg {
        opacity: 1;
        transform: scale(1.05);
        filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
      }
      
      /* Cast Button Specific Styling */
      #uvf-cast-btn {
        background: var(--uvf-button-bg);
        border: 1px solid var(--uvf-button-border);
        position: relative;
        z-index: 10;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      
      #uvf-cast-btn:hover {
        transform: scale(1.08);
        box-shadow: 0 4px 12px var(--uvf-button-shadow);
      }
      
      #uvf-cast-btn:active {
        transform: scale(0.95);
        transition: all 0.1s ease;
      }
      
      #uvf-cast-btn svg {
        opacity: 0.9;
        transition: all 0.3s ease;
        filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
      }
      
      #uvf-cast-btn:hover svg {
        opacity: 1;
        transform: scale(1.05);
        filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
      }
      
      /* Share Button Specific Styling */
      #uvf-share-btn {
        background: var(--uvf-button-bg);
        border: 1px solid var(--uvf-button-border);
        position: relative;
        z-index: 10;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      
      #uvf-share-btn:hover {
        transform: scale(1.08);
        box-shadow: 0 4px 12px var(--uvf-button-shadow);
      }
      
      #uvf-share-btn:active {
        transform: scale(0.95);
        transition: all 0.1s ease;
      }
      
      #uvf-share-btn svg {
        opacity: 0.9;
        transition: all 0.3s ease;
        filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
      }
      
      #uvf-share-btn:hover svg {
        opacity: 1;
        transform: scale(1.05);
        filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
      }
      
      /* EPG Button Specific Styling */
      #uvf-epg-btn {
        background: var(--uvf-button-bg);
        border: 1px solid var(--uvf-button-border);
        position: relative;
        z-index: 10;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      
      #uvf-epg-btn:hover {
        transform: scale(1.08);
        box-shadow: 0 4px 12px var(--uvf-button-shadow);
      }
      
      #uvf-epg-btn:active {
        transform: scale(0.95);
        transition: all 0.1s ease;
      }
      
      #uvf-epg-btn svg {
        opacity: 0.9;
        transition: all 0.3s ease;
        filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
      }
      
      #uvf-epg-btn:hover svg {
        opacity: 1;
        transform: scale(1.05);
        filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
      }
      
      .uvf-control-btn.play-pause {
        width: 50px;
        height: 50px;
        background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      
      .uvf-control-btn.play-pause:hover {
        transform: scale(1.08);
        box-shadow: 0 4px 12px var(--uvf-accent-1-20);
        filter: saturate(1.1) brightness(1.05);
      }
      .uvf-control-btn.play-pause:active {
        transform: scale(0.95);
        transition: all 0.1s ease;
      }
      
      .uvf-control-btn.play-pause svg {
        width: 24px;
        height: 24px;
        opacity: 0.95;
        transition: all 0.3s ease;
        filter: drop-shadow(0 1px 3px rgba(0,0,0,0.4));
      }
      
      .uvf-control-btn.play-pause:hover svg {
        opacity: 1;
        transform: scale(1.02);
        filter: drop-shadow(0 2px 5px rgba(0,0,0,0.5));
      }
      
      /* Time Display */
      .uvf-time-display {
        color: var(--uvf-text-primary);
        font-size: 14px;
        font-weight: 500;
        padding: 0 10px;
        text-shadow: 0 1px 2px rgba(0,0,0,0.5);
      }

      /* LIVE Indicator */
      .uvf-time-display.uvf-is-live {
        display: flex;
        align-items: center;
        gap: 6px;
      }

      .uvf-live-dot {
        width: 10px;
        height: 10px;
        background: #ff0000;
        border-radius: 50%;
        display: inline-block;
        animation: uvf-live-pulse 1.5s ease-in-out infinite;
        box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.7);
      }

      .uvf-live-text {
        color: #ffffff;
        font-size: 14px;
        font-weight: 700;
        letter-spacing: 1px;
        text-shadow: 0 0 10px rgba(255, 0, 0, 0.5);
      }

      @keyframes uvf-live-pulse {
        0% {
          transform: scale(1);
        }
        50% {
          transform: scale(1.2);
        }
        100% {
          transform: scale(1);
        }
      }

      /* Volume Control */
      .uvf-volume-control {
        display: flex;
        align-items: center;
        position: relative;
      }
      
      .uvf-volume-panel {
        position: absolute;
        left: 40px;
        top: 50%;
        transform: translateY(-50%);
        display: flex;
        align-items: center;
        background: rgba(0,0,0,0.95);
        backdrop-filter: blur(15px);
        border: 1px solid rgba(255,255,255,0.2);
        border-radius: 20px;
        padding: 10px 15px;
        opacity: 0;
        visibility: hidden;
        pointer-events: none;
        transition: opacity 0.2s ease, visibility 0.2s ease, left 0.3s ease;
        z-index: 100;
      }
      
      .uvf-volume-control:hover .uvf-volume-panel,
      .uvf-volume-panel:hover,
      .uvf-volume-panel.active {
        opacity: 1;
        visibility: visible;
        pointer-events: all;
        left: 50px;
      }
      
      .uvf-volume-slider {
        width: 120px;
        height: 8px;
        background: rgba(255,255,255,0.2);
        border-radius: 4px;
        cursor: pointer;
        position: relative;
        margin: 0 10px;
      }
      
      .uvf-volume-fill {
        height: 100%;
        background: linear-gradient(90deg, var(--uvf-accent-1), var(--uvf-accent-2));
        border-radius: 4px;
        pointer-events: none;
        transition: width 0.1s ease;
        position: absolute;
        top: 0;
        left: 0;
      }
      
      .uvf-volume-value {
        color: var(--uvf-text-primary);
        font-size: 12px;
        min-width: 30px;
        text-align: center;
      }
      
      /* Right Controls */
      .uvf-right-controls {
        margin-left: auto;
        display: flex;
        align-items: center;
        gap: 10px;
      }
      
      /* Settings Menu */
      .uvf-settings-menu {
        position: absolute;
        bottom: 50px;
        right: 0;
        background: rgba(0,0,0,0.92);
        backdrop-filter: blur(20px);
        -webkit-backdrop-filter: blur(20px);
        border: 1px solid rgba(255,255,255,0.15);
        border-radius: 12px;
        padding: 8px 0;
        min-width: 280px;
        max-width: 350px;
        max-height: 60vh;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        overscroll-behavior: contain;
        z-index: 9999;
        user-select: none;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        box-shadow: 0 20px 40px rgba(0,0,0,0.4), 0 8px 25px rgba(0,0,0,0.3);
        /* Firefox */
        scrollbar-width: thin;
        scrollbar-color: rgba(255,255,255,0.3) transparent;
        /* Avoid layout shift when scrollbar appears */
        scrollbar-gutter: stable both-edges;
        /* Space on the right so content doesn't hug the scrollbar */
        padding-right: 8px;
        /* Initial hidden state */
        opacity: 0;
        visibility: hidden;
        transform: translateY(15px) scale(0.95);
        pointer-events: none;
        transition: opacity 0.25s ease, visibility 0.25s ease, transform 0.25s cubic-bezier(0.4, 0.0, 0.2, 1);
      }
      
      /* WebKit-based browsers (Chrome, Edge, Safari) */
      .uvf-settings-menu::-webkit-scrollbar {
        width: var(--uvf-scrollbar-width);
      }
      .uvf-settings-menu::-webkit-scrollbar-track {
        background: transparent;
      }
      .uvf-settings-menu::-webkit-scrollbar-thumb {
        background: linear-gradient(180deg, var(--uvf-scrollbar-thumb-start), var(--uvf-scrollbar-thumb-end));
        border-radius: 8px;
      }
      .uvf-settings-menu::-webkit-scrollbar-thumb:hover {
        background: linear-gradient(180deg, var(--uvf-scrollbar-thumb-hover-start), var(--uvf-scrollbar-thumb-hover-end));
      }
      .uvf-settings-menu::-webkit-scrollbar-corner {
        background: transparent;
      }

      /* Scrollbar mode variants */
      .uvf-player-wrapper.uvf-scrollbar-compact {
        --uvf-scrollbar-width: 6px;
        --uvf-scrollbar-thumb-start: rgba(255,0,0,0.30);
        --uvf-scrollbar-thumb-end: rgba(255,0,0,0.38);
        --uvf-scrollbar-thumb-hover-start: rgba(255,0,0,0.42);
        --uvf-scrollbar-thumb-hover-end: rgba(255,0,0,0.52);
        --uvf-firefox-scrollbar-color: rgba(255,255,255,0.20);
      }
      .uvf-player-wrapper.uvf-scrollbar-overlay {
        --uvf-scrollbar-width: 6px;
      }
      .uvf-player-wrapper.uvf-scrollbar-overlay .uvf-settings-menu {
        scrollbar-gutter: auto;
        padding-right: 0;
      }

      .uvf-settings-menu.active {
        opacity: 1;
        visibility: visible;
        transform: translateY(0) scale(1);
        pointer-events: all;
      }
      
      /* Improved Accordion Styles */
      .uvf-settings-accordion {
        padding: 8px 0;
      }
      
      .uvf-accordion-item {
        margin-bottom: 2px;
        border-radius: 8px;
        overflow: hidden;
        background: rgba(255,255,255,0.03);
      }
      
      .uvf-accordion-item:last-child {
        margin-bottom: 0;
      }
      
      .uvf-accordion-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 12px 16px;
        cursor: pointer;
        transition: all 0.2s ease;
        background: rgba(255,255,255,0.05);
        border-bottom: 1px solid rgba(255,255,255,0.08);
      }
      
      .uvf-accordion-header:hover {
        background: rgba(255,255,255,0.1);
      }
      
      .uvf-accordion-item.expanded .uvf-accordion-header {
        background: rgba(255,255,255,0.08);
        border-bottom-color: rgba(255,255,255,0.12);
      }
      
      .uvf-accordion-title {
        display: flex;
        align-items: center;
        gap: 8px;
        font-size: 13px;
        font-weight: 500;
        color: #fff;
        flex: 1;
      }
      
      .uvf-accordion-icon {
        display: flex;
        align-items: center;
        justify-content: center;
        opacity: 0.9;
        width: 16px;
        height: 16px;
      }
      
      .uvf-accordion-icon svg {
        width: 14px;
        height: 14px;
        fill: currentColor;
      }
      
      .uvf-accordion-current {
        font-size: 11px;
        color: var(--uvf-accent-1);
        background: rgba(255,255,255,0.08);
        padding: 2px 8px;
        border-radius: 8px;
        font-weight: 600;
        margin-right: 8px;
      }
      
      .uvf-accordion-arrow {
        font-size: 10px;
        color: rgba(255,255,255,0.7);
        transition: transform 0.25s ease;
        width: 16px;
        text-align: center;
      }
      
      .uvf-accordion-item.expanded .uvf-accordion-arrow {
        transform: rotate(180deg);
      }
      
      .uvf-accordion-content {
        max-height: 0;
        overflow: hidden;
        transition: max-height 0.25s cubic-bezier(0.4, 0, 0.2, 1);
        background: rgba(0,0,0,0.2);
      }

      .uvf-accordion-item.expanded .uvf-accordion-content {
        max-height: min(400px, 50vh); /* Responsive: 400px max or 50% viewport, whichever is smaller */
        overflow-y: auto; /* Allow scrolling if content exceeds height */
        -webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */
      }
      
      /* Settings options within accordion */
      .uvf-accordion-content .uvf-settings-option {
        color: #fff;
        font-size: 13px;
        padding: 10px 16px;
        cursor: pointer;
        transition: all 0.2s ease;
        border-bottom: 1px solid rgba(255,255,255,0.05);
        display: flex;
        align-items: center;
        justify-content: space-between;
      }
      
      .uvf-accordion-content .uvf-settings-option:last-child {
        border-bottom: none;
      }
      
      .uvf-accordion-content .uvf-settings-option:hover {
        background: rgba(255,255,255,0.06);
        padding-left: 20px;
      }
      
      .uvf-accordion-content .uvf-settings-option.active {
        color: var(--uvf-accent-1);
        background: rgba(255,255,255,0.08);
        font-weight: 600;
      }
      
      .uvf-accordion-content .uvf-settings-option.active::after {
        content: '✓';
        font-size: 12px;
        opacity: 0.8;
      }
      
      .uvf-settings-empty {
        padding: 20px;
        text-align: center;
        color: rgba(255,255,255,0.6);
        font-size: 14px;
      }
      
      .uvf-settings-group {
        padding: 10px 0;
        border-bottom: 1px solid rgba(255,255,255,0.1);
      }
      
      .uvf-settings-group:last-child {
        border-bottom: none;
      }
      
      .uvf-settings-label {
        color: rgba(255,255,255,0.5);
        font-size: 11px;
        text-transform: uppercase;
        letter-spacing: 1px;
        padding: 0 15px 5px;
      }
      
      .uvf-settings-option {
        color: #fff;
        font-size: 14px;
        padding: 8px 15px;
        cursor: pointer;
        transition: all 0.2s ease;
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      
      .uvf-settings-option:hover {
        background: rgba(255,255,255,0.1);
        padding-left: 20px;
      }
      
      .uvf-settings-option.active {
        color: var(--uvf-accent-2);
      }
      
      .uvf-settings-option.active::after {
        content: '✓';
        margin-left: 10px;
      }
      
      /* Top Bar Container - Contains Navigation + Title (left) and Controls (right) */
      .uvf-top-bar {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        padding: 20px;
        z-index: 98;
        display: flex;
        justify-content: space-between;
        align-items: flex-start;
        gap: 20px;
        opacity: 0;
        transform: translateY(-10px);
        transition: all 0.3s ease;
        user-select: none;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
      }
      
      /* Left side container for navigation + title */
      .uvf-left-side {
        display: flex;
        align-items: center;
        gap: 12px;
        flex: 1;
        max-width: 70%;
      }
      
      /* Navigation controls container */
      .uvf-navigation-controls {
        display: flex;
        align-items: center;
        gap: 8px;
        flex-shrink: 0;
      }
      
      /* Navigation button styles - Follow theme like skip/volume buttons */
      .uvf-nav-btn {
        width: 40px;
        height: 40px;
        min-width: 40px;
        min-height: 40px;
        border-radius: 50%;
        background: var(--uvf-button-bg);
        border: 1px solid var(--uvf-button-border);
        color: white;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        backdrop-filter: blur(10px);
        position: relative;
        overflow: hidden;
        z-index: 10;
      }
      
      .uvf-nav-btn:hover {
        transform: scale(1.08);
        box-shadow: 0 4px 12px var(--uvf-button-shadow);
      }
      
      .uvf-nav-btn:active {
        transform: scale(0.95);
        transition: all 0.1s ease;
      }
      
      .uvf-nav-btn svg {
        width: 20px;
        height: 20px;
        fill: currentColor;
        opacity: 0.9;
        transition: all 0.3s ease;
        filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
      }
      
      .uvf-nav-btn:hover svg {
        opacity: 1;
        transform: scale(1.05);
        filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
      }
      
      .uvf-player-wrapper:hover .uvf-top-bar,
      .uvf-player-wrapper.controls-visible .uvf-top-bar {
        opacity: 1 !important;
        transform: translateY(0) !important;
      }
      
      /* Title Bar - After navigation buttons */
      .uvf-title-bar {
        flex: 1;
        min-width: 0; /* Allow shrinking */
      }

      /* Top Controls - Right side of top bar */
      .uvf-top-controls {
        display: flex;
        align-items: center;
        justify-content: flex-end;
        gap: 12px;
        flex-shrink: 0;
      }
      
      .uvf-title-content {
        display: flex;
        align-items: center;
        width: 100%;
        min-width: 0; /* Allow shrinking */
      }
      
      .uvf-title-text { 
        display: flex; 
        flex-direction: column;
        min-width: 0; /* Allow shrinking */
        flex: 1;
      }
      
      .uvf-video-title {
        color: var(--uvf-text-primary);
        font-size: clamp(14px, 2.5vw, 18px); /* Responsive font size */
        font-weight: 600;
        text-shadow: 0 2px 4px rgba(0,0,0,0.5);
        line-height: 1.3;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        max-width: 100%;
        cursor: pointer;
        transition: color 0.3s ease;
        position: relative;
      }
      
      .uvf-video-title:hover {
        color: var(--uvf-accent-1, #ff0000);
      }
      
      .uvf-video-subtitle {
        color: var(--uvf-text-secondary);
        font-size: clamp(11px, 1.8vw, 13px); /* Responsive font size */
        margin-top: 2px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        max-width: 100%;
        opacity: 0.9;
        line-height: 1.4;
        cursor: pointer;
        transition: opacity 0.3s ease;
        position: relative;
      }
      
      .uvf-video-subtitle:hover {
        opacity: 1;
      }
      
      /* Tooltip for long text */
      .uvf-text-tooltip {
        position: absolute;
        bottom: 100%;
        left: 0;
        background: rgba(0, 0, 0, 0.9);
        color: white;
        padding: 8px 12px;
        border-radius: 6px;
        font-size: 13px;
        line-height: 1.4;
        max-width: 400px;
        word-wrap: break-word;
        white-space: normal;
        z-index: 1000;
        opacity: 0;
        visibility: hidden;
        transform: translateY(-5px);
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        pointer-events: none;
        border: 1px solid rgba(255, 255, 255, 0.2);
        backdrop-filter: blur(8px);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
      }
      
      .uvf-text-tooltip::before {
        content: '';
        position: absolute;
        top: 100%;
        left: 12px;
        border: 5px solid transparent;
        border-top-color: rgba(0, 0, 0, 0.9);
      }
      
      .uvf-text-tooltip.show {
        opacity: 1;
        visibility: visible;
        transform: translateY(0);
      }
      
      /* Multi-line title option for desktop */
      .uvf-video-title.multiline {
        white-space: normal;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        line-height: 1.2;
        max-height: 2.4em;
      }
      
      .uvf-video-subtitle.multiline {
        white-space: normal;
        display: -webkit-box;
        -webkit-line-clamp: 3;
        -webkit-box-orient: vertical;
        line-height: 1.3;
        max-height: 3.9em;
      }
      
                /* Above seekbar section with time and branding */
                .uvf-above-seekbar-section {
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                    margin-bottom: 8px;
                    opacity: 0;
                    transform: translateY(10px);
                    transition: all 0.3s ease;
                }
                
                .uvf-player-wrapper:hover .uvf-above-seekbar-section,
                .uvf-player-wrapper.controls-visible .uvf-above-seekbar-section {
                    opacity: 1;
                    transform: translateY(0);
                }
                
                .uvf-time-display.uvf-above-seekbar {
                    position: static;
                    font-size: 13px;
                    font-weight: 500;
                    color: var(--uvf-text-primary);
                    text-shadow: 0 1px 3px rgba(0,0,0,0.7);
                    background: rgba(0,0,0,0.3);
                    padding: 4px 8px;
                    border-radius: 12px;
                    backdrop-filter: blur(4px);
                    border: 1px solid rgba(255,255,255,0.1);
                    width: auto;
                    white-space: nowrap;
                }
                
                .uvf-framework-branding {
                    position: static;
                    bottom: unset;
                    right: unset;
                    opacity: 1;
                    transform: none;
                    margin: 0;
                    cursor: pointer;
                    transition: all 0.2s ease;
                }
                
                .uvf-logo-svg {
                    height: 18px;
                    width: auto;
                    opacity: 0.85;
                    filter: drop-shadow(0 1px 3px rgba(0,0,0,0.4));
                    transition: all 0.2s ease;
                }
                
                .uvf-framework-branding:hover .uvf-logo-svg {
                    opacity: 1;
                    transform: scale(1.05);
                }
                
                .uvf-framework-branding:active .uvf-logo-svg {
                    transform: scale(0.95);
                }
                
                /* Show on mobile - positioned above seekbar */
                @media (max-width: 768px) {
                    .uvf-above-seekbar-section {
                        margin-bottom: 6px;
                    }
                    
                    .uvf-time-display.uvf-above-seekbar {
                        font-size: 12px;
                        padding: 3px 6px;
                    }
                    
                    .uvf-logo-svg {
                        height: 16px;
                    }
                }
                
                /* Ultra small screens */
                @media (max-width: 480px) {
                    .uvf-video-title {
                        font-size: clamp(11px, 3.5vw, 14px) !important;
                    }
                    
                    .uvf-video-subtitle {
                        font-size: clamp(9px, 2.5vw, 11px) !important;
                        -webkit-line-clamp: 1; /* Single line on very small screens */
                    }
                    
                    .uvf-left-side {
                        max-width: 80%;
                    }
                    
                    .uvf-above-seekbar-section {
                        margin-bottom: 5px;
                    }
                    
                    .uvf-time-display.uvf-above-seekbar {
                        font-size: 11px;
                        padding: 2px 5px;
                    }
                    
                    .uvf-logo-svg {
                        height: 14px;
                    }
                }
                
                @media (max-height: 400px) {
                    .uvf-above-seekbar-section {
                        margin-bottom: 4px;
                    }
                    
                    .uvf-time-display.uvf-above-seekbar {
                        font-size: 11px;
                        padding: 2px 4px;
                    }
                    
                    .uvf-logo-svg {
                        height: 12px;
                    }
                }
          height: 16px;
        }
      }


      /* Cast button grey state when casting */
      .uvf-control-btn.cast-grey {
        opacity: 0.6;
        filter: grayscale(0.6);
      }
      
      .uvf-control-btn.cast-grey:hover {
        transform: none;
        opacity: 0.7;
      }
      
      /* Pill-style button for prominent actions */
      .uvf-pill-btn {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        height: 40px;
        padding: 0 14px;
        border-radius: 999px;
        border: 1px solid rgba(255,255,255,0.25);
        background: rgba(255,255,255,0.08);
        color: #fff;
        cursor: pointer;
        transition: all 0.2s ease;
        box-shadow: 0 4px 14px rgba(0,0,0,0.4);
      }
      .uvf-pill-btn:hover {
        transform: translateY(-1px);
        background: rgba(255,255,255,0.15);
        box-shadow: 0 6px 18px rgba(0,0,0,0.5);
      }
      .uvf-pill-btn:active {
        transform: translateY(0);
      }
      .uvf-pill-btn svg {
        width: 18px;
        height: 18px;
        fill: currentColor;
      }
      .uvf-stop-cast-btn {
        background: linear-gradient(135deg, #ff4d4f, #d9363e);
        border: 1px solid rgba(255, 77, 79, 0.6);
        box-shadow: 0 0 20px rgba(255, 77, 79, 0.35);
      }
      .uvf-stop-cast-btn:hover {
        background: linear-gradient(135deg, #ff6b6d, #f0444b);
        box-shadow: 0 0 26px rgba(255, 77, 79, 0.5);
      }
      
      /* Quality Badge */
      .uvf-quality-badge {
        background: var(--uvf-accent-1-20);
        border: 1px solid var(--uvf-accent-1);
        color: var(--uvf-accent-1);
        font-size: 11px;
        font-weight: 600;
        padding: 4px 8px;
        border-radius: 4px;
        text-transform: uppercase;
        white-space: nowrap;
        display: none; /* Hidden by default, only shown when quality info is available */
      }
      
      .uvf-quality-badge.active {
        display: inline-block;
      }
      
      /* Time Tooltip */
      .uvf-time-tooltip {
        position: absolute;
        bottom: 20px;
        background: rgba(0,0,0,0.9);
        color: #fff;
        padding: 5px 10px;
        border-radius: 4px;
        font-size: 12px;
        pointer-events: none;
        opacity: 0;
        transform: translateX(-50%);
        transition: opacity 0.2s ease;
      }
      
      .uvf-progress-bar-wrapper:hover .uvf-time-tooltip {
        opacity: 1;
      }
      
      /* Shortcut Indicator */
      .uvf-shortcut-indicator {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: rgba(0,0,0,0.88);
        color: #fff;
        padding: 22px 32px;
        border-radius: 12px;
        font-size: 24px;
        font-weight: 600;
        opacity: 0;
        pointer-events: none;
        z-index: 20;
        transition: opacity 0.3s ease;
        white-space: nowrap;
        text-align: center;
        min-width: auto;
        max-width: 400px;
        backdrop-filter: blur(16px);
        box-shadow: 0 8px 32px rgba(0,0,0,0.5), 0 2px 8px rgba(0,0,0,0.3);
      }
      
      /* Time-specific indicator styling */
      .uvf-shortcut-indicator.uvf-time-indicator {
        padding: 12px 18px;
        font-size: 18px;
        font-weight: 500;
        font-family: 'Courier New', monospace;
        letter-spacing: 0.5px;
        border-radius: 6px;
        background: rgba(0,0,0,0.85);
        backdrop-filter: blur(4px);
      }
      
      .uvf-shortcut-indicator.active {
        animation: uvf-fadeInOut 1s ease;
      }
      
      /* Key action overlay styles (YouTube-like) */
      .uvf-shortcut-indicator.uvf-ki-icon {
        background: transparent;
        padding: 0;
        border-radius: 50%;
      }
      .uvf-shortcut-indicator .uvf-ki {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 10px;
        color: var(--uvf-icon-color);
      }
      .uvf-shortcut-indicator .uvf-ki svg {
        width: 72px;
        height: 72px;
        fill: var(--uvf-icon-color);
        filter: drop-shadow(0 2px 6px rgba(0,0,0,0.45));
      }
      .uvf-shortcut-indicator .uvf-ki-skip {
        position: relative;
        width: 110px;
        height: 110px;
      }
      .uvf-shortcut-indicator .uvf-ki-skip svg {
        width: 110px;
        height: 110px;
        position: relative;
        z-index: 1;
      }
      .uvf-shortcut-indicator .uvf-ki-skip .uvf-ki-skip-num {
        position: absolute;
        top: 52%;
        left: 50%;
        transform: translate(-50%, -50%);
        color: var(--uvf-text-primary);
        font-weight: 800;
        font-size: 22px;
        text-shadow: 0 2px 6px rgba(0,0,0,0.5);
        pointer-events: none;
        z-index: 2;
      }
      .uvf-shortcut-indicator .uvf-ki-volume { 
        align-items: center;
        gap: 16px;
      }
      .uvf-shortcut-indicator .uvf-ki-vol-icon svg { 
        width: 40px; 
        height: 40px;
        filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
      }
      .uvf-shortcut-indicator .uvf-ki-vol-bar {
        width: 220px;
        height: 10px;
        background: rgba(255,255,255,0.2);
        border-radius: 5px;
        overflow: hidden;
        box-shadow: inset 0 1px 3px rgba(0,0,0,0.3);
      }
      .uvf-shortcut-indicator .uvf-ki-vol-fill {
        height: 100%;
        background: linear-gradient(90deg, var(--uvf-accent-1), var(--uvf-accent-2));
        border-radius: 5px;
        box-shadow: 0 1px 4px rgba(255,87,34,0.4);
      }
      .uvf-shortcut-indicator .uvf-ki-vol-text {
        font-size: 20px;
        font-weight: 700;
        color: var(--uvf-text-primary);
        min-width: 56px;
        text-align: right;
        letter-spacing: 0.5px;
        text-shadow: 0 2px 6px rgba(0,0,0,0.4);
      }
      .uvf-shortcut-indicator .uvf-ki-text {
        font-size: 18px;
        color: var(--uvf-text-primary);
      }
      
      @keyframes uvf-fadeInOut {
        0% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
        20% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
        80% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
        100% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
      }
      
      /* Responsive shortcut/volume indicator for mobile and tablet */
      @media screen and (max-width: 767px) {
        .uvf-shortcut-indicator {
          padding: 18px 26px;
          font-size: 20px;
          max-width: calc(100vw - 48px);
          border-radius: 16px;
          backdrop-filter: blur(12px);
          background: rgba(0,0,0,0.85);
          box-shadow: 0 8px 32px rgba(0,0,0,0.4);
        }
        
        /* Volume indicator - vertical layout on mobile */
        .uvf-shortcut-indicator .uvf-ki-volume {
          flex-direction: column;
          gap: 14px;
          align-items: center;
        }
        
        .uvf-shortcut-indicator .uvf-ki-vol-icon svg {
          width: 36px;
          height: 36px;
          filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
        }
        
        .uvf-shortcut-indicator .uvf-ki-vol-bar {
          width: clamp(220px, 75vw, 300px);
          height: 12px;
          border-radius: 6px;
          background: rgba(255,255,255,0.2);
          box-shadow: inset 0 1px 3px rgba(0,0,0,0.3);
        }
        
        .uvf-shortcut-indicator .uvf-ki-vol-fill {
          height: 100%;
          border-radius: 6px;
          box-shadow: 0 1px 4px rgba(255,87,34,0.4);
        }
        
        .uvf-shortcut-indicator .uvf-ki-vol-text {
          font-size: 28px;
          font-weight: 700;
          min-width: auto;
          text-align: center;
          width: 100%;
          letter-spacing: 0.5px;
          text-shadow: 0 2px 8px rgba(0,0,0,0.5);
        }
        
        /* Skip indicators - optimized for mobile */
        .uvf-shortcut-indicator .uvf-ki-skip {
          width: 96px;
          height: 96px;
        }
        
        .uvf-shortcut-indicator .uvf-ki-skip svg {
          width: 96px;
          height: 96px;
          filter: drop-shadow(0 2px 6px rgba(0,0,0,0.4));
        }
        
        .uvf-shortcut-indicator .uvf-ki-skip .uvf-ki-skip-num {
          font-size: 20px;
          font-weight: 800;
        }
        
        /* Icon indicators */
        .uvf-shortcut-indicator .uvf-ki svg {
          width: 60px;
          height: 60px;
          filter: drop-shadow(0 2px 6px rgba(0,0,0,0.4));
        }
        
        .uvf-shortcut-indicator .uvf-ki-text {
          font-size: 17px;
          font-weight: 600;
        }
      }
      
      /* Mobile landscape - more compact */
      @media screen and (max-width: 767px) and (orientation: landscape) {
        .uvf-shortcut-indicator {
          padding: 14px 20px;
          max-width: calc(100vw - 80px);
        }
        
        .uvf-shortcut-indicator .uvf-ki-volume {
          flex-direction: row;
          gap: 12px;
        }
        
        .uvf-shortcut-indicator .uvf-ki-vol-icon svg {
          width: 28px;
          height: 28px;
        }
        
        .uvf-shortcut-indicator .uvf-ki-vol-bar {
          width: clamp(140px, 50vw, 200px);
          height: 10px;
        }
        
        .uvf-shortcut-indicator .uvf-ki-vol-text {
          font-size: 18px;
        }
      }
      
      @media screen and (min-width: 768px) and (max-width: 1023px) {
        /* Tablet optimization */
        .uvf-shortcut-indicator {
          padding: 18px 28px;
          border-radius: 14px;
        }
        
        .uvf-shortcut-indicator .uvf-ki-volume {
          gap: 14px;
        }
        
        .uvf-shortcut-indicator .uvf-ki-vol-bar {
          width: 180px;
          height: 10px;
        }
        
        .uvf-shortcut-indicator .uvf-ki-vol-icon svg {
          width: 34px;
          height: 34px;
        }
        
        .uvf-shortcut-indicator .uvf-ki-vol-text {
          font-size: 18px;
          font-weight: 600;
        }
      }
      
      /* Hide top bar when no cursor */
      .uvf-player-wrapper.no-cursor .uvf-top-bar {
        opacity: 0 !important;
        transform: translateY(-10px) !important;
        pointer-events: none;
      }

      /* Fullscreen specific styles - DESKTOP AND LANDSCAPE ONLY */
      /* Mobile portrait uses Material You layout in fullscreen */
      @media not all and (max-width: 767px) and (orientation: portrait) {
        .uvf-player-wrapper.uvf-fullscreen {
          position: fixed !important;
          top: 0 !important;
          left: 0 !important;
          width: 100vw !important;
          height: 100vh !important;
          z-index: 2147483646; /* One below ad container (2147483647) */
          background: #000;
        }
        
        .uvf-player-wrapper.uvf-fullscreen .uvf-video-container {
          width: 100vw !important;
          height: 100vh !important;
          max-width: none !important;
          max-height: none !important;
          aspect-ratio: unset !important;
          z-index: 0; /* Below YouTube iframe (z-index 1) and overlay (z-index 2) */
          overflow: hidden; /* Keep overflow hidden for video clipping */
        }
        
        .uvf-player-wrapper.uvf-fullscreen .uvf-video {
          width: 100vw !important;
          height: 100vh !important;
        }
        
        /* Maintain consistent control sizing in fullscreen - DESKTOP/LANDSCAPE ONLY */
        .uvf-player-wrapper.uvf-fullscreen .uvf-control-btn {
          width: 40px;
          height: 40px;
          min-width: 40px;
          min-height: 40px;
        }
        
        .uvf-player-wrapper.uvf-fullscreen .uvf-control-btn.play-pause {
          width: 50px;
          height: 50px;
          min-width: 50px;
          min-height: 50px;
        }
        
        .uvf-player-wrapper.uvf-fullscreen .uvf-control-btn svg {
          width: 20px;
          height: 20px;
        }
        
        .uvf-player-wrapper.uvf-fullscreen .uvf-control-btn.play-pause svg {
          width: 24px;
          height: 24px;
        }
        
        .uvf-player-wrapper.uvf-fullscreen .uvf-time-display {
          font-size: 14px;
          padding: 0 10px;
        }
        
        .uvf-player-wrapper.uvf-fullscreen .uvf-center-play-btn {
          width: 64px;
          height: 64px;
        }
        
        .uvf-player-wrapper.uvf-fullscreen .uvf-center-play-btn svg {
          width: 28px;
          height: 28px;
        }
        
        /* Ensure overlays remain visible in fullscreen with consistent spacing */
        .uvf-player-wrapper.uvf-fullscreen .uvf-top-bar,
        .uvf-player-wrapper.uvf-fullscreen .uvf-controls-bar,
        .uvf-player-wrapper.uvf-fullscreen .uvf-top-gradient,
        .uvf-player-wrapper.uvf-fullscreen .uvf-controls-gradient {
          z-index: 2147483645; /* Below ads (2147483647), above video */
        }

        /* Ensure ad container is always on top in fullscreen */
        .uvf-player-wrapper.uvf-fullscreen .uvf-ad-container {
          position: fixed !important;
          top: 0 !important;
          left: 0 !important;
          width: 100vw !important;
          height: 100vh !important;
          z-index: 2147483647 !important; /* Maximum - always on top */
          pointer-events: auto;
        }

        /* Ensure video container doesn't block ads or YouTube iframe */
        .uvf-player-wrapper.uvf-fullscreen .uvf-video-container {
          z-index: 0; /* Below YouTube iframe (z-index 1) and overlay (z-index 2) */
        }
        
        .uvf-player-wrapper.uvf-fullscreen .uvf-controls-bar {
          padding: 20px 30px; /* More generous padding in fullscreen */
        }
        
        .uvf-player-wrapper.uvf-fullscreen .uvf-controls-row {
          gap: 15px; /* Consistent gap in fullscreen */
        }
        
        .uvf-player-wrapper.uvf-fullscreen .uvf-top-bar {
          padding: 20px 30px;
        }
        
        /* Fullscreen hover and visibility states */
        .uvf-player-wrapper.uvf-fullscreen:hover .uvf-top-bar,
        .uvf-player-wrapper.uvf-fullscreen.controls-visible .uvf-top-bar {
          opacity: 1;
          transform: translateY(0);
        }


      }
      
      /* Safe Area Variables - Support for modern mobile devices */
      :root {
        /* iOS Safe Area Fallbacks */
        --uvf-safe-area-top: env(safe-area-inset-top, 0px);
        --uvf-safe-area-right: env(safe-area-inset-right, 0px);
        --uvf-safe-area-bottom: env(safe-area-inset-bottom, 0px);
        --uvf-safe-area-left: env(safe-area-inset-left, 0px);
        
        /* Dynamic Viewport Support */
        --uvf-dvh: 1dvh;
        --uvf-svh: 1svh;
        --uvf-lvh: 1lvh;
      }
      
      /* Cross-Browser Mobile Viewport Fixes */
      
      /* Modern browsers with dynamic viewport support */
      @supports (height: 100dvh) {
        /* Mobile devices - use dvh for dynamic viewport */
        @media screen and (max-width: 767px) {
          .uvf-player-wrapper,
          .uvf-video-container,
          .uvf-responsive-container {
            height: 100dvh !important;
            min-height: 100dvh !important;
          }
          
          /* Ensure controls stay visible with dvh */
          .uvf-controls-bar {
            bottom: env(safe-area-inset-bottom, 0px);
          }
        }
        
        /* Desktop - standard height */
        @media screen and (min-width: 768px) {
          /* Only force viewport height in fullscreen — in embedded/non-fullscreen mode
             the wrapper must size from content (aspect-ratio) so controls at bottom:0
             land at the video bottom, not at the viewport bottom (which gets clipped
             by any ancestor with overflow:hidden). */
          .uvf-player-wrapper.uvf-fullscreen {
            height: 100dvh;
          }
          .uvf-player-wrapper.uvf-fullscreen .uvf-video-container {
            height: 100dvh;
          }

          .uvf-responsive-container {
            height: 100dvh;
          }
        }
      }
      
      /* iOS Safari specific fixes - fullscreen only */
      @supports (-webkit-appearance: none) {
        .uvf-player-wrapper.uvf-fullscreen,
        .uvf-video-container.uvf-fullscreen {
          height: -webkit-fill-available;
          min-height: -webkit-fill-available;
        }
      }
      
      
      /* Mobile responsive styles for navigation buttons */
      @media screen and (max-width: 767px) {
        .uvf-nav-btn {
          width: 36px;
          height: 36px;
          min-width: 36px;
          min-height: 36px;
        }
        
        .uvf-nav-btn svg {
          width: 18px;
          height: 18px;
        }
        
        .uvf-navigation-controls {
          gap: 6px;
        }
        
        .uvf-left-side {
          gap: 8px;
          max-width: 75%;
        }
        
        /* Mobile title adjustments */
        .uvf-video-title {
          font-size: clamp(12px, 3vw, 16px) !important;
          line-height: 1.2;
        }
        
        .uvf-video-subtitle {
          font-size: clamp(10px, 2.2vw, 12px) !important;
          margin-top: 1px;
          /* Allow wrapping on mobile if needed */
          white-space: normal;
          display: -webkit-box;
          -webkit-line-clamp: 2;
          -webkit-box-orient: vertical;
          overflow: hidden;
        }
      }
      
      /* Mobile portrait - hide skip buttons, ensure top bar syncs with controls */
      @media screen and (max-width: 767px) and (orientation: portrait) {
        #uvf-skip-back,
        #uvf-skip-forward {
          display: none !important;
        }
        
        /* Top bar structure */
        .uvf-top-bar {
          display: flex !important;
          z-index: 10 !important;
        }
        
        .uvf-top-controls {
          display: flex !important;
        }
        
        /* Disable hover effect on mobile - use only controls-visible class */
        .uvf-player-wrapper:hover .uvf-top-bar {
          opacity: 0 !important;
          transform: translateY(-10px) !important;
        }
        
        /* Show top bar ONLY when controls are visible */
        .uvf-player-wrapper.controls-visible .uvf-top-bar {
          opacity: 1 !important;
          transform: translateY(0) !important;
        }
      }
      
      /* Mobile devices (landscape) - Optimized for fullscreen viewing with safe areas */
      @media screen and (max-width: 767px) and (orientation: landscape) {
        .uvf-responsive-container {
          width: 100vw !important;
          height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
          margin: 0;
          padding: 0;
          position: relative;
          overflow: hidden;
        }
        
        @supports (height: 100dvh) {
          .uvf-responsive-container {
            height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
          }
        }
        
        .uvf-responsive-container .uvf-player-wrapper {
          width: 100vw !important;
          height: 100% !important;
          min-height: calc(100vh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
        }
        
        @supports (height: 100dvh) {
          .uvf-responsive-container .uvf-player-wrapper {
            min-height: calc(100dvh - var(--uvf-safe-area-top) - var(--uvf-safe-area-bottom));
          }
        }
        
        .uvf-responsive-container .uvf-video-container {
          width: 100vw !important;
          height: 100% !important;
          aspect-ratio: unset !important;
          min-height: inherit;
        }
        
        /* Compact controls for landscape with safe area padding */
        .uvf-controls-bar {
          position: absolute;
          bottom: 0;
          left: 0;
          right: 0;
          padding: 10px 12px;
          padding-bottom: calc(10px + var(--uvf-safe-area-bottom));
          padding-left: calc(12px + var(--uvf-safe-area-left));
          padding-right: calc(12px + var(--uvf-safe-area-right));
          background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 80%, var(--uvf-overlay-transparent) 100%);
          box-sizing: border-box;
          z-index: 1000;
        }
        
        .uvf-progress-section {
          margin-bottom: 10px;
        }
        
        .uvf-controls-row {
          gap: 10px;
        }
        
        /* Touch-friendly but compact controls for landscape */
        .uvf-control-btn {
          width: 40px;
          height: 40px;
          min-width: 40px;
          min-height: 40px;
        }
        
        .uvf-control-btn.play-pause {
          width: 48px;
          height: 48px;
          min-width: 48px;
          min-height: 48px;
        }
        
        .uvf-control-btn svg {
          width: 18px;
          height: 18px;
        }
        
        .uvf-control-btn.play-pause svg {
          width: 22px;
          height: 22px;
        }
        
        /* Top bar for landscape - compact padding */
        .uvf-top-bar,
        .uvf-video-container .uvf-top-bar,
        .uvf-responsive-container .uvf-video-container .uvf-top-bar {
          padding: 8px 12px !important;
          padding-top: calc(8px + var(--uvf-safe-area-top)) !important;
          padding-left: calc(12px + var(--uvf-safe-area-left)) !important;
          padding-right: calc(12px + var(--uvf-safe-area-right)) !important;
          gap: 6px !important;
        }
        
        /* Title bar within top bar - landscape */
        .uvf-top-bar .uvf-title-bar {
          padding: 0;
        }
        
        .uvf-video-title {
          font-size: 14px;
        }
        
        .uvf-video-subtitle {
          font-size: 11px;
        }
        
        .uvf-time-display {
          font-size: 11px;
          padding: 0 6px;
        }
        
        /* Hide volume panel in landscape too */
        .uvf-volume-panel {
          display: none;
        }
        
        /* Compact progress bar for landscape */
        .uvf-progress-bar {
          height: 3px;
          margin-bottom: 8px;
        }
        
        .uvf-progress-handle {
          width: 16px;
          height: 16px;
        }
        
        /* Disable hover effect on mobile landscape - use only controls-visible class */
        .uvf-player-wrapper:hover .uvf-top-bar {
          opacity: 0 !important;
          transform: translateY(-10px) !important;
        }
        
        /* Show top bar ONLY when controls are visible */
        .uvf-player-wrapper.controls-visible .uvf-top-bar {
          opacity: 1 !important;
          transform: translateY(0) !important;
        }
        
        /* Top bar in fullscreen landscape */
        .uvf-player-wrapper.uvf-fullscreen .uvf-top-bar,
        .uvf-player-wrapper.uvf-fullscreen .uvf-video-container .uvf-top-bar,
        .uvf-responsive-container .uvf-player-wrapper.uvf-fullscreen .uvf-top-bar,
        .uvf-responsive-container .uvf-player-wrapper.uvf-fullscreen .uvf-video-container .uvf-top-bar {
          display: flex !important;
        }
      }
      }

      
      /* Tablet devices - Enhanced UX with desktop features */
      @media screen and (min-width: 768px) and (max-width: 1023px) {
        /* Tablet navigation and title adjustments */
        .uvf-nav-btn {
          width: 38px;
          height: 38px;
          min-width: 38px;
          min-height: 38px;
        }
        
        .uvf-nav-btn svg {
          width: 19px;
          height: 19px;
        }
        
        .uvf-left-side {
          max-width: 70%;
        }
        
        .uvf-video-title {
          font-size: clamp(15px, 2.2vw, 17px) !important;
        }
        
        .uvf-video-subtitle {
          font-size: clamp(12px, 1.8vw, 13px) !important;
        }
        
        .uvf-controls-bar {
          padding: 18px 16px;
          background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 70%, var(--uvf-overlay-transparent) 100%);
        }
        
        .uvf-progress-section {
          margin-bottom: 14px;
        }
        
        .uvf-controls-row {
          gap: 12px;
        }
        
        /* Touch-optimized tablet controls */
        .uvf-control-btn {
          width: 42px;
          height: 42px;
          min-width: 42px;
          min-height: 42px;
        }
        
        .uvf-control-btn.play-pause {
          width: 50px;
          height: 50px;
          min-width: 50px;
          min-height: 50px;
        }
        
        .uvf-control-btn svg {
          width: 19px;
          height: 19px;
        }
        
        .uvf-control-btn.play-pause svg {
          width: 23px;
          height: 23px;
        }
        
        /* Top bar for tablet */
        .uvf-top-bar {
          padding: 16px;
          gap: 12px;
        }
        
        .uvf-video-title {
          font-size: 17px;
          font-weight: 600;
        }
        
        .uvf-video-subtitle {
          font-size: 12px;
        }
        
        .uvf-time-display {
          font-size: 13px;
          font-weight: 500;
          padding: 0 8px;
        }
        
        /* Tablet center play button - consistent theming */
        .uvf-center-play-btn {
          width: clamp(48px, 10vw, 64px);
          height: clamp(48px, 10vw, 64px);
          background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
          border: 0;
          box-shadow: 0 4px 16px var(--uvf-accent-1-20);
        }
        
        .uvf-center-play-btn:hover {
          transform: scale(1.08);
          filter: saturate(1.08) brightness(1.05);
          box-shadow: 0 6px 20px var(--uvf-accent-1-20);
        }
        
        .uvf-center-play-btn svg {
          width: clamp(20px, 3.5vw, 26px);
          height: clamp(20px, 3.5vw, 26px);
        }
        
        /* Tablet volume control - keep desktop functionality */
        .uvf-volume-panel {
          display: flex;
        }
        
        .uvf-volume-slider {
          width: 100px;
        }
        
        /* Show quality badge on tablets */
        .uvf-quality-badge {
          font-size: 10px;
          padding: 3px 6px;
        }
        
        /* Tablet progress bar */
        .uvf-progress-bar {
          height: 3px;
        }
        
        .uvf-progress-handle {
          width: 16px;
          height: 16px;
        }
        
        /* Settings menu for tablets */
        .uvf-settings-menu {
          min-width: 240px;
          font-size: 13px;
        }
        
        .uvf-settings-option {
          padding: 10px 14px;
          font-size: 13px;
        }
      }
      
      /* Large screens - Enhanced desktop experience */
      @media screen and (min-width: 1024px) {
        .uvf-responsive-container {
          padding: 10px;
        }
        
        .uvf-controls-bar {
          padding: 20px;
          background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 60%, var(--uvf-overlay-transparent) 100%);
        }
        
        .uvf-progress-section {
          margin-bottom: 16px;
        }
        
        .uvf-controls-row {
          gap: 14px;
        }
        
        /* Desktop controls with enhanced hover */
        .uvf-control-btn {
          width: 40px;
          height: 40px;
          transition: all 0.2s cubic-bezier(0.4, 0.0, 0.2, 1);
        }
        
        .uvf-control-btn:hover {
          transform: scale(1.1);
          background: var(--uvf-overlay-medium);
        }
        
        .uvf-control-btn:active {
          transform: scale(0.95);
        }
        
        .uvf-control-btn svg {
          width: 20px;
          height: 20px;
          transition: all 0.2s ease;
        }
        
        .uvf-control-btn:hover svg {
          opacity: 1;
        }
        
        .uvf-control-btn.play-pause {
          width: 50px;
          height: 50px;
        }
        
        .uvf-control-btn.play-pause:hover {
          transform: scale(1.08);
          background: var(--uvf-primary-color);
        }
        
        .uvf-control-btn.play-pause svg {
          width: 24px;
          height: 24px;
        }
        
        /* Top bar for desktop 1024px+ */
        .uvf-top-bar {
          padding: 20px;
          gap: 20px;
        }
        
        .uvf-video-title {
          font-size: 18px;
          font-weight: 600;
        }
        
        .uvf-video-subtitle {
          font-size: 13px;
          opacity: 0.9;
        }
        
        .uvf-time-display {
          font-size: 14px;
          font-weight: 500;
          padding: 0 10px;
        }
        
        /* Enhanced center play button with smooth transitions */
        .uvf-center-play-btn {
          width: clamp(56px, 10vw, 72px);
          height: clamp(56px, 10vw, 72px);
          background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
          border: 0;
          border-radius: 50%;
          opacity: 1;
          transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
          display: flex;
          align-items: center;
          justify-content: center;
          cursor: pointer;
          box-shadow: 0 4px 16px var(--uvf-accent-1-20);
        }
        
        .uvf-center-play-btn:hover {
          transform: scale(1.08);
          filter: saturate(1.08) brightness(1.05);
          box-shadow: 0 6px 20px var(--uvf-accent-1-20);
        }
        
        .uvf-center-play-btn:active {
          transform: scale(0.95);
          transition: all 0.1s ease;
        }
        
        .uvf-center-play-btn svg {
          width: clamp(24px, 4vw, 30px);
          height: clamp(24px, 4vw, 30px);
          fill: #fff;
          margin-left: 3px;
          filter: drop-shadow(0 1px 3px rgba(0,0,0,0.35));
        }
        
        /* Optional: Add subtle pulse animation on idle */
        @keyframes uvf-pulse {
          0%, 100% { transform: scale(1); opacity: 0.9; }
          50% { transform: scale(1.05); opacity: 1; }
        }
        
        .uvf-center-play-btn {
          animation: uvf-pulse 2s ease-in-out infinite;
        }
        
        .uvf-progress-bar-wrapper:hover .uvf-progress-bar {
          height: 6px;
        }
        
        /* Volume slider enhancements */
        .uvf-volume-panel:hover .uvf-volume-slider {
          width: 120px;
        }
        
        .uvf-volume-slider {
          transition: width 0.2s ease;
        }
        
        /* Settings menu enhancement */
        .uvf-settings-menu {
          backdrop-filter: blur(10px);
          background: rgba(0, 0, 0, 0.85);
          border: 1px solid rgba(255, 255, 255, 0.1);
        }
        
        .uvf-settings-option:hover {
          background: var(--uvf-overlay-medium);
          transform: translateX(4px);
        }
      }
      
      /* Ultra-wide screens (1440px and above) */
      @media screen and (min-width: 1440px) {
        .uvf-controls-bar {
          padding: 24px;
        }
        
        .uvf-control-btn {
          width: 44px;
          height: 44px;
        }
        
        .uvf-control-btn svg {
          width: 22px;
          height: 22px;
        }
        
        .uvf-control-btn.play-pause {
          width: 56px;
          height: 56px;
        }
        
        .uvf-control-btn.play-pause svg {
          width: 28px;
          height: 28px;
        }
        
        .uvf-center-play-btn {
          width: clamp(64px, 10vw, 76px);
          height: clamp(64px, 10vw, 76px);
          background: linear-gradient(135deg, var(--uvf-accent-1), var(--uvf-accent-2));
          border: 0;
          box-shadow: 0 4px 16px var(--uvf-accent-1-20);
        }
        
        .uvf-center-play-btn:hover {
          transform: scale(1.08);
          filter: saturate(1.08) brightness(1.05);
          box-shadow: 0 6px 20px var(--uvf-accent-1-20);
        }
        
        .uvf-center-play-btn svg {
          width: clamp(28px, 4.5vw, 32px);
          height: clamp(28px, 4.5vw, 32px);
          margin-left: 3px;
        }
        
        .uvf-video-title {
          font-size: 20px;
        }
        
        .uvf-time-display {
          font-size: 15px;
        }
        
        .uvf-volume-slider {
          width: 130px;
        }
        
        .uvf-volume-panel:hover .uvf-volume-slider {
          width: 150px;
        }
      }
      
      /* High-DPI display optimizations */
      @media screen and (-webkit-min-device-pixel-ratio: 2), 
             screen and (min-resolution: 192dpi) {
        .uvf-control-btn {
          image-rendering: -webkit-optimize-contrast;
          image-rendering: crisp-edges;
        }
        
        .uvf-progress-handle {
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
        }
        
        .uvf-settings-menu {
          border-width: 0.5px;
        }
      }
      
      /* Reduced motion accessibility */
      @media (prefers-reduced-motion: reduce) {
        .uvf-control-btn,
        .uvf-center-play-btn,
        .uvf-progress-handle,
        .uvf-volume-slider,
        .uvf-settings-option {
          transition: none !important;
        }
        
        .uvf-control-btn:hover,
        .uvf-center-play-btn:hover {
          transform: none !important;
        }
      }

      /* Define overlay variables late to allow theme overrides elsewhere if needed */
      .uvf-player-wrapper {
        --uvf-overlay-strong: var(--uvf-overlay-strong, rgba(0,0,0,0.95));
        --uvf-overlay-medium: var(--uvf-overlay-medium, rgba(0,0,0,0.7));
        --uvf-overlay-transparent: var(--uvf-overlay-transparent, rgba(0,0,0,0));
      }
      
      /* Touch device optimizations */
      @media (hover: none) and (pointer: coarse) {
        .uvf-control-btn {
          min-width: 44px;
          min-height: 44px;
          border-radius: 50%
        }
        
        .uvf-progress-bar {
          height: 3px;
        }
        
        .uvf-progress-handle {
          width: 20px;
          height: 20px;
        }
        
        .uvf-volume-slider {
          height: 10px;
        }
        
        /* Touch-specific hover replacements */
        .uvf-control-btn:active {
          background: var(--uvf-overlay-medium);
          transform: scale(0.95);
        }
      }
      
      /* Keyboard navigation and accessibility */
      .uvf-control-btn:focus-visible,
      .uvf-center-play-btn:focus-visible {
        outline: 2px solid var(--uvf-primary-color, #007bff);
        outline-offset: 2px;
        background: var(--uvf-overlay-medium);
      }
      
      .uvf-progress-bar-wrapper:focus-visible {
        outline: 2px solid var(--uvf-primary-color, #007bff);
        outline-offset: 2px;
      }
      
      .uvf-volume-slider:focus-visible {
        outline: 2px solid var(--uvf-primary-color, #007bff);
        outline-offset: 1px;
      }
      
      .uvf-settings-option:focus-visible {
        background: var(--uvf-overlay-medium);
        outline: 2px solid var(--uvf-primary-color, #007bff);
        outline-offset: -2px;
      }
      
      /* Screen reader and accessibility improvements */
      .uvf-sr-only {
        position: absolute !important;
        width: 1px !important;
        height: 1px !important;
        padding: 0 !important;
        margin: -1px !important;
        overflow: hidden !important;
        clip: rect(0, 0, 0, 0) !important;
        white-space: nowrap !important;
        border: 0 !important;
      }
      
      /* High contrast mode support */
      @media (prefers-contrast: high) {
        .uvf-control-btn {
          border: 1px solid;
        }
        
        .uvf-progress-bar {
          border: 1px solid;
        }
        
        .uvf-progress-handle {
          border: 2px solid;
        }
        
        .uvf-settings-menu {
          border: 2px solid;
        }
      }
      
      /* Paywall Responsive Styles */
      .uvf-paywall-overlay {
        position: absolute !important;
        inset: 0 !important;
        background: rgba(0,0,0,0.9) !important;
        z-index: 2147483000 !important;
        display: none !important;
        align-items: center !important;
        justify-content: center !important;
        padding: 10px !important;
        box-sizing: border-box !important;
      }
      
      .uvf-paywall-modal {
        width: 90vw !important;
        height: auto !important;
        max-width: 500px !important;
        max-height: 90vh !important;
        background: #0f0f10 !important;
        border: 1px solid rgba(255,255,255,0.15) !important;
        border-radius: 12px !important;
        display: flex !important;
        flex-direction: column !important;
        overflow: auto !important;
        box-shadow: 0 20px 60px rgba(0,0,0,0.7) !important;
      }
      
      /* Paywall Mobile Portrait */
      @media screen and (max-width: 767px) and (orientation: portrait) {
        .uvf-paywall-modal {
          width: 95vw !important;
          max-width: none !important;
          max-height: 85vh !important;
          margin: 0 !important;
        }
        
        .uvf-paywall-modal > div:first-child {
          padding: 12px 16px !important;
          font-size: 16px !important;
        }
        
        .uvf-paywall-modal > div:last-child {
          padding: 16px !important;
        }
      }
      
      /* Paywall Mobile Landscape */
      @media screen and (max-width: 767px) and (orientation: landscape) {
        .uvf-paywall-modal {
          width: 85vw !important;
          max-height: 80vh !important;
        }
        
        .uvf-paywall-modal > div:first-child {
          padding: 10px 16px !important;
        }
        
        .uvf-paywall-modal > div:last-child {
          padding: 14px !important;
        }
      }
      
      /* Paywall Tablet */
      @media screen and (min-width: 768px) and (max-width: 1023px) {
        .uvf-paywall-modal {
          width: 80vw !important;
          max-width: 600px !important;
          max-height: 80vh !important;
        }
      }
      
      /* Desktop styles for title and navigation */
      @media screen and (min-width: 1024px) {
        .uvf-left-side {
          max-width: 65%; /* More space for title on desktop */
        }
        
        .uvf-video-title {
          font-size: clamp(16px, 1.8vw, 20px) !important;
          font-weight: 700; /* Bolder on desktop */
        }
        
        .uvf-video-subtitle {
          font-size: clamp(13px, 1.4vw, 15px) !important;
          margin-top: 3px;
        }
        
        /* Allow hover effects on desktop */
        .uvf-title-bar:hover .uvf-video-title {
          color: var(--uvf-accent-1, #ff0000);
          transition: color 0.3s ease;
        }
      }
      
      /* Hide YouTube UI elements */
      iframe[src*="youtube.com"] {
        pointer-events: auto !important;
      }
      
      /* Hide YouTube title, logo, and controls overlay */
      .ytp-chrome-top, 
      .ytp-chrome-bottom,
      .ytp-watermark,
      .ytp-gradient-top,
      .ytp-gradient-bottom,
      .ytp-show-cards-title,
      .ytp-cards-teaser,
      .ytp-endscreen-element,
      .ytp-ce-element,
      .ytp-suggested-action,
      .ytp-pause-overlay,
      .ytp-endscreen,
      .ytp-videowall-still,
      .ytp-ce-covering-overlay,
      .ytp-ce-element-show,
      .ytp-cards-button,
      .ytp-cards-button-icon,
      .ytp-impression-link {
        display: none !important;
        visibility: hidden !important;
        opacity: 0 !important;
        pointer-events: none !important;
      }

      /* YouTube overlay to block native controls */
      .uvf-youtube-overlay {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: calc(100% - 120px); /* Leave 120px at bottom for custom controls */
        z-index: 2;
        cursor: pointer;
        background: transparent;
        pointer-events: none; /* Allow clicks to pass through to controls bar */
      }

      /* Overlay should NEVER cover the controls - always leave space */
      .uvf-player-wrapper:not(:hover) .uvf-youtube-overlay {
        height: calc(100% - 120px); /* Keep same height - don't expand */
      }

      /* Ultra-wide screens */
      @media screen and (min-width: 1440px) {
        .uvf-video-title {
          font-size: clamp(18px, 1.6vw, 22px) !important;
        }
        
        .uvf-video-subtitle {
          font-size: clamp(14px, 1.2vw, 16px) !important;
        }
      }
      
      /* Paywall Desktop */
      @media screen and (min-width: 1024px) {
        .uvf-paywall-modal {
          width: 70vw !important;
          max-width: 800px !important;
          max-height: 70vh !important;
        }
      }
      
      /* Hide controls when controls={false} */
      .uvf-player-wrapper.controls-disabled .uvf-controls-bar {
        display: none !important;
        visibility: hidden !important;
        position: absolute !important;
        width: 0 !important;
        height: 0 !important;
        opacity: 0 !important;
        pointer-events: none !important;
      }
      
      .uvf-player-wrapper.controls-disabled .uvf-top-bar {
        display: none !important;
        visibility: hidden !important;
        position: absolute !important;
        width: 0 !important;
        height: 0 !important;
        opacity: 0 !important;
        pointer-events: none !important;
      }
      
      .uvf-player-wrapper.controls-disabled .uvf-center-play-container {
        display: none !important;
        visibility: hidden !important;
        position: absolute !important;
        width: 0 !important;
        height: 0 !important;
        opacity: 0 !important;
        pointer-events: none !important;
      }
      
      .uvf-player-wrapper.controls-disabled .uvf-center-play-btn {
        display: none !important;
        visibility: hidden !important;
        opacity: 0 !important;
        pointer-events: none !important;
      }
      
      .uvf-player-wrapper.controls-disabled .uvf-pulse {
        display: none !important;
        visibility: hidden !important;
        opacity: 0 !important;
        pointer-events: none !important;
      }

      /* Disable controls when paywall is active - prevent playback bypass */
      .uvf-player-wrapper.paywall-active .uvf-play-btn,
      .uvf-player-wrapper.paywall-active .uvf-center-play-btn,
      .uvf-player-wrapper.paywall-active .uvf-progress-bar,
      .uvf-player-wrapper.paywall-active .uvf-seek-btn {
        opacity: 0.4 !important;
        pointer-events: none !important;
        cursor: not-allowed !important;
      }

      .uvf-player-wrapper.paywall-active .uvf-video-container {
        pointer-events: none !important;
      }

      .uvf-player-wrapper.paywall-active::after {
        content: '' !important;
        position: absolute !important;
        top: 0 !important;
        left: 0 !important;
        right: 0 !important;
        bottom: 0 !important;
        background: transparent !important;
        z-index: 1000 !important;
        pointer-events: auto !important;
        cursor: not-allowed !important;
      }

      /* Hide custom controls when YouTube native controls are active */
      .uvf-player-wrapper.youtube-native-controls-mode .uvf-controls-bar {
        display: none !important;
        visibility: hidden !important;
        opacity: 0 !important;
        pointer-events: none !important;
      }
      
      .uvf-player-wrapper.youtube-native-controls-mode .uvf-top-bar {
        display: none !important;
        visibility: hidden !important;
        opacity: 0 !important;
        pointer-events: none !important;
      }
      
      .uvf-player-wrapper.youtube-native-controls-mode .uvf-center-play-container {
        display: none !important;
        visibility: hidden !important;
        opacity: 0 !important;
        pointer-events: none !important;
      }



      /* Override any inline styles */
      .controls-disabled .uvf-controls-bar,
      .controls-disabled .uvf-top-bar,
      .controls-disabled .uvf-center-play-container,
      .controls-disabled .uvf-center-play-btn,
      .controls-disabled .uvf-pulse {
        display: none !important;
        visibility: hidden !important;
      }

      /* Playlist prev/next navigation buttons */
      .uvf-btn-prev svg,
      .uvf-btn-next svg {
        width: 20px;
        height: 20px;
        fill: var(--uvf-icon-color);
        pointer-events: none;
      }
    `;
  }

  /**
   * Creates framework branding logo
   * Only creates branding if showFrameworkBranding is not explicitly set to false
   */
  private createFrameworkBranding(container: HTMLElement): void {
    // Double-check configuration (defensive programming)
    if ((this.config as any).showFrameworkBranding === false) {
      this.debugLog('Framework branding disabled by configuration');
      return;
    }
    const brandingContainer = document.createElement('div');
    brandingContainer.className = 'uvf-framework-branding';
    brandingContainer.setAttribute('title', 'Powered by flicknexs');

    const logoSvg = `
            <svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" id="Layer_1" viewBox="0 0 180 29" class="uvf-logo-svg">
                <defs>
                    <style>.cls-1{fill:#0d5ef8;}.cls-2{fill:#ff0366;opacity:0.94;}.cls-3{fill:#fff;}</style>
                </defs>
                <title>flicknexs</title>
                <path class="cls-1" d="M45.93,1.53q-.87.15-3.18.48l-3.59.51c-1.26.16-2.42.3-3.51.42s-2.17.19-3.26.25q.7,4,1.17,10.68T34,25.67a22.58,22.58,0,0,0,2.73-.14,19.27,19.27,0,0,0,2.47-.45c.09-1.29.16-2.69.22-4.22s.1-3.16.14-4.9c.75-.09,1.54-.16,2.37-.2s1.76,0,2.77,0c0-1,0-1.89-.1-2.57a12,12,0,0,0-.35-2,20.14,20.14,0,0,0-2.34.13,18.77,18.77,0,0,0-2.38.4c0-.78,0-1.57,0-2.37s-.06-1.59-.1-2.37c.51,0,1.3-.11,2.39-.23l4.1-.42A10.41,10.41,0,0,0,46.13,5,8.75,8.75,0,0,0,46.21,4a10.85,10.85,0,0,0-.08-1.34,6.31,6.31,0,0,0-.2-1.08Z"/>
                <path class="cls-1" d="M61.11,21a20.41,20.41,0,0,0-3.07.25,26,26,0,0,0-3.25.7c-.14-.88-.25-1.94-.32-3.17s-.1-3.13-.1-5.67c0-1.76.05-3.42.14-5s.22-2.81.37-3.74a6.81,6.81,0,0,0-.74-.07l-.72,0a19.18,19.18,0,0,0-2.49.17A20.39,20.39,0,0,0,48.39,5c0,2.84.18,6.4.52,10.68s.75,7.81,1.2,10.6q3.78-.54,6.33-.73c1.69-.14,3.46-.2,5.32-.2a14.9,14.9,0,0,0-.21-2.26A15.21,15.21,0,0,0,61.11,21Z"/>
                <path class="cls-1" d="M69.2,4.25A10.1,10.1,0,0,0,67.86,4a11.09,11.09,0,0,0-1.36-.07,7.12,7.12,0,0,0-1.92.25,5.51,5.51,0,0,0-1.59.7q.51,3.83.76,8.62T64,24.16A20.88,20.88,0,0,0,66.51,24a14.38,14.38,0,0,0,2.15-.39q.28-3,.45-7.12t.17-8q0-1.2,0-2.28c0-.72,0-1.37,0-2Z"/>
                <path class="cls-1" d="M80.84,7.18a1.42,1.42,0,0,1,1.31,1,6.72,6.72,0,0,1,.46,2.8,15.48,15.48,0,0,0,2.7-.19,12.09,12.09,0,0,0,2.55-.84A7,7,0,0,0,86,5.42,5.65,5.65,0,0,0,81.8,3.81a8.68,8.68,0,0,0-7.07,3.51,13.28,13.28,0,0,0-2.81,8.61,8.82,8.82,0,0,0,2,6.15,7,7,0,0,0,5.45,2.16,8.74,8.74,0,0,0,5.8-1.78,6.86,6.86,0,0,0,2.34-5,4.63,4.63,0,0,0-1.84-.66,8.79,8.79,0,0,0-2.43-.13,4.83,4.83,0,0,1-.39,3,2.1,2.1,0,0,1-2,1.06,2.23,2.23,0,0,1-2-1.58,11.41,11.41,0,0,1-.72-4.56,15.42,15.42,0,0,1,.79-5.22c.52-1.44,1.18-2.16,2-2.16Z"/>
                <path class="cls-1" d="M99.82,14.22a24.24,24.24,0,0,0,3-3.4,36.6,36.6,0,0,0,2.75-4.29a11.67,11.67,0,0,0-2.17-2,8.72,8.72,0,0,0-2.35-1.16a51.71,51.71,0,0,1-2.76,5.54,24.14,24.14,0,0,1-2.79,3.84c.07-1.54.19-3.05.36-4.54s.38-2.93.65-4.34a11.73,11.73,0,0,0-1.29-.25,12.48,12.48,0,0,0-1.44-.08a8.38,8.38,0,0,0-2.07.25,5.37,5.37,0,0,0-1.69.7c-.07,1.12-.13,2.22-.17,3.29s0,2.1,0,3.11q0,3.89.32,7.57a67.32,67.32,0,0,0,1,7,22.86,22.86,0,0,0,2.71-.17,13.09,13.09,0,0,0,2.23-.39q-.25-1.84-.39-3.66c-.1-1.21-.15-2.39-.17-3.55a.77.77,0,0,0,.15-.1l.16-.1a35.18,35.18,0,0,1,3.53,4.28a39,39,0,0,1,2.9,5A11.7,11.7,0,0,0,105,25.1a9.65,9.65,0,0,0,2.08-2.23A47.65,47.65,0,0,0,103.63,18a33.51,33.51,0,0,0-3.81-3.82Z"/>
                <path class="cls-1" d="M124.86,1.87a17.07,17.07,0,0,0-2.83.24,22.53,22.53,0,0,0-2.9.69c.17,1.1.3,2.53.4,4.28s.14,3.74.14,6a50.57,50.57,0,0,0-2.37-5,55,55,0,0,0-3-4.79,14.37,14.37,0,0,0-3,.41,11.7,11.7,0,0,0-2.53.91l-.22.11a3,3,0,0,0-.31.2q0,3.48.27,8.49t.8,11.13a19.17,19.17,0,0,0,2.49-.17A12.81,12.81,0,0,0,114,24V14.4c0-.92,0-1.77,0-2.54a49.47,49.47,0,0,1,2.4,4.34q1.16,2.37,3,6.72c.71,0,1.44-.1,2.2-.18s1.72-.22,2.88-.41c.17-2.32.3-4.74.41-7.25s.15-4.93.15-7.23c0-.94,0-1.9,0-2.89s0-2-.11-3.09Z"/>
                <path class="cls-1" d="M142.27,19.19c-.89.17-2.19.36-3.93.57s-2.89.33-3.43.33c-.07-.73-.13-1.5-.17-2.3s-.06-1.63-.08-2.47q1.49-.18,3.06-.27c1.05-.07,2.1-.1,3.17-.1,0-.93,0-1.75-.11-2.44a18.5,18.5,0,0,0-.34-2,21.7,21.7,0,0,0-2.92.19,19.5,19.5,0,0,0-2.86.62c0-1.07,0-1.87.05-2.4s0-1,.09-1.42c1.21-.07,2.39-.13,3.51-.16s2.2-.06,3.25-.06A20.56,20.56,0,0,0,142,4.83a19.15,19.15,0,0,0,.13-2.2,55.58,55.58,0,0,0-6.25.35c-2.12.23-4.63.62-7.51,1.16q.06,4,.63,9.91c.39,4,.81,7.44,1.28,10.42,2,0,4.41-.12,7.34-.34a37,37,0,0,0,5.21-.59,19.88,19.88,0,0,0-.16-2.43,8.59,8.59,0,0,0-.37-1.92Z"/>
                <path class="cls-1" d="M157,11.89q1.72-2.16,2.95-3.82c.83-1.1,1.56-2.16,2.22-3.17A9.76,9.76,0,0,0,160.4,3a9.62,9.62,0,0,0-2.13-1.4c-.53,1-1.09,1.95-1.69,2.94s-1.27,2-2,3.15c-.71-1.14-1.44-2.28-2.19-3.4S150.85,2.08,150,1a15.86,15.86,0,0,0-2.71,1.35,19.56,19.56,0,0,0-2.57,1.88Q146.37,6,147.85,8c1,1.32,2,2.73,3,4.25-.58.79-1.19,1.58-1.83,2.39s-2.26,2.78-4.88,5.95a10.68,10.68,0,0,0,1.35,1.5A10.94,10.94,0,0,0,147,23.32q1.74-1.77,3.21-3.42c1-1.09,2-2.28,3.05-3.57.56,1,1.16,2.12,1.8,3.35s1.49,2.94,2.58,5.15A25.27,25.27,0,0,0,160,23.46a8.81,8.81,0,0,0,1.61-1.32c-.69-1.82-1.42-3.57-2.18-5.27s-1.6-3.35-2.48-5Z"/>
                <path class="cls-1" d="M175.73,13.66a8.41,8.41,0,0,0-1.1-1.26,20.35,20.35,0,0,0-2-1.66,11.73,11.73,0,0,1-2.47-2.27,3.19,3.19,0,0,1-.56-1.77,1.53,1.53,0,0,1,.38-1.08,1.33,1.33,0,0,1,1-.41,1.93,1.93,0,0,1,1.63.76,5.25,5.25,0,0,1,.84,2.5a14.43,14.43,0,0,0,2.73-.41A6.64,6.64,0,0,0,178,7.23a6.74,6.74,0,0,0-2.32-4.11A7.23,7.23,0,0,0,171,1.73a7.69,7.69,0,0,0-5.27,1.77,5.83,5.83,0,0,0-2,4.57a6.91,6.91,0,0,0,.34,2.21a7.42,7.42,0,0,0,1,2,10.78,10.78,0,0,0,1.15,1.26a26.14,26.14,0,0,0,2.07,1.71a12.65,12.65,0,0,1,2.43,2.2,2.71,2.71,0,0,1,.55,1.59,2.06,2.06,0,0,1-.53,1.5,2,2,0,0,1-1.52.55,2.42,2.42,0,0,1-2.17-1.31,6.43,6.43,0,0,1-.81-3.43a8.78,8.78,0,0,0-2.12.32,10.48,10.48,0,0,0-2.17.77a6.45,6.45,0,0,0,2.23,4.9,7.93,7.93,0,0,0,5.57,2.06,7.31,7.31,0,0,0,5.11-1.89A6.18,6.18,0,0,0,177,17.7a7.12,7.12,0,0,0-.31-2.15,6.71,6.71,0,0,0-1-1.89Z"/>
                <path class="cls-2" d="M24.28,12a1.43,1.43,0,0,1,.44,2.44l-8.11,6.84-8.1,6.85a1.43,1.43,0,0,1-2.33-.85L4.3,16.84,2.43,6.4A1.43,1.43,0,0,1,4.32,4.8l10,3.59Z"/>
                <path class="cls-1" d="M29.4,13.7a1.62,1.62,0,0,1,0,2.81L19,22.5l-10.39,6a1.62,1.62,0,0,1-2.43-1.4v-24a1.62,1.62,0,0,1,2.43-1.4L19,7.7Z"/>
                <path class="cls-3" d="M17.5,8.84l-2.33.74c-1.12.36-2,.63-2.63.82-.92.28-1.78.52-2.58.74s-1.61.41-2.41.59C8.22,13.68,9,16.3,9.72,19.6s1.37,6.23,1.79,8.8a18.88,18.88,0,0,0,2-.44,14.36,14.36,0,0,0,1.8-.64c-.08-1-.2-2-.34-3.2s-.31-2.38-.5-3.69c.55-.16,1.14-.3,1.76-.43s1.31-.26,2.07-.38q-.2-1.15-.39-1.92a8.8,8.8,0,0,0-.5-1.42,16.83,16.83,0,0,0-1.74.38,14.8,14.8,0,0,0-1.73.6c-.1-.59-.2-1.18-.32-1.78s-.24-1.18-.37-1.76L15,13.26l3-.82a8.59,8.59,0,0,0,0-1,6.88,6.88,0,0,0-.08-.83q-.09-.54-.21-1a6.18,6.18,0,0,0-.29-.78Z"/>
            </svg>
        `;

    brandingContainer.innerHTML = logoSvg;

    // Add click handler to redirect to flicknexs.com
    brandingContainer.addEventListener('click', (event) => {
      event.preventDefault();
      event.stopPropagation();

      // Open flicknexs.com in a new tab/window
      const link = document.createElement('a');
      link.href = 'https://flicknexs.com/video-player-sdk?utm_source=npm&utm_medium=referral&utm_campaign=unified_video_framework';
      link.target = '_blank';
      link.rel = 'noopener noreferrer';
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);

      // Emit analytics event
      this.emit('frameworkBrandingClick', {
        timestamp: Date.now(),
        url: 'https://flicknexs.com/video-player-sdk?utm_source=npm&utm_medium=referral&utm_campaign=unified_video_framework',
        userAgent: navigator.userAgent
      });
    });

    container.appendChild(brandingContainer);

    this.debugLog('Framework branding added');
  }

  /**
   * Resolves control visibility configuration with backward compatibility
   * Priority: controlsVisibility config > legacy props > smart defaults > true
   */
  private resolveControlVisibility(): ControlsVisibilityConfig {
    const config = this.config as any;
    const cv = config.controlsVisibility || {};

    // Smart defaults based on context
    const hasPlaylist = config.playlist?.showPrevNext;
    const replaceSkip = config.playlist?.replaceSkipWithPrevNext;
    const hasPlaylistCallback = config.playlist?.onPlaylistToggle;
    const hasNavigation = config.navigation;

    return {
      playback: {
        centerPlayButton: cv.playback?.centerPlayButton ?? true,
        playPauseButton: cv.playback?.playPauseButton ?? true,
        skipButtons: cv.playback?.skipButtons ?? true,
        previousButton: cv.playback?.previousButton ?? !!(hasPlaylist && !replaceSkip),
        nextButton: cv.playback?.nextButton ?? !!(hasPlaylist && !replaceSkip),
      },
      audio: {
        volumeButton: cv.audio?.volumeButton ?? true,
        volumeSlider: cv.audio?.volumeSlider ?? true,
      },
      progress: {
        progressBar: cv.progress?.progressBar ?? true,
        timeDisplay: cv.progress?.timeDisplay ?? true,
      },
      quality: {
        badge: cv.quality?.badge ?? false, // Hide quality badge by default (e.g., "AUTO (360P)")
        settingsButton: cv.quality?.settingsButton ?? ((config.settings?.enabled ?? true) && (this.currentStreamType === 'hls' || this.currentStreamType === 'dash')),
      },
      display: {
        fullscreenButton: cv.display?.fullscreenButton ?? true,
        pipButton: cv.display?.pipButton ?? true,
      },
      features: {
        epgButton: cv.features?.epgButton ?? false,  // Hidden by default, shown when EPG data is set
        playlistButton: cv.features?.playlistButton ?? (hasPlaylistCallback ? true : false),
        castButton: cv.features?.castButton ?? true,
        // stopCastButton is controlled by runtime casting state via _syncCastButtons(), not user config
        shareButton: cv.features?.shareButton ?? (config.share?.enabled ?? true),
      },
      chrome: {
        navigationButtons: cv.chrome?.navigationButtons ?? (hasNavigation ? true : false),
        frameworkBranding: cv.chrome?.frameworkBranding ?? (config.showFrameworkBranding ?? true),
      },
    };
  }

  /**
   * Create navigation buttons (back/close) based on configuration
   */
  private createNavigationButtons(container: HTMLElement): void {
    const navigationConfig = (this.config as any).navigation;
    if (!navigationConfig) return;

    const { backButton, closeButton } = navigationConfig;

    // Back button
    if (backButton?.enabled) {
      const backBtn = document.createElement('button');
      backBtn.className = 'uvf-control-btn uvf-nav-btn';
      backBtn.id = this.getElementId('back-btn');
      backBtn.title = backButton.title || 'Back';
      backBtn.setAttribute('aria-label', backButton.ariaLabel || 'Go back');

      // Get icon based on config
      const backIcon = this.getNavigationIcon(backButton.icon || 'arrow', backButton.customIcon);
      backBtn.innerHTML = backIcon;

      // Add click handler
      backBtn.addEventListener('click', async (e) => {
        e.preventDefault();
        e.stopPropagation();

        if (backButton.onClick) {
          await backButton.onClick();
        } else if (backButton.href) {
          if (backButton.replace) {
            window.history.replaceState(null, '', backButton.href);
          } else {
            window.location.href = backButton.href;
          }
        } else {
          // Default: go back in history
          window.history.back();
        }

        this.emit('navigationBackClicked');
      });

      container.appendChild(backBtn);
    }

    // Close button
    if (closeButton?.enabled) {
      const closeBtn = document.createElement('button');
      closeBtn.className = 'uvf-control-btn uvf-nav-btn';
      closeBtn.id = this.getElementId('close-btn');
      closeBtn.title = closeButton.title || 'Close';
      closeBtn.setAttribute('aria-label', closeButton.ariaLabel || 'Close player');

      // Get icon based on config
      const closeIcon = this.getNavigationIcon(closeButton.icon || 'x', closeButton.customIcon);
      closeBtn.innerHTML = closeIcon;

      // Add click handler
      closeBtn.addEventListener('click', async (e) => {
        e.preventDefault();
        e.stopPropagation();

        if (closeButton.onClick) {
          await closeButton.onClick();
        } else {
          // Default behaviors
          if (closeButton.exitFullscreen && this.isFullscreen()) {
            await this.exitFullscreen();
          }

          if (closeButton.closeModal) {
            // Hide player or remove from DOM
            const playerWrapper = this.container?.querySelector('.uvf-player-wrapper') as HTMLElement;
            if (playerWrapper) {
              playerWrapper.style.display = 'none';
            }
          }
        }

        this.emit('navigationCloseClicked');
      });

      container.appendChild(closeBtn);
    }
  }

  /**
   * Get navigation icon SVG based on type
   */
  private getNavigationIcon(iconType: string, customIcon?: string): string {
    if (customIcon) {
      // If it's a URL, create img tag, otherwise assume it's SVG
      if (customIcon.startsWith('http') || customIcon.includes('.')) {
        return `<img src="${customIcon}" alt="" style="width: 20px; height: 20px;" />`;
      }
      return customIcon;
    }

    switch (iconType) {
      case 'arrow':
        return `<svg viewBox="0 0 24 24">
          <path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.42-1.41L7.83 13H20v-2z" fill="currentColor"/>
        </svg>`;

      case 'chevron':
        return `<svg viewBox="0 0 24 24">
          <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" fill="currentColor"/>
        </svg>`;

      case 'x':
        return `<svg viewBox="0 0 24 24">
          <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="currentColor"/>
        </svg>`;

      case 'close':
        return `<svg viewBox="0 0 24 24">
          <path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z" fill="currentColor"/>
        </svg>`;

      default:
        return `<svg viewBox="0 0 24 24">
          <path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.42-1.41L7.83 13H20v-2z" fill="currentColor"/>
        </svg>`;
    }
  }

  private createCustomControls(container: HTMLElement): void {
    // Add gradients
    const topGradient = document.createElement('div');
    topGradient.className = 'uvf-top-gradient';
    container.appendChild(topGradient);

    const controlsGradient = document.createElement('div');
    controlsGradient.className = 'uvf-controls-gradient';
    container.appendChild(controlsGradient);

    // Combined top bar: navigation buttons → title → controls
    const topBar = document.createElement('div');
    topBar.className = 'uvf-top-bar';

    // Left side container for navigation + title
    const leftSide = document.createElement('div');
    leftSide.className = 'uvf-left-side';

    // Navigation buttons (back/close)
    const navigationControls = document.createElement('div');
    navigationControls.className = 'uvf-navigation-controls';
    this.createNavigationButtons(navigationControls);
    leftSide.appendChild(navigationControls);

    // Title bar (after navigation buttons)
    const titleBar = document.createElement('div');
    titleBar.className = 'uvf-title-bar';
    titleBar.innerHTML = `
      <div class="uvf-title-content">
        <div class="uvf-title-text">
          <div class=\"uvf-video-title\" id=\"uvf-video-title\" style=\"display:none;\"></div>
          <div class=\"uvf-video-subtitle\" id=\"uvf-video-description\" style=\"display:none;\"></div>
        </div>
      </div>
    `;
    leftSide.appendChild(titleBar);

    topBar.appendChild(leftSide);

    // Top controls (right side - Cast and Share buttons)
    const topControls = document.createElement('div');
    topControls.className = 'uvf-top-controls';

    // Cast button
    const castBtn = document.createElement('button');
    castBtn.className = 'uvf-control-btn';
    castBtn.id = this.getElementId('cast-btn');
    castBtn.title = 'Cast';
    castBtn.setAttribute('aria-label', 'Cast');
    castBtn.innerHTML = `
      <svg viewBox="0 0 24 24">
        <path d="M1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm18-7H5v1.63c3.96 1.28 7.09 4.41 8.37 8.37H19V7zM1 10v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11zm20-7H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
      </svg>
    `;
    topControls.appendChild(castBtn);

    // Stop casting button
    const stopCastBtn = document.createElement('button');
    stopCastBtn.className = 'uvf-pill-btn uvf-stop-cast-btn';
    stopCastBtn.id = this.getElementId('stop-cast-btn');
    stopCastBtn.title = 'Stop Casting';
    stopCastBtn.setAttribute('aria-label', 'Stop Casting');
    stopCastBtn.style.display = 'none';
    stopCastBtn.innerHTML = `
      <svg viewBox="0 0 24 24" aria-hidden="true">
        <path d="M6 6h12v12H6z"/>
      </svg>
      <span>Stop Casting</span>
    `;
    topControls.appendChild(stopCastBtn);

    // Share button
    const shareBtn = document.createElement('button');
    shareBtn.className = 'uvf-control-btn';
    shareBtn.id = this.getElementId('share-btn');
    shareBtn.title = 'Share';
    shareBtn.setAttribute('aria-label', 'Share');
    shareBtn.innerHTML = `
      <svg viewBox="0 0 24 24">
        <path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z"/>
      </svg>
    `;
    topControls.appendChild(shareBtn);

    topBar.appendChild(topControls);

    container.appendChild(topBar);

    // Add loading spinner (show by default until video is ready)
    const loadingContainer = document.createElement('div');
    loadingContainer.className = 'uvf-loading-container active'; // Start with active class
    loadingContainer.id = this.getElementId('loading');
    loadingContainer.innerHTML = `
      <div class="uvf-loading-spinner"></div>
      <div class="uvf-loading-message"></div>
    `;
    container.appendChild(loadingContainer);

    // Add center play button container for proper responsive centering
    const centerPlayContainer = document.createElement('div');
    centerPlayContainer.className = 'uvf-center-play-container';

    const centerPlayBtn = document.createElement('div');
    centerPlayBtn.className = 'uvf-center-play-btn uvf-pulse';
    centerPlayBtn.id = this.getElementId('center-play');
    centerPlayBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z" stroke="currentColor" stroke-width="0.5" fill="currentColor"/></svg>';

    centerPlayContainer.appendChild(centerPlayBtn);
    container.appendChild(centerPlayContainer);

    // Add shortcut indicator
    const shortcutIndicator = document.createElement('div');
    shortcutIndicator.className = 'uvf-shortcut-indicator';
    shortcutIndicator.id = this.getElementId('shortcut-indicator');
    container.appendChild(shortcutIndicator);

    // Create controls bar
    const controlsBar = document.createElement('div');
    controlsBar.className = 'uvf-controls-bar';
    controlsBar.id = this.getElementId('controls');

    // Time and branding section above seekbar
    const aboveSeekbarSection = document.createElement('div');
    aboveSeekbarSection.className = 'uvf-above-seekbar-section';

    // Time display (moved to above seekbar)
    const timeDisplay = document.createElement('div');
    timeDisplay.className = 'uvf-time-display uvf-above-seekbar';
    timeDisplay.id = this.getElementId('time-display');
    timeDisplay.textContent = '00:00 / 00:00';
    aboveSeekbarSection.appendChild(timeDisplay);

    // Add framework branding next to time display
    this.createFrameworkBranding(aboveSeekbarSection);

    // Progress section
    const progressSection = document.createElement('div');
    progressSection.className = 'uvf-progress-section';

    const progressBar = document.createElement('div');
    progressBar.className = 'uvf-progress-bar-wrapper';
    progressBar.id = this.getElementId('progress-bar');
    progressBar.innerHTML = `
      <div class="uvf-progress-bar">
        <div class="uvf-progress-buffered" id="${this.getElementId('progress-buffered')}"></div>
        <div class="uvf-progress-filled" id="${this.getElementId('progress-filled')}"></div>
        <div class="uvf-progress-handle" id="${this.getElementId('progress-handle')}"></div>
      </div>
      <div class="uvf-thumbnail-preview" id="${this.getElementId('thumbnail-preview')}">
        <div class="uvf-thumbnail-preview-container">
          <div class="uvf-thumbnail-preview-image-wrapper">
            <img class="uvf-thumbnail-preview-image" id="${this.getElementId('thumbnail-image')}" alt="Preview" />
            <div class="uvf-thumbnail-preview-loading"></div>
          </div>
          <div class="uvf-thumbnail-preview-time" id="${this.getElementId('thumbnail-time')}">00:00</div>
        </div>
        <div class="uvf-thumbnail-preview-arrow"></div>
      </div>
      <div class="uvf-time-tooltip" id="${this.getElementId('time-tooltip')}">00:00</div>
    `;
    progressSection.appendChild(progressBar);

    // Controls row
    const controlsRow = document.createElement('div');
    controlsRow.className = 'uvf-controls-row';

    // Previous track button
    const prevBtn = document.createElement('button');
    prevBtn.className = 'uvf-control-btn uvf-btn-prev';
    prevBtn.id = this.getElementId('btn-prev');
    prevBtn.setAttribute('title', 'Previous track');
    prevBtn.setAttribute('aria-label', 'Previous track');
    prevBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M6 6h2v12H6zm3.5 6l8.5-6v12z" fill="currentColor"/></svg>';
    controlsRow.appendChild(prevBtn);

    // Play/Pause button
    const playPauseBtn = document.createElement('button');
    playPauseBtn.className = 'uvf-control-btn play-pause';
    playPauseBtn.id = this.getElementId('play-pause');
    playPauseBtn.innerHTML = `
      <svg viewBox="0 0 24 24" id="${this.getElementId('play-icon')}">
        <path d="M8 5v14l11-7z" stroke="currentColor" stroke-width="0.5" fill="currentColor"/>
      </svg>
      <svg viewBox="0 0 24 24" id="${this.getElementId('pause-icon')}" style="display: none;">
        <path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" stroke="currentColor" stroke-width="0.5" fill="currentColor"/>
      </svg>
    `;
    controlsRow.appendChild(playPauseBtn);

    // Next track button
    const nextBtn = document.createElement('button');
    nextBtn.className = 'uvf-control-btn uvf-btn-next';
    nextBtn.id = this.getElementId('btn-next');
    nextBtn.setAttribute('title', 'Next track');
    nextBtn.setAttribute('aria-label', 'Next track');
    nextBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M16 6h2v12h-2zM6 6L14.5 12L6 18z" fill="currentColor"/></svg>';
    controlsRow.appendChild(nextBtn);

    // Skip buttons — repurposed as prev/next track when replaceSkipWithPrevNext is set
    const hasPlaylistNav = !!(this.config as any).playlist?.replaceSkipWithPrevNext;

    const skipBackBtn = document.createElement('button');
    skipBackBtn.className = 'uvf-control-btn';
    skipBackBtn.id = this.getElementId('skip-back');
    if (hasPlaylistNav) {
      skipBackBtn.setAttribute('title', 'Previous track');
      skipBackBtn.setAttribute('aria-label', 'Previous track');
      skipBackBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M6 6h2v12H6zm3.5 6l8.5-6v12z" fill="currentColor"/></svg>';
    } else {
      skipBackBtn.setAttribute('title', 'Skip backward 10s');
      skipBackBtn.setAttribute('aria-label', 'Skip backward 10 seconds');
      skipBackBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z" stroke="currentColor" stroke-width="0.5" fill="currentColor"/></svg>';
    }
    controlsRow.appendChild(skipBackBtn);

    const skipForwardBtn = document.createElement('button');
    skipForwardBtn.className = 'uvf-control-btn';
    skipForwardBtn.id = this.getElementId('skip-forward');
    if (hasPlaylistNav) {
      skipForwardBtn.setAttribute('title', 'Next track');
      skipForwardBtn.setAttribute('aria-label', 'Next track');
      // ⏭ right-triangle + bar-right — right-side arrow for next track
      skipForwardBtn.innerHTML = '<svg viewBox="0 0 24 24" style="transform: scaleX(1) !important;"><path d="M16 6h2v12h-2zM6 6L14.5 12L6 18z" fill="currentColor"/></svg>';
    } else {
      skipForwardBtn.setAttribute('title', 'Skip forward 10s');
      skipForwardBtn.setAttribute('aria-label', 'Skip forward 10 seconds');
      // double-right-chevrons — proper RIGHT-pointing SVG with inline style override
      skipForwardBtn.innerHTML = '<svg viewBox="0 0 24 24" style="transform: scaleX(1) !important;"><path d="M13 6v12l8.5-6-8.5-6zm-.5 6l-8.5-6v12l8.5-6z" stroke="currentColor" stroke-width="0.5" fill="currentColor"/></svg>';
    }
    controlsRow.appendChild(skipForwardBtn);

    // Volume control
    const volumeControl = document.createElement('div');
    volumeControl.className = 'uvf-volume-control';

    volumeControl.innerHTML = `
      <button class="uvf-control-btn" id="${this.getElementId('volume-btn')}">
        <svg viewBox="0 0 24 24" id="${this.getElementId('volume-icon')}">
          <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z" stroke="currentColor" stroke-width="0.5" fill="currentColor"/>
        </svg>
        <svg viewBox="0 0 24 24" id="${this.getElementId('mute-icon')}" style="display: none;">
          <path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.20.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z" stroke="currentColor" stroke-width="0.5" fill="currentColor"/>
        </svg>
      </button>
      <div class="uvf-volume-panel" id="${this.getElementId('volume-panel')}">
        <div class="uvf-volume-slider" id="${this.getElementId('volume-slider')}">
          <div class="uvf-volume-fill" id="${this.getElementId('volume-fill')}" style="width: 100%;"></div>
        </div>
        <div class="uvf-volume-value" id="${this.getElementId('volume-value')}">100</div>
      </div>
    `;
    controlsRow.appendChild(volumeControl);

    // Right controls
    const rightControls = document.createElement('div');
    rightControls.className = 'uvf-right-controls';

    // Quality badge
    const qualityBadge = document.createElement('div');
    qualityBadge.className = 'uvf-quality-badge';
    qualityBadge.id = this.getElementId('quality-badge');
    qualityBadge.textContent = 'HD';
    rightControls.appendChild(qualityBadge);

    // Settings button with menu
    this.debugLog('Settings config check:', this.settingsConfig);
    this.debugLog('Settings visibility:', this.controlsVisibility.quality?.settingsButton);
    this.debugLog('Custom controls enabled:', this.useCustomControls);

    const settingsContainer = document.createElement('div');
    settingsContainer.className = 'uvf-settings-container';
    settingsContainer.style.position = 'relative';
    settingsContainer.style.display = 'flex';
    settingsContainer.style.alignItems = 'center';
    settingsContainer.style.justifyContent = 'center';

    const settingsBtn = document.createElement('button');
    settingsBtn.className = 'uvf-control-btn';
    settingsBtn.id = this.getElementId('settings-btn');
    settingsBtn.setAttribute('title', 'Settings');
    settingsBtn.setAttribute('aria-label', 'Settings');
    settingsBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>';
    settingsContainer.appendChild(settingsBtn);

    // Settings menu - will be populated dynamically based on video capabilities and configuration
    const settingsMenu = document.createElement('div');
    settingsMenu.className = 'uvf-settings-menu';
    settingsMenu.id = this.getElementId('settings-menu');
    // Initially empty - will be populated by updateSettingsMenu method
    settingsMenu.innerHTML = '';
    // CSS handles initial hidden state
    settingsContainer.appendChild(settingsMenu);
    rightControls.appendChild(settingsContainer);

    this.debugLog('Settings button created and added to controls');

    // EPG button (Electronic Program Guide)
    const epgBtn = document.createElement('button');
    epgBtn.className = 'uvf-control-btn';
    epgBtn.id = this.getElementId('epg-btn');
    epgBtn.title = 'Electronic Program Guide (Ctrl+G)';
    epgBtn.innerHTML = `<svg viewBox="0 0 24 24">
      <path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/>
      <rect x="17" y="3" width="2" height="2"/>
      <rect x="19" y="3" width="2" height="2"/>
      <path d="M17 1v2h2V1h2v2h1c.55 0 1 .45 1 1v16c0 .55-.45 1-1 1H2c-.55 0-1-.45-1-1V4c0-.55.45-1 1-1h1V1h2v2h12z" fill="none" stroke="currentColor" stroke-width="0.5"/>
    </svg>`;
    epgBtn.style.display = 'none'; // Initially hidden, will be shown when EPG data is available
    rightControls.appendChild(epgBtn);

    // PiP button - only show on desktop/supported browsers
    const pipBtn = document.createElement('button');
    pipBtn.className = 'uvf-control-btn';
    pipBtn.id = this.getElementId('pip-btn');
    pipBtn.title = 'Picture-in-Picture';
    pipBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z" stroke="currentColor" stroke-width="0.5" fill="currentColor"/></svg>';

    // Hide PiP button on mobile devices and browsers that don't support it
    if (this.isMobileDevice() || !this.isPipSupported()) {
      pipBtn.style.display = 'none';
    }

    rightControls.appendChild(pipBtn);

    // Playlist toggle button
    const playlistBtn = document.createElement('button');
    playlistBtn.className = 'uvf-control-btn';
    playlistBtn.id = this.getElementId('playlist-btn');
    playlistBtn.setAttribute('title', 'Toggle playlist');
    playlistBtn.setAttribute('aria-label', 'Toggle playlist');
    playlistBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>';
    rightControls.appendChild(playlistBtn);

    // Fullscreen button
    const fullscreenBtn = document.createElement('button');
    fullscreenBtn.className = 'uvf-control-btn';
    fullscreenBtn.id = this.getElementId('fullscreen-btn');
    fullscreenBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" stroke="currentColor" stroke-width="0.5" fill="currentColor"/></svg>';
    rightControls.appendChild(fullscreenBtn);

    controlsRow.appendChild(rightControls);

    // Assemble controls bar
    controlsBar.appendChild(aboveSeekbarSection);
    controlsBar.appendChild(progressSection);
    controlsBar.appendChild(controlsRow);

    // Force z-index to ensure controls are above YouTube overlay and video
    controlsBar.style.zIndex = '100';

    container.appendChild(controlsBar);

    this.controlsContainer = controlsBar;

    // NOTE: Do NOT call applyControlsVisibility() here!
    // The controls are not yet attached to the document, so getElementById() won't find them.
    // applyControlsVisibility() is called after the wrapper is appended to this.container.
  }

  private setupControlsEventListeners(): void {
    // Only check if custom controls are enabled, not video element
    // (YouTube players don't use video element but still need control listeners)
    if (!this.useCustomControls) return;

    const wrapper = this.container?.querySelector('.uvf-player-wrapper') as HTMLElement;
    const centerPlay = this.cachedElements.centerPlay;
    const playPauseBtn = this.getElement('play-pause');
    const skipBackBtn = this.getElement('skip-back');
    const skipForwardBtn = this.getElement('skip-forward');
    const volumeBtn = this.getElement('volume-btn');
    const volumePanel = this.getElement('volume-panel');
    const volumeSlider = this.getElement('volume-slider');
    const progressBar = this.cachedElements.progressBar;
    const fullscreenBtn = this.cachedElements.fullscreenBtn;
    const settingsBtn = this.getElement('settings-btn');

    // Get the event target (video element or YouTube player)
    const getEventTarget = () => this.youtubePlayer && this.youtubePlayerReady ? this.youtubePlayer : this.video;

    // Disable right-click context menu
    if (this.video) {
      this.video.addEventListener('contextmenu', (e) => {
        e.preventDefault();
        return false;
      });
    }

    wrapper?.addEventListener('contextmenu', (e) => {
      e.preventDefault();
      return false;
    });

    // Playlist prev/next buttons (standalone — only when showPrevNext without replaceSkipWithPrevNext)
    const prevTrackBtn = this.getElement('btn-prev');
    const nextTrackBtn = this.getElement('btn-next');
    prevTrackBtn?.addEventListener('click', () => (this.config as any).playlist?.onPreviousTrack?.());
    nextTrackBtn?.addEventListener('click', () => (this.config as any).playlist?.onNextTrack?.());

    // Playlist toggle button (in right controls)
    const playlistBtn = this.getElement('playlist-btn');
    playlistBtn?.addEventListener('click', () => (this.config as any).playlist?.onPlaylistToggle?.());

    // Play/Pause
    centerPlay?.addEventListener('click', () => this.togglePlayPause());
    playPauseBtn?.addEventListener('click', () => this.togglePlayPause());

    // Video click behavior will be handled by the comprehensive tap system below
    // Desktop click for play/pause
    if (this.video && !this.isMobileDevice()) {
      this.video.addEventListener('click', (e) => {
        this.togglePlayPause();
      });
    }

    // Video-specific event listeners (only if video element exists)
    // For YouTube players, these won't be set up but control buttons will still work
    if (this.video) {
      // Update play/pause icons
      this.video.addEventListener('play', () => {
        const playIcon = this.cachedElements.playIcon;
        const pauseIcon = this.cachedElements.pauseIcon;
        if (playIcon) playIcon.style.display = 'none';
        if (pauseIcon) pauseIcon.style.display = 'block';

        // Hide center play button when playing
        if (centerPlay) {
          centerPlay.classList.add('hidden');
          this.debugLog('Center play button hidden - video playing');
        }

        // Schedule hide controls
        setTimeout(() => {
          if (this.state.isPlaying) {
            this.scheduleHideControls();
          }
        }, 1000);
      });

      this.video.addEventListener('pause', () => {
        const playIcon = this.cachedElements.playIcon;
        const pauseIcon = this.cachedElements.pauseIcon;
        if (playIcon) playIcon.style.display = 'block';
        if (pauseIcon) pauseIcon.style.display = 'none';

        // Show center play button when paused
        if (centerPlay) {
          centerPlay.classList.remove('hidden');
          this.debugLog('Center play button shown - video paused');
        }
        this.showControls();
      });

      // Ensure center play button is visible initially when video is paused/stopped
      this.video.addEventListener('loadeddata', () => {
        if (centerPlay && (this.video?.paused || this.video?.ended)) {
          centerPlay.classList.remove('hidden');
          this.debugLog('Center play button shown - video loaded and paused');
        }
      });

      this.video.addEventListener('ended', () => {
        if (centerPlay) {
          centerPlay.classList.remove('hidden');
          this.debugLog('Center play button shown - video ended');
        }
      });

      // Skip buttons — prev/next track when replaceSkipWithPrevNext, else seek ±10s
      skipBackBtn?.addEventListener('click', () => {
        if ((this.config as any).playlist?.replaceSkipWithPrevNext) {
          (this.config as any).playlist?.onPreviousTrack?.();
        } else {
          const currentTime = this.getCurrentTime();
          const duration = this.getDuration();
          if (!isNaN(duration) && duration > 0) {
            this.seek(Math.max(0, currentTime - 10));
            this.showShortcutIndicator('-10s');
          }
        }
      });
      skipForwardBtn?.addEventListener('click', () => {
        if ((this.config as any).playlist?.replaceSkipWithPrevNext) {
          (this.config as any).playlist?.onNextTrack?.();
        } else {
          const currentTime = this.getCurrentTime();
          const duration = this.getDuration();
          if (!isNaN(duration) && duration > 0) {
            this.seek(Math.min(duration, currentTime + 10));
            this.showShortcutIndicator('+10s');
          }
        }
      });

      // Volume control
      volumeBtn?.addEventListener('click', (e) => {
        e.stopPropagation();
        this.toggleMuteAction();
      });

      // Volume panel interactions
      volumeBtn?.addEventListener('mouseenter', () => {
        if (this.volumeHideTimeout) clearTimeout(this.volumeHideTimeout);
        volumePanel?.classList.add('active');
      });

      volumeBtn?.addEventListener('mouseleave', () => {
        this.volumeHideTimeout = setTimeout(() => {
          if (!volumePanel?.matches(':hover')) {
            volumePanel?.classList.remove('active');
          }
        }, 800);
      });

      volumePanel?.addEventListener('mouseenter', () => {
        if (this.volumeHideTimeout) clearTimeout(this.volumeHideTimeout);
        volumePanel.classList.add('active');
      });

      volumePanel?.addEventListener('mouseleave', () => {
        if (!this.isVolumeSliding) {
          setTimeout(() => {
            if (!volumePanel.matches(':hover') && !volumeBtn?.matches(':hover')) {
              volumePanel.classList.remove('active');
            }
          }, 1500);
        }
      });

      // Volume slider
      volumeSlider?.addEventListener('mousedown', (e) => {
        e.stopPropagation();
        this.isVolumeSliding = true;
        volumePanel?.classList.add('active');
        this.handleVolumeChange(e as MouseEvent);
      });

      volumeSlider?.addEventListener('click', (e) => {
        e.stopPropagation();
        this.handleVolumeChange(e as MouseEvent);
      });


      // Progress bar interactions
      progressBar?.addEventListener('click', (e) => {
        this.seekToPosition(e as MouseEvent);
      });

      progressBar?.addEventListener('mousedown', (e) => {
        this.isDragging = true;
        this.showTimeTooltip = true;
        // Maintain seekbar expanded state and prevent text selection during drag
        progressBar.classList.add('dragging');
        this.playerWrapper?.classList.add('seeking');
        this.seekToPosition(e as MouseEvent);
        this.updateTimeTooltip(e as MouseEvent);
      });

      // Hover tooltip functionality
      progressBar?.addEventListener('mouseenter', () => {
        this.showTimeTooltip = true;
      });

      progressBar?.addEventListener('mouseleave', () => {
        if (!this.isDragging) {
          this.showTimeTooltip = false;
          this.hideTimeTooltip();
          this.hideThumbnailPreview();
        }
      });

      progressBar?.addEventListener('mousemove', (e) => {
        if (this.showTimeTooltip) {
          this.updateTimeTooltip(e as MouseEvent);
          // Update thumbnail preview on hover
          this.updateThumbnailPreview(e as MouseEvent);
        }
      });

      // Touch support for mobile devices
      progressBar?.addEventListener('touchstart', (e) => {
        e.preventDefault(); // Prevent scrolling
        this.isDragging = true;
        // Maintain seekbar expanded state and prevent text selection during drag
        progressBar.classList.add('dragging');
        this.playerWrapper?.classList.add('seeking');
        const touch = e.touches[0];
        const mouseEvent = new MouseEvent('mousedown', {
          clientX: touch.clientX,
          clientY: touch.clientY
        });
        this.seekToPosition(mouseEvent);
      }, { passive: false });

      // Global mouse and touch events for enhanced dragging
      // Store handler references for cleanup
      this.globalMouseMoveHandler = (e: MouseEvent) => {
        if (this.isVolumeSliding) {
          this.handleVolumeChange(e);
        }
        if (this.isDragging && progressBar) {
          this.seekToPosition(e);
          // Update tooltip position during dragging
          this.updateTimeTooltip(e);
          // Update thumbnail preview during dragging
          this.updateThumbnailPreview(e);
        }
      };

      this.globalTouchMoveHandler = (e: TouchEvent) => {
        if (this.isDragging && progressBar) {
          e.preventDefault(); // Prevent scrolling
          const touch = e.touches[0];
          const mouseEvent = new MouseEvent('mousemove', {
            clientX: touch.clientX,
            clientY: touch.clientY
          });
          this.seekToPosition(mouseEvent);
        }
      };

      this.globalMouseUpHandler = () => {
        if (this.isVolumeSliding) {
          this.isVolumeSliding = false;
          setTimeout(() => {
            if (!volumePanel?.matches(':hover') && !volumeBtn?.matches(':hover')) {
              volumePanel?.classList.remove('active');
            }
          }, 2000);
        }

        if (this.isDragging) {
          this.isDragging = false;
          // Remove dragging classes
          progressBar?.classList.remove('dragging');
          this.playerWrapper?.classList.remove('seeking');
          // Remove dragging class from handle
          const handle = this.cachedElements.progressHandle;
          handle?.classList.remove('dragging');
          // Hide tooltip and thumbnail preview if mouse is not over progress bar
          if (progressBar && !progressBar.matches(':hover')) {
            this.showTimeTooltip = false;
            this.hideTimeTooltip();
            this.hideThumbnailPreview();
          }
        }
      };

      this.globalTouchEndHandler = () => {
        if (this.isDragging) {
          this.isDragging = false;
          // Remove dragging classes
          progressBar?.classList.remove('dragging');
          this.playerWrapper?.classList.remove('seeking');
          // Remove dragging class from handle
          const handle = this.cachedElements.progressHandle;
          handle?.classList.remove('dragging');
          // Hide tooltip and thumbnail preview on touch end
          this.showTimeTooltip = false;
          this.hideTimeTooltip();
          this.hideThumbnailPreview();
        }
      };

      // Add event listeners
      document.addEventListener('mousemove', this.globalMouseMoveHandler);
      document.addEventListener('touchmove', this.globalTouchMoveHandler, { passive: false });
      document.addEventListener('mouseup', this.globalMouseUpHandler);
      document.addEventListener('touchend', this.globalTouchEndHandler);

      // Update progress bar
      this.video.addEventListener('timeupdate', () => {
        const progressFilled = this.cachedElements.progressFilled as HTMLElement;
        const progressHandle = this.cachedElements.progressHandle as HTMLElement;

        if (this.video && progressFilled) {
          // Validate duration to prevent division by zero
          if (isFinite(this.video.duration) && this.video.duration > 0) {
            const percent = (this.video.currentTime / this.video.duration) * 100;
            progressFilled.style.width = percent + '%';

            // Update handle position (only when not dragging)
            if (progressHandle && !this.isDragging) {
              progressHandle.style.left = percent + '%';
            }
          }
        }

        // Update time display using the dedicated method
        this.updateTimeDisplay();
      });

      // Update buffered progress
      this.video.addEventListener('progress', () => {
        const progressBuffered = this.cachedElements.progressBuffered as HTMLElement;
        if (this.video && progressBuffered && this.video.buffered.length > 0) {
          // Validate duration to prevent division by zero
          if (!isFinite(this.video.duration) || this.video.duration <= 0) {
            return;
          }

          // Find the buffered range that contains the current playback position
          let bufferedEnd = 0;
          const currentTime = this.video.currentTime;

          for (let i = 0; i < this.video.buffered.length; i++) {
            const start = this.video.buffered.start(i);
            const end = this.video.buffered.end(i);

            // If current time is within this range, use this range's end
            if (currentTime >= start && currentTime <= end) {
              bufferedEnd = end;
              break;
            }
            // Otherwise, track the furthest buffered position
            if (end > bufferedEnd) {
              bufferedEnd = end;
            }
          }

          const buffered = (bufferedEnd / this.video.duration) * 100;
          progressBuffered.style.width = buffered + '%';
        }
      });

      // Update volume display
      this.video.addEventListener('volumechange', () => {
        const volumeFill = this.getElement('volume-fill') as HTMLElement;
        const volumeValue = this.getElement('volume-value');
        const volumeIcon = this.getElement('volume-icon');
        const muteIcon = this.getElement('mute-icon');

        if (this.video && volumeFill && volumeValue) {
          const percent = Math.round(this.video.volume * 100);
          volumeFill.style.width = percent + '%';
          volumeValue.textContent = String(percent);
        }

        if (this.video && volumeIcon && muteIcon) {
          // Sync state with video element
          this.state.isMuted = this.video.muted;

          if (this.video.muted || this.video.volume === 0) {
            volumeIcon.style.display = 'none';
            muteIcon.style.display = 'block';
          } else {
            volumeIcon.style.display = 'block';
            muteIcon.style.display = 'none';
            // Hide unmute button when video is unmuted
            this.hideUnmuteButton();
          }
        }
      });

      // Fullscreen button with enhanced cross-platform support
      fullscreenBtn?.addEventListener('click', (event) => {
        // Enhanced debugging for all platforms
        const isBrave = this.isBraveBrowser();
        const isPrivate = this.isPrivateWindow();
        const isIOS = this.isIOSDevice();
        const isAndroid = this.isAndroidDevice();
        const isMobile = this.isMobileDevice();

        this.debugLog('Fullscreen button clicked:', {
          isBrave,
          isPrivate,
          isIOS,
          isAndroid,
          isMobile,
          isFullscreen: this.isFullscreen(),
          eventTrusted: event.isTrusted,
          eventType: event.type,
          timestamp: Date.now(),
          fullscreenSupported: this.isFullscreenSupported()
        });

        // Update user interaction timestamp
        this.lastUserInteraction = Date.now();

        // Run permissions check before attempting fullscreen
        this.checkFullscreenPermissions();

        if (this.isFullscreen()) {
          this.debugLog('Exiting fullscreen via button');
          this.exitFullscreen().catch(err => {
            this.debugWarn('Exit fullscreen button failed:', err.message);
          });
        } else {
          this.debugLog('Entering fullscreen via button');

          // iOS Safari special message
          if (isIOS) {
            this.showShortcutIndicator('Using iOS video fullscreen');
          } else if (isAndroid) {
            this.showShortcutIndicator('Entering fullscreen - rotate to landscape');
          }

          // Use enhanced cross-platform fullscreen method
          this.enterFullscreen().catch(err => {
            this.debugWarn('Fullscreen button failed:', err.message);

            // Platform-specific error messages
            if (isIOS) {
              this.showTemporaryMessage('iOS: Use device rotation or video controls for fullscreen');
            } else if (isAndroid) {
              this.showTemporaryMessage('Android: Try rotating device to landscape mode');
            } else if (isBrave) {
              this.showTemporaryMessage('Brave Browser: Please allow fullscreen in site settings');
            } else {
              this.showTemporaryMessage('Fullscreen not supported in this browser');
            }
          });
        }
      });

      // Update fullscreen button icon based on state
      const updateFullscreenIcon = () => {
        const fullscreenBtn = this.cachedElements.fullscreenBtn;
        if (fullscreenBtn) {
          fullscreenBtn.innerHTML = this.isFullscreen()
            ? '<svg viewBox="0 0 24 24"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/></svg>'
            : '<svg viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>';
        }
      };

      // Listen for fullscreen state changes to update icon
      this.on('onFullscreenChanged', updateFullscreenIcon);

      // Spinner show/hide is now handled in setupVideoEventListeners() so it
      // works regardless of whether custom controls are enabled.

      // Update settings menu when video metadata / qualities are ready
      this.video.addEventListener('canplay', () => {
        this.updateSettingsMenu();
      });

      this.video.addEventListener('loadedmetadata', () => {
        this.updateSettingsMenu();
      });
    } // End of if (this.video) block

    // Note: Enhanced mouse movement and control visibility handled in setupFullscreenListeners()

    this.controlsContainer?.addEventListener('mouseenter', () => {
      if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
    });

    this.controlsContainer?.addEventListener('mouseleave', () => {
      if (this.state.isPlaying) {
        this.scheduleHideControls();
      }
    });


    // Settings menu - dynamically populated
    const settingsMenu = this.cachedElements.settingsMenu;
    this.debugLog('Settings menu element found:', !!settingsMenu);
    this.debugLog('Settings button found:', !!settingsBtn);

    settingsBtn?.addEventListener('click', (e) => {
      e.stopPropagation();
      this.debugLog('Settings button clicked!');
      this.debugLog('Settings menu before update:', settingsMenu?.innerHTML?.length || 0, 'characters');

      // Update the menu content before showing it
      this.updateSettingsMenu();

      this.debugLog('Settings menu after update:', settingsMenu?.innerHTML?.length || 0, 'characters');
      this.debugLog('Settings menu classes before toggle:', Array.from(settingsMenu?.classList || []).join(' '));

      settingsMenu?.classList.toggle('active');

      // Force visibility if menu is active, hide if not active
      if (settingsMenu) {
        if (settingsMenu.classList.contains('active')) {
          settingsMenu.style.display = 'block';
          settingsMenu.style.visibility = 'visible';
          settingsMenu.style.opacity = '1';
          settingsMenu.style.transform = 'translateY(0)';
          settingsMenu.style.zIndex = '9999';
          settingsMenu.style.position = 'absolute';
          settingsMenu.style.bottom = '50px';
          settingsMenu.style.right = '0';
          settingsMenu.style.background = 'rgba(0,0,0,0.9)';
          settingsMenu.style.border = '1px solid rgba(255,255,255,0.2)';
          settingsMenu.style.borderRadius = '8px';
          settingsMenu.style.minWidth = '280px';
          settingsMenu.style.padding = '10px 0';
          this.debugLog('Applied fallback styles to show menu');
        } else {
          settingsMenu.style.display = 'none';
          settingsMenu.style.visibility = 'hidden';
          settingsMenu.style.opacity = '0';
          this.debugLog('Applied fallback styles to hide menu');
        }
      }

      this.debugLog('Settings menu classes after toggle:', Array.from(settingsMenu?.classList || []).join(' '));
      this.debugLog('Settings menu computed display:', window.getComputedStyle(settingsMenu || document.body).display);
      this.debugLog('Settings menu computed visibility:', window.getComputedStyle(settingsMenu || document.body).visibility);
      this.debugLog('Settings menu computed opacity:', window.getComputedStyle(settingsMenu || document.body).opacity);
    });

    // EPG button
    const epgBtn = this.getElement('epg-btn');
    epgBtn?.addEventListener('click', (e) => {
      e.stopPropagation();
      this.debugLog('EPG button clicked');
      // Trigger custom event for EPG toggle
      this.emit('epgToggle', {});
    });

    // PiP button
    const pipBtn = this.getElement('pip-btn');
    pipBtn?.addEventListener('click', () => this.togglePiP());

    // Top control buttons
    const castBtn = this.getElement('cast-btn');
    const stopCastBtn = this.getElement('stop-cast-btn');
    const shareBtn = this.getElement('share-btn');

    // Update cast button icon and functionality for iOS (AirPlay)
    if (this.isIOSDevice() && castBtn) {
      castBtn.innerHTML = `
        <svg viewBox="0 0 24 24">
          <path d="M1 18h6v-2H1v2zm0-4h12v-2H1v2zm16.5 4.5c-1.25 0-2.45-.5-3.35-1.41L12 18.5l2.09 2.09c1.8 1.8 4.72 1.8 6.52 0 1.8-1.8 1.8-4.72 0-6.52L12 5.5 3.39 14.11c-1.8 1.8-1.8 4.72 0 6.52.9.9 2.1 1.41 3.35 1.41l6.76-6.76M12 7.91l6.89 6.89c.78.78.78 2.05 0 2.83-.78.78-2.05.78-2.83 0L12 13.57 7.94 17.63c-.78.78-2.05.78-2.83 0-.78-.78-.78-2.05 0-2.83L12 7.91z"/>
        </svg>
      `;
      castBtn.setAttribute('title', 'AirPlay');
      castBtn.setAttribute('aria-label', 'AirPlay');
    }

    castBtn?.addEventListener('click', () => this.onCastButtonClick());
    stopCastBtn?.addEventListener('click', () => this.stopCasting());
    shareBtn?.addEventListener('click', () => this.shareVideo());

    // Hide settings menu when clicking outside or pressing Escape
    document.addEventListener('click', (e) => {
      const target = e.target as HTMLElement;
      // Only handle if click is within this player instance
      if (!this.playerWrapper?.contains(target)) return;

      if (!target.closest(`#${this.getElementId('settings-btn')}`) &&
        !target.closest(`#${this.getElementId('settings-menu')}`)) {
        this.hideSettingsMenu();
      }
    });

    // Add Escape key handler for settings menu
    document.addEventListener('keydown', (e) => {
      // Only handle if settings menu belongs to this instance
      if (e.key === 'Escape' && settingsMenu?.classList.contains('active')) {
        // Check if this instance's settings menu is active
        const activeMenu = document.activeElement?.closest('.uvf-settings-container');
        if (activeMenu && this.playerWrapper?.contains(activeMenu)) {
          this.hideSettingsMenu();
        }
      }
    });
  }

  protected setupKeyboardShortcuts(): void {
    // Add keyboard event listener to both document and player wrapper for better coverage
    const handleKeydown = (e: KeyboardEvent) => {
      // Don't handle if typing in an input or textarea
      const target = e.target as HTMLElement;
      if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
        return;
      }

      // Only handle keyboard shortcuts if player wrapper is focused or contains the focused element
      if (this.playerWrapper && !this.playerWrapper.contains(document.activeElement as Node)) {
        return;
      }

      // Block all keyboard controls during ads (Google IMA handles ad controls)
      if (this.isAdPlaying) {
        this.debugLog('Keyboard blocked: Ad is playing');
        e.preventDefault();
        return;
      }

      // SECURITY: Block all keyboard controls when paywall is active
      if (this.isPaywallActive) {
        this.debugLog('🔒 Keyboard blocked: Paywall is active');
        e.preventDefault();
        e.stopPropagation();
        return;
      }

      // SECURITY: Block all keyboard controls when free preview gate was hit
      if (this.previewGateHit) {
        this.debugLog('🔒 Keyboard blocked: Free preview gate hit');
        e.preventDefault();
        e.stopPropagation();
        return;
      }

      // Debug logging
      this.debugLog('Keyboard event:', e.key, 'target:', target.tagName);

      let shortcutText = '';

      // Update interaction timestamp
      this.lastUserInteraction = Date.now();

      switch (e.key) {
        case ' ':
        case 'Spacebar': // For older browsers
        case 'k':
          e.preventDefault();
          e.stopPropagation();
          this.debugLog('Space/K pressed, current state:', {
            isPlaying: this.state.isPlaying,
            videoPaused: this.video?.paused,
            videoExists: !!this.video
          });

          // Determine what action we're about to take based on current video state
          const willPlay = (this.youtubePlayer && this.youtubePlayerReady)
            ? this.youtubePlayer.getPlayerState() !== window.YT.PlayerState.PLAYING
            : this.video?.paused || false;
          this.debugLog('Will perform action:', willPlay ? 'PLAY' : 'PAUSE');

          this.togglePlayPause();

          // Show the action we're taking, not the current state
          shortcutText = willPlay ? 'Play' : 'Pause';
          this.debugLog('Showing icon:', shortcutText);
          break;
        case 'ArrowLeft':
          e.preventDefault();
          e.stopImmediatePropagation(); // Prevent duplicate handler triggers
          const currentTime = this.getCurrentTime();
          const duration = this.getDuration();
          if (!isNaN(duration) && duration > 0) {
            this.seek(Math.max(0, currentTime - 10));
            shortcutText = '-10s';
          }
          break;
        case 'ArrowRight':
          e.preventDefault();
          e.stopImmediatePropagation(); // Prevent duplicate handler triggers
          const currentTimeRight = this.getCurrentTime();
          const durationRight = this.getDuration();
          if (!isNaN(durationRight) && durationRight > 0) {
            this.seek(Math.min(durationRight, currentTimeRight + 10));
            shortcutText = '+10s';
          }
          break;
        case 'ArrowUp':
          e.preventDefault();
          this.changeVolume(0.1);
          let volumeUp = 0;
          if (this.youtubePlayer && this.youtubePlayerReady) {
            volumeUp = this.youtubePlayer.getVolume();
          } else if (this.isCasting && this.remotePlayer) {
            volumeUp = (this.remotePlayer.volumeLevel || 0) * 100;
          } else {
            volumeUp = (this.video?.volume || 0) * 100;
          }
          shortcutText = `Volume ${Math.round(volumeUp)}%`;
          break;
        case 'ArrowDown':
          e.preventDefault();
          this.changeVolume(-0.1);
          let volumeDown = 0;
          if (this.youtubePlayer && this.youtubePlayerReady) {
            volumeDown = this.youtubePlayer.getVolume();
          } else if (this.isCasting && this.remotePlayer) {
            volumeDown = (this.remotePlayer.volumeLevel || 0) * 100;
          } else {
            volumeDown = (this.video?.volume || 0) * 100;
          }
          shortcutText = `Volume ${Math.round(volumeDown)}%`;
          break;
        case 'm':
          e.preventDefault();
          this.toggleMuteAction();
          let isMuted = false;
          if (this.youtubePlayer && this.youtubePlayerReady) {
            isMuted = this.youtubePlayer.isMuted();
          } else if (this.isCasting && this.remotePlayer) {
            isMuted = this.remotePlayer.isMuted;
          } else {
            isMuted = this.video?.muted || false;
          }
          shortcutText = isMuted ? 'Muted' : 'Unmuted';
          break;
        case 'f':
          e.preventDefault();

          if (!document.fullscreenElement) {
            this.enterFullscreen().catch(err => {
              this.debugWarn('Fullscreen shortcut failed:', err.message);
            });
            this.showShortcutIndicator('Fullscreen');
          } else {
            this.exitFullscreen().catch(err => {
              this.debugWarn('Exit fullscreen shortcut failed:', err.message);
            });
            this.showShortcutIndicator('Exit Fullscreen');
          }
          break;
        case 'p':
          e.preventDefault();
          this.togglePiP();
          shortcutText = 'Picture-in-Picture';
          break;
        case 'g':
          e.preventDefault();
          this.debugLog('G key pressed - toggling EPG');
          this.emit('epgToggle', {});
          shortcutText = 'Toggle EPG';
          break;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
          e.preventDefault();
          // Only jump to position if video is loaded and duration is valid
          const durationForSeek = this.getDuration();
          if (!isNaN(durationForSeek) && durationForSeek > 0) {
            const percent = parseInt(e.key) * 10;
            const seekTime = (durationForSeek * percent) / 100;
            this.seek(seekTime);
            shortcutText = `${percent}%`;
          }
          break;
      }

      if (shortcutText) {
        this.debugLog('Showing shortcut indicator:', shortcutText);
        this.showShortcutIndicator(shortcutText);
      }
    };

    // Add event listeners to multiple targets for better coverage
    document.addEventListener('keydown', handleKeydown, { capture: true });

    // Also add to the player wrapper if it exists
    if (this.playerWrapper) {
      this.playerWrapper.addEventListener('keydown', handleKeydown);
      this.playerWrapper.setAttribute('tabindex', '0'); // Make it focusable

      // Add visual feedback when player is focused for better UX
      this.playerWrapper.addEventListener('focus', () => {
        this.debugLog('Player focused - keyboard shortcuts available');
      });

      // Auto-focus the player when clicked to enable keyboard shortcuts
      this.playerWrapper.addEventListener('click', (e) => {
        // Don't focus if clicking on a control button
        const target = e.target as HTMLElement;
        if (!target.closest('.uvf-controls')) {
          this.playerWrapper?.focus();
          // Also store the click event for potential fullscreen use
          this.lastUserInteraction = Date.now();
        }
      });

      // Also focus on any interaction with the video area
      this.playerWrapper.addEventListener('mousedown', () => {
        this.playerWrapper?.focus();
        this.lastUserInteraction = Date.now();
      });

      // Advanced tap handling system for mobile
      this.setupAdvancedTapHandling();
    }

    // Add to the video element
    if (this.video) {
      this.video.addEventListener('keydown', handleKeydown);
    }
  }

  protected setupWatermark(): void {
    if (!this.watermarkCanvas) return;

    // Get watermark configuration
    const watermarkConfig = (this.config as any).watermark;

    // Check if watermark is disabled or not configured
    if (!watermarkConfig || watermarkConfig.enabled === false) {
      this.debugLog('Watermark disabled or not configured');
      return;
    }

    // If watermark config exists but enabled is not explicitly set, default to disabled
    if (watermarkConfig.enabled !== true) {
      this.debugLog('Watermark not explicitly enabled');
      return;
    }

    const ctx = this.watermarkCanvas.getContext('2d');
    if (!ctx) return;

    // Default configuration values
    const config = {
      text: watermarkConfig.text || 'PREMIUM',
      showTime: watermarkConfig.showTime !== false, // default true
      updateInterval: watermarkConfig.updateInterval || 5000,
      randomPosition: watermarkConfig.randomPosition !== false, // default true
      position: watermarkConfig.position || {},
      style: {
        fontSize: watermarkConfig.style?.fontSize || 14,
        fontFamily: watermarkConfig.style?.fontFamily || 'Arial',
        opacity: watermarkConfig.style?.opacity ?? 0.3,
        color: watermarkConfig.style?.color,
        gradientColors: watermarkConfig.style?.gradientColors || ['#ff0000', '#ff4d4f']
      }
    };

    this.debugLog('Watermark configuration:', config);

    const renderWatermark = () => {
      const container = this.watermarkCanvas!.parentElement;
      if (!container) return;

      this.watermarkCanvas!.width = container.offsetWidth;
      this.watermarkCanvas!.height = container.offsetHeight;

      ctx.clearRect(0, 0, this.watermarkCanvas!.width, this.watermarkCanvas!.height);

      // Build watermark text
      let text = config.text;
      if (config.showTime) {
        const timeStr = new Date().toLocaleTimeString();
        text += ` • ${timeStr}`;
      }

      // Set up styling
      ctx.save();
      ctx.globalAlpha = config.style.opacity;
      ctx.font = `${config.style.fontSize}px ${config.style.fontFamily}`;
      ctx.textAlign = 'left';

      // Set fill style
      if (config.style.color) {
        // Use solid color
        ctx.fillStyle = config.style.color;
      } else {
        // Use gradient (default or custom)
        const wrapper = this.playerWrapper as HTMLElement | null;
        let c1 = config.style.gradientColors[0];
        let c2 = config.style.gradientColors[1];

        // Try to get theme colors if using defaults
        if (!watermarkConfig.style?.gradientColors) {
          try {
            if (wrapper) {
              const styles = getComputedStyle(wrapper);
              const v1 = styles.getPropertyValue('--uvf-accent-1').trim();
              const v2 = styles.getPropertyValue('--uvf-accent-2').trim();
              if (v1) c1 = v1;
              if (v2) c2 = v2;
            }
          } catch (_) { }
        }

        const gradient = ctx.createLinearGradient(0, 0, 200, 0);
        gradient.addColorStop(0, c1);
        gradient.addColorStop(1, c2);
        ctx.fillStyle = gradient;
      }

      // Calculate position
      let x: number, y: number;

      if (config.randomPosition) {
        // Random position (default behavior)
        x = 20 + Math.random() * Math.max(0, this.watermarkCanvas!.width - 200);
        y = 40 + Math.random() * Math.max(0, this.watermarkCanvas!.height - 80);
      } else {
        // Fixed or calculated position
        const posX = config.position.x;
        const posY = config.position.y;

        // Calculate X position
        if (typeof posX === 'number') {
          x = posX;
        } else {
          switch (posX) {
            case 'left':
              x = 20;
              break;
            case 'center':
              x = this.watermarkCanvas!.width / 2;
              ctx.textAlign = 'center';
              break;
            case 'right':
              x = this.watermarkCanvas!.width - 20;
              ctx.textAlign = 'right';
              break;
            case 'random':
              x = 20 + Math.random() * Math.max(0, this.watermarkCanvas!.width - 200);
              break;
            default:
              x = 20; // default left
          }
        }

        // Calculate Y position
        if (typeof posY === 'number') {
          y = posY;
        } else {
          switch (posY) {
            case 'top':
              y = 40;
              break;
            case 'center':
              y = this.watermarkCanvas!.height / 2;
              break;
            case 'bottom':
              y = this.watermarkCanvas!.height - 20;
              break;
            case 'random':
              y = 40 + Math.random() * Math.max(0, this.watermarkCanvas!.height - 80);
              break;
            default:
              y = 40; // default top
          }
        }
      }

      // Render the watermark
      ctx.fillText(text, x, y);
      ctx.restore();

      this.debugLog('Watermark rendered:', { text, x, y });
    };

    // Set up interval with configured frequency
    setInterval(renderWatermark, config.updateInterval);
    renderWatermark(); // Render immediately

    this.debugLog('Watermark setup complete with update interval:', config.updateInterval + 'ms');
  }

  public setPaywallConfig(config: any) {
    try {
      if (!config) return;
      if (this.paywallController && typeof this.paywallController.updateConfig === 'function') {
        this.paywallController.updateConfig(config);
      } else {
        // lazy-init if not created yet
        if (config.enabled) {
          import('./paywall/PaywallController').then((m: any) => {
            this.paywallController = new m.PaywallController(config, {
              getOverlayContainer: () => this.playerWrapper,
              onResume: (accessInfo?: any) => {
                try {
                  // Reset preview gate after successful auth/payment
                  this.previewGateHit = false;
                  this.paymentSuccessTime = Date.now();

                  // Check if access was granted via email auth
                  if (accessInfo && (accessInfo.accessGranted || accessInfo.paymentSuccessful)) {
                    this.paymentSuccessful = true;
                    this.debugLog('Access granted via email auth - preview gate permanently disabled, resuming playback');
                  } else {
                    this.paymentSuccessful = true;
                    this.debugLog('Payment successful (via setPaywallConfig) - preview gate permanently disabled, resuming playback');
                  }

                  this.play();
                } catch (_) { }
              },
              onShow: () => {
                // Use safe pause method to avoid race conditions
                try { this.requestPause(); } catch (_) { }
              },
              onClose: () => {
                // Resume video if auth was successful
              }
            });
          }).catch(() => { });
        }
      }
    } catch (_) { }
  }

  /**
   * Dismiss the paywall overlay and stop security monitoring.
   * Call this after a successful custom login to resume playback.
   */
  public dismissPaywall(options?: { resume?: boolean }): void {
    this.debugLog('dismissPaywall called');
    // Stop security monitoring
    this.isPaywallActive = false;
    this.overlayRemovalAttempts = 0;
    if (this.authValidationInterval) {
      clearInterval(this.authValidationInterval);
      this.authValidationInterval = null;
    }
    // Close overlay
    if (this.paywallController) {
      if (typeof this.paywallController.destroyOverlays === 'function') {
        this.paywallController.destroyOverlays();
      } else if (typeof this.paywallController.closeOverlay === 'function') {
        this.paywallController.closeOverlay();
      }
    }
    // Optionally resume playback
    if (options?.resume !== false) {
      this.previewGateHit = false;
      this.paymentSuccessful = true;
      this.paymentSuccessTime = Date.now();
      try { this.play(); } catch (_) { }
    }
  }

  private togglePlayPause(): void {
    this.debugLog('togglePlayPause called, video state:', {
      videoExists: !!this.video,
      youtubePlayerExists: !!this.youtubePlayer,
      youtubePlayerReady: this.youtubePlayerReady,
      playerState: this.state
    });

    // Handle YouTube player
    if (this.youtubePlayer && this.youtubePlayerReady) {
      const playerState = this.youtubePlayer.getPlayerState();
      this.debugLog('YouTube player state:', playerState);

      if (playerState === window.YT.PlayerState.PLAYING) {
        this.debugLog('YouTube video is playing, calling pause()');
        this.pause();
      } else {
        this.debugLog('YouTube video is paused, calling play()');
        this.play();
      }
      return;
    }

    // Handle regular video element
    if (!this.video) {
      this.debugError('No video element or YouTube player available for toggle');
      return;
    }

    if (this.video.paused) {
      this.debugLog('Video is paused, calling play()');
      this.play();
    } else {
      this.debugLog('Video is playing, calling pause()');
      this.pause();
    }
  }

  // Enforce free preview gate for local or casting playback
  private enforceFreePreviewGate(current: number, fromSeek: boolean = false): void {
    try {
      const lim = Number(this.config.freeDuration || 0);
      if (!(lim > 0)) return;
      if (this.previewGateHit && !fromSeek) return;

      // Don't trigger gate if payment was successful for this session
      if (this.paymentSuccessful) {
        this.debugLog('Skipping preview gate - payment was successful, access granted permanently for this session');
        return;
      }

      // Don't trigger gate if payment was successful recently (within 5 seconds)
      const timeSincePayment = Date.now() - this.paymentSuccessTime;
      if (this.paymentSuccessTime > 0 && timeSincePayment < 5000) {
        this.debugLog('Skipping preview gate - recent payment success:', timeSincePayment + 'ms ago');
        return;
      }

      if (current >= lim - 0.01 && !this.previewGateHit) {
        this.previewGateHit = true;
        this.showNotification('Free preview ended.');
        this.emit('onFreePreviewEnded');

        // Trigger paywall controller which will handle auth/payment flow
        this.debugLog('Free preview gate hit, paywallController exists:', !!this.paywallController);
        if (this.paywallController) {
          this.debugLog('Calling paywallController.openOverlay() directly');
          this.paywallController.openOverlay();
        } else {
          this.debugLog('No paywallController available');
        }
      }
      if (current >= lim - 0.01) {
        if (this.isCasting && this.remoteController) {
          try {
            if (this.remotePlayer && this.remotePlayer.isPaused === false) {
              this.remoteController.playOrPause();
            }
          } catch (_) { }
        } else if (this.video) {
          try {
            // Use deferred pause to avoid race conditions
            this.requestPause();
            if (fromSeek || ((this.video.currentTime || 0) > lim)) {
              this.safeSetCurrentTime(lim - 0.1);
            }
          } catch (_) { }
        }
      }
    } catch (_) { }
  }

  // Public runtime controls for free preview
  public setFreeDuration(seconds: number): void {
    try {
      const s = Math.max(0, Number(seconds) || 0);
      const oldDuration = Number(this.config.freeDuration || 0);
      (this.config as any).freeDuration = s;

      // Always reset gate when duration changes (unless payment was successful)
      // This ensures the gate re-evaluates with the new limit
      if (!this.paymentSuccessful && s !== oldDuration) {
        this.previewGateHit = false;
      }

      // If already past new limit, enforce immediately (but not if payment was successful)
      if (!this.paymentSuccessful && s > 0) {
        const cur = this.video ? (this.video.currentTime || 0) : 0;
        if (cur >= s - 0.01) {
          // Beyond limit - trigger gate immediately
          this.enforceFreePreviewGate(cur, true);
        }
      }
    } catch (_) { }
  }
  public resetFreePreviewGate(): void {
    // Only reset if payment hasn't been successful
    if (!this.paymentSuccessful) {
      this.previewGateHit = false;
    }
  }

  public resetPaymentStatus(): void {
    this.paymentSuccessful = false;
    this.paymentSuccessTime = 0;
    this.previewGateHit = false;
    this.debugLog('Payment status reset - preview gate re-enabled');
  }

  private toggleMuteAction(): void {
    if (this.isCasting && this.remoteController) {
      try { this.remoteController.muteOrUnmute(); } catch (_) { }
      return;
    }
    if (this.video?.muted) {
      this.unmute();
    } else {
      this.mute();
    }
  }

  /**
   * Detect if user is on a mobile device
   */
  private isMobileDevice(): boolean {
    const userAgent = navigator.userAgent.toLowerCase();
    const mobileKeywords = ['android', 'iphone', 'ipad', 'ipod', 'blackberry', 'windows phone', 'mobile'];
    const isMobileUserAgent = mobileKeywords.some(keyword => userAgent.includes(keyword));
    const isSmallScreen = window.innerWidth <= 768;
    const hasTouchScreen = 'ontouchstart' in window || navigator.maxTouchPoints > 0;

    return isMobileUserAgent || (isSmallScreen && hasTouchScreen);
  }

  /**
   * Check if Picture-in-Picture is supported by the browser
   */
  private isPipSupported(): boolean {
    return !!(
      document.pictureInPictureEnabled &&
      HTMLVideoElement.prototype.requestPictureInPicture &&
      typeof HTMLVideoElement.prototype.requestPictureInPicture === 'function'
    );
  }

  /**
   * Detect if user is on iOS device
   */
  private isIOSDevice(): boolean {
    const userAgent = navigator.userAgent.toLowerCase();
    return /iphone|ipad|ipod/.test(userAgent);
  }

  /**
   * Detect if user is on Android device
   */
  private isAndroidDevice(): boolean {
    const userAgent = navigator.userAgent.toLowerCase();
    return /android/.test(userAgent);
  }

  /**
   * Check if fullscreen is supported on current platform
   */
  private isFullscreenSupported(): boolean {
    return !!(
      document.fullscreenEnabled ||
      (document as any).webkitFullscreenEnabled ||
      (document as any).mozFullScreenEnabled ||
      (document as any).msFullscreenEnabled
    );
  }

  /**
   * Lock screen orientation to landscape when entering fullscreen
   */
  private async lockOrientationLandscape(): Promise<void> {
    try {
      // Only attempt orientation lock on mobile devices
      if (!this.isMobileDevice()) {
        this.debugLog('Skipping orientation lock - not a mobile device');
        return;
      }

      // Check if Screen Orientation API is supported
      const screenOrientation = screen.orientation as any;
      if (screenOrientation && typeof screenOrientation.lock === 'function') {
        try {
          // Try to lock to landscape orientation
          await screenOrientation.lock('landscape');
          this.debugLog('Screen orientation locked to landscape');
        } catch (error) {
          this.debugWarn('Failed to lock orientation to landscape:', (error as Error).message);
          // Some browsers require fullscreen to be active before locking orientation
          // If it fails, we'll just show a message
          if (this.isAndroidDevice()) {
            this.showShortcutIndicator('Please rotate device to landscape');
          }
        }
      } else {
        // Fallback for older browsers or iOS (which doesn't support orientation lock)
        this.debugLog('Screen Orientation API not supported');
        if (this.isMobileDevice()) {
          // Show a subtle hint for devices that don't support orientation lock
          this.showShortcutIndicator('Rotate device to landscape for best experience');
        }
      }
    } catch (error) {
      this.debugWarn('Orientation lock error:', (error as Error).message);
    }
  }

  /**
   * Unlock screen orientation when exiting fullscreen
   */
  private async unlockOrientation(): Promise<void> {
    try {
      // Check if Screen Orientation API is supported
      const screenOrientation = screen.orientation as any;
      if (screenOrientation && typeof screenOrientation.unlock === 'function') {
        try {
          screenOrientation.unlock();
          this.debugLog('Screen orientation unlocked');
        } catch (error) {
          this.debugWarn('Failed to unlock orientation:', (error as Error).message);
        }
      } else {
        this.debugLog('Screen Orientation API not supported for unlock');
      }
    } catch (error) {
      this.debugWarn('Orientation unlock error:', (error as Error).message);
    }
  }

  private handleVolumeChange(e: MouseEvent): void {
    const slider = this.getElement('volume-slider');
    if (!slider) return;

    const rect = slider.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const width = rect.width;
    const percent = Math.max(0, Math.min(1, x / width));

    if (this.isCasting && this.remoteController && this.remotePlayer) {
      try {
        if (this.remotePlayer.isMuted) {
          try { this.remoteController.muteOrUnmute(); } catch (_) { }
          this.remotePlayer.isMuted = false;
        }
        this.remotePlayer.volumeLevel = percent;
        this.remoteController.setVolumeLevel();
      } catch (_) { }
      this.updateVolumeUIFromRemote();
    } else if (this.video) {
      this.setVolume(percent);
      this.video.muted = false;
    }
  }

  private seekToPosition(e: MouseEvent): void {
    const progressBar = this.cachedElements.progressBar;
    const progressFilled = this.cachedElements.progressFilled as HTMLElement;
    const progressHandle = this.cachedElements.progressHandle as HTMLElement;
    if (!progressBar) return;

    // Get duration from YouTube player or regular video
    let duration: number;
    if (this.youtubePlayer && this.youtubePlayerReady) {
      duration = this.youtubePlayer.getDuration();
    } else if (this.video) {
      duration = this.video.duration;
    } else {
      return; // No video source available
    }

    // Validate duration before calculating seek time
    if (!isFinite(duration) || isNaN(duration) || duration <= 0) {
      this.debugWarn('Invalid video duration, cannot seek via progress bar');
      return;
    }

    const rect = progressBar.getBoundingClientRect();

    // Validate rect width to prevent division by zero
    if (!rect.width || rect.width <= 0) {
      this.debugWarn('Progress bar has no width, cannot seek');
      return;
    }

    const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
    const percent = (x / rect.width) * 100;
    const time = (percent / 100) * duration;

    // Validate calculated time
    if (!isFinite(time) || isNaN(time)) {
      this.debugWarn('Calculated seek time is invalid:', time);
      return;
    }

    // Update UI immediately for responsive feedback
    if (progressFilled) {
      progressFilled.style.width = percent + '%';
    }
    if (progressHandle) {
      progressHandle.style.left = percent + '%';
      // Add dragging class for visual feedback
      if (this.isDragging) {
        progressHandle.classList.add('dragging');
      } else {
        progressHandle.classList.remove('dragging');
      }
    }

    this.seek(time);
  }

  private formatTime(seconds: number): string {
    if (!seconds || isNaN(seconds)) return '00:00';

    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    const secs = Math.floor(seconds % 60);

    if (hours > 0) {
      return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
    } else {
      return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
    }
  }

  /**
   * Format countdown time in human-readable format
   * @param totalSeconds - Total seconds remaining
   * @returns Formatted string like "5 minutes 31 seconds"
   */
  private formatCountdownTime(totalSeconds: number): string {
    if (totalSeconds <= 0) return '0 seconds';

    const hours = Math.floor(totalSeconds / 3600);
    const minutes = Math.floor((totalSeconds % 3600) / 60);
    const seconds = Math.floor(totalSeconds % 60);

    const parts: string[] = [];

    if (hours > 0) {
      parts.push(`${hours} ${hours === 1 ? 'hour' : 'hours'}`);
    }
    if (minutes > 0) {
      parts.push(`${minutes} ${minutes === 1 ? 'minute' : 'minutes'}`);
    }
    if (seconds > 0 || parts.length === 0) {
      parts.push(`${seconds} ${seconds === 1 ? 'second' : 'seconds'}`);
    }

    return parts.join(' ');
  }

  /**
   * Reset progress bar UI to initial state (0%)
   * Called when loading a new video source to prevent visual glitches
   */
  private resetProgressBar(): void {
    const progressFilled = this.cachedElements.progressFilled as HTMLElement;
    const progressHandle = this.cachedElements.progressHandle as HTMLElement;
    const progressBuffered = this.cachedElements.progressBuffered as HTMLElement;
    const timeDisplay = this.cachedElements.timeDisplay;

    // Reset progress bar elements to 0%
    if (progressFilled) {
      progressFilled.style.width = '0%';
    }
    if (progressHandle) {
      progressHandle.style.left = '0%';
    }
    if (progressBuffered) {
      progressBuffered.style.width = '0%';
    }

    // Reset time display to 00:00 / 00:00
    if (timeDisplay) {
      timeDisplay.textContent = '00:00 / 00:00';
    }
  }

  /**
   * Get height in pixels for YouTube quality levels
   */
  private getYouTubeHeight(level: string): number {
    const map: Record<string, number> = {
      'tiny': 144,
      'small': 240,
      'medium': 360,
      'large': 480,
      'hd720': 720,
      'hd1080': 1080,
      'hd1440': 1440,
      'hd2160': 2160,
      'highres': 4320
    };
    return map[level] || 0;
  }

  private updateTimeDisplay(): void {
    const timeDisplay = this.cachedElements.timeDisplay;
    if (!timeDisplay) return;

    let current = 0;
    let duration = 0;

    // Get time from YouTube player if available
    if (this.youtubePlayer && this.youtubePlayerReady) {
      try {
        current = this.youtubePlayer.getCurrentTime() || 0;
        duration = this.youtubePlayer.getDuration() || 0;
      } catch (error) {
        this.debugWarn('Error getting YouTube player time:', error);
      }
    } else if (this.video) {
      current = this.video.currentTime || 0;
      duration = this.video.duration || 0;
    }

    // Detect if duration is increasing (live stream with DVR)
    const isDurationIncreasing = duration > this.lastDuration && this.lastDuration > 0 && duration > 60;

    // Once we detect duration is increasing, remember it's a live stream
    if (isDurationIncreasing) {
      this.isDetectedAsLive = true;
    }

    this.lastDuration = duration;

    // Check if this is a live stream - only use isLive prop
    if (this.config.isLive === true) {
      // Show LIVE indicator for live streams
      timeDisplay.innerHTML = '<span class="uvf-live-dot"></span><span class="uvf-live-text">LIVE</span>';
      timeDisplay.classList.add('uvf-is-live');
      this.debugLog('Time display: LIVE stream (isLive=true)');
    } else {
      // Show normal time display for VOD
      const currentFormatted = this.formatTime(current);
      const durationFormatted = this.formatTime(duration);
      timeDisplay.textContent = `${currentFormatted} / ${durationFormatted}`;
      timeDisplay.classList.remove('uvf-is-live');
      this.debugLog('Time display updated:', `${currentFormatted} / ${durationFormatted}`);
    }
  }

  private showControls(): void {
    if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
    const wrapper = this.container?.querySelector('.uvf-player-wrapper') as HTMLElement | null;
    if (wrapper) {
      // Measure the real controls bar height and expose it as a CSS variable so the
      // subtitle overlay can position itself precisely above the bar at every
      // breakpoint / orientation / fullscreen state — no hardcoded magic numbers.
      if (this.controlsContainer) {
        wrapper.style.setProperty('--uvf-ctrl-height', `${this.controlsContainer.getBoundingClientRect().height}px`);
      }
      wrapper.classList.add('controls-visible');
      wrapper.classList.remove('no-cursor');
    }
  }

  private hideControls(): void {
    if (!this.state.isPlaying) return;

    const wrapper = this.container?.querySelector('.uvf-player-wrapper');
    if (wrapper) {
      wrapper.classList.remove('controls-visible');
      wrapper.classList.add('no-cursor');
    }

    // Close settings menu when controls hide (standard video player behavior)
    this.hideSettingsMenu();
  }

  private scheduleHideControls(): void {
    if (!this.state.isPlaying) return;

    if (this.hideControlsTimeout) clearTimeout(this.hideControlsTimeout);
    // Use longer timeout in fullscreen for better UX
    const timeout = this.isFullscreen() ? 4000 : 3000;
    this.hideControlsTimeout = setTimeout(() => {
      if (this.state.isPlaying && !this.controlsContainer?.matches(':hover')) {
        this.hideControls();
      }
    }, timeout);
  }

  /**
   * Setup advanced tap handling for mobile with:
   * - Single tap: toggle controls (immediate response)
   * - Double tap left: skip backward 10s
   * - Double tap right: skip forward 10s
   * - Long press left: 2x speed backward
   * - Long press right: 2x speed forward
   */
  private setupAdvancedTapHandling(): void {
    if (!this.video || !this.playerWrapper) return;

    const DOUBLE_TAP_DELAY = 300; // ms
    const LONG_PRESS_DELAY = 500; // ms
    const TAP_MOVEMENT_THRESHOLD = 10; // pixels
    const SKIP_SECONDS = 10;
    const FAST_PLAYBACK_RATE = 2;

    // Track if we're currently in a double-tap window
    let inDoubleTapWindow = false;

    const videoElement = this.video;
    const wrapper = this.playerWrapper;

    // Touch start handler
    const handleTouchStart = (e: TouchEvent) => {
      // Ignore if touching controls
      const target = e.target as HTMLElement;
      if (target.closest('.uvf-controls')) {
        return;
      }

      const touch = e.touches[0];
      this.tapStartTime = Date.now();
      this.tapStartX = touch.clientX;
      this.tapStartY = touch.clientY;

      // Start long press timer
      this.longPressTimer = setTimeout(() => {
        this.isLongPressing = true;
        this.handleLongPress(this.tapStartX);
      }, LONG_PRESS_DELAY);
    };

    // Touch move handler
    const handleTouchMove = (e: TouchEvent) => {
      const touch = e.touches[0];
      const deltaX = Math.abs(touch.clientX - this.tapStartX);
      const deltaY = Math.abs(touch.clientY - this.tapStartY);

      // Cancel long press if moved too much
      if (deltaX > TAP_MOVEMENT_THRESHOLD || deltaY > TAP_MOVEMENT_THRESHOLD) {
        if (this.longPressTimer) {
          clearTimeout(this.longPressTimer);
          this.longPressTimer = null;
        }
      }
    };

    // Touch end handler
    const handleTouchEnd = (e: TouchEvent) => {
      // Clear long press timer
      if (this.longPressTimer) {
        clearTimeout(this.longPressTimer);
        this.longPressTimer = null;
      }

      // Handle long press end
      if (this.isLongPressing) {
        this.handleLongPressEnd();
        this.isLongPressing = false;
        return;
      }

      // Ignore if touching controls
      const target = e.target as HTMLElement;
      if (target.closest('.uvf-controls')) {
        return;
      }

      const touch = e.changedTouches[0];
      const touchEndX = touch.clientX;
      const touchEndY = touch.clientY;
      const tapDuration = Date.now() - this.tapStartTime;

      // Check if it was a tap (not a drag)
      const deltaX = Math.abs(touchEndX - this.tapStartX);
      const deltaY = Math.abs(touchEndY - this.tapStartY);

      if (deltaX > TAP_MOVEMENT_THRESHOLD || deltaY > TAP_MOVEMENT_THRESHOLD) {
        // It was a drag, not a tap
        return;
      }

      // Check if it was a quick tap (not a long press)
      if (tapDuration > LONG_PRESS_DELAY) {
        return;
      }

      // Determine if this is a double tap
      const now = Date.now();
      const timeSinceLastTap = now - this.lastTapTime;

      if (timeSinceLastTap < DOUBLE_TAP_DELAY && Math.abs(touchEndX - this.lastTapX) < 100) {
        // Double tap detected
        this.tapCount = 2;
        if (this.tapResetTimer) {
          clearTimeout(this.tapResetTimer);
          this.tapResetTimer = null;
        }
        inDoubleTapWindow = false;
        this.handleDoubleTap(touchEndX);
      } else {
        // First tap - execute immediately for responsive feel
        this.tapCount = 1;
        this.lastTapTime = now;
        this.lastTapX = touchEndX;
        inDoubleTapWindow = true;

        // Execute single tap immediately
        this.handleSingleTap();

        // Wait to see if there's a second tap
        if (this.tapResetTimer) {
          clearTimeout(this.tapResetTimer);
        }
        this.tapResetTimer = setTimeout(() => {
          this.tapCount = 0;
          inDoubleTapWindow = false;
        }, DOUBLE_TAP_DELAY);
      }
    };

    // Single tap: toggle controls
    const handleSingleTap = () => {
      this.debugLog('Single tap detected - toggling controls');
      const wrapper = this.container?.querySelector('.uvf-player-wrapper');
      const areControlsVisible = wrapper?.classList.contains('controls-visible');

      if (areControlsVisible) {
        // Hide controls and top UI elements
        this.hideControls();
        this.debugLog('Single tap: hiding controls');
      } else {
        // Show controls and top UI elements
        this.showControls();
        this.debugLog('Single tap: showing controls');

        // Schedule auto-hide if video is playing
        if (this.state.isPlaying) {
          this.scheduleHideControls();
          this.debugLog('Single tap: scheduled auto-hide');
        }
      }
    };

    // Double tap: skip backward/forward based on screen side
    const handleDoubleTap = (tapX: number) => {
      if (!wrapper) return;

      const wrapperRect = wrapper.getBoundingClientRect();
      const tapPosition = tapX - wrapperRect.left;
      const wrapperWidth = wrapperRect.width;
      const isLeftSide = tapPosition < wrapperWidth / 2;

      const currentTime = this.getCurrentTime();
      const duration = this.getDuration();

      // Validate current time and duration before calculating new time
      if (!isFinite(currentTime) || isNaN(currentTime) || !isFinite(duration) || isNaN(duration)) {
        this.debugWarn('Invalid video time values, skipping double-tap action');
        return;
      }

      if (isLeftSide) {
        // Skip backward
        const newTime = Math.max(0, currentTime - SKIP_SECONDS);
        this.seek(newTime);
        this.showShortcutIndicator(`-${SKIP_SECONDS}s`);
        this.debugLog('Double tap left - skip backward');
      } else {
        // Skip forward
        const newTime = Math.min(duration, currentTime + SKIP_SECONDS);
        this.seek(newTime);
        this.showShortcutIndicator(`+${SKIP_SECONDS}s`);
        this.debugLog('Double tap right - skip forward');
      }
    };

    // Long press: fast forward/rewind based on screen side
    const handleLongPress = (tapX: number) => {
      if (!wrapper) return;

      const wrapperRect = wrapper.getBoundingClientRect();
      const tapPosition = tapX - wrapperRect.left;
      const wrapperWidth = wrapperRect.width;
      const isLeftSide = tapPosition < wrapperWidth / 2;

      // Save original playback rate
      this.longPressPlaybackRate = this.getPlaybackRate();

      if (isLeftSide) {
        // Fast backward by setting negative time interval
        const skipIcon = `<svg viewBox="0 0 24 24" style="width:32px;height:32px;display:inline-block;vertical-align:middle;margin-right:8px"><path d="M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z" stroke="currentColor" stroke-width="0.5" fill="currentColor"/></svg>`;
        this.showShortcutIndicator(skipIcon + ' 2x');
        this.debugLog('Long press left - fast backward');
        this.startFastBackward();
      } else {
        // Fast forward
        this.setSpeed(FAST_PLAYBACK_RATE);
        const skipIcon = `<svg viewBox="0 0 24 24" style="width:32px;height:32px;display:inline-block;vertical-align:middle;margin-right:8px;transform:scaleX(-1)"><path d="M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z" stroke="currentColor" stroke-width="0.5" fill="currentColor"/></svg>`;
        this.showShortcutIndicator(skipIcon + ' 2x');
        this.debugLog('Long press right - fast forward');
      }
    };

    // Long press end: restore normal playback
    const handleLongPressEnd = () => {
      // Stop fast backward if active
      this.stopFastBackward();

      // Restore original playback rate
      this.setSpeed(this.longPressPlaybackRate || 1);
      this.debugLog('Long press ended - restored playback rate');
    };

    // Bind handlers
    this.handleSingleTap = handleSingleTap.bind(this);
    this.handleDoubleTap = handleDoubleTap.bind(this);
    this.handleLongPress = handleLongPress.bind(this);
    this.handleLongPressEnd = handleLongPressEnd.bind(this);

    // Attach event listeners
    videoElement.addEventListener('touchstart', handleTouchStart, { passive: true });
    videoElement.addEventListener('touchmove', handleTouchMove, { passive: true });
    videoElement.addEventListener('touchend', handleTouchEnd, { passive: true });

    this.debugLog('Advanced tap handling initialized');
  }

  // Fast backward using interval-based seeking
  private startFastBackward(): void {
    if (!this.video || this.fastBackwardInterval) return;

    this.fastBackwardInterval = setInterval(() => {
      if (this.video) {
        const currentTime = this.video.currentTime;
        if (isFinite(currentTime) && !isNaN(currentTime)) {
          const newTime = Math.max(0, currentTime - 0.1); // Go back 0.1s every frame
          this.safeSetCurrentTime(newTime);
        }
      }
    }, 50); // Update every 50ms for smooth backward motion
  }

  private stopFastBackward(): void {
    if (this.fastBackwardInterval) {
      clearInterval(this.fastBackwardInterval);
      this.fastBackwardInterval = null;
    }
  }

  private isFullscreen(): boolean {
    return !!(document.fullscreenElement ||
      (document as any).webkitFullscreenElement ||
      (document as any).mozFullScreenElement ||
      (document as any).msFullscreenElement);
  }

  private setupFullscreenListeners(): void {
    // Handle fullscreen changes from browser/keyboard shortcuts
    const handleFullscreenChange = () => {
      const isFullscreen = this.isFullscreen();

      if (this.playerWrapper) {
        if (isFullscreen) {
          this.playerWrapper.classList.add('uvf-fullscreen');
        } else {
          this.playerWrapper.classList.remove('uvf-fullscreen');
        }
      }

      // Show controls when entering/exiting fullscreen
      this.showControls();
      if (isFullscreen && this.state.isPlaying) {
        this.scheduleHideControls();
      }

      this.emit('onFullscreenChanged', isFullscreen);
    };

    // Listen for fullscreen change events (all browser prefixes)
    document.addEventListener('fullscreenchange', handleFullscreenChange);
    document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
    document.addEventListener('mozfullscreenchange', handleFullscreenChange);
    document.addEventListener('MSFullscreenChange', handleFullscreenChange);

    // Enhanced mouse/touch movement detection for control visibility
    let lastMouseMoveTime = 0;
    let mouseInactivityTimeout: any = null;

    const handleMouseMovement = () => {
      const now = Date.now();
      lastMouseMoveTime = now;

      // Show controls immediately on mouse movement
      this.showControls();

      // Clear existing inactivity timeout
      clearTimeout(mouseInactivityTimeout);

      // Set new inactivity timeout
      if (this.state.isPlaying) {
        const timeout = this.isFullscreen() ? 4000 : 3000;
        mouseInactivityTimeout = setTimeout(() => {
          const timeSinceLastMove = Date.now() - lastMouseMoveTime;
          if (timeSinceLastMove >= timeout && this.state.isPlaying) {
            this.hideControls();
          }
        }, timeout);
      }
    };

    // Touch movement detection for mobile - only for actual dragging/scrolling
    // Note: Don't handle touchstart here as it conflicts with advanced tap handling
    const handleTouchMovement = () => {
      // Only show controls on actual touch movement, not touchstart
      this.showControls();
      if (this.state.isPlaying) {
        this.scheduleHideControls();
      }
    };

    // Add event listeners to the player wrapper
    if (this.playerWrapper) {
      this.playerWrapper.addEventListener('mousemove', handleMouseMovement, { passive: true });
      this.playerWrapper.addEventListener('mouseenter', () => this.showControls());
      // Only listen to touchmove (actual dragging), not touchstart
      // touchstart is handled by advanced tap handling system on video element
      this.playerWrapper.addEventListener('touchmove', handleTouchMovement, { passive: true });
    }
  }



  private showShortcutIndicator(text: string): void {
    const el = this.getElement('shortcut-indicator');
    this.debugLog('showShortcutIndicator called with:', text, 'element found:', !!el);
    if (!el) {
      this.debugError('uvf-shortcut-indicator element not found!');
      return;
    }
    try {
      const resetAnim = () => {
        el.classList.remove('active');
        // force reflow to restart animation
        void (el as HTMLElement).offsetWidth;
        el.classList.add('active');
      };
      const setIcon = (svg: string) => {
        el.classList.add('uvf-ki-icon');
        el.innerHTML = `<div class="uvf-ki uvf-ki-icon">${svg}</div>`;
        resetAnim();
      };
      const setSkip = (dir: 'fwd' | 'back', num: number) => {
        el.classList.add('uvf-ki-icon');
        const svg = dir === 'fwd'
          ? `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12.01 19c-3.31 0-6-2.69-6-6s2.69-6 6-6V5l5 5-5 5V9c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4h2c0 3.31-2.69 6-6 6z"/></svg>`
          : `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M11.99 5V1l-5 5 5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6h-2c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/></svg>`;
        el.innerHTML = `<div class="uvf-ki uvf-ki-skip"><div class="uvf-ki-skip-num">${num}</div>${svg}</div>`;
        resetAnim();
      };
      const setVolume = (percent: number, muted: boolean = false) => {
        el.classList.remove('uvf-ki-icon');
        const p = Math.max(0, Math.min(100, Math.round(percent)));
        const icon = muted ? `
          <svg viewBox="0 0 24 24" aria-hidden="true">
            <path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>
          </svg>` : `
          <svg viewBox="0 0 24 24" aria-hidden="true">
            <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
          </svg>`;
        el.innerHTML = `
          <div class="uvf-ki uvf-ki-volume" role="status" aria-live="polite">
            <div class="uvf-ki-vol-icon">${icon}</div>
            <div class="uvf-ki-vol-bar"><div class="uvf-ki-vol-fill" style="width:${p}%"></div></div>
            <div class="uvf-ki-vol-text">${p}%</div>
          </div>`;
        resetAnim();
      };
      const setText = (t: string) => {
        el.classList.remove('uvf-ki-icon');
        el.innerHTML = `<div class="uvf-ki uvf-ki-text">${t}</div>`;
        resetAnim();
      };

      // Map text cues to icon overlays (YouTube-like)
      if (text === 'Play') {
        setIcon(`<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>`);
      } else if (text === 'Pause') {
        setIcon(`<svg viewBox="0 0 24 24"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>`);
      } else if (text === '+10s') {
        setSkip('fwd', 10);
      } else if (text === '-10s') {
        setSkip('back', 10);
      } else if (/^Volume\s+(\d+)%$/.test(text)) {
        const m = text.match(/^Volume\s+(\d+)%$/);
        const val = m ? parseInt(m[1], 10) : 0;
        setVolume(val);
      } else if (text === 'Muted' || text === 'Unmuted') {
        const muted = text === 'Muted';
        const level = (this.isCasting && this.remotePlayer) ? Math.round(((this.remotePlayer.volumeLevel || 0) * 100)) : Math.round((this.video?.volume || 0) * 100);
        setVolume(level, muted);
      } else if (text === 'Fullscreen') {
        setIcon(`<svg viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>`);
      } else if (text === 'Exit Fullscreen') {
        setIcon(`<svg viewBox="0 0 24 24"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/></svg>`);
      } else if (text === 'Picture-in-Picture') {
        setIcon(`<svg viewBox="0 0 24 24"><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z"/></svg>`);
      } else if (/^\d+%$/.test(text)) {
        setText(text);
      } else {
        setText(text);
      }

      // auto-hide after animation
      clearTimeout(this._kiTo);
      this._kiTo = setTimeout(() => {
        try { el.classList.remove('active'); } catch (_) { }
      }, 1000);
    } catch (err) {
      try {
        (el as HTMLElement).textContent = String(text || '');
        el.classList.add('active');
        setTimeout(() => el.classList.remove('active'), 1000);
      } catch (_) { }
    }
  }

  public setSettingsScrollbarStyle(mode: 'default' | 'compact' | 'overlay'): void {
    const wrapper = this.playerWrapper;
    if (!wrapper) return;
    wrapper.classList.remove('uvf-scrollbar-compact', 'uvf-scrollbar-overlay');
    switch (mode) {
      case 'compact':
        wrapper.classList.add('uvf-scrollbar-compact');
        break;
      case 'overlay':
        wrapper.classList.add('uvf-scrollbar-overlay');
        break;
      default:
        // default
        break;
    }
  }

  public setSettingsScrollbarConfig(options: { widthPx?: number; intensity?: number }): void {
    const wrapper = this.playerWrapper;
    if (!wrapper) return;
    const { widthPx, intensity } = options || {};

    if (typeof widthPx === 'number' && isFinite(widthPx)) {
      const w = Math.max(4, Math.min(16, Math.round(widthPx)));
      wrapper.style.setProperty('--uvf-scrollbar-width', `${w}px`);
    }

    if (typeof intensity === 'number' && isFinite(intensity)) {
      const i = Math.max(0, Math.min(1, intensity));
      const s1 = 0.35 * i;
      const e1 = 0.45 * i;
      const s2 = 0.50 * i;
      const e2 = 0.60 * i;
      wrapper.style.setProperty('--uvf-scrollbar-thumb-start', `rgba(255,0,0,${s1.toFixed(3)})`);
      wrapper.style.setProperty('--uvf-scrollbar-thumb-end', `rgba(255,0,0,${e1.toFixed(3)})`);
      wrapper.style.setProperty('--uvf-scrollbar-thumb-hover-start', `rgba(255,0,0,${s2.toFixed(3)})`);
      wrapper.style.setProperty('--uvf-scrollbar-thumb-hover-end', `rgba(255,0,0,${e2.toFixed(3)})`);
      wrapper.style.setProperty('--uvf-firefox-scrollbar-color', `rgba(255,255,255,${(0.25 * i).toFixed(3)})`);
    }
  }

  private applyScrollbarPreferencesFromDataset(): void {
    const container = this.container as HTMLElement | null;
    if (!container) return;
    const ds = container.dataset || {};

    const stylePref = (ds.scrollbarStyle || '').toLowerCase();
    if (stylePref === 'compact' || stylePref === 'overlay' || stylePref === 'default') {
      this.setSettingsScrollbarStyle(stylePref as 'default' | 'compact' | 'overlay');
    }

    const width = Number(ds.scrollbarWidth);
    const intensity = Number(ds.scrollbarIntensity);
    const options: { widthPx?: number; intensity?: number } = {};
    if (Number.isFinite(width)) options.widthPx = width;
    if (Number.isFinite(intensity)) options.intensity = intensity;
    if (options.widthPx !== undefined || options.intensity !== undefined) {
      this.setSettingsScrollbarConfig(options);
    }
  }

  /**
   * Setup chapter manager for skip functionality
   */
  private setupChapterManager(): void {
    if (!this.video || !this.playerWrapper) {
      this.debugWarn('Cannot setup chapter manager: video or wrapper not available');
      return;
    }

    try {
      // Initialize the web-specific chapter manager (for UI controls)
      this.chapterManager = new ChapterManager(
        this.playerWrapper,
        this.video,
        this.chapterConfig
      );

      // Initialize the core chapter manager (for basic chapter functionality)
      const coreChapterConfig = {
        enabled: this.chapterConfig.enabled,
        chapters: this.convertToChapters(this.chapterConfig.data),
        segments: this.convertToChapterSegments(this.chapterConfig.data),
        dataUrl: this.chapterConfig.dataUrl,
        autoSkip: this.chapterConfig.userPreferences?.autoSkipIntro || false,
        onChapterChange: (chapter: Chapter | null) => {
          this.debugLog('Core chapter changed:', chapter?.title || 'none');
          this.emit('onChapterchange', chapter);
        },
        onSegmentEntered: (segment: ChapterSegment) => {
          this.debugLog('Core segment entered:', segment.title);
          this.emit('segmententered', segment);
        },
        onSegmentExited: (segment: ChapterSegment) => {
          this.debugLog('Core segment exited:', segment.title);
          this.emit('segmentexited', segment);
        },
        onSegmentSkipped: (segment: ChapterSegment) => {
          this.debugLog('Core segment skipped:', segment.title);
          this.emit('segmentskipped', segment);
        }
      };

      this.coreChapterManager = new CoreChapterManager(coreChapterConfig);

      // Initialize the core chapter manager
      this.coreChapterManager.initialize();

      // Set up event listeners for web chapter events
      this.chapterManager.on('segmentEntered', (data) => {
        this.debugLog('Entered segment:', data.segment.type, data.segment.title);
        this.emit('chapterSegmentEntered', data);
      });

      this.chapterManager.on('segmentSkipped', (data) => {
        this.debugLog('Skipped segment:', data.fromSegment.type, 'to', data.toSegment?.type || 'end');
        this.emit('chapterSegmentSkipped', data);
      });

      this.chapterManager.on('skipButtonShown', (data) => {
        this.debugLog('Skip button shown for:', data.segment.type);
        this.emit('chapterSkipButtonShown', data);
      });

      this.chapterManager.on('skipButtonHidden', (data) => {
        this.debugLog('Skip button hidden for:', data.segment.type, 'reason:', data.reason);
        this.emit('chapterSkipButtonHidden', data);
      });

      this.chapterManager.on('chaptersLoaded', (data) => {
        this.debugLog('Chapters loaded:', data.segmentCount, 'segments');
        this.emit('chaptersLoaded', data);
      });

      this.chapterManager.on('chaptersLoadError', (data) => {
        this.debugError('Failed to load chapters:', data.error.message);
        this.emit('chaptersLoadError', data);
      });

      this.debugLog('Chapter managers initialized successfully');
    } catch (error) {
      this.debugError('Failed to initialize chapter managers:', error);
    }
  }

  /**
   * Convert web chapter data to core Chapter format
   */
  private convertToChapters(webChapterData: any): Chapter[] {
    if (!webChapterData || !webChapterData.segments) {
      return [];
    }

    return webChapterData.segments
      .filter((segment: any) => segment.type === 'content')
      .map((segment: any, index: number) => ({
        id: segment.id || `chapter-${index}`,
        title: segment.title || `Chapter ${index + 1}`,
        startTime: segment.startTime,
        endTime: segment.endTime,
        thumbnail: segment.thumbnail,
        description: segment.description,
        metadata: segment.metadata || {}
      }));
  }

  /**
   * Convert web chapter data to core ChapterSegment format
   */
  private convertToChapterSegments(webChapterData: any): ChapterSegment[] {
    if (!webChapterData || !webChapterData.segments) {
      return [];
    }

    return webChapterData.segments
      .filter((segment: any) => segment.type !== 'content')
      .map((segment: any) => ({
        id: segment.id,
        startTime: segment.startTime,
        endTime: segment.endTime,
        category: segment.type,
        action: this.mapSegmentAction(segment.type),
        title: segment.title,
        description: segment.description
      }));
  }

  /**
   * Map web segment types to core segment actions
   */
  private mapSegmentAction(segmentType: string): 'skip' | 'mute' | 'warn' {
    switch (segmentType) {
      case 'intro':
      case 'recap':
      case 'credits':
      case 'sponsor':
        return 'skip';
      case 'offensive':
        return 'mute';
      default:
        return 'warn';
    }
  }

  /**
   * Load chapters data
   */
  public async loadChapters(chapters: VideoChapters): Promise<void> {
    if (!this.chapterManager) {
      throw new Error('Chapter manager not initialized. Enable chapters in config first.');
    }

    try {
      await this.chapterManager.loadChapters(chapters);
      this.debugLog('Chapters loaded successfully');
    } catch (error) {
      this.debugError('Failed to load chapters:', error);
      throw error;
    }
  }

  /**
   * Load chapters from URL
   */
  public async loadChaptersFromUrl(url: string): Promise<void> {
    if (!this.chapterManager) {
      throw new Error('Chapter manager not initialized. Enable chapters in config first.');
    }

    try {
      await this.chapterManager.loadChaptersFromUrl(url);
      this.debugLog('Chapters loaded from URL successfully');
    } catch (error) {
      this.debugError('Failed to load chapters from URL:', error);
      throw error;
    }
  }

  /**
   * Get current video segment
   */
  public getCurrentSegment(): VideoSegment | null {
    if (!this.chapterManager || !this.video) {
      return null;
    }

    return this.chapterManager.getCurrentSegment(this.video.currentTime);
  }

  /**
   * Skip to specific segment by ID
   */
  public skipToSegment(segmentId: string): void {
    if (!this.chapterManager) {
      this.debugWarn('Cannot skip segment: chapter manager not initialized');
      return;
    }

    this.chapterManager.skipToSegment(segmentId);
  }

  /**
   * Get all video segments
   */
  public getSegments(): VideoSegment[] {
    if (!this.chapterManager) {
      return [];
    }

    return this.chapterManager.getSegments();
  }

  /**
   * Update chapter configuration
   */
  public updateChapterConfig(newConfig: Partial<ChapterConfig>): void {
    this.chapterConfig = { ...this.chapterConfig, ...newConfig };

    if (this.chapterManager) {
      this.chapterManager.updateConfig(this.chapterConfig);
    }
  }

  /**
   * Check if chapters are loaded
   */
  public hasChapters(): boolean {
    return this.chapterManager?.hasChapters() || false;
  }

  /**
   * Get chapter data
   */
  public getChapters(): VideoChapters | null {
    return this.chapterManager?.getChapters() || null;
  }

  /**
   * Get core chapters (Chapter format)
   */
  public getCoreChapters(): Chapter[] {
    return this.coreChapterManager?.getChapters() || [];
  }

  /**
   * Get core chapter segments (ChapterSegment format)
   */
  public getCoreSegments(): ChapterSegment[] {
    return this.coreChapterManager?.getSegments() || [];
  }

  /**
   * Get current chapter info from core manager
   */
  public getCurrentChapterInfo(): Chapter | null {
    return this.coreChapterManager?.getCurrentChapterInfo() || null;
  }

  /**
   * Seek to chapter by ID (core chapter functionality)
   */
  public seekToChapter(chapterId: string): void {
    if (!this.coreChapterManager || !this.video) {
      this.debugWarn('Cannot seek to chapter: core chapter manager or video not available');
      return;
    }

    const chapter = this.coreChapterManager.seekToChapter(chapterId);
    if (chapter && isFinite(chapter.startTime) && !isNaN(chapter.startTime)) {
      this.safeSetCurrentTime(chapter.startTime);
      this.debugLog('Seeked to chapter:', chapter.title);
    }
  }

  /**
   * Get next chapter from current time
   */
  public getNextChapter(): Chapter | null {
    if (!this.coreChapterManager || !this.video) {
      return null;
    }
    return this.coreChapterManager.getNextChapter(this.video.currentTime);
  }

  /**
   * Get previous chapter from current time
   */
  public getPreviousChapter(): Chapter | null {
    if (!this.coreChapterManager || !this.video) {
      return null;
    }
    return this.coreChapterManager.getPreviousChapter(this.video.currentTime);
  }

  // Theme API: set CSS variables on the wrapper to apply dynamic colors
  public setTheme(theme: any): void {
    const wrapper = this.playerWrapper;
    if (!wrapper) return;
    try {
      let accent1: string | null = null;
      let accent2: string | null = null;
      let iconColor: string | null = null;
      let textPrimary: string | null = null;
      let textSecondary: string | null = null;
      let overlayStrong: string | null = null;
      let overlayMedium: string | null = null;
      let overlayTransparent: string | null = null;

      if (typeof theme === 'string') {
        accent1 = theme;
      } else if (theme && typeof theme === 'object') {
        accent1 = theme.accent || null;
        accent2 = theme.accent2 || null;
        iconColor = theme.iconColor || null;
        textPrimary = theme.textPrimary || null;
        textSecondary = theme.textSecondary || null;
        overlayStrong = theme.overlayStrong || null;
        overlayMedium = theme.overlayMedium || null;
        overlayTransparent = theme.overlayTransparent || null;
      }

      if (accent1) wrapper.style.setProperty('--uvf-accent-1', accent1);
      // Derive accent2 if missing
      if (!accent2 && accent1) {
        const rgb = this._parseRgb(accent1);
        if (rgb) {
          const lighter = this._lightenRgb(rgb, 0.35);
          accent2 = this._rgbToString(lighter);
        } else {
          // Fallback: use the same accent for both ends of the gradient
          accent2 = accent1;
        }
      }
      if (accent2) wrapper.style.setProperty('--uvf-accent-2', accent2);

      // Provide a translucent version of accent1 for badges
      if (accent1) {
        const a20 = this._toRgba(accent1, 0.2);
        if (a20) wrapper.style.setProperty('--uvf-accent-1-20', a20);
      }

      if (iconColor) wrapper.style.setProperty('--uvf-icon-color', iconColor);
      if (textPrimary) wrapper.style.setProperty('--uvf-text-primary', textPrimary);
      if (textSecondary) wrapper.style.setProperty('--uvf-text-secondary', textSecondary);

      // Set overlay colors for gradient backgrounds
      if (overlayStrong) wrapper.style.setProperty('--uvf-overlay-strong', overlayStrong);
      if (overlayMedium) wrapper.style.setProperty('--uvf-overlay-medium', overlayMedium);
      if (overlayTransparent) wrapper.style.setProperty('--uvf-overlay-transparent', overlayTransparent);
    } catch (_) {
      // ignore
    }
  }

  private _parseRgb(input: string): { r: number; g: number; b: number } | null {
    try {
      const s = (input || '').trim().toLowerCase();
      // #rrggbb or #rgb
      if (s.startsWith('#')) {
        const hex = s.substring(1);
        if (hex.length === 3) {
          const r = parseInt(hex[0] + hex[0], 16);
          const g = parseInt(hex[1] + hex[1], 16);
          const b = parseInt(hex[2] + hex[2], 16);
          return { r, g, b };
        }
        if (hex.length === 6) {
          const r = parseInt(hex.substring(0, 2), 16);
          const g = parseInt(hex.substring(2, 4), 16);
          const b = parseInt(hex.substring(4, 6), 16);
          return { r, g, b };
        }
      }
      // rgb(a)
      if (s.startsWith('rgb(') || s.startsWith('rgba(')) {
        const nums = s.replace(/rgba?\(/, '').replace(/\)/, '').split(',').map(x => parseFloat(x.trim()));
        if (nums.length >= 3) {
          return { r: Math.round(nums[0]), g: Math.round(nums[1]), b: Math.round(nums[2]) };
        }
      }
    } catch (_) { }
    return null;
  }

  private _rgbToString(rgb: { r: number; g: number; b: number }): string {
    const c = (n: number) => Math.max(0, Math.min(255, Math.round(n)));
    return `rgb(${c(rgb.r)}, ${c(rgb.g)}, ${c(rgb.b)})`;
  }

  private _lightenRgb(rgb: { r: number; g: number; b: number }, amount: number): { r: number; g: number; b: number } {
    const clamp = (n: number) => Math.max(0, Math.min(255, Math.round(n)));
    const amt = Math.max(0, Math.min(1, amount));
    return {
      r: clamp(rgb.r + (255 - rgb.r) * amt),
      g: clamp(rgb.g + (255 - rgb.g) * amt),
      b: clamp(rgb.b + (255 - rgb.b) * amt),
    };
  }

  private _toRgba(input: string, alpha: number): string | null {
    const rgb = this._parseRgb(input);
    if (!rgb) return null;
    const a = Math.max(0, Math.min(1, alpha));
    return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a})`;
  }

  private changeVolume(delta: number): void {
    if (this.isCasting && this.remoteController && this.remotePlayer) {
      const cur = this.remotePlayer.volumeLevel || 0;
      const next = Math.max(0, Math.min(1, cur + delta));
      try {
        if (this.remotePlayer.isMuted) {
          try { this.remoteController.muteOrUnmute(); } catch (_) { }
          this.remotePlayer.isMuted = false;
        }
        this.remotePlayer.volumeLevel = next;
        this.remoteController.setVolumeLevel();
      } catch (_) { }
      this.updateVolumeUIFromRemote();
      return;
    }
    if (this.youtubePlayer && this.youtubePlayerReady) {
      const currentPercent = this.youtubePlayer.getVolume() / 100;
      const newPercent = Math.max(0, Math.min(1, currentPercent + delta));
      this.youtubePlayer.setVolume(newPercent * 100);
      if (this.video) {
        this.video.volume = newPercent;
      }
      return;
    }
    if (!this.video) return;
    this.video.volume = Math.max(0, Math.min(1, this.video.volume + delta));
  }

  private setSpeed(speed: number): void {
    // Handle YouTube player
    if (this.youtubePlayer && this.youtubePlayerReady) {
      try {
        this.youtubePlayer.setPlaybackRate(speed);
      } catch (error) {
        this.debugWarn('YouTube playback rate not supported:', error);
      }
    } else if (this.video) {
      this.video.playbackRate = speed;
    }

    // Update UI
    document.querySelectorAll('.speed-option').forEach(option => {
      option.classList.remove('active');
      if (parseFloat((option as HTMLElement).dataset.speed || '1') === speed) {
        option.classList.add('active');
      }
    });
  }

  private setQualityByLabel(quality: string): void {
    // Handle YouTube player
    if (this.youtubePlayer && this.youtubePlayerReady) {
      if (quality === 'auto') {
        this.youtubePlayer.setPlaybackQuality('auto');
      } else {
        const levels = this.youtubePlayer.getAvailableQualityLevels();
        const level = levels.find((l: string) => this.getYouTubeHeight(l).toString() === quality);
        if (level) {
          this.youtubePlayer.setPlaybackQuality(level);
        }
      }
    }

    const qualityBadge = this.getElement('quality-badge');

    // Update UI
    document.querySelectorAll('.quality-option').forEach(option => {
      option.classList.remove('active');
      if ((option as HTMLElement).dataset.quality === quality) {
        option.classList.add('active');
      }
    });

    // Update badge
    this.updateQualityBadgeText();

    // If we have actual quality levels from HLS/DASH, apply them
    if (quality !== 'auto' && this.qualities.length > 0) {
      const qualityLevel = this.qualities.find(q => q.label === quality + 'p');
      if (qualityLevel) {
        const index = this.qualities.findIndex(q => q.label === quality + 'p');
        this.setQuality(index);
      }
    } else if (quality === 'auto') {
      this.setAutoQuality(true);
    }
  }

  /**
   * Update quality badge text based on current quality state
   */
  private updateQualityBadgeText(): void {
    const qualityBadge = this.getElement('quality-badge');
    if (!qualityBadge) return;

    // Activate badge if we have qualities available
    if (this.qualities.length > 0) {
      qualityBadge.classList.add('active');
      // Ensure badge is visible by clearing any restrictive inline styles
      // Check if controlsVisibility allows the badge to be shown
      if (this.controlsVisibility.quality?.badge !== false) {
        qualityBadge.classList.remove('uvf-hidden');
        qualityBadge.style.display = '';
      }
    }

    if (this.autoQuality) {
      // Auto mode - show both AUTO and current quality
      const currentQuality = this.getCurrentQuality();
      if (currentQuality && currentQuality.label) {
        qualityBadge.textContent = `AUTO (${currentQuality.label})`;
      } else {
        qualityBadge.textContent = 'AUTO';
      }
    } else {
      // Manual mode - show just the quality
      const currentQuality = this.getCurrentQuality();
      if (currentQuality && currentQuality.label) {
        qualityBadge.textContent = currentQuality.label;
      } else if (this.currentQuality && this.currentQuality !== 'auto') {
        qualityBadge.textContent = this.currentQuality + 'p';
      }
    }
  }

  /**
   * Update only the quality accordion current label (without rebuilding entire menu)
   * Used for real-time quality updates in Auto mode
   */
  private updateQualityLabel(): void {
    const settingsMenu = this.cachedElements.settingsMenu;
    if (!settingsMenu) return;

    // Find the quality accordion's current label element
    const qualityHeader = settingsMenu.querySelector('[data-section="quality"]');
    if (!qualityHeader) return;

    const currentLabel = qualityHeader.querySelector('.uvf-accordion-current');
    if (!currentLabel) return;

    // Update the label text
    if (this.currentQuality === 'auto' && this.autoQuality) {
      const playingQuality = this.getCurrentQuality();
      if (playingQuality && playingQuality.label) {
        currentLabel.textContent = `Auto (${playingQuality.label})`;
      } else {
        currentLabel.textContent = 'Auto';
      }
    } else {
      const currentQuality = this.availableQualities.find(q => q.value === this.currentQuality);
      currentLabel.textContent = currentQuality ? currentQuality.label : 'Auto';
    }
  }

  private async togglePiP(): Promise<void> {
    try {
      if ((document as any).pictureInPictureElement) {
        await this.exitPictureInPicture();
      } else {
        await this.enterPictureInPicture();
      }
    } catch (error) {
      console.error('PiP toggle failed:', error);
    }
  }

  private setupCastContextSafe(): void {
    try {
      const castNs = (window as any).cast;
      if (castNs && castNs.framework) {
        this.setupCastContext();
      }
    } catch (_) { }
  }

  private setupCastContext(): void {
    if (this.castContext) return;
    try {
      const castNs = (window as any).cast;
      this.castContext = castNs.framework.CastContext.getInstance();
      const chromeNs = (window as any).chrome;
      const options: any = { receiverApplicationId: chromeNs?.cast?.media?.DEFAULT_MEDIA_RECEIVER_APP_ID };
      try {
        const autoJoin = chromeNs?.cast?.AutoJoinPolicy?.ORIGIN_SCOPED;
        if (autoJoin) options.autoJoinPolicy = autoJoin;
      } catch (_) { }
      this.castContext.setOptions(options);
      this.castContext.addEventListener(
        castNs.framework.CastContextEventType.SESSION_STATE_CHANGED,
        (ev: any) => {
          const state = ev.sessionState;
          if (state === castNs.framework.SessionState.SESSION_STARTED ||
            state === castNs.framework.SessionState.SESSION_RESUMED) {
            this.enableCastRemoteControl();
          } else if (state === castNs.framework.SessionState.SESSION_ENDED) {
            this.disableCastRemoteControl();
          }
        }
      );
    } catch (err) {
      if (this.config.debug) console.warn('[Cast] setupCastContext failed', err);
    }
  }

  private enableCastRemoteControl(): void {
    try {
      const castNs = (window as any).cast;
      if (!castNs || !castNs.framework) return;
      const session = castNs.framework.CastContext.getInstance().getCurrentSession();
      if (!session) return;
      if (!this.remotePlayer) this.remotePlayer = new castNs.framework.RemotePlayer();
      if (!this.remoteController) {
        this.remoteController = new castNs.framework.RemotePlayerController(this.remotePlayer);
        this._bindRemotePlayerEvents();
      }
      this.isCasting = true;
      try { this.video?.pause(); } catch (_) { }
      this._syncUIFromRemote();
      this._syncCastButtons();
    } catch (err) {
      if (this.config.debug) console.warn('[Cast] enableCastRemoteControl failed', err);
    }
  }

  private disableCastRemoteControl(): void {
    this.isCasting = false;
    this._syncCastButtons();
  }

  private _bindRemotePlayerEvents(): void {
    const castNs = (window as any).cast;
    if (!this.remoteController || !castNs) return;
    const RPET = castNs.framework.RemotePlayerEventType;
    const rc = this.remoteController;

    rc.addEventListener(RPET.IS_PAUSED_CHANGED, () => {
      if (!this.isCasting) return;
      if (this.remotePlayer && this.remotePlayer.isPaused === false) {
        // reflect playing UI
        const playIcon = this.cachedElements.playIcon;
        const pauseIcon = this.cachedElements.pauseIcon;
        if (playIcon) playIcon.style.display = 'none';
        if (pauseIcon) pauseIcon.style.display = 'block';
      } else {
        const playIcon = this.cachedElements.playIcon;
        const pauseIcon = this.cachedElements.pauseIcon;
        if (playIcon) playIcon.style.display = 'block';
        if (pauseIcon) pauseIcon.style.display = 'none';
      }
    });

    rc.addEventListener(RPET.CURRENT_TIME_CHANGED, () => {
      if (!this.isCasting) return;
      const progressFilled = this.cachedElements.progressFilled as HTMLElement;
      const progressHandle = this.cachedElements.progressHandle as HTMLElement;
      const timeDisplay = this.cachedElements.timeDisplay;
      const duration = this.remotePlayer?.duration || 0;
      const current = Math.max(0, Math.min(this.remotePlayer?.currentTime || 0, duration));
      const percent = duration > 0 ? (current / duration) * 100 : 0;
      if (progressFilled) progressFilled.style.width = percent + '%';
      if (progressHandle) progressHandle.style.left = percent + '%';
      if (timeDisplay) (timeDisplay as HTMLElement).textContent = `${this.formatTime(current)} / ${this.formatTime(duration)}`;
      // Enforce gate while casting
      this.enforceFreePreviewGate(current);
    });

    rc.addEventListener(RPET.DURATION_CHANGED, () => {
      if (!this.isCasting) return;
      const timeDisplay = this.cachedElements.timeDisplay;
      const duration = this.remotePlayer?.duration || 0;
      if (timeDisplay) (timeDisplay as HTMLElement).textContent = `${this.formatTime(this.remotePlayer?.currentTime || 0)} / ${this.formatTime(duration)}`;
    });

    rc.addEventListener(RPET.IS_MUTED_CHANGED, () => {
      if (!this.isCasting) return;
      this.updateVolumeUIFromRemote();
    });

    rc.addEventListener(RPET.VOLUME_LEVEL_CHANGED, () => {
      if (!this.isCasting) return;
      this.updateVolumeUIFromRemote();
    });

    rc.addEventListener(RPET.IS_CONNECTED_CHANGED, () => {
      if (!this.remotePlayer?.isConnected) {
        this.disableCastRemoteControl();
      }
    });
  }

  private updateVolumeUIFromRemote(): void {
    const volumeFill = this.getElement('volume-fill') as HTMLElement;
    const volumeValue = this.getElement('volume-value');
    const volumeIcon = this.getElement('volume-icon');
    const muteIcon = this.getElement('mute-icon');
    const level = Math.round(((this.remotePlayer?.volumeLevel || 0) * 100));
    if (volumeFill) volumeFill.style.width = level + '%';
    if (volumeValue) (volumeValue as HTMLElement).textContent = String(level);
    const isMuted = !!this.remotePlayer?.isMuted || level === 0;
    if (volumeIcon && muteIcon) {
      if (isMuted) {
        (volumeIcon as HTMLElement).style.display = 'none';
        (muteIcon as HTMLElement).style.display = 'block';
      } else {
        (volumeIcon as HTMLElement).style.display = 'block';
        (muteIcon as HTMLElement).style.display = 'none';
      }
    }
  }

  private _syncUIFromRemote(): void {
    const duration = this.remotePlayer?.duration || 0;
    const current = this.remotePlayer?.currentTime || 0;
    const percent = duration > 0 ? (current / duration) * 100 : 0;
    const progressFilled = this.cachedElements.progressFilled as HTMLElement;
    const progressHandle = this.cachedElements.progressHandle as HTMLElement;
    const timeDisplay = this.cachedElements.timeDisplay;
    if (progressFilled) progressFilled.style.width = percent + '%';
    if (progressHandle) progressHandle.style.left = percent + '%';
    if (timeDisplay) (timeDisplay as HTMLElement).textContent = `${this.formatTime(current)} / ${this.formatTime(duration)}`;
    this.updateVolumeUIFromRemote();
    // Also enforce gate in case of immediate resume
    this.enforceFreePreviewGate(current);
  }

  private _syncCastButtons(): void {
    const castBtn = this.getElement('cast-btn');
    const stopBtn = this.getElement('stop-cast-btn');
    const wrapper = this.playerWrapper || this.container?.querySelector('.uvf-player-wrapper');
    if (stopBtn) (stopBtn as HTMLElement).style.display = this.isCasting ? 'inline-flex' : 'none';
    if (castBtn) {
      if (this.isCasting) {
        castBtn.classList.add('cast-grey');
        let title = 'Pick device';
        try {
          const castNs = (window as any).cast;
          const sess = castNs?.framework?.CastContext?.getInstance()?.getCurrentSession?.();
          const dev = sess && sess.getCastDevice ? sess.getCastDevice() : null;
          if (dev && dev.friendlyName) title += ` (${dev.friendlyName})`;
        } catch (_) { }
        castBtn.setAttribute('title', title);
        castBtn.setAttribute('aria-label', title);
      } else {
        castBtn.classList.remove('cast-grey');
        castBtn.setAttribute('title', 'Cast');
        castBtn.setAttribute('aria-label', 'Cast');
      }
    }
    if (wrapper) {
      if (this.isCasting) (wrapper as HTMLElement).classList.add('uvf-casting');
      else (wrapper as HTMLElement).classList.remove('uvf-casting');
    }
  }

  /**
   * Dynamically populate settings menu based on video capabilities
   */
  private updateSettingsMenu(): void {
    this.debugLog('updateSettingsMenu called');
    const settingsMenu = this.cachedElements.settingsMenu;
    if (!settingsMenu) {
      this.debugError('Settings menu element not found!');
      return;
    }

    this.debugLog('Settings menu element found, updating content...');
    // Detect available qualities from video
    this.detectAvailableQualities();
    // Detect available subtitles
    this.detectAvailableSubtitles();

    this.debugLog('Available qualities:', this.availableQualities);
    this.debugLog('Available subtitles:', this.availableSubtitles);
    this.debugLog('Settings config:', this.settingsConfig);

    // Generate accordion-style menu
    this.generateAccordionMenu();
  }

  /**
   * Generate accordion-style settings menu
   */
  private generateAccordionMenu(): void {
    const settingsMenu = this.cachedElements.settingsMenu;
    if (!settingsMenu) return;

    let menuHTML = '<div class="uvf-settings-accordion">';

    // Playback Speed Accordion Section (only if enabled in config)
    if (this.settingsConfig.speed) {
      const currentSpeedLabel = this.currentPlaybackRate === 1 ? 'Normal' : `${this.currentPlaybackRate}x`;
      menuHTML += `
        <div class="uvf-accordion-item">
          <div class="uvf-accordion-header" data-section="speed">
            <div class="uvf-accordion-title">
              <span class="uvf-accordion-icon">
                <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
                  <path d="M12,16A3,3 0 0,1 9,13C9,11.88 9.61,10.9 10.5,10.39L20.21,4.77L14.68,14.35C14.18,15.33 13.17,16 12,16M12,3C13.81,3 15.5,3.5 16.97,4.32L14.87,5.53C14,5.19 13,5 12,5A8,8 0 0,0 4,13C4,15.21 4.89,17.21 6.34,18.65H6.35C6.74,19.04 6.74,19.67 6.35,20.06C5.96,20.45 5.32,20.45 4.93,20.07V20.07C3.12,18.26 2,15.76 2,13A10,10 0 0,1 12,3M22,13C22,15.76 20.88,18.26 19.07,20.07V20.07C18.68,20.45 18.05,20.45 17.66,20.06C17.27,19.67 17.27,19.04 17.66,18.65V18.65C19.11,17.21 20,15.21 20,13C20,12 19.81,11 19.46,10.1L20.67,8C21.5,9.5 22,11.18 22,13Z"/>
                </svg>
              </span>
              <span>Playback Speed</span>
            </div>
            <div class="uvf-accordion-current">${currentSpeedLabel}</div>
            <div class="uvf-accordion-arrow">▼</div>
          </div>
          <div class="uvf-accordion-content" data-section="speed">
            <div class="uvf-settings-option speed-option ${this.currentPlaybackRate === 0.25 ? 'active' : ''}" data-speed="0.25">0.25x</div>
            <div class="uvf-settings-option speed-option ${this.currentPlaybackRate === 0.5 ? 'active' : ''}" data-speed="0.5">0.5x</div>
            <div class="uvf-settings-option speed-option ${this.currentPlaybackRate === 0.75 ? 'active' : ''}" data-speed="0.75">0.75x</div>
            <div class="uvf-settings-option speed-option ${this.currentPlaybackRate === 1 ? 'active' : ''}" data-speed="1">Normal</div>
            <div class="uvf-settings-option speed-option ${this.currentPlaybackRate === 1.25 ? 'active' : ''}" data-speed="1.25">1.25x</div>
            <div class="uvf-settings-option speed-option ${this.currentPlaybackRate === 1.5 ? 'active' : ''}" data-speed="1.5">1.5x</div>
            <div class="uvf-settings-option speed-option ${this.currentPlaybackRate === 1.75 ? 'active' : ''}" data-speed="1.75">1.75x</div>
            <div class="uvf-settings-option speed-option ${this.currentPlaybackRate === 2 ? 'active' : ''}" data-speed="2">2x</div>
          </div>
        </div>`;
    }

    // Quality Accordion Section (only if enabled in config and qualities detected)
    if (this.settingsConfig.quality && this.availableQualities.length > 0) {
      // Fix inconsistent state: if currentQuality is 'auto', autoQuality should be true
      if (this.currentQuality === 'auto' && !this.autoQuality) {
        this.debugLog(`Fixing inconsistent state: currentQuality='auto' but autoQuality=false. Setting autoQuality=true.`);
        this.autoQuality = true;
        this.currentQualityIndex = -1;
      }

      let currentQualityLabel: string;
      this.debugLog(`Building quality menu: currentQuality=${this.currentQuality}, autoQuality=${this.autoQuality}`);
      if (this.currentQuality === 'auto' && this.autoQuality) {
        const playingQuality = this.getCurrentQuality();
        this.debugLog(`Playing quality:`, playingQuality);
        if (playingQuality && playingQuality.label) {
          currentQualityLabel = `Auto (${playingQuality.label})`;
          this.debugLog(`Set quality label to: ${currentQualityLabel}`);
        } else {
          currentQualityLabel = 'Auto';
          this.debugLog(`Playing quality missing or no label, using just 'Auto'`);
        }
      } else {
        const currentQuality = this.availableQualities.find(q => q.value === this.currentQuality);
        currentQualityLabel = currentQuality ? currentQuality.label : 'Auto';
        this.debugLog(`Manual quality mode, label: ${currentQualityLabel}`);
      }

      menuHTML += `
        <div class="uvf-accordion-item">
          <div class="uvf-accordion-header" data-section="quality">
            <div class="uvf-accordion-title">
              <span class="uvf-accordion-icon">
                <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
                  <path d="M3,17V19H9V17H3M3,5V7H13V5H3M13,21V19H21V17H13V15H11V21H13M7,9V11H3V13H7V15H9V9H7M21,13V11H11V13H21M15,9H17V7H21V5H17V3H15V9Z"/>
                </svg>
              </span>
              <span>Quality</span>
            </div>
            <div class="uvf-accordion-current">${currentQualityLabel}</div>
            <div class="uvf-accordion-arrow">▼</div>
          </div>
          <div class="uvf-accordion-content" data-section="quality">`;

      // Loop through available qualities (Auto is already included in availableQualities)
      this.availableQualities.forEach(quality => {
        const isActive = quality.value === this.currentQuality ? 'active' : '';
        const isPremium = this.isQualityPremium(quality);
        const isLocked = isPremium && !this.isPremiumUser();
        const qualityHeight = (quality as any).height || 0;

        if (isLocked) {
          const premiumLabel = this.premiumQualities?.premiumLabel || '🔒 Premium';
          menuHTML += `<div class="uvf-settings-option quality-option premium-locked ${isActive}" data-quality="${quality.value}" data-quality-height="${qualityHeight}" data-quality-label="${quality.label}">${quality.label} <span style="margin-left: 4px; opacity: 0.7; font-size: 11px;">${premiumLabel}</span></div>`;
        } else {
          menuHTML += `<div class="uvf-settings-option quality-option ${isActive}" data-quality="${quality.value}">${quality.label}</div>`;
        }
      });

      menuHTML += `</div></div>`;
    }

    // Subtitles Accordion Section (only if enabled in config and subtitles available)
    if (this.settingsConfig.subtitles && this.availableSubtitles.length > 0) {
      const currentSubtitle = this.availableSubtitles.find(s => s.value === this.currentSubtitle);
      const currentSubtitleLabel = currentSubtitle ? currentSubtitle.label : 'Off';

      menuHTML += `
        <div class="uvf-accordion-item">
          <div class="uvf-accordion-header" data-section="subtitles">
            <div class="uvf-accordion-title">
              <span class="uvf-accordion-icon">
                <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
                  <path d="M18,11H16.5V10.5H14.5V13.5H16.5V13H18V14A1,1 0 0,1 17,15H14A1,1 0 0,1 13,14V10A1,1 0 0,1 14,9H17A1,1 0 0,1 18,10M11,11H9.5V10.5H7.5V13.5H9.5V13H11V14A1,1 0 0,1 10,15H7A1,1 0 0,1 6,14V10A1,1 0 0,1 7,9H10A1,1 0 0,1 11,10M19,4H5C3.89,4 3,4.89 3,6V18A2,2 0 0,0 5,20H19A2,2 0 0,0 21,18V6C21,4.89 20.11,4 19,4Z"/>
                </svg>
              </span>
              <span>Subtitles</span>
            </div>
            <div class="uvf-accordion-current">${currentSubtitleLabel}</div>
            <div class="uvf-accordion-arrow">▼</div>
          </div>
          <div class="uvf-accordion-content" data-section="subtitles">`;

      this.availableSubtitles.forEach(subtitle => {
        const isActive = subtitle.value === this.currentSubtitle ? 'active' : '';
        menuHTML += `<div class="uvf-settings-option subtitle-option ${isActive}" data-subtitle="${subtitle.value}">${subtitle.label}</div>`;
      });

      menuHTML += `</div></div>`;
    }

    // Close accordion container
    menuHTML += '</div>';

    // If no sections are enabled or available, show a message
    if (menuHTML === '<div class="uvf-settings-accordion"></div>') {
      menuHTML = '<div class="uvf-settings-accordion"><div class="uvf-settings-empty">No settings available</div></div>';
    }

    this.debugLog('Generated menu HTML length:', menuHTML.length);
    this.debugLog('Generated menu HTML content:', menuHTML.substring(0, 200) + (menuHTML.length > 200 ? '...' : ''));

    settingsMenu.innerHTML = menuHTML;
    this.debugLog('Settings menu HTML set successfully');

    // Add event listeners for settings options
    this.setupSettingsEventListeners();
    this.debugLog('Settings event listeners setup complete');
  }

  /**
   * Detect available video qualities from different sources
   */
  private detectAvailableQualities(): void {
    // Only add "Auto" option if autoQuality is enabled
    this.availableQualities = this.autoQuality ? [{ value: 'auto', label: 'Auto' }] : [];
    let detectedQualities: Array<{ value: string; label: string; height?: number }> = [];

    if (this.hls && this.hls.levels) {
      // HLS qualities
      this.hls.levels.forEach((level: any, index: number) => {
        if (level.height) {
          detectedQualities.push({
            value: index.toString(),
            label: `${level.height}p`,
            height: level.height
          });
        }
      });
    } else if (this.dash && this.dash.getBitrateInfoListFor) {
      // DASH qualities
      try {
        const videoQualities = this.dash.getBitrateInfoListFor('video');
        videoQualities.forEach((quality: any, index: number) => {
          if (quality.height) {
            detectedQualities.push({
              value: index.toString(),
              label: `${quality.height}p`,
              height: quality.height
            });
          }
        });
      } catch (e) {
        this.debugError('Error detecting DASH qualities:', e);
      }
    } else if (this.youtubePlayer && this.youtubePlayerReady) {
      // YouTube qualities
      try {
        const levels = this.youtubePlayer.getAvailableQualityLevels();
        levels.forEach((level: string) => {
          if (level === 'auto' || level === 'default') return;
          const height = this.getYouTubeHeight(level);
          if (height) {
            detectedQualities.push({
              value: level,
              label: `${height}p`,
              height: height
            });
          }
        });
        // Sort by height descending
        detectedQualities.sort((a, b) => (b.height || 0) - (a.height || 0));
      } catch (e) {
        this.debugError('Error detecting YouTube qualities:', e);
      }
    } else if (this.video?.videoHeight) {
      // Native video - add common resolutions based on current resolution
      const height = this.video.videoHeight;
      const commonQualities = [2160, 1440, 1080, 720, 480, 360];

      commonQualities.forEach(quality => {
        if (quality <= height) {
          detectedQualities.push({
            value: quality.toString(),
            label: `${quality}p`,
            height: quality
          });
        }
      });
    }

    // Apply quality filter if configured
    if (this.qualityFilter) {
      detectedQualities = this.applyQualityFilter(detectedQualities);
    }

    // Add filtered qualities to available list
    this.availableQualities.push(...detectedQualities);
  }

  /**
   * Apply quality filter to detected qualities
   */
  private applyQualityFilter(qualities: Array<{ value: string; label: string; height?: number }>): Array<{ value: string; label: string; height?: number }> {
    let filtered = [...qualities];
    const filter = this.qualityFilter;

    // Filter by allowed heights
    if (filter.allowedHeights && filter.allowedHeights.length > 0) {
      filtered = filtered.filter(q => q.height && filter.allowedHeights.includes(q.height));
    }

    // Filter by allowed labels
    if (filter.allowedLabels && filter.allowedLabels.length > 0) {
      filtered = filtered.filter(q => filter.allowedLabels.includes(q.label));
    }

    // Filter by minimum height
    if (filter.minHeight !== undefined) {
      filtered = filtered.filter(q => q.height && q.height >= filter.minHeight);
    }

    // Filter by maximum height
    if (filter.maxHeight !== undefined) {
      filtered = filtered.filter(q => q.height && q.height <= filter.maxHeight);
    }

    return filtered;
  }

  /**
   * Set quality filter (can be called at runtime)
   */
  public setQualityFilter(filter: any): void {
    this.qualityFilter = filter;
    // Refresh settings menu to apply the filter
    if (this.useCustomControls) {
      this.updateSettingsMenu();
    }
  }

  /**
   * Set ad playing state (called by Google Ads Manager)
   */
  public setAdPlaying(isPlaying: boolean): void {
    this.isAdPlaying = isPlaying;
    this.debugLog('Ad playing state:', isPlaying);

    if (isPlaying) {
      // Hide spinner while ad is playing — video is intentionally paused
      const loading = this.cachedElements.loading;
      if (loading) loading.classList.remove('active');
    }
  }

  /**
   * Check if ad is currently playing
   */
  public isAdCurrentlyPlaying(): boolean {
    return this.isAdPlaying;
  }

  /**
   * Check if a quality level is premium
   */
  private isQualityPremium(quality: any): boolean {
    if (!this.premiumQualities || !this.premiumQualities.enabled) {
      return false;
    }

    const config = this.premiumQualities;
    const height = quality.height || 0;
    const label = quality.label || '';

    // Check by specific heights
    if (config.requiredHeights && config.requiredHeights.length > 0) {
      if (config.requiredHeights.includes(height)) return true;
    }

    // Check by specific labels
    if (config.requiredLabels && config.requiredLabels.length > 0) {
      if (config.requiredLabels.includes(label)) return true;
    }

    // Check by minimum height threshold
    if (config.minPremiumHeight !== undefined) {
      if (height >= config.minPremiumHeight) return true;
    }

    return false;
  }

  /**
   * Check if current user is premium
   */
  private isPremiumUser(): boolean {
    return this.premiumQualities?.isPremiumUser === true;
  }

  /**
   * Handle click on locked premium quality
   */
  private handlePremiumQualityClick(element: HTMLElement): void {
    const height = parseInt(element.dataset.qualityHeight || '0');
    const label = element.dataset.qualityLabel || '';

    this.debugLog(`Premium quality clicked: ${label} (${height}p)`);

    // Call custom callback if provided
    if (this.premiumQualities?.onPremiumQualityClick) {
      this.premiumQualities.onPremiumQualityClick({ height, label });
    }

    // Show notification
    const message = this.premiumQualities?.premiumLabel || 'Premium';
    this.showNotification(`${label} requires ${message.replace('🔒 ', '')}`);

    // Redirect to unlock URL if provided
    if (this.premiumQualities?.unlockUrl) {
      setTimeout(() => {
        window.location.href = this.premiumQualities.unlockUrl;
      }, 1500);
    }

    // Close settings menu
    this.hideSettingsMenu();
  }

  /**
   * Detect available subtitle tracks
   */
  private detectAvailableSubtitles(): void {
    this.availableSubtitles = [{ value: 'off', label: 'Off' }];

    // YouTube player - subtitles available but not fully controllable
    if (this.youtubePlayer && this.youtubePlayerReady) {
      // YouTube supports captions but control is limited
      // We can only toggle on/off, not select language
      this.availableSubtitles.push({
        value: 'youtube-cc',
        label: 'YouTube Captions'
      });
      return;
    }

    if (this.video?.textTracks) {
      // HTML5 text tracks
      Array.from(this.video.textTracks).forEach((track, index) => {
        if (track.kind === 'subtitles' || track.kind === 'captions') {
          this.availableSubtitles.push({
            value: index.toString(),
            label: track.label || track.language || `Track ${index + 1}`
          });
        }
      });
    }

    // HLS subtitles
    if (this.hls && this.hls.subtitleTracks) {
      this.hls.subtitleTracks.forEach((track: any, index: number) => {
        this.availableSubtitles.push({
          value: `hls-${index}`,
          label: track.name || track.lang || `Track ${index + 1}`
        });
      });
    }
  }

  /**
   * Setup event listeners for accordion-style settings menu
   */
  private setupSettingsEventListeners(): void {
    const settingsMenu = this.cachedElements.settingsMenu;
    if (!settingsMenu) return;

    // Accordion header click handlers
    settingsMenu.querySelectorAll('.uvf-accordion-header').forEach(header => {
      header.addEventListener('click', (e) => {
        e.preventDefault();
        e.stopPropagation();

        const accordionItem = header.parentElement;
        const section = header.getAttribute('data-section');

        if (accordionItem && section) {
          this.toggleAccordionSection(accordionItem, section);
        }
      });
    });

    // Speed options
    settingsMenu.querySelectorAll('.speed-option').forEach(option => {
      option.addEventListener('click', (e) => {
        e.stopPropagation();
        const speed = parseFloat((e.target as HTMLElement).dataset.speed || '1');
        this.setPlaybackRateFromSettings(speed);
        this.updateAccordionAfterSelection('speed');
      });
    });

    // Quality options
    settingsMenu.querySelectorAll('.quality-option').forEach(option => {
      option.addEventListener('click', (e) => {
        e.stopPropagation();
        const target = e.target as HTMLElement;
        const quality = target.dataset.quality || 'auto';

        // Check if this is a locked premium quality
        if (target.classList.contains('premium-locked')) {
          this.handlePremiumQualityClick(target);
          return;
        }

        this.setQualityFromSettings(quality);
        this.updateAccordionAfterSelection('quality');
      });
    });

    // Subtitle options
    settingsMenu.querySelectorAll('.subtitle-option').forEach(option => {
      option.addEventListener('click', (e) => {
        e.stopPropagation();
        const subtitle = (e.target as HTMLElement).dataset.subtitle || 'off';
        this.setSubtitle(subtitle);
        this.updateAccordionAfterSelection('subtitles');
      });
    });
  }

  /**
   * Toggle accordion section
   */
  private toggleAccordionSection(accordionItem: Element, section: string): void {
    const isExpanded = accordionItem.classList.contains('expanded');

    // If clicking the same section that's already expanded, just close it
    if (isExpanded) {
      accordionItem.classList.remove('expanded');
      return;
    }

    // Otherwise, close all sections and open the clicked one
    const settingsMenu = this.cachedElements.settingsMenu;
    if (settingsMenu) {
      settingsMenu.querySelectorAll('.uvf-accordion-item.expanded').forEach(item => {
        item.classList.remove('expanded');
      });
    }

    // Open the clicked section
    accordionItem.classList.add('expanded');
  }

  /**
   * Hide settings menu with proper styling
   */
  private hideSettingsMenu(): void {
    const settingsMenu = this.cachedElements.settingsMenu;
    if (!settingsMenu) return;

    settingsMenu.classList.remove('active');

    // Apply fallback styles to ensure menu is hidden
    settingsMenu.style.display = 'none';
    settingsMenu.style.visibility = 'hidden';
    settingsMenu.style.opacity = '0';

    // Also close any expanded accordions
    settingsMenu.querySelectorAll('.uvf-accordion-item.expanded').forEach(item => {
      item.classList.remove('expanded');
    });

    this.debugLog('Settings menu hidden via hideSettingsMenu()');
  }

  /**
   * Update accordion after user makes a selection
   */
  private updateAccordionAfterSelection(section: string): void {
    // Close the accordion section after selection
    const settingsMenu = this.cachedElements.settingsMenu;
    if (settingsMenu) {
      settingsMenu.querySelectorAll('.uvf-accordion-item.expanded').forEach(item => {
        item.classList.remove('expanded');
      });
    }

    // Close settings menu after selection on all devices
    setTimeout(() => {
      this.hideSettingsMenu();
    }, 200);
  }

  /**
   * Update active states in settings menu
   */
  private updateSettingsActiveStates(className: string, activeElement: HTMLElement): void {
    const settingsMenu = this.cachedElements.settingsMenu;
    if (!settingsMenu) return;

    // Remove active class from all options of this type
    settingsMenu.querySelectorAll(`.${className}`).forEach(option => {
      option.classList.remove('active');
    });

    // Add active class to selected option
    activeElement.classList.add('active');
  }

  /**
   * Set playback rate for settings menu
   */
  private setPlaybackRateFromSettings(rate: number): void {
    this.currentPlaybackRate = rate;
    if (this.youtubePlayer && this.youtubePlayerReady) {
      this.youtubePlayer.setPlaybackRate(rate);
    } else if (this.video) {
      this.video.playbackRate = rate;
    }
    this.debugLog(`Playback rate set to ${rate}x`);
  }

  /**
   * Set video quality for settings menu
   */
  private setQualityFromSettings(quality: string): void {
    this.currentQuality = quality;

    if (this.youtubePlayer && this.youtubePlayerReady) {
      this.youtubePlayer.setPlaybackQuality(quality);
      this.debugLog(`YouTube quality set to ${quality}`);
      return;
    }

    if (quality === 'auto') {
      // Enable auto quality mode
      this.autoQuality = true;
      this.currentQualityIndex = -1;

      // Enable auto quality with filter consideration
      if (this.hls) {
        if (this.qualityFilter) {
          // If filter is set, apply it to HLS auto quality
          this.applyHLSQualityFilter();
        } else {
          // No filter - use all levels
          this.hls.currentLevel = -1; // Auto
        }
      } else if (this.dash) {
        this.dash.setAutoSwitchQualityFor('video', true);
        if (this.qualityFilter) {
          // If filter is set, apply it to DASH auto quality
          this.applyDASHQualityFilter();
        }
      }

      // Update UI to show "Auto (currentQuality)"
      this.updateQualityBadgeText();
      this.updateQualityLabel();
    } else {
      // Set specific quality - disable auto mode
      this.autoQuality = false;
      const qualityIndex = parseInt(quality);

      if (this.hls && !isNaN(qualityIndex) && this.hls.levels[qualityIndex]) {
        this.hls.currentLevel = qualityIndex;
        this.currentQualityIndex = qualityIndex;
      } else if (this.dash && !isNaN(qualityIndex)) {
        this.dash.setAutoSwitchQualityFor('video', false);
        this.dash.setQualityFor('video', qualityIndex);
        this.currentQualityIndex = qualityIndex;
      }

      // Update UI to show specific quality
      this.updateQualityBadgeText();
      this.updateQualityLabel();
    }

    this.debugLog(`Quality set to ${quality}, autoQuality=${this.autoQuality}`);
  }

  /**
   * Apply quality filter to HLS auto quality selection
   */
  private applyHLSQualityFilter(): void {
    if (!this.hls || !this.hls.levels) return;

    const allowedLevels: number[] = [];

    this.hls.levels.forEach((level: any, index: number) => {
      let allowed = true;

      // Apply quality filter if exists
      if (this.qualityFilter) {
        const filter = this.qualityFilter;

        if (filter.allowedHeights && filter.allowedHeights.length > 0) {
          allowed = allowed && filter.allowedHeights.includes(level.height);
        }

        if (filter.minHeight !== undefined) {
          allowed = allowed && level.height >= filter.minHeight;
        }

        if (filter.maxHeight !== undefined) {
          allowed = allowed && level.height <= filter.maxHeight;
        }
      }

      // Apply premium quality restrictions for non-premium users
      if (this.premiumQualities && this.premiumQualities.enabled && !this.isPremiumUser()) {
        const isPremium = this.isQualityPremium({ height: level.height, label: `${level.height}p` });
        if (isPremium) {
          allowed = false;  // Exclude premium qualities for non-premium users
        }
      }

      if (allowed) {
        allowedLevels.push(index);
      }
    });

    // Set max and min auto level based on filter and premium restrictions
    if (allowedLevels.length > 0) {
      // Set the maximum level cap
      this.hls.autoLevelCapping = Math.max(...allowedLevels);

      // Enable auto quality
      this.hls.currentLevel = -1;

      // For minimum level, we need to use a different approach
      // Monitor level changes and prevent switching below minimum
      const minLevel = Math.min(...allowedLevels);

      // Set up level switching listener to enforce minimum
      const enforceLevelConstraints = () => {
        if (this.hls && this.hls.currentLevel !== -1 && this.hls.currentLevel < minLevel) {
          this.debugLog(`Quality below minimum (${this.hls.currentLevel} < ${minLevel}), switching to ${minLevel}`);
          this.hls.currentLevel = minLevel;
        }
      };

      // Remove old listener if exists
      if (this.hls.listeners('hlsLevelSwitching').length > 0) {
        this.hls.off('hlsLevelSwitching', enforceLevelConstraints);
      }

      // Add listener to enforce constraints
      this.hls.on('hlsLevelSwitching', enforceLevelConstraints);

      this.debugLog(`Auto quality limited to levels: ${allowedLevels.join(', ')} (min: ${minLevel}, max: ${Math.max(...allowedLevels)})`);
    } else {
      // No allowed levels - just use auto
      this.hls.currentLevel = -1;
    }
  }

  /**
   * Apply quality filter to DASH auto quality selection
   */
  private applyDASHQualityFilter(): void {
    if (!this.dash) return;

    try {
      const videoQualities = this.dash.getBitrateInfoListFor('video');
      const allowedIndices: number[] = [];

      videoQualities.forEach((quality: any, index: number) => {
        let allowed = true;

        // Apply quality filter if exists
        if (this.qualityFilter) {
          const filter = this.qualityFilter;

          if (filter.allowedHeights && filter.allowedHeights.length > 0) {
            allowed = allowed && filter.allowedHeights.includes(quality.height);
          }

          if (filter.minHeight !== undefined) {
            allowed = allowed && quality.height >= filter.minHeight;
          }

          if (filter.maxHeight !== undefined) {
            allowed = allowed && quality.height <= filter.maxHeight;
          }
        }

        // Apply premium quality restrictions for non-premium users
        if (this.premiumQualities && this.premiumQualities.enabled && !this.isPremiumUser()) {
          const isPremium = this.isQualityPremium({ height: quality.height, label: `${quality.height}p` });
          if (isPremium) {
            allowed = false;  // Exclude premium qualities for non-premium users
          }
        }

        if (allowed) {
          allowedIndices.push(index);
        }
      });

      // Set quality bounds for DASH
      if (allowedIndices.length > 0) {
        const settings = {
          streaming: {
            abr: {
              limitBitrateByPortal: false,
              maxBitrate: { video: videoQualities[Math.max(...allowedIndices)].bitrate },
              minBitrate: { video: videoQualities[Math.min(...allowedIndices)].bitrate },
            }
          }
        };
        this.dash.updateSettings(settings);
        this.debugLog(`Auto quality limited to DASH indices: ${allowedIndices.join(', ')}`);
      }
    } catch (e) {
      this.debugError('Error applying DASH quality filter:', e);
    }
  }

  /**
   * Set subtitle track
   */
  private setSubtitle(subtitle: string): void {
    this.currentSubtitle = subtitle;

    if (this.youtubePlayer && this.youtubePlayerReady) {
      // YouTube subtitles - limited control
      if (subtitle === 'off') {
        try {
          this.youtubePlayer.setOption('captions', 'fontSize', -1); // Hide captions
        } catch (e) {
          this.debugWarn('YouTube caption control limited:', e);
        }
      } else if (subtitle === 'youtube-cc') {
        try {
          this.youtubePlayer.setOption('captions', 'fontSize', 0); // Show captions
        } catch (e) {
          this.debugWarn('YouTube caption control limited:', e);
        }
      }
      this.showShortcutIndicator('Subtitle language selection not available for YouTube');
      return;
    }

    if (subtitle === 'off') {
      // Disable all subtitles
      this.currentSubtitleIndex = -1;
      if (this.video?.textTracks) {
        Array.from(this.video.textTracks).forEach(track => {
          track.mode = 'disabled';
        });
      }
      if (this.hls) {
        this.hls.subtitleTrack = -1;
      }
      // Clear the overlay immediately so no stale cue remains on screen
      if (this.subtitleOverlay) {
        this.subtitleOverlay.innerHTML = '';
        this.subtitleOverlay.style.display = 'none';
      }
    } else if (subtitle.startsWith('hls-')) {
      // HLS subtitle
      const index = parseInt(subtitle.replace('hls-', ''), 10);
      if (this.hls && !isNaN(index)) {
        this.hls.subtitleTrack = index;
        this.currentSubtitleIndex = index;
      }
    } else {
      // HTML5 text track — use 'hidden' so cuechange events fire but native rendering is suppressed
      const trackIndex = parseInt(subtitle, 10);
      if (this.video?.textTracks && !isNaN(trackIndex)) {
        Array.from(this.video.textTracks).forEach((track, index) => {
          track.mode = index === trackIndex ? 'hidden' : 'disabled';
        });
        this.currentSubtitleIndex = trackIndex;
        this.updateSubtitleOverlay();
      }
    }

    this.debugLog(`Subtitle set to ${subtitle}`);
  }

  private _updateCastActiveTracks(): void {
    try {
      const castNs = (window as any).cast;
      if (!castNs || !castNs.framework) return;
      const session = castNs.framework.CastContext.getInstance().getCurrentSession();
      if (!session) return;
      const media = session.getMediaSession && session.getMediaSession();
      if (!media) return;
      let ids: number[] = [];
      if (this.selectedSubtitleKey && this.selectedSubtitleKey !== 'off') {
        const tid = this._castTrackIdByKey ? this._castTrackIdByKey[this.selectedSubtitleKey] : null;
        if (tid) ids = [tid];
      }
      if (typeof media.setActiveTracks === 'function') {
        media.setActiveTracks(ids, () => { }, () => { });
      } else if (typeof media.setActiveTrackIds === 'function') {
        media.setActiveTrackIds(ids);
      }
    } catch (_) { }
  }

  private onCastButtonClick(): void {
    // On iOS, use AirPlay instead of Google Cast
    if (this.isIOSDevice()) {
      this.showAirPlayPicker();
      return;
    }

    // Google Cast for non-iOS devices
    try {
      const castNs = (window as any).cast;
      if (this.isCasting && castNs && castNs.framework) {
        const ctx = castNs.framework.CastContext.getInstance();
        ctx.requestSession().catch(() => { });
        return;
      }
    } catch (_) { }
    // Not casting yet
    this.initCast();
  }

  /**
   * Show AirPlay picker for iOS devices
   */
  private showAirPlayPicker(): void {
    if (!this.video) {
      this.showNotification('Video not ready');
      return;
    }

    // Check if AirPlay is supported
    const videoElement = this.video as any;
    if (typeof videoElement.webkitShowPlaybackTargetPicker === 'function') {
      try {
        videoElement.webkitShowPlaybackTargetPicker();
        this.debugLog('AirPlay picker shown');
      } catch (error) {
        this.debugWarn('Failed to show AirPlay picker:', (error as Error).message);
        this.showNotification('AirPlay not available');
      }
    } else {
      this.debugWarn('AirPlay not supported on this device');
      this.showNotification('AirPlay not supported');
    }
  }

  private stopCasting(): void {
    try {
      const castNs = (window as any).cast;
      if (!castNs || !castNs.framework) { this.showNotification('Cast not ready'); return; }
      const ctx = castNs.framework.CastContext.getInstance();
      const sess = ctx.getCurrentSession && ctx.getCurrentSession();
      if (sess) {
        try { sess.endSession(true); } catch (_) { }
        this.disableCastRemoteControl();
        this.showNotification('Stopped casting');
      } else {
        this.showNotification('Not casting');
      }
    } catch (_) {
      // ignore
    } finally {
      this._syncCastButtons();
    }
  }

  private async initCast(): Promise<void> {
    try {
      let castNs = (window as any).cast;
      if (!castNs || !castNs.framework) {
        await this.loadScript('https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1');
        // wait briefly for framework
        const start = Date.now();
        while ((!((window as any).cast && (window as any).cast.framework)) && Date.now() - start < 3000) {
          await new Promise(r => setTimeout(r, 100));
        }
        castNs = (window as any).cast;
      }
      if (!(castNs && castNs.framework)) {
        this.showNotification('Cast framework not ready');
        return;
      }

      this.setupCastContext();
      const ctx = castNs.framework.CastContext.getInstance();
      await ctx.requestSession();
      const session = ctx.getCurrentSession();
      if (!session) { this.showNotification('No cast session'); return; }

      const url = this.source?.url || this.video?.src || '';
      const u = (url || '').toLowerCase();
      const contentType = u.includes('.m3u8') ? 'application/x-mpegurl'
        : u.includes('.mpd') ? 'application/dash+xml'
          : u.includes('.webm') ? 'video/webm'
            : 'video/mp4';

      const chromeNs = (window as any).chrome;
      const mediaInfo = new chromeNs.cast.media.MediaInfo(url, contentType);
      mediaInfo.streamType = chromeNs.cast.media.StreamType.BUFFERED;
      try {
        const md = new chromeNs.cast.media.GenericMediaMetadata();
        md.title = this.source?.metadata?.title || (this.video?.currentSrc ? this.video!.currentSrc.split('/').slice(-1)[0] : 'Web Player');
        mediaInfo.metadata = md;
      } catch (_) { }

      // Subtitle tracks -> Cast tracks mapping
      const castTracks: any[] = [];
      this._castTrackIdByKey = {};
      const inferTextTrackContentType = (trackUrl: string) => {
        // Strip query-string and fragment before checking extension so that
        // signed CDN URLs (e.g. subtitle.vtt?token=abc) are detected correctly.
        const lu = (trackUrl || '').split('?')[0].split('#')[0].toLowerCase();
        if (lu.endsWith('.vtt')) return 'text/vtt';
        if (lu.endsWith('.srt')) return 'text/vtt'; // SRT is pre-converted to VTT blob by loadSubtitles
        if (lu.endsWith('.ttml') || lu.endsWith('.dfxp') || lu.endsWith('.xml')) return 'application/ttml+xml';
        return 'text/vtt';
      };
      if (Array.isArray(this.subtitles) && this.subtitles.length > 0) {
        let nextId = 1;
        for (let i = 0; i < this.subtitles.length; i++) {
          const t = this.subtitles[i];
          const key = t.label || t.language || `Track ${i + 1}`;
          try {
            const track = new chromeNs.cast.media.Track(nextId, chromeNs.cast.media.TrackType.TEXT);
            track.trackContentId = t.url;
            track.trackContentType = inferTextTrackContentType(t.url);
            track.subtype = chromeNs.cast.media.TextTrackType.SUBTITLES;
            track.name = key;
            track.language = t.language || '';
            track.customData = null;
            castTracks.push(track);
            this._castTrackIdByKey[key] = nextId;
            nextId++;
          } catch (_) { }
        }
      }
      if (castTracks.length > 0) {
        mediaInfo.tracks = castTracks;
        try {
          const style = new chromeNs.cast.media.TextTrackStyle();
          style.backgroundColor = '#00000000';
          style.foregroundColor = '#FFFFFFFF';
          style.edgeType = chromeNs.cast.media.TextTrackEdgeType.DROP_SHADOW;
          style.edgeColor = '#000000FF';
          style.fontScale = 1.0;
          mediaInfo.textTrackStyle = style;
        } catch (_) { }
      }

      const request = new chromeNs.cast.media.LoadRequest(mediaInfo);
      request.autoplay = true;
      try { request.currentTime = Math.max(0, Math.floor(this.video?.currentTime || 0)); } catch (_) { }
      // Determine selected subtitle key from currentSubtitleIndex
      const currentIdx = this.currentSubtitleIndex;
      this.selectedSubtitleKey = (currentIdx >= 0 && this.subtitles[currentIdx]) ? (this.subtitles[currentIdx].label || this.subtitles[currentIdx].language) : 'off';
      if (this.selectedSubtitleKey && this.selectedSubtitleKey !== 'off') {
        const tid = this._castTrackIdByKey[this.selectedSubtitleKey];
        if (tid) request.activeTrackIds = [tid];
      }

      await session.loadMedia(request);
      this.enableCastRemoteControl();
      this.showNotification('Casting to device');
    } catch (err) {
      if (this.config.debug) console.error('[Cast] init cast failed:', err);
      this.showNotification('Cast failed');
    }
  }

  private async shareVideo(): Promise<void> {
    // Get share configuration
    const shareConfig = this.config.share;

    // Determine share URL
    let shareUrl: string;
    if (shareConfig?.url) {
      // Use custom static URL
      shareUrl = shareConfig.url;
    } else if (shareConfig?.generateUrl) {
      // Use dynamic URL generator
      try {
        shareUrl = shareConfig.generateUrl({
          videoId: this.source?.metadata?.videoId,
          metadata: this.source?.metadata
        });
      } catch (error) {
        console.warn('Share URL generator failed, falling back to window.location.href:', error);
        shareUrl = window.location.href;
      }
    } else {
      // Default: Use current page URL
      shareUrl = window.location.href;
    }

    // Prepare share data
    const shareData: ShareData = { url: shareUrl };

    // Get title and text from config or metadata
    const t = (shareConfig?.title || this.source?.metadata?.title || '').toString().trim();
    const d = (shareConfig?.text || this.source?.metadata?.description || '').toString().trim();
    if (t) shareData.title = t;
    if (d) shareData.text = d;

    try {
      if (navigator.share) {
        await navigator.share(shareData);
      } else {
        // Fallback: Copy to clipboard
        await navigator.clipboard.writeText(shareUrl);
        this.showNotification('Link copied to clipboard');
      }
    } catch (error) {
      console.error('Share failed:', error);
      this.showNotification('Share failed');
    }
  }

  /**
   * Check if text is truncated and needs tooltip
   */
  private isTextTruncated(element: HTMLElement): boolean {
    return element.scrollWidth > element.offsetWidth || element.scrollHeight > element.offsetHeight;
  }

  /**
   * Show tooltip for truncated text
   */
  private showTextTooltip(element: HTMLElement, text: string): void {
    // Remove existing tooltip
    const existingTooltip = element.querySelector('.uvf-text-tooltip');
    if (existingTooltip) {
      existingTooltip.remove();
    }

    // Create tooltip
    const tooltip = document.createElement('div');
    tooltip.className = 'uvf-text-tooltip';
    tooltip.textContent = text;

    element.appendChild(tooltip);

    // Show tooltip with delay
    setTimeout(() => {
      tooltip.classList.add('show');
    }, 100);
  }

  /**
   * Hide tooltip
   */
  private hideTextTooltip(element: HTMLElement): void {
    const tooltip = element.querySelector('.uvf-text-tooltip');
    if (tooltip) {
      tooltip.classList.remove('show');
      setTimeout(() => {
        if (tooltip.parentElement) {
          tooltip.remove();
        }
      }, 300);
    }
  }

  /**
   * Setup tooltip handlers for title and description
   */
  private setupTextTooltips(): void {
    const titleElement = this.getElement('video-title');
    const descElement = this.getElement('video-description');

    if (titleElement) {
      titleElement.addEventListener('mouseenter', () => {
        const titleText = (this.source?.metadata?.title || '').toString().trim();
        if (this.isTextTruncated(titleElement) && titleText) {
          this.showTextTooltip(titleElement, titleText);
        }
      });

      titleElement.addEventListener('mouseleave', () => {
        this.hideTextTooltip(titleElement);
      });

      // Touch support for mobile
      titleElement.addEventListener('touchstart', () => {
        const titleText = (this.source?.metadata?.title || '').toString().trim();
        if (this.isTextTruncated(titleElement) && titleText) {
          this.showTextTooltip(titleElement, titleText);
          // Auto-hide after 3 seconds on touch
          setTimeout(() => {
            this.hideTextTooltip(titleElement);
          }, 3000);
        }
      });
    }

    if (descElement) {
      descElement.addEventListener('mouseenter', () => {
        const descText = (this.source?.metadata?.description || '').toString().trim();
        if (this.isTextTruncated(descElement) && descText) {
          this.showTextTooltip(descElement, descText);
        }
      });

      descElement.addEventListener('mouseleave', () => {
        this.hideTextTooltip(descElement);
      });

      // Touch support for mobile
      descElement.addEventListener('touchstart', () => {
        const descText = (this.source?.metadata?.description || '').toString().trim();
        if (this.isTextTruncated(descElement) && descText) {
          this.showTextTooltip(descElement, descText);
          // Auto-hide after 3 seconds on touch
          setTimeout(() => {
            this.hideTextTooltip(descElement);
          }, 3000);
        }
      });
    }
  }

  /**
   * Smart text truncation based on word count
   */
  private smartTruncateText(text: string, maxWords: number = 12): { truncated: string, needsTooltip: boolean } {
    const words = text.split(' ');
    if (words.length <= maxWords) {
      return { truncated: text, needsTooltip: false };
    }

    const truncated = words.slice(0, maxWords).join(' ') + '...';
    return { truncated, needsTooltip: true };
  }

  /**
   * Apply smart text display based on screen size and content length
   */
  private applySmartTextDisplay(titleEl: HTMLElement | null, descEl: HTMLElement | null, titleText: string, descText: string): void {
    const isDesktop = window.innerWidth >= 1024;
    const isMobile = window.innerWidth < 768;

    if (titleEl && titleText) {
      const wordCount = titleText.split(' ').length;

      if (isDesktop && wordCount > 8 && wordCount <= 15) {
        // Use multiline for moderately long titles on desktop
        titleEl.classList.add('multiline');
        titleEl.textContent = titleText;
      } else if (wordCount > 12) {
        // Smart truncation for very long titles
        const maxWords = isMobile ? 8 : isDesktop ? 12 : 10;
        const { truncated } = this.smartTruncateText(titleText, maxWords);
        titleEl.textContent = truncated;
        titleEl.classList.remove('multiline');
      } else {
        titleEl.textContent = titleText;
        titleEl.classList.remove('multiline');
      }
    }

    if (descEl && descText) {
      const wordCount = descText.split(' ').length;

      if (isDesktop && wordCount > 15 && wordCount <= 25) {
        // Use multiline for moderately long descriptions on desktop
        descEl.classList.add('multiline');
        descEl.textContent = descText;
      } else if (wordCount > 20) {
        // Smart truncation for very long descriptions
        const maxWords = isMobile ? 12 : isDesktop ? 18 : 15;
        const { truncated } = this.smartTruncateText(descText, maxWords);
        descEl.textContent = truncated;
        descEl.classList.remove('multiline');
      } else {
        descEl.textContent = descText;
        descEl.classList.remove('multiline');
      }
    }
  }

  private updateMetadataUI(): void {
    try {
      const md = this.source?.metadata || ({} as any);
      const titleBar = (this.container?.querySelector('.uvf-title-bar') as HTMLElement) || null;
      const titleEl = this.getElement('video-title') as HTMLElement | null;
      const descEl = this.getElement('video-description') as HTMLElement | null;
      const thumbEl = this.getElement('video-thumb') as HTMLImageElement | null;

      const titleText = (md.title || '').toString().trim();
      const descText = (md.description || '').toString().trim();
      const thumbUrl = (md.thumbnailUrl || '').toString().trim();

      // Apply smart text display with truncation and multiline support
      this.applySmartTextDisplay(titleEl, descEl, titleText, descText);

      // Show/hide elements
      if (titleEl) {
        titleEl.style.display = titleText ? 'block' : 'none';
      }
      if (descEl) {
        descEl.style.display = descText ? 'block' : 'none';
      }

      // Thumbnail (removed from layout but keeping for compatibility)
      if (thumbEl) {
        thumbEl.style.display = 'none'; // Always hidden in new layout
      }

      // Hide entire title bar if nothing to show
      const hasAny = !!(titleText || descText);
      if (titleBar) {
        titleBar.style.display = hasAny ? '' : 'none';
      }

      // Setup tooltips for truncated text
      setTimeout(() => {
        this.setupTextTooltips();
      }, 100); // Small delay to ensure elements are rendered

    } catch (_) { /* ignore */ }
  }

  private showNotification(message: string): void {
    // Use the shortcut indicator for notifications
    this.showShortcutIndicator(message);
  }

  /**
   * Security check to determine if video can be played
   */
  private canPlayVideo(): boolean {
    // CRITICAL: Block playback if paywall is currently active
    if (this.isPaywallActive) {
      this.debugWarn('🔒 Playback blocked: Paywall is active');
      return false;
    }

    const freeDuration = Number(this.config.freeDuration || 0);
    const currentTime = this.video?.currentTime || 0;

    // Always allow if no free duration limit is set
    if (freeDuration <= 0) return true;

    // Allow if payment was successful
    if (this.paymentSuccessful) return true;

    // Block if free preview gate was hit
    if (this.previewGateHit) {
      this.debugWarn('🔒 Playback blocked: Free preview gate hit');
      return false;
    }

    // Allow if within free preview duration
    if (currentTime < freeDuration) return true;

    // Check if paywall controller indicates user is authenticated
    if (this.paywallController &&
      typeof this.paywallController.isAuthenticated === 'function') {
      const isAuth = this.paywallController.isAuthenticated();
      if (isAuth) {
        this.paymentSuccessful = true;
        return true;
      }
    }

    return false;
  }

  /**
   * Enforce paywall security when bypass attempts are detected
   */
  private enforcePaywallSecurity(): void {
    this.debugLog('Enforcing paywall security');

    // Pause video immediately
    try {
      if (this.video && !this.video.paused) {
        this.video.pause();
      }
    } catch (_) { }

    // Activate paywall state
    this.isPaywallActive = true;

    // Show paywall overlay
    if (this.paywallController) {
      try {
        this.paywallController.openOverlay();
      } catch (error) {
        this.debugError('Error showing paywall overlay:', error);
      }
    }

    // Start monitoring for overlay tampering
    this.startOverlayMonitoring();
  }

  /**
   * Monitor overlay elements to detect removal attempts
   */
  private startOverlayMonitoring(): void {
    if (!this.playerWrapper || this.paymentSuccessful) return;

    // Clear existing monitor
    if (this.authValidationInterval) {
      clearInterval(this.authValidationInterval);
      this.authValidationInterval = null;
    }

    this.debugLog('Starting overlay monitoring - payment successful:', this.paymentSuccessful, 'paywall active:', this.isPaywallActive);

    // Monitor every 1000ms (less aggressive than before)
    this.authValidationInterval = setInterval(() => {
      // First check: stop monitoring if payment successful or paywall inactive
      if (!this.isPaywallActive || this.paymentSuccessful) {
        this.debugLog('Stopping overlay monitoring - payment successful:', this.paymentSuccessful, 'paywall active:', this.isPaywallActive);
        if (this.authValidationInterval) {
          clearInterval(this.authValidationInterval);
          this.authValidationInterval = null;
        }
        return;
      }

      // Double-check payment success before enforcing security
      if (this.paymentSuccessful) {
        this.debugLog('Payment successful detected during monitoring, stopping');
        if (this.authValidationInterval) {
          clearInterval(this.authValidationInterval);
          this.authValidationInterval = null;
        }
        return;
      }

      // Check for overlay presence
      const paywallOverlays = this.playerWrapper!.querySelectorAll('.uvf-paywall-overlay, .uvf-auth-overlay');
      const visibleOverlays = Array.from(paywallOverlays).filter(overlay => {
        const element = overlay as HTMLElement;
        const style = window.getComputedStyle(element);
        return style.display !== 'none' && style.visibility !== 'hidden';
      });

      if (visibleOverlays.length === 0) {
        this.overlayRemovalAttempts++;
        this.debugWarn(`Overlay removal attempt detected (${this.overlayRemovalAttempts}/${this.maxOverlayRemovalAttempts})`);

        // Final check before taking action - ensure payment wasn't just completed
        if (this.paymentSuccessful) {
          this.debugLog('Payment successful detected, ignoring overlay removal');
          if (this.authValidationInterval) {
            clearInterval(this.authValidationInterval);
            this.authValidationInterval = null;
          }
          return;
        }

        if (this.overlayRemovalAttempts >= this.maxOverlayRemovalAttempts) {
          this.handleSecurityViolation();
        } else {
          // Recreate overlay
          this.enforcePaywallSecurity();
        }
      }

      // Additional check: ensure video is paused if not authenticated
      if (this.video && !this.video.paused && !this.paymentSuccessful) {
        this.debugWarn('Unauthorized playback detected, pausing video');
        try {
          this.video.pause();
          const freeDuration = Number(this.config.freeDuration || 0);
          if (freeDuration > 0 && isFinite(freeDuration)) {
            this.safeSetCurrentTime(freeDuration - 1);
          }
        } catch (_) { }
      }
    }, 1000);
  }

  /**
   * Handle severe security violation attempts
   */
  private handleSecurityViolation(): void {
    this.debugError('Security violation detected - disabling video');

    // Disable video completely
    if (this.video) {
      this.video.pause();
      this.safeSetCurrentTime(0);
      this.video.src = ''; // Clear video source
      this.video.style.display = 'none';
    }

    // Show security violation message
    this.showSecurityViolationMessage();

    // Clear monitoring interval
    if (this.authValidationInterval) {
      clearInterval(this.authValidationInterval);
    }
  }

  /**
   * Show security violation message
   */
  private showSecurityViolationMessage(): void {
    if (!this.playerWrapper) return;

    // Clear existing content
    this.playerWrapper.innerHTML = '';

    // Create security violation overlay
    const securityOverlay = document.createElement('div');
    securityOverlay.style.cssText = `
      position: absolute;
      inset: 0;
      background: rgba(0, 0, 0, 0.95);
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 2147483647;
      color: #ff6b6b;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      text-align: center;
      padding: 40px;
    `;

    const messageContainer = document.createElement('div');
    messageContainer.innerHTML = `
      <div style="font-size: 24px; font-weight: bold; margin-bottom: 16px; color: #ff6b6b;">
        🔒 Security Violation Detected
      </div>
      <div style="font-size: 16px; line-height: 1.5; color: rgba(255, 255, 255, 0.9);">
        Unauthorized access attempt detected.<br>
        Please refresh the page and complete authentication to continue.
      </div>
      <div style="margin-top: 24px;">
        <button onclick="window.location.reload()" style="
          background: #ff4d4f;
          color: white;
          border: none;
          padding: 12px 24px;
          border-radius: 8px;
          cursor: pointer;
          font-size: 14px;
          font-weight: 600;
        ">Reload Page</button>
      </div>
    `;

    securityOverlay.appendChild(messageContainer);
    this.playerWrapper.appendChild(securityOverlay);
  }

  /**
   * Force cleanup of all paywall/auth overlays
   */
  private forceCleanupOverlays(): void {
    this.debugLog('Force cleanup of overlays called');

    if (!this.playerWrapper) return;

    // Find and remove all overlay elements
    const overlays = this.playerWrapper.querySelectorAll('.uvf-paywall-overlay, .uvf-auth-overlay');
    overlays.forEach((overlay: Element) => {
      const htmlOverlay = overlay as HTMLElement;
      this.debugLog('Removing overlay:', htmlOverlay.className);

      // Hide immediately
      htmlOverlay.style.display = 'none';
      htmlOverlay.classList.remove('active');

      // Remove from DOM
      if (htmlOverlay.parentNode) {
        htmlOverlay.parentNode.removeChild(htmlOverlay);
      }
    });

    // Also tell paywall controller to clean up
    if (this.paywallController && typeof this.paywallController.destroyOverlays === 'function') {
      this.debugLog('Calling paywallController.destroyOverlays()');
      this.paywallController.destroyOverlays();
    }
  }

  /**
   * Show the EPG button in the controls
   */
  public showEPGButton(): void {
    const epgBtn = this.getElement('epg-btn');
    if (epgBtn) {
      epgBtn.style.display = 'block';
      this.debugLog('EPG button shown');
    } else {
      this.debugLog('EPG button not found in DOM');
    }
  }

  /**
   * Hide the EPG button in the controls
   */
  public hideEPGButton(): void {
    const epgBtn = this.getElement('epg-btn');
    if (epgBtn) {
      epgBtn.style.display = 'none';
      this.debugLog('EPG button hidden');
    }
  }

  /**
   * Set EPG data and show the EPG button if data is available
   * @param epgData - The EPG data to set
   */
  public setEPGData(epgData: any): void {
    if (epgData && Object.keys(epgData).length > 0) {
      this.showEPGButton();
      this.debugLog('EPG data set, button shown');
      // Emit event to notify that EPG data is available
      this.emit('epgDataSet', { data: epgData });
    } else {
      this.hideEPGButton();
      this.debugLog('No EPG data provided, button hidden');
    }
  }

  /**
   * Check if EPG button is currently visible
   */
  public isEPGButtonVisible(): boolean {
    const epgBtn = this.getElement('epg-btn');
    return epgBtn ? epgBtn.style.display !== 'none' : false;
  }

  private async cleanup(): Promise<void> {
    // Clean up DRM resources first
    if (this.drmManager) {
      try {
        await this.drmManager.destroy();
        this.debugLog('DRM manager destroyed');
      } catch (error) {
        this.debugError('Error destroying DRM manager:', error);
      }
      this.drmManager = null;
    }
    this.isDRMProtected = false;

    if (this.hls) {
      this.hls.destroy();
      this.hls = null;
    }

    if (this.dash) {
      this.dash.reset();
      this.dash = null;
    }

    this.qualities = [];
    this.currentQualityIndex = -1;
    this.autoQuality = false;

    // Clean up YouTube player resources
    if (this.youtubeTimeTrackingInterval) {
      clearInterval(this.youtubeTimeTrackingInterval);
      this.youtubeTimeTrackingInterval = null;
      this.debugLog('YouTube time tracking interval cleared');
    }

    if (this.youtubePlayer) {
      try {
        // Destroy YouTube player instance
        if (typeof this.youtubePlayer.destroy === 'function') {
          this.youtubePlayer.destroy();
        }
        this.youtubePlayer = null;
        this.youtubePlayerReady = false;
        this.debugLog('YouTube player destroyed');
      } catch (error) {
        this.debugError('Error destroying YouTube player:', error);
      }
    }

    // Remove YouTube iframe from DOM
    if (this.youtubeIframe && this.youtubeIframe.parentElement) {
      this.youtubeIframe.parentElement.remove(); // Remove the container div
      this.youtubeIframe = null;
      this.debugLog('YouTube iframe removed from DOM');
    }

    // Show regular video element if it was hidden for YouTube
    if (this.video && this.video.style.display === 'none') {
      this.video.style.display = '';
    }

    // Revoke subtitle Blob URLs
    this.subtitleBlobUrls.forEach(url => {
      try {
        URL.revokeObjectURL(url);
      } catch (e) {
        this.debugError('Failed to revoke subtitle Blob URL in cleanup:', e);
      }
    });
    this.subtitleBlobUrls = [];
  }

  async destroy(): Promise<void> {
    await this.cleanup();

    // Clear cached element references
    this.cachedElements = {};

    // Remove global event listeners to prevent memory leaks
    if (this.globalMouseMoveHandler) {
      document.removeEventListener('mousemove', this.globalMouseMoveHandler);
      this.globalMouseMoveHandler = null;
    }
    if (this.globalTouchMoveHandler) {
      document.removeEventListener('touchmove', this.globalTouchMoveHandler);
      this.globalTouchMoveHandler = null;
    }
    if (this.globalMouseUpHandler) {
      document.removeEventListener('mouseup', this.globalMouseUpHandler);
      this.globalMouseUpHandler = null;
    }
    if (this.globalTouchEndHandler) {
      document.removeEventListener('touchend', this.globalTouchEndHandler);
      this.globalTouchEndHandler = null;
    }

    // Clear timeouts
    if (this.hideControlsTimeout) {
      clearTimeout(this.hideControlsTimeout);
      this.hideControlsTimeout = null;
    }
    if (this.volumeHideTimeout) {
      clearTimeout(this.volumeHideTimeout);
      this.volumeHideTimeout = null;
    }

    // Clear live stream timers
    if (this.liveRetryTimer) {
      clearTimeout(this.liveRetryTimer);
      this.liveRetryTimer = null;
    }
    if (this.liveMessageRotationTimer) {
      clearInterval(this.liveMessageRotationTimer);
      this.liveMessageRotationTimer = null;
    }
    if (this.liveBufferingTimeoutTimer) {
      clearTimeout(this.liveBufferingTimeoutTimer);
      this.liveBufferingTimeoutTimer = null;
    }
    if (this.liveCountdownTimer) {
      clearInterval(this.liveCountdownTimer);
      this.liveCountdownTimer = null;
    }
    this.isWaitingForLiveStream = false;
    this.isShowingLiveCountdown = false;
    this.hasCountdownCompleted = false;
    this.isReloadingAfterCountdown = false;
    this.countdownCallbackFired = false;
    this.liveStreamOriginalSource = null;
    this.liveMessageIndex = 0;
    this.liveCountdownRemainingSeconds = 0;

    // Clear security monitoring
    if (this.authValidationInterval) {
      clearInterval(this.authValidationInterval);
      this.authValidationInterval = null;
    }

    // Reset security state
    this.isPaywallActive = false;
    this.overlayRemovalAttempts = 0;

    // Destroy paywall controller
    if (this.paywallController && typeof this.paywallController.destroy === 'function') {
      this.paywallController.destroy();
      this.paywallController = null;
    }

    // Destroy chapter managers
    if (this.chapterManager && typeof this.chapterManager.destroy === 'function') {
      this.chapterManager.destroy();
      this.chapterManager = null;
    }
    if (this.coreChapterManager) {
      this.coreChapterManager.destroy();
      this.coreChapterManager = null;
    }

    if (this.video) {
      this.video.pause();
      this.video.removeAttribute('src');
      this.video.load();
      this.video.remove();
      this.video = null;
    }

    if (this.container) {
      this.container.innerHTML = '';
    }

    this.subtitleOverlay = null;
    this.events.removeAllListeners();
  }
}
