Pārlūkot izejas kodu

Removed movie index backup

Toby Chui 1 nedēļu atpakaļ
vecāks
revīzija
95afcf13c7
2 mainītis faili ar 474 papildinājumiem un 1695 dzēšanām
  1. 0 1694
      src/web/Movie/index.htm.bak
  2. 474 1
      src/web/Movie/index.html

+ 0 - 1694
src/web/Movie/index.htm.bak

@@ -1,1694 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
-    <meta name="apple-mobile-web-app-capable" content="yes">
-    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
-    <meta name="theme-color" content="#000000">
-    <link rel="manifest" crossorigin="use-credentials" href="manifest.json">
-    <title>Movie</title>
-
-    <!-- ArozOS module helpers -->
-    <script src="../script/jquery.min.js"></script>
-    <script src="../script/ao_module.js"></script>
-
-    <!-- App path config (single source of truth for all API paths) -->
-    <script src="backend/common.js"></script>
-
-<style>
-/* ─── Reset & base ──────────────────────────────────────────────────────────── */
-*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
-
-:root {
-    --bg:          #0a0a0a;
-    --surface:     #1c1c1e;
-    --surface2:    #2c2c2e;
-    --accent:      #0a84ff;
-    --accent2:     #30d158;
-    --text:        #f5f5f7;
-    --text-sub:    #98989d;
-    --radius:      12px;
-    --card-w:      180px;
-    --card-ratio:  1.5;     /* height = width * ratio  (poster aspect) */
-    --header-h:    56px;
-    --transition:  0.2s ease;
-}
-
-html, body {
-    background: var(--bg);
-    color: var(--text);
-    font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", sans-serif;
-    height: 100%;
-    overflow: hidden;
-}
-
-/* ─── App shell ──────────────────────────────────────────────────────────────── */
-#app { width: 100vw; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
-
-/* ─── Top nav bar ────────────────────────────────────────────────────────────── */
-#topbar {
-    flex-shrink: 0;
-    height: var(--header-h);
-    display: flex;
-    align-items: center;
-    padding: 0 20px;
-    gap: 16px;
-    background: linear-gradient(to bottom, rgba(0,0,0,0.9) 0%, transparent 100%);
-    position: relative;
-    z-index: 10;
-}
-#topbar h1 { font-size: 22px; font-weight: 700; letter-spacing: -0.3px; }
-#topbar h1 span { color: var(--accent); }
-
-#search-wrap { margin-left: auto; display: flex; align-items: center; gap: 8px; }
-#search-input {
-    background: var(--surface2);
-    border: none;
-    border-radius: 20px;
-    color: var(--text);
-    font-size: 14px;
-    padding: 7px 14px;
-    width: 200px;
-    outline: none;
-    transition: width var(--transition);
-}
-#search-input:focus { width: 280px; box-shadow: 0 0 0 2px var(--accent); }
-#search-input::placeholder { color: var(--text-sub); }
-
-/* ─── View containers ────────────────────────────────────────────────────────── */
-.view { display: none; flex: 1; overflow: hidden; flex-direction: column; }
-.view.active { display: flex; }
-
-/* ─── Library view ───────────────────────────────────────────────────────────── */
-#view-library { padding: 0; }
-
-#library-scroll {
-    flex: 1;
-    overflow-y: auto;
-    overflow-x: hidden;
-    padding: 8px 20px 40px;
-    scroll-behavior: smooth;
-}
-#library-scroll::-webkit-scrollbar { width: 4px; }
-#library-scroll::-webkit-scrollbar-track { background: transparent; }
-#library-scroll::-webkit-scrollbar-thumb { background: var(--surface2); border-radius: 4px; }
-
-#no-content {
-    display: none;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    height: 60%;
-    gap: 12px;
-    color: var(--text-sub);
-    font-size: 16px;
-}
-#no-content .icon img { width: 80px; height: 80px; opacity: 0.3; }
-
-#loading-overlay {
-    position: absolute; inset: 0;
-    background: var(--bg);
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    gap: 16px;
-    z-index: 100;
-    font-size: 15px;
-    color: var(--text-sub);
-}
-.spinner {
-    width: 40px; height: 40px;
-    border: 3px solid var(--surface2);
-    border-top-color: var(--accent);
-    border-radius: 50%;
-    animation: spin 0.8s linear infinite;
-}
-@keyframes spin { to { transform: rotate(360deg); } }
-
-/* ─── Section heading ────────────────────────────────────────────────────────── */
-.section-title {
-    font-size: 20px;
-    font-weight: 600;
-    margin: 24px 0 12px;
-    color: var(--text);
-}
-
-/* ─── Album grid ─────────────────────────────────────────────────────────────── */
-.album-grid {
-    display: grid;
-    grid-template-columns: repeat(auto-fill, minmax(var(--card-w), 1fr));
-    gap: 16px;
-}
-
-.album-card {
-    cursor: pointer;
-    border-radius: var(--radius);
-    overflow: hidden;
-    background: var(--surface);
-    transition: transform var(--transition), box-shadow var(--transition);
-    outline: none;
-    position: relative;
-}
-.album-card:hover,
-.album-card.focused {
-    transform: scale(1.04);
-    box-shadow: 0 8px 32px rgba(0,0,0,0.7), 0 0 0 2px var(--accent);
-    z-index: 2;
-}
-
-.album-card .poster {
-    width: 100%;
-    aspect-ratio: 16 / 9;
-    object-fit: cover;
-    display: block;
-    background: var(--surface2);
-}
-.poster-placeholder {
-    width: 100%;
-    aspect-ratio: 16 / 9;
-    background: linear-gradient(135deg, var(--surface) 0%, var(--surface2) 100%);
-    display: flex;
-    align-items: center;
-    justify-content: center;
-}
-.poster-placeholder img { width: 100%;  opacity: 1; }
-
-.album-card .card-info {
-    padding: 8px 10px 10px;
-}
-.album-card .card-title {
-    font-size: 13px;
-    font-weight: 600;
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-}
-.album-card .card-meta {
-    font-size: 11px;
-    color: var(--text-sub);
-    margin-top: 2px;
-}
-.badge {
-    position: absolute;
-    top: 7px; right: 7px;
-    background: rgba(0,0,0,0.65);
-    backdrop-filter: blur(4px);
-    border-radius: 6px;
-    font-size: 10px;
-    font-weight: 600;
-    padding: 2px 6px;
-    color: #fff;
-    letter-spacing: 0.3px;
-    text-transform: uppercase;
-}
-.badge.series { color: var(--accent2); }
-
-/* ─── Detail view ────────────────────────────────────────────────────────────── */
-#view-detail { position: relative; }
-
-#detail-hero {
-    flex-shrink: 0;
-    height: 38vh;
-    min-height: 200px;
-    position: relative;
-    overflow: hidden;
-}
-#detail-hero-bg {
-    position: absolute; inset: 0;
-    background-size: cover;
-    background-position: center top;
-    filter: blur(28px) brightness(0.35);
-    transform: scale(1.1);
-}
-#detail-hero-content {
-    position: relative;
-    display: flex;
-    align-items: flex-end;
-    height: 100%;
-    padding: 0 28px 20px;
-    gap: 20px;
-}
-#detail-poster {
-    width: 120px;
-    flex-shrink: 0;
-    border-radius: 8px;
-    overflow: hidden;
-    box-shadow: 0 8px 24px rgba(0,0,0,0.6);
-}
-#detail-poster img, #detail-poster .poster-placeholder {
-    width: 120px;
-    aspect-ratio: 2 / 3;
-    object-fit: cover;
-    display: block;
-}
-
-#detail-meta { flex: 1; min-width: 0; }
-#detail-title { font-size: 26px; font-weight: 700; line-height: 1.15; }
-#detail-subtitle { font-size: 14px; color: var(--text-sub); margin-top: 4px; }
-#detail-actions { display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap; }
-
-.btn {
-    border: none; cursor: pointer; border-radius: 8px;
-    font-size: 14px; font-weight: 600; padding: 10px 22px;
-    transition: opacity var(--transition), transform var(--transition);
-    outline: none;
-}
-.btn:hover, .btn.focused { opacity: 0.85; transform: scale(1.03); }
-.btn-primary { background: var(--text); color: #000; }
-.btn-secondary { background: var(--surface2); color: var(--text); }
-
-/* ─── Season tabs ────────────────────────────────────────────────────────────── */
-#season-tabs {
-    flex-shrink: 0;
-    display: flex;
-    gap: 8px;
-    padding: 12px 28px 0;
-    overflow-x: auto;
-    scrollbar-width: none;
-}
-#season-tabs::-webkit-scrollbar { display: none; }
-
-.season-tab {
-    flex-shrink: 0;
-    background: var(--surface2);
-    border: none; cursor: pointer;
-    border-radius: 20px;
-    color: var(--text-sub);
-    font-size: 13px; font-weight: 500;
-    padding: 6px 16px;
-    transition: background var(--transition), color var(--transition);
-    outline: none;
-}
-.season-tab.active, .season-tab.focused {
-    background: var(--text);
-    color: #000;
-}
-
-/* ─── Episode list ───────────────────────────────────────────────────────────── */
-#episode-scroll {
-    flex: 1;
-    overflow-y: auto;
-    padding: 12px 28px 40px;
-    scroll-behavior: smooth;
-}
-#episode-scroll::-webkit-scrollbar { width: 4px; }
-#episode-scroll::-webkit-scrollbar-thumb { background: var(--surface2); border-radius: 4px; }
-
-.episode-item {
-    display: flex;
-    align-items: center;
-    gap: 14px;
-    padding: 10px 12px;
-    border-radius: var(--radius);
-    cursor: pointer;
-    transition: background var(--transition);
-    outline: none;
-}
-.episode-item:hover, .episode-item.focused {
-    background: var(--surface);
-    box-shadow: 0 0 0 2px var(--accent);
-}
-.episode-item.playing { background: rgba(10,132,255,0.15); }
-
-.ep-thumb {
-    width: 100px; flex-shrink: 0;
-    aspect-ratio: 16 / 9;
-    border-radius: 6px;
-    overflow: hidden;
-    background: var(--surface2);
-}
-.ep-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
-.ep-thumb-placeholder {
-    width: 100%; height: 100%;
-    display: flex; align-items: center; justify-content: center;
-}
-.ep-thumb-placeholder img { width: 22px; height: 22px; opacity: 0.5; }
-.ep-info { flex: 1; min-width: 0; }
-.ep-name { font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
-.ep-path { font-size: 11px; color: var(--text-sub); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.ep-play-icon {
-    flex-shrink: 0;
-    width: 32px; height: 32px;
-    background: var(--surface2);
-    border-radius: 50%;
-    display: flex; align-items: center; justify-content: center;
-    transition: background var(--transition);
-}
-.ep-play-icon img { width: 16px; height: 16px; }
-.episode-item.focused .ep-play-icon,
-.episode-item:hover .ep-play-icon { background: var(--accent); }
-
-#detail-back {
-    position: absolute;
-    top: 12px; left: 16px;
-    background: rgba(0,0,0,0.5);
-    backdrop-filter: blur(8px);
-    border: none; cursor: pointer;
-    border-radius: 20px;
-    color: var(--text);
-    font-size: 13px; font-weight: 500;
-    padding: 6px 16px;
-    z-index: 5;
-    transition: background var(--transition);
-    outline: none;
-}
-#detail-back:hover, #detail-back.focused { background: var(--accent); }
-
-/* ─── Player view ────────────────────────────────────────────────────────────── */
-#view-player {
-    position: fixed; inset: 0;
-    background: #000;
-    z-index: 200;
-    flex-direction: row;
-}
-#view-player.active { display: flex; }
-
-#video-container {
-    flex: 1;
-    display: flex;
-    flex-direction: column;
-    position: relative;
-    overflow: hidden;
-}
-
-#main-video {
-    width: 100%; height: 100%;
-    object-fit: contain;
-    background: #000;
-    display: block;
-}
-
-/* ─── Custom video controls ──────────────────────────────────────────────────── */
-#video-controls {
-    position: absolute;
-    bottom: 0; left: 0; right: 0;
-    background: linear-gradient(transparent, rgba(0,0,0,0.85) 100%);
-    padding: 40px 20px 16px;
-    transition: opacity 0.3s;
-}
-#video-controls.hidden { opacity: 0; pointer-events: none; }
-/* Hide cursor when controls auto-hide */
-#video-container:has(#video-controls.hidden) { cursor: none; }
-
-#progress-wrap {
-    position: relative;
-    height: 4px;
-    background: rgba(255,255,255,0.25);
-    border-radius: 4px;
-    cursor: pointer;
-    margin-bottom: 12px;
-}
-#progress-bar {
-    height: 100%; border-radius: 4px;
-    background: var(--accent);
-    pointer-events: none;
-}
-#progress-wrap:hover { height: 6px; }
-/* Transparent hit-zone 16px above the visual bar */
-#progress-wrap::before {
-    content: '';
-    position: absolute;
-    top: -16px; left: 0; right: 0;
-    height: 16px;
-}
-#progress-thumb {
-    position: absolute;
-    top: 50%; transform: translateY(-50%);
-    width: 14px; height: 14px;
-    background: #fff; border-radius: 50%;
-    pointer-events: none;
-    display: none;
-}
-#progress-wrap:hover #progress-thumb { display: block; }
-
-#controls-row {
-    display: flex;
-    align-items: center;
-    gap: 10px;
-}
-.ctrl-btn {
-    background: none; border: none; cursor: pointer;
-    color: #fff; padding: 4px;
-    line-height: 1;
-    transition: opacity var(--transition);
-    outline: none;
-    display: flex; align-items: center; justify-content: center;
-}
-.ctrl-btn img { width: 24px; height: 24px; display: block; }
-.ctrl-btn:hover { opacity: 0.7; }
-.ctrl-btn.focused { background: rgba(10,132,255,0.18); border-radius: 6px; }
-
-#volume-wrap { display: flex; align-items: center; gap: 8px; }
-#volume-slider {
-    -webkit-appearance: none;
-    width: 80px; height: 4px;
-    background: rgba(255,255,255,0.3);
-    border-radius: 4px; outline: none;
-}
-#volume-slider::-webkit-slider-thumb {
-    -webkit-appearance: none;
-    width: 12px; height: 12px;
-    background: #fff; border-radius: 50%;
-}
-
-#time-display { font-size: 13px; color: rgba(255,255,255,0.8); margin-left: 6px; }
-#now-playing-title {
-    font-size: 15px; font-weight: 600;
-    flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
-    margin-left: 10px;
-}
-
-/* ─── Playlist sidebar ───────────────────────────────────────────────────────── */
-#playlist-sidebar {
-    width: 300px;
-    flex-shrink: 0;
-    background: rgba(20,20,20,0.95);
-    backdrop-filter: blur(12px);
-    display: flex;
-    flex-direction: column;
-    border-left: 1px solid rgba(255,255,255,0.08);
-    transform: translateX(0);
-    transition: transform var(--transition), width var(--transition);
-}
-#playlist-sidebar.collapsed { width: 0; overflow: hidden; }
-
-#sidebar-header {
-    padding: 14px 16px 10px;
-    font-size: 15px; font-weight: 600;
-    border-bottom: 1px solid rgba(255,255,255,0.08);
-    display: flex; align-items: center; gap: 8px;
-}
-#sidebar-close {
-    margin-left: auto;
-    background: none; border: none; cursor: pointer;
-    color: var(--text-sub); font-size: 18px; padding: 2px;
-    outline: none;
-}
-#sidebar-close:hover { color: var(--text); }
-
-#sidebar-list { flex: 1; overflow-y: auto; padding: 8px; }
-#sidebar-list::-webkit-scrollbar { width: 3px; }
-#sidebar-list::-webkit-scrollbar-thumb { background: var(--surface2); border-radius: 3px; }
-
-.sidebar-ep {
-    display: flex; align-items: center; gap: 10px;
-    padding: 8px;
-    border-radius: 8px; cursor: pointer;
-    transition: background var(--transition);
-    font-size: 13px; color: var(--text);
-    outline: none;
-}
-.sidebar-ep:hover, .sidebar-ep.focused { background: var(--surface2); }
-.sidebar-ep.playing { background: rgba(10,132,255,0.2); color: var(--accent); }
-.sidebar-ep-num { flex-shrink: 0; width: 24px; text-align: center; color: var(--text-sub); font-size: 12px; }
-.sidebar-ep-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-
-#player-back {
-    position: absolute;
-    top: 14px; left: 14px;
-    z-index: 10;
-    background: rgba(0,0,0,0.55);
-    backdrop-filter: blur(8px);
-    border: none; cursor: pointer;
-    border-radius: 20px;
-    color: #fff;
-    font-size: 13px; font-weight: 500;
-    padding: 7px 16px;
-    transition: background var(--transition);
-    outline: none;
-}
-#player-back:hover { background: var(--accent); }
-
-/* ─── Toast notification ─────────────────────────────────────────────────────── */
-#toast {
-    position: fixed;
-    bottom: 30px; left: 50%;
-    transform: translateX(-50%) translateY(20px);
-    background: rgba(30,30,30,0.95);
-    backdrop-filter: blur(8px);
-    color: var(--text);
-    padding: 10px 20px;
-    border-radius: 20px;
-    font-size: 13px;
-    opacity: 0;
-    transition: opacity 0.3s, transform 0.3s;
-    pointer-events: none;
-    z-index: 1000;
-    white-space: nowrap;
-}
-#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
-
-/* ─── Responsive ─────────────────────────────────────────────────────────────── */
-@media (max-width: 600px) {
-    :root { --card-w: 130px; }
-    #library-scroll { padding: 8px 12px 40px; }
-    #detail-hero { height: 44vw; min-height: 160px; }
-    #detail-poster { width: 80px; }
-    #detail-title { font-size: 18px; }
-    #playlist-sidebar { width: 100%; position: absolute; right: 0; top: 0; bottom: 0; z-index: 5; }
-    #playlist-sidebar.collapsed { width: 0; }
-    #episode-scroll { padding: 10px 12px 40px; }
-    .ep-thumb { width: 72px; }
-}
-
-@media (max-width: 400px) {
-    :root { --card-w: 110px; }
-}
-
-/* TV / large screen layout */
-@media (min-width: 1400px) {
-    :root { --card-w: 200px; }
-}
-@media (min-width: 1800px) {
-    :root { --card-w: 240px; }
-}
-
-/* Focus ring for TV remote navigation */
-.tv-focused {
-    outline: 3px solid var(--accent) !important;
-    outline-offset: 2px !important;
-}
-
-/* Hide back button when in fullscreen */
-#view-player:fullscreen #player-back,
-#view-player:-webkit-full-screen #player-back,
-#view-player:-moz-full-screen #player-back { display: none; }
-
-/* ─── Load More button ───────────────────────────────────────────────────────── */
-.load-more-wrap { text-align: center; padding: 16px 0 24px; }
-.load-more-btn {
-    background: var(--surface2);
-    border: 1px solid rgba(255,255,255,0.08); cursor: pointer;
-    border-radius: 8px; color: var(--text);
-    font-size: 13px; font-weight: 500; padding: 9px 28px;
-    outline: none; transition: background var(--transition), box-shadow var(--transition);
-}
-.load-more-btn:hover { background: rgba(255,255,255,0.08); box-shadow: 0 0 0 1px var(--accent); }
-
-/* ─── Autoplay toggle (sidebar) ─────────────────────────────────────────────── */
-.autoplay-label {
-    display: flex; align-items: center; gap: 5px;
-    font-size: 11px; color: var(--text-sub);
-    cursor: pointer; user-select: none;
-    font-weight: 500;
-}
-.autoplay-label input[type="checkbox"] { display: none; }
-.autoplay-track {
-    width: 30px; height: 17px;
-    background: var(--surface2); border-radius: 9px;
-    position: relative; flex-shrink: 0;
-    transition: background 0.2s;
-}
-.autoplay-label input:checked + .autoplay-track { background: var(--accent2); }
-.autoplay-track::after {
-    content: '';
-    position: absolute; top: 2px; left: 2px;
-    width: 13px; height: 13px;
-    background: #fff; border-radius: 50%;
-    transition: transform 0.2s;
-}
-.autoplay-label input:checked + .autoplay-track::after { transform: translateX(13px); }
-
-/* ─── Next-episode countdown ────────────────────────────────────────────────── */
-#next-countdown {
-    display: none;
-    position: absolute; bottom: 80px; right: 20px;
-    background: rgba(20,20,20,0.92);
-    backdrop-filter: blur(8px);
-    border-radius: 10px; padding: 12px 16px;
-    min-width: 210px; z-index: 20;
-}
-#next-countdown-text { font-size: 13px; color: var(--text-sub); margin-bottom: 8px; }
-#next-countdown-track {
-    height: 4px; background: var(--surface2);
-    border-radius: 4px; margin-bottom: 8px; overflow: hidden;
-}
-#next-countdown-bar { height: 100%; background: var(--accent); border-radius: 4px; transition: width 1s linear; }
-#next-countdown-cancel {
-    display: block; width: 100%; background: var(--surface2);
-    border: none; cursor: pointer; border-radius: 6px;
-    color: var(--text); font-size: 12px; padding: 5px; outline: none;
-    transition: background var(--transition);
-}
-#next-countdown-cancel:hover { background: rgba(255,59,48,0.28); }
-
-/* ─── Player context menu ────────────────────────────────────────────────────── */
-#player-ctx {
-    display: none;
-    position: absolute;
-    z-index: 30;
-    background: rgba(28,28,30,0.97);
-    backdrop-filter: blur(16px);
-    -webkit-backdrop-filter: blur(16px);
-    border-radius: 10px;
-    padding: 4px 0;
-    min-width: 192px;
-    box-shadow: 0 4px 24px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.08);
-    user-select: none;
-}
-.ctx-item {
-    padding: 9px 14px;
-    font-size: 13px;
-    cursor: pointer;
-    display: flex;
-    align-items: center;
-    gap: 10px;
-    color: var(--text);
-    transition: background var(--transition);
-}
-.ctx-item:hover { background: rgba(255,255,255,0.08); }
-.ctx-item.ctx-active { color: var(--accent); }
-.ctx-item.ctx-disabled { opacity: 0.3; pointer-events: none; }
-.ctx-icon { width: 16px; text-align: center; flex-shrink: 0; font-style: normal; }
-.ctx-divider { height: 1px; background: rgba(255,255,255,0.08); margin: 3px 0; }
-
-/* ─── Video info modal ───────────────────────────────────────────────────────── */
-#video-info-modal {
-    display: none;
-    position: absolute;
-    top: 50%; left: 50%;
-    transform: translate(-50%, -50%);
-    z-index: 40;
-    background: rgba(18,18,20,0.97);
-    backdrop-filter: blur(20px);
-    -webkit-backdrop-filter: blur(20px);
-    border-radius: 14px;
-    padding: 20px 22px 16px;
-    width: 340px;
-    box-shadow: 0 8px 40px rgba(0,0,0,0.8), 0 0 0 1px rgba(255,255,255,0.1);
-}
-#video-info-modal h3 {
-    font-size: 14px; font-weight: 600;
-    margin-bottom: 12px;
-    display: flex; align-items: center; justify-content: space-between;
-}
-#video-info-close {
-    background: none; border: none; cursor: pointer;
-    color: var(--text-sub); font-size: 17px; line-height: 1; padding: 0; outline: none;
-}
-#video-info-close:hover { color: var(--text); }
-#video-info-tabs {
-    display: flex; gap: 3px;
-    background: var(--surface2);
-    border-radius: 8px;
-    padding: 3px;
-    margin-bottom: 12px;
-}
-.info-tab {
-    flex: 1; text-align: center; padding: 5px 0;
-    font-size: 12px; font-weight: 500;
-    border-radius: 6px; cursor: pointer;
-    color: var(--text-sub);
-    transition: background var(--transition), color var(--transition);
-}
-.info-tab.active { background: var(--surface); color: var(--text); }
-.info-row {
-    display: flex; gap: 8px;
-    padding: 5px 0;
-    border-bottom: 1px solid rgba(255,255,255,0.05);
-    font-size: 12px;
-    line-height: 1.4;
-}
-.info-row:last-child { border-bottom: none; }
-.info-label { color: var(--text-sub); flex-shrink: 0; width: 110px; }
-.info-value { color: var(--text); word-break: break-all; }
-</style>
-</head>
-<body>
-<div id="app">
-
-    <!-- ─ Loading overlay ─────────────────────────────────────────────────── -->
-    <div id="loading-overlay">
-        <div class="spinner"></div>
-        <span>Loading library…</span>
-    </div>
-
-    <!-- ─ Top bar ─────────────────────────────────────────────────────────── -->
-    <div id="topbar">
-        <h1><img src="img/icons/movie_white.svg" width="22" height="22" alt="" style="vertical-align:middle;margin-right:6px;"><span>Movie</span></h1>
-        <div id="search-wrap">
-            <input id="search-input" type="text" placeholder="Search…" autocomplete="off">
-        </div>
-    </div>
-
-    <!-- ═══════════════ LIBRARY VIEW ═══════════════════════════════════════ -->
-    <div id="view-library" class="view active">
-        <div id="library-scroll">
-            <div id="no-content">
-                <div class="icon"><img src="img/icons/movie_white.svg" alt=""></div>
-                <div>No videos found. Place your videos in a <strong>Video/</strong> folder on any storage.</div>
-            </div>
-            <div id="movies-section" style="display:none">
-                <div class="section-title">Movies</div>
-                <div id="movies-grid" class="album-grid"></div>
-                <div class="load-more-wrap"><button class="load-more-btn" id="movies-load-more" style="display:none" onclick="loadMoreSection('movies')">Load more</button></div>
-            </div>
-            <div id="series-section" style="display:none">
-                <div class="section-title">TV / Shows</div>
-                <div id="series-grid" class="album-grid"></div>
-                <div class="load-more-wrap"><button class="load-more-btn" id="shows-load-more" style="display:none" onclick="loadMoreSection('shows')">Load more</button></div>
-            </div>
-            <div id="anime-section" style="display:none">
-                <div class="section-title">Anime</div>
-                <div id="anime-grid" class="album-grid"></div>
-                <div class="load-more-wrap"><button class="load-more-btn" id="anime-load-more" style="display:none" onclick="loadMoreSection('anime')">Load more</button></div>
-            </div>
-            <div id="shorts-section" style="display:none">
-                <div class="section-title">Shorts</div>
-                <div id="shorts-grid" class="album-grid"></div>
-                <div class="load-more-wrap"><button class="load-more-btn" id="shorts-load-more" style="display:none" onclick="loadMoreSection('shorts')">Load more</button></div>
-            </div>
-        </div>
-    </div>
-
-    <!-- ═══════════════ DETAIL VIEW ════════════════════════════════════════ -->
-    <div id="view-detail" class="view">
-        <div id="detail-hero">
-            <div id="detail-hero-bg"></div>
-            <div id="detail-hero-content">
-                <div id="detail-poster"><div class="poster-placeholder"><img src="img/icons/movie_white.svg" alt=""></div></div>
-                <div id="detail-meta">
-                    <div id="detail-title">Album Title</div>
-                    <div id="detail-subtitle">0 episodes</div>
-                    <div id="detail-actions">
-                        <button class="btn btn-primary" id="btn-play-first"><img src="img/icons/play_black.svg" width="15" height="15" alt="" style="vertical-align:middle;margin-right:5px;">Play</button>
-                        <button class="btn btn-secondary" id="btn-shuffle"><img src="img/icons/shuffle_white.svg" width="15" height="15" alt="" style="vertical-align:middle;margin-right:5px;">Shuffle</button>
-                    </div>
-                </div>
-            </div>
-        </div>
-        <button id="detail-back" onclick="showLibrary()"><img src="img/icons/back_arrow_white.svg" width="14" height="14" alt="" style="vertical-align:middle;margin-right:4px;">Back</button>
-
-        <div id="season-tabs"></div>
-
-        <div id="episode-scroll">
-            <div id="episode-list"></div>
-        </div>
-    </div>
-
-    <!-- ═══════════════ PLAYER VIEW ════════════════════════════════════════ -->
-    <div id="view-player" class="view">
-        <div id="video-container">
-            <button id="player-back" onclick="closePlayer()"><img src="img/icons/back_arrow_white.svg" width="14" height="14" alt="" style="vertical-align:middle;margin-right:4px;">Back</button>
-            <video id="main-video" preload="metadata"></video>
-
-            <!-- Auto-play countdown -->
-            <div id="next-countdown">
-                <div id="next-countdown-text">Next episode in <span id="countdown-num">5</span>s&hellip;</div>
-                <div id="next-countdown-track"><div id="next-countdown-bar" style="width:100%"></div></div>
-                <button id="next-countdown-cancel" onclick="cancelCountdown()">Cancel</button>
-            </div>
-
-            <!-- Player context menu -->
-            <div id="player-ctx">
-                <div class="ctx-item" id="ctx-play"><i class="ctx-icon">▶</i>Play</div>
-                <div class="ctx-item" id="ctx-pause"><i class="ctx-icon">⏸</i>Pause</div>
-                <div class="ctx-divider"></div>
-                <div class="ctx-item" id="ctx-prev"><i class="ctx-icon">⏮</i>Previous</div>
-                <div class="ctx-item" id="ctx-next"><i class="ctx-icon">⏭</i>Next</div>
-                <div class="ctx-divider"></div>
-                <div class="ctx-item" id="ctx-repeat"><i class="ctx-icon">↺</i>Repeat: Off</div>
-                <div class="ctx-divider"></div>
-                <div class="ctx-item" id="ctx-props"><i class="ctx-icon">ℹ</i>Video Properties</div>
-                <!-- <div class="ctx-item" id="ctx-stats"><i class="ctx-icon">⧉</i>Streaming Stats</div> -->
-            </div>
-
-            <!-- Video info / stats modal -->
-            <div id="video-info-modal">
-                <h3><span id="video-info-title">Video Properties</span><button id="video-info-close" onclick="closeVideoInfo()">✕</button></h3>
-                <div id="video-info-tabs">
-                    <div class="info-tab active" onclick="showInfoTab('props')">Properties</div>
-                    <div class="info-tab" onclick="showInfoTab('stats')">Stats</div>
-                </div>
-                <div id="video-info-body"></div>
-            </div>
-
-            <!-- Custom controls -->
-            <div id="video-controls">
-                <div id="progress-wrap">
-                    <div id="progress-bar" style="width:0%"></div>
-                    <div id="progress-thumb" style="left:0%"></div>
-                </div>
-                <div id="controls-row">
-                    <button class="ctrl-btn" id="ctrl-prev"    title="Previous (←)"><img src="img/icons/skip_previous_white.svg" alt=""></button>
-                    <button class="ctrl-btn" id="ctrl-play"    title="Play/Pause (Space)"><img id="play-icon" src="img/icons/play_white.svg" alt=""></button>
-                    <button class="ctrl-btn" id="ctrl-next"    title="Next (→)"><img src="img/icons/skip_next_white.svg" alt=""></button>
-                    <div id="volume-wrap">
-                        <button class="ctrl-btn" id="ctrl-mute" title="Mute (M)"><img id="mute-icon" src="img/icons/volume_white.svg" alt=""></button>
-                        <input id="volume-slider" type="range" min="0" max="1" step="0.05" value="1">
-                    </div>
-                    <span id="time-display">0:00 / 0:00</span>
-                    <span id="now-playing-title"></span>
-                    <button class="ctrl-btn" id="ctrl-list"   title="Episode list (L)"><img src="img/icons/menu_white.svg" alt=""></button>
-                    <button class="ctrl-btn" id="ctrl-fs"     title="Fullscreen (F)"><img src="img/icons/fullscreen_white.svg" alt=""></button>
-                </div>
-            </div>
-        </div>
-
-        <!-- Sidebar playlist -->
-        <div id="playlist-sidebar">
-            <div id="sidebar-header">
-                <span>Playlist</span>
-                <label class="autoplay-label" title="Auto-play next episode">
-                    <input type="checkbox" id="autoplay-check">
-                    <span class="autoplay-track"></span>
-                    <span>Auto</span>
-                </label>
-                <button id="sidebar-close" onclick="toggleSidebar()"><img style="width: 16px;" src="img/icons/close_white.svg" alt=""></button>
-            </div>
-            <div id="sidebar-list"></div>
-        </div>
-    </div>
-
-</div>
-
-<!-- Toast -->
-<div id="toast"></div>
-
-<!-- ═══════════════ JAVASCRIPT ════════════════════════════════════════════════ -->
-<script>
-// ─── All configurable paths come from backend/common.js ──────────────────────
-// (SCRIPT_GET_LIBRARY, SCRIPT_GET_EPISODES, SCRIPT_GET_THUMBNAIL, MEDIA_API)
-
-// ─── App state ────────────────────────────────────────────────────────────────
-var library        = [];   // full album array from server
-var currentAlbum   = null; // album object currently shown in detail view
-var currentSeason  = null; // season object currently active
-var currentEpisodes = [];  // flat episode array for current season/album
-var playingIndex   = -1;   // index in currentEpisodes being played
-
-// TV-remote focus management
-var focusMode      = false;  // set to true when arrow-key pressed
-var focusedEl      = null;
-
-// Controls auto-hide timer
-var controlsTimer  = null;
-
-// Library pagination
-var PAGE_SIZE   = 24;
-var moviesData  = [];
-var showsData   = [];
-var shortsData  = [];
-var moviesShown = 0;
-var showsShown  = 0;
-var shortsShown = 0;
-
-// Auto-play between episodes
-var autoplayEnabled = localStorage.getItem('movie_autoplay') !== '0'; // on by default
-var countdownTimer  = null;
-
-// Single-repeat
-var repeatSingle = false;
-
-// ─── Init ─────────────────────────────────────────────────────────────────────
-$(document).ready(function () {
-    loadLibrary();
-    initVideoControls();
-    initKeyboard();
-    initSearch();
-    initContextMenu();
-    // Autoplay toggle
-    $('#autoplay-check').prop('checked', autoplayEnabled);
-    $('#autoplay-check').on('change', function () {
-        autoplayEnabled = $(this).is(':checked');
-        localStorage.setItem('movie_autoplay', autoplayEnabled ? '1' : '0');
-    });
-});
-
-// ─── Load library from backend ────────────────────────────────────────────────
-function loadLibrary() {
-    ao_module_agirun(SCRIPT_GET_LIBRARY, {}, function (data) {
-        $('#loading-overlay').fadeOut(300);
-        if (!data || data.error) {
-            showToast('Failed to load library');
-            return;
-        }
-        library = data;
-        renderLibrary(library);
-    }, function () {
-        $('#loading-overlay').fadeOut(300);
-        showToast('Error loading library');
-    });
-}
-
-// ─── Render library grid ──────────────────────────────────────────────────────
-function renderLibrary(albums) {
-    // Movies: non-short, single-file  |  Shows: multi-episode  |  Shorts: type=short
-    moviesData  = albums.filter(function (a) { return a.type !== 'series' && a.type !== 'short' && a.episodeCount === 1; });
-    showsData   = albums.filter(function (a) { return a.episodeCount > 1; });
-    shortsData  = albums.filter(function (a) { return a.type === 'short'; });
-    moviesShown = 0;
-    showsShown  = 0;
-    shortsShown = 0;
-    $('#movies-grid').empty();
-    $('#series-grid').empty();
-    $('#shorts-grid').empty();
-
-    if (moviesData.length === 0 && showsData.length === 0 && shortsData.length === 0) {
-        $('#no-content').show();
-        return;
-    }
-    $('#no-content').hide();
-
-    if (moviesData.length > 0) {
-        $('#movies-section').show();
-        loadMoreSection('movies');
-    } else {
-        $('#movies-section').hide();
-    }
-
-    if (showsData.length > 0) {
-        $('#series-section').show();
-        loadMoreSection('shows');
-    } else {
-        $('#series-section').hide();
-    }
-
-    if (shortsData.length > 0) {
-        $('#shorts-section').show();
-        loadMoreSection('shorts');
-    } else {
-        $('#shorts-section').hide();
-    }
-}
-
-function loadMoreSection(which) {
-    var data, $grid, $btn, start, end, i;
-    if (which === 'movies') {
-        data  = moviesData;
-        $grid = $('#movies-grid');
-        $btn  = $('#movies-load-more');
-        start = moviesShown;
-        end   = Math.min(start + PAGE_SIZE, data.length);
-        for (i = start; i < end; i++) { $grid.append(buildCard(data[i], i)); }
-        moviesShown = end;
-        $btn.toggle(moviesShown < data.length);
-    } else if (which === 'shows') {
-        data  = showsData;
-        $grid = $('#series-grid');
-        $btn  = $('#shows-load-more');
-        start = showsShown;
-        end   = Math.min(start + PAGE_SIZE, data.length);
-        for (i = start; i < end; i++) { $grid.append(buildCard(data[i], i)); }
-        showsShown = end;
-        $btn.toggle(showsShown < data.length);
-    } else {
-        data  = shortsData;
-        $grid = $('#shorts-grid');
-        $btn  = $('#shorts-load-more');
-        start = shortsShown;
-        end   = Math.min(start + PAGE_SIZE, data.length);
-        for (i = start; i < end; i++) { $grid.append(buildCard(data[i], i)); }
-        shortsShown = end;
-        $btn.toggle(shortsShown < data.length);
-    }
-    loadCardThumbnails();
-}
-
-function buildCard(album, idx) {
-    var thumb = album.thumbnail && album.thumbnail.length > 0
-        ? '<img class="poster" src="data:image/jpeg;base64,' + album.thumbnail + '" alt="">'
-        : '<div class="poster-placeholder"><img src="img/thumbnail.png" alt=""></div>';
-
-    var badge = album.type === 'series' ? '<span class="badge series">Series</span>' : '';
-    var ext   = album._singleFile ? album._singleFile.split('.').pop().toUpperCase() : '';
-    var meta  = album.type === 'series'
-        ? album.episodeCount + ' ep'
-        : album.type === 'short'
-            ? (ext || 'Short')
-            : album.episodeCount + (album.episodeCount > 1 ? ' parts' : ' movie');
-
-    var card = $('<div class="album-card" tabindex="0" role="button" aria-label="' + escapeAttr(album.name) + '">'
-        + thumb
-        + badge
-        + '<div class="card-info">'
-        +   '<div class="card-title">' + escapeHtml(album.name) + '</div>'
-        +   '<div class="card-meta">' + escapeHtml(meta) + '</div>'
-        + '</div>'
-        + '</div>');
-
-    card.data('album', album);
-    card.on('click', function () {
-        if (album.type === 'short' && album._singleFile) {
-            currentAlbum = album; currentSeason = null;
-            currentEpisodes = [{ name: album.name, filepath: album._singleFile,
-                ext: album._singleFile.split('.').pop().toLowerCase(), index: 0 }];
-            startPlayback(0);
-        } else { openDetail(album); }
-    });
-    card.on('keydown', function (e) {
-        if (e.key === 'Enter' || e.key === ' ') {
-            e.preventDefault();
-            if (album.type === 'short' && album._singleFile) {
-                currentAlbum = album; currentSeason = null;
-                currentEpisodes = [{ name: album.name, filepath: album._singleFile,
-                    ext: album._singleFile.split('.').pop().toLowerCase(), index: 0 }];
-                startPlayback(0);
-            } else { openDetail(album); }
-        }
-    });
-    return card;
-}
-
-// Load thumbnails in background (for cards that don't have one embedded)
-function loadCardThumbnails() {
-    $('.album-card').each(function () {
-        var $card = $(this);
-        var album = $card.data('album');
-        if (!album || album.thumbnail) { return; } // already has thumb
-        ao_module_agirun(SCRIPT_GET_THUMBNAIL, { file: album.folderpath }, function (data) {
-            if (data && !data.error && data.length > 20) {
-                $card.find('.poster-placeholder')
-                    .replaceWith('<img class="poster" src="data:image/jpeg;base64,' + data + '" alt="">');
-            }
-        });
-    });
-}
-
-// ─── Detail view ──────────────────────────────────────────────────────────────
-function openDetail(album) {
-    currentAlbum = album;
-
-    // Hero background
-    var bg = album.thumbnail ? 'data:image/jpeg;base64,' + album.thumbnail : '';
-    $('#detail-hero-bg').css('background-image', bg ? 'url(' + bg + ')' : 'none');
-
-    // Poster
-    var posterHtml = album.thumbnail
-        ? '<img src="data:image/jpeg;base64,' + album.thumbnail + '" alt="" style="width:100%;aspect-ratio:2/3;object-fit:cover;">'
-        : '<div class="poster-placeholder"><img src="img/icons/movie_white.svg" alt=""></div>';
-    $('#detail-poster').html(posterHtml);
-
-    // Title & subtitle
-    $('#detail-title').text(album.name);
-    var sub = album.type === 'series'
-        ? album.seasons.length + ' season' + (album.seasons.length !== 1 ? 's' : '') + ' · ' + album.episodeCount + ' episodes'
-        : album.episodeCount + (album.episodeCount > 1 ? ' parts' : ' movie');
-    $('#detail-subtitle').text(sub);
-
-    // Season tabs
-    var $tabs = $('#season-tabs').empty();
-    if (album.type === 'series' && album.seasons.length > 0) {
-        album.seasons.forEach(function (s, i) {
-            var tab = $('<button class="season-tab" tabindex="0">' + escapeHtml(s.name) + '</button>');
-            tab.data('season', s);
-            tab.on('click', function () { selectSeason($(this).data('season')); activateTab(this); });
-            $tabs.append(tab);
-        });
-        $tabs.show();
-        selectSeason(album.seasons[0]);
-        $tabs.find('.season-tab').first().addClass('active');
-    } else {
-        $tabs.hide();
-        // Load the folder directly as episodes
-        loadEpisodes(album.folderpath);
-    }
-
-    // Play / shuffle button bindings
-    $('#btn-play-first').off('click').on('click', function () {
-        if (currentEpisodes.length > 0) { startPlayback(0); }
-    });
-    $('#btn-shuffle').off('click').on('click', function () {
-        if (currentEpisodes.length === 0) { return; }
-        var idx = Math.floor(Math.random() * currentEpisodes.length);
-        startPlayback(idx);
-    });
-
-    showView('detail');
-}
-
-function selectSeason(season) {
-    currentSeason = season;
-    loadEpisodes(season.folderpath);
-}
-
-function activateTab(el) {
-    $('#season-tabs .season-tab').removeClass('active');
-    $(el).addClass('active');
-}
-
-function loadEpisodes(folderpath) {
-    $('#episode-list').html('<div style="color:var(--text-sub);padding:20px 0;">Loading…</div>');
-    ao_module_agirun(SCRIPT_GET_EPISODES, { folder: folderpath }, function (data) {
-        if (!data || data.error) {
-            $('#episode-list').html('<div style="color:var(--text-sub);padding:20px 0;">No episodes found.</div>');
-            currentEpisodes = [];
-            return;
-        }
-        currentEpisodes = data;
-        renderEpisodes(data);
-    }, function () {
-        $('#episode-list').html('<div style="color:var(--text-sub);padding:20px 0;">Error loading episodes.</div>');
-    });
-}
-
-function renderEpisodes(episodes) {
-    var $list = $('#episode-list').empty();
-    episodes.forEach(function (ep, i) {
-        var row = $('<div class="episode-item" tabindex="0" role="button">'
-            + '<div class="ep-thumb"><div class="ep-thumb-placeholder"><img src="img/icons/play_white.svg" alt=""></div></div>'
-            + '<div class="ep-info">'
-            +   '<div class="ep-name">' + escapeHtml(ep.name) + '</div>'
-            +   '<div class="ep-path">' + escapeHtml(ep.ext.replace('.', '').toUpperCase()) + '</div>'
-            + '</div>'
-            + '<div class="ep-play-icon"><img src="img/icons/play_white.svg" alt=""></div>'
-            + '</div>');
-        row.data('ep', ep);
-        row.data('idx', i);
-        row.on('click', function () { startPlayback($(this).data('idx')); });
-        row.on('keydown', function (e) {
-            if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); startPlayback($(this).data('idx')); }
-        });
-        $list.append(row);
-
-        // Lazy-load thumbnail
-        (function (epObj, rowEl) {
-            ao_module_agirun(SCRIPT_GET_THUMBNAIL, { file: epObj.filepath }, function (data) {
-                if (data && !data.error && data.length > 20) {
-                    rowEl.find('.ep-thumb-placeholder')
-                        .replaceWith('<img src="data:image/jpeg;base64,' + data + '" alt="">');
-                }
-            });
-        })(ep, row);
-    });
-}
-
-// ─── Player ───────────────────────────────────────────────────────────────────
-function isWebPlayable(ext) {
-    return ['mp4', 'webm', 'ogg'].includes(ext);
-}
-function startPlayback(index) {
-    cancelCountdown();
-    if (!currentEpisodes || currentEpisodes.length === 0) { return; }
-    playingIndex = index;
-    var ep = currentEpisodes[index];
-
-    // Choose endpoint based on file extension
-    var ext = ep.ext ? ep.ext.toLowerCase().replace(/^\./, '') : '';
-    var src = '';
-    if (isWebPlayable(ext)) {
-        src = MEDIA_API + '?file=' + encodeURIComponent(ep.filepath);
-    } else {
-        src = TRANSCODE_API + '?file=' + encodeURIComponent(ep.filepath);
-    }
-    var vid = document.getElementById('main-video');
-    vid.src = src;
-    vid.play();
-
-    $('#now-playing-title').text(ep.name);
-    ao_module_setWindowTitle('Movie – ' + ep.name);
-
-    // Build sidebar list
-    renderSidebar(currentEpisodes, index);
-
-    // Highlight in episode list
-    highlightPlayingEpisode(index);
-
-    showView('player');
-    showControls();
-}
-
-function renderSidebar(episodes, playing) {
-    var $list = $('#sidebar-list').empty();
-    episodes.forEach(function (ep, i) {
-        var row = $('<div class="sidebar-ep' + (i === playing ? ' playing' : '') + '" tabindex="0" role="button">'
-            + '<span class="sidebar-ep-num">' + (i + 1) + '</span>'
-            + '<span class="sidebar-ep-name">' + escapeHtml(ep.name) + '</span>'
-            + '</div>');
-        row.data('idx', i);
-        row.on('click', function () {
-            cancelCountdown();
-            var idx = $(this).data('idx');
-            playingIndex = idx;
-            var e2 = currentEpisodes[idx];
-            var ext = e2.ext ? e2.ext.toLowerCase().replace(/^\./, '') : '';
-            var src = '';
-            if (isWebPlayable(ext)) {
-                src = MEDIA_API + '?file=' + encodeURIComponent(e2.filepath);
-            } else {
-                src = TRANSCODE_API + '?file=' + encodeURIComponent(e2.filepath);
-            }
-            var vid = document.getElementById('main-video');
-            vid.src = src;
-            vid.play();
-            $('#now-playing-title').text(e2.name);
-            ao_module_setWindowTitle('Movie – ' + e2.name);
-            highlightPlayingEpisode(idx);
-            renderSidebar(currentEpisodes, idx);
-        });
-        $list.append(row);
-    });
-    // Scroll to playing
-    var $playing = $list.find('.playing');
-    if ($playing.length) {
-        setTimeout(function () { $playing[0].scrollIntoView({ block: 'center' }); }, 50);
-    }
-}
-
-function highlightPlayingEpisode(idx) {
-    $('#episode-list .episode-item').removeClass('playing');
-    $('#episode-list .episode-item').eq(idx).addClass('playing');
-}
-
-function closePlayer() {
-    cancelCountdown();
-    var vid = document.getElementById('main-video');
-    vid.pause();
-    vid.src = '';
-    // Return to detail only if user was watching a series with a detail page;
-    // for movies/shorts go straight back to library.
-    showView(currentAlbum && currentAlbum.type === 'series' ? 'detail' : 'library');
-}
-
-function showLibrary() {
-    showView('library');
-    currentAlbum = null;
-}
-
-// ─── View switching ───────────────────────────────────────────────────────────
-function showView(name) {
-    $('.view').removeClass('active');
-    $('#view-' + name).addClass('active');
-}
-
-// ─── Video controls ───────────────────────────────────────────────────────────
-function initVideoControls() {
-    var vid = document.getElementById('main-video');
-    var $ctrl = $('#video-controls');
-    var $prog = $('#progress-bar');
-    var $thumb = $('#progress-thumb');
-    var $time  = $('#time-display');
-    var $play  = $('#ctrl-play');
-    var $mute  = $('#ctrl-mute');
-
-    // Restore saved volume from last session
-    var savedVol = parseFloat(localStorage.getItem('movie_volume'));
-    if (!isNaN(savedVol) && savedVol >= 0 && savedVol <= 1) {
-        vid.volume = savedVol;
-        $('#volume-slider').val(savedVol);
-    }
-    if (localStorage.getItem('movie_muted') === '1') { vid.muted = true; }
-
-    // Play/Pause
-    $('#ctrl-play').on('click', function () { togglePlay(); });
-    $('#ctrl-mute').on('click', function () { vid.muted = !vid.muted; updateMuteIcon(); });
-    $('#ctrl-prev').on('click', function () { playOffset(-1); });
-    $('#ctrl-next').on('click', function () { playOffset(1); });
-    $('#ctrl-fs').on('click', function () { toggleFullscreen(); });
-    $('#ctrl-list').on('click', function () { toggleSidebar(); });
-
-    $('#volume-slider').on('input', function () {
-        vid.volume = parseFloat($(this).val());
-        updateMuteIcon();
-    });
-
-    // Progress bar click / drag
-    $('#progress-wrap').on('click', function (e) {
-        if (vid.duration) {
-            var pct = e.offsetX / $(this).width();
-            vid.currentTime = pct * vid.duration;
-        }
-    });
-
-    // Video events
-    $(vid).on('timeupdate', function () {
-        if (!vid.duration) { return; }
-        var pct = (vid.currentTime / vid.duration) * 100;
-        $prog.css('width', pct + '%');
-        $thumb.css('left', 'calc(' + pct + '% - 7px)');
-        $time.text(formatTime(vid.currentTime) + ' / ' + formatTime(vid.duration));
-    });
-
-    $(vid).on('play',  function () { $('#play-icon').attr('src', 'img/icons/pause_white.svg'); });
-    $(vid).on('pause', function () { $('#play-icon').attr('src', 'img/icons/play_white.svg'); });
-    $(vid).on('ended', function () {
-        cancelCountdown();
-        if (repeatSingle) {
-            vid.currentTime = 0;
-            vid.play();
-        } else if (playingIndex < currentEpisodes.length - 1 && autoplayEnabled) {
-            startNextCountdown();
-        }
-    });
-    $(vid).on('volumechange', function () {
-        updateMuteIcon();
-        localStorage.setItem('movie_volume', vid.volume);
-        localStorage.setItem('movie_muted', vid.muted ? '1' : '0');
-    });
-
-    // Auto-hide controls
-    $('#video-container').on('mousemove touchstart', function () { showControls(); });
-
-    // Click on video = play/pause
-    $(vid).on('click', function () { togglePlay(); });
-
-    // Hide back button when in fullscreen (JS reinforcement)
-    document.addEventListener('fullscreenchange', function () {
-        $('#player-back').toggle(!document.fullscreenElement);
-    });
-    document.addEventListener('webkitfullscreenchange', function () {
-        $('#player-back').toggle(!document.webkitFullscreenElement);
-    });
-
-    function updateMuteIcon() {
-        var isMuted = vid.muted || vid.volume === 0;
-        $('#mute-icon').attr('src', isMuted ? 'img/icons/mute_white.svg' : 'img/icons/volume_white.svg');
-        $('#volume-slider').val(vid.muted ? 0 : vid.volume);
-    }
-}
-
-function togglePlay() {
-    var vid = document.getElementById('main-video');
-    if (vid.paused) { vid.play(); } else { vid.pause(); }
-}
-
-function playOffset(offset) {
-    var next = playingIndex + offset;
-    if (next < 0) { next = 0; }
-    if (next >= currentEpisodes.length) { next = currentEpisodes.length - 1; }
-    if (next !== playingIndex) { startPlayback(next); }
-}
-
-function showControls() {
-    $('#video-controls').removeClass('hidden');
-    clearTimeout(controlsTimer);
-    controlsTimer = setTimeout(function () {
-        var vid = document.getElementById('main-video');
-        if (!vid.paused) { $('#video-controls').addClass('hidden'); }
-    }, 3000);
-}
-
-function toggleFullscreen() {
-    var el = document.getElementById('view-player');
-    if (!document.fullscreenElement) {
-        el.requestFullscreen && el.requestFullscreen();
-    } else {
-        document.exitFullscreen && document.exitFullscreen();
-    }
-}
-
-function toggleSidebar() {
-    $('#playlist-sidebar').toggleClass('collapsed');
-}
-
-function startNextCountdown() {
-    var total = 5;
-    var remaining = total;
-    var $bar = $('#next-countdown-bar');
-    var $num = $('#countdown-num');
-    $bar.css('transition', 'none').css('width', '100%');
-    $num.text(remaining);
-    $('#next-countdown').show();
-    countdownTimer = setInterval(function () {
-        remaining--;
-        $num.text(remaining);
-        $bar.css('transition', 'width 1s linear').css('width', (remaining / total * 100) + '%');
-        if (remaining <= 0) { cancelCountdown(); playOffset(1); }
-    }, 1000);
-}
-
-function cancelCountdown() {
-    if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; }
-    $('#next-countdown').hide();
-}
-
-// ─── Player context menu ──────────────────────────────────────────────────────
-var infoRefreshTimer = null;
-var currentInfoTab   = 'props';
-
-function initContextMenu() {
-    var vid = document.getElementById('main-video');
-    var $ctx = $('#player-ctx');
-
-    // Right-click on the video container
-    $('#video-container').on('contextmenu', function (e) {
-        e.preventDefault();
-        if (playingIndex < 0) { return; }
-
-        // Update item disabled / active states
-        var paused = vid.paused;
-        $('#ctx-play').toggleClass('ctx-disabled', !paused);
-        $('#ctx-pause').toggleClass('ctx-disabled', paused);
-        $('#ctx-prev').toggleClass('ctx-disabled', playingIndex <= 0);
-        $('#ctx-next').toggleClass('ctx-disabled', playingIndex >= currentEpisodes.length - 1);
-        $('#ctx-repeat').toggleClass('ctx-active', repeatSingle)
-            .html('<i class="ctx-icon">' + (repeatSingle ? '✓' : '↺') + '</i>Repeat: ' + (repeatSingle ? 'On' : 'Off'));
-
-        // Position menu, clamp inside container
-        var rect = this.getBoundingClientRect();
-        var x = e.clientX - rect.left;
-        var y = e.clientY - rect.top;
-        $ctx.css({ left: x, top: y, display: 'block' });
-        var mw = $ctx.outerWidth(), mh = $ctx.outerHeight();
-        if (x + mw > rect.width)  { $ctx.css('left', Math.max(0, x - mw)); }
-        if (y + mh > rect.height) { $ctx.css('top',  Math.max(0, y - mh)); }
-        showControls();
-    });
-
-    // Dismiss on any click outside the menu
-    $(document).on('mousedown.ctx', function (e) {
-        if (!$(e.target).closest('#player-ctx').length) { $ctx.hide(); }
-    });
-    // Dismiss on Escape
-    $(document).on('keydown.ctx', function (e) {
-        if (e.key === 'Escape') { $ctx.hide(); closeVideoInfo(); }
-    });
-
-    $('#ctx-play').on('click',   function () { vid.play();               $ctx.hide(); });
-    $('#ctx-pause').on('click',  function () { vid.pause();              $ctx.hide(); });
-    $('#ctx-prev').on('click',   function () { cancelCountdown(); playOffset(-1); $ctx.hide(); });
-    $('#ctx-next').on('click',   function () { cancelCountdown(); playOffset(1);  $ctx.hide(); });
-    $('#ctx-repeat').on('click', function () { repeatSingle = !repeatSingle;      $ctx.hide(); });
-    $('#ctx-props').on('click',  function () { $ctx.hide(); openVideoInfo('props'); });
-    $('#ctx-stats').on('click',  function () { $ctx.hide(); openVideoInfo('stats'); });
-}
-
-function openVideoInfo(tab) {
-    currentInfoTab = tab || 'props';
-    $('#video-info-modal').show();
-    $('.info-tab').removeClass('active').eq(currentInfoTab === 'props' ? 0 : 1).addClass('active');
-    $('#video-info-title').text(currentInfoTab === 'props' ? 'Video Properties' : 'Streaming Stats');
-    renderInfoContent();
-    if (infoRefreshTimer) { clearInterval(infoRefreshTimer); infoRefreshTimer = null; }
-    if (currentInfoTab === 'stats') {
-        infoRefreshTimer = setInterval(renderInfoContent, 1000);
-    }
-}
-
-function closeVideoInfo() {
-    $('#video-info-modal').hide();
-    if (infoRefreshTimer) { clearInterval(infoRefreshTimer); infoRefreshTimer = null; }
-}
-
-function showInfoTab(tab) {
-    currentInfoTab = tab;
-    $('.info-tab').removeClass('active').eq(tab === 'props' ? 0 : 1).addClass('active');
-    $('#video-info-title').text(tab === 'props' ? 'Video Properties' : 'Streaming Stats');
-    if (infoRefreshTimer) { clearInterval(infoRefreshTimer); infoRefreshTimer = null; }
-    if (tab === 'stats') { infoRefreshTimer = setInterval(renderInfoContent, 1000); }
-    renderInfoContent();
-}
-
-function infoRow(label, value) {
-    return '<div class="info-row"><span class="info-label">' + escapeHtml(String(label)) + '</span>'
-        + '<span class="info-value">' + escapeHtml(String(value)) + '</span></div>';
-}
-
-function renderInfoContent() {
-    var vid = document.getElementById('main-video');
-    var ep  = (playingIndex >= 0 && currentEpisodes[playingIndex]) ? currentEpisodes[playingIndex] : null;
-    var html = '';
-
-    if (currentInfoTab === 'props') {
-        html += infoRow('Title',          ep ? ep.name : '–');
-        html += infoRow('Resolution',     (vid.videoWidth && vid.videoHeight)
-            ? vid.videoWidth + ' × ' + vid.videoHeight : '–');
-        html += infoRow('Duration',       vid.duration   ? formatTime(vid.duration)    : '–');
-        html += infoRow('Position',       vid.currentTime ? formatTime(vid.currentTime) : '–');
-        html += infoRow('Playback speed', vid.playbackRate + '×');
-        html += infoRow('Volume',         vid.muted ? 'Muted' : Math.round(vid.volume * 100) + '%');
-        if (ep) { html += infoRow('File path', ep.filepath || '–'); }
-    } else {
-        var rsLabels = ['No info', 'Metadata only', 'Have current data', 'Have future data', 'Enough data'];
-        var nsLabels = ['Empty', 'Idle', 'Loading', 'No source'];
-        html += infoRow('Ready state',   rsLabels[vid.readyState]  || vid.readyState);
-        html += infoRow('Network state', nsLabels[vid.networkState] || vid.networkState);
-
-        // Buffer ahead
-        var bufEnd = 0;
-        if (vid.buffered && vid.buffered.length > 0) {
-            for (var i = 0; i < vid.buffered.length; i++) {
-                if (vid.buffered.start(i) <= vid.currentTime + 0.1) {
-                    bufEnd = Math.max(bufEnd, vid.buffered.end(i));
-                }
-            }
-        }
-        html += infoRow('Buffer ahead',   Math.max(0, bufEnd - vid.currentTime).toFixed(1) + 's');
-        html += infoRow('Total buffered',  vid.buffered.length > 0
-            ? formatTime(vid.buffered.end(vid.buffered.length - 1)) : '–');
-
-        if (vid.getVideoPlaybackQuality) {
-            var q = vid.getVideoPlaybackQuality();
-            html += infoRow('Frames decoded',  q.totalVideoFrames   || 0);
-            html += infoRow('Frames dropped',  q.droppedVideoFrames || 0);
-        }
-        html += infoRow('Stalled / ended', vid.ended ? 'Ended' : (vid.readyState < 3 ? 'Yes' : 'No'));
-    }
-    $('#video-info-body').html(html);
-}
-
-function formatTime(s) {
-    if (isNaN(s)) { return '0:00'; }
-    var h = Math.floor(s / 3600);
-    var m = Math.floor((s % 3600) / 60);
-    var sec = Math.floor(s % 60);
-    var parts = [];
-    if (h > 0) { parts.push(h); }
-    parts.push(m);
-    parts.push(sec < 10 ? '0' + sec : sec);
-    return parts.join(':');
-}
-
-// ─── Keyboard / TV-remote navigation ─────────────────────────────────────────
-function initKeyboard() {
-    $(document).on('keydown', function (e) {
-        var activeView = $('.view.active').attr('id');
-
-        // ── Player shortcuts ──────────────────────────────────────────────────
-        if (activeView === 'view-player') {
-            var vid = document.getElementById('main-video');
-            switch (e.key) {
-                case ' ':
-                case 'k':
-                    e.preventDefault(); togglePlay(); showControls(); break;
-                case 'ArrowRight':
-                    e.preventDefault(); vid.currentTime = Math.min(vid.duration || 0, vid.currentTime + 10); showControls(); break;
-                case 'ArrowLeft':
-                    e.preventDefault(); vid.currentTime = Math.max(0, vid.currentTime - 10); showControls(); break;
-                case 'ArrowUp':
-                    e.preventDefault(); vid.volume = Math.min(1, vid.volume + 0.1); showControls(); break;
-                case 'ArrowDown':
-                    e.preventDefault(); vid.volume = Math.max(0, vid.volume - 0.1); showControls(); break;
-                case 'f':
-                case 'F':
-                    e.preventDefault(); toggleFullscreen(); break;
-                case 'm':
-                case 'M':
-                    e.preventDefault(); vid.muted = !vid.muted; break;
-                case 'l':
-                case 'L':
-                    e.preventDefault(); toggleSidebar(); break;
-                case 'n':
-                    e.preventDefault(); playOffset(1); break;
-                case 'p':
-                    e.preventDefault(); playOffset(-1); break;
-                case 'Escape':
-                    e.preventDefault(); closePlayer(); break;
-                case 'MediaPlayPause':
-                    e.preventDefault(); togglePlay(); break;
-                case 'MediaTrackNext':
-                    e.preventDefault(); playOffset(1); break;
-                case 'MediaTrackPrevious':
-                    e.preventDefault(); playOffset(-1); break;
-            }
-            return;
-        }
-
-        // ── Grid / detail navigation (TV remote) ──────────────────────────────
-        if (e.key === 'Escape') {
-            e.preventDefault();
-            if (activeView === 'view-detail') { showLibrary(); }
-            return;
-        }
-
-        if (['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','Enter'].indexOf(e.key) === -1) { return; }
-
-        e.preventDefault();
-        focusMode = true;
-
-        var $focusables = $('.view.active [tabindex="0"]:visible');
-        if ($focusables.length === 0) { return; }
-
-        var $cur = $(document.activeElement);
-        var curIdx = $focusables.index($cur);
-
-        if (curIdx < 0) {
-            // Nothing focused yet – focus first element
-            $focusables.first().focus();
-            return;
-        }
-
-        if (e.key === 'Enter') {
-            $cur.trigger('click');
-            return;
-        }
-
-        // Compute next focus index based on grid layout
-        var nextIdx = computeNextFocus($focusables, curIdx, e.key);
-        if (nextIdx >= 0 && nextIdx < $focusables.length) {
-            $focusables.eq(nextIdx).focus();
-            $focusables.eq(nextIdx)[0].scrollIntoView({ block: 'nearest', inline: 'nearest' });
-        }
-    });
-}
-
-function computeNextFocus($els, curIdx, key) {
-    if (key === 'ArrowDown') { return Math.min($els.length - 1, curIdx + 1); }
-    if (key === 'ArrowUp')   { return Math.max(0, curIdx - 1); }
-
-    // For left/right in a grid, figure out how many columns there are
-    var $cur = $els.eq(curIdx);
-    var $parent = $cur.parent();
-    if ($parent.hasClass('album-grid')) {
-        var colCount = Math.round($parent.width() / ($cur.outerWidth(true) || 1)) || 1;
-        if (key === 'ArrowRight') { return Math.min($els.length - 1, curIdx + 1); }
-        if (key === 'ArrowLeft')  { return Math.max(0, curIdx - 1); }
-    }
-    if (key === 'ArrowRight') { return Math.min($els.length - 1, curIdx + 1); }
-    if (key === 'ArrowLeft')  { return Math.max(0, curIdx - 1); }
-    return curIdx;
-}
-
-// ─── Search ───────────────────────────────────────────────────────────────────
-function initSearch() {
-    $('#search-input').on('input', function () {
-        var q = $(this).val().trim().toLowerCase();
-        if (q.length === 0) {
-            renderLibrary(library);
-            return;
-        }
-        var filtered = library.filter(function (a) {
-            return a.name.toLowerCase().indexOf(q) > -1;
-        });
-        renderLibrary(filtered);
-    });
-
-    // Prevent keyboard nav from hijacking search input
-    $('#search-input').on('keydown', function (e) {
-        e.stopPropagation();
-        if (e.key === 'Escape') { $(this).val(''); renderLibrary(library); $(this).blur(); }
-    });
-}
-
-// ─── Utilities ────────────────────────────────────────────────────────────────
-function escapeHtml(str) {
-    if (!str) { return ''; }
-    return String(str)
-        .replace(/&/g, '&amp;')
-        .replace(/</g, '&lt;')
-        .replace(/>/g, '&gt;')
-        .replace(/"/g, '&quot;');
-}
-function escapeAttr(str) { return escapeHtml(str); }
-
-var toastTimer;
-function showToast(msg) {
-    clearTimeout(toastTimer);
-    $('#toast').text(msg).addClass('show');
-    toastTimer = setTimeout(function () { $('#toast').removeClass('show'); }, 2800);
-}
-</script>
-</body>
-</html>

+ 474 - 1
src/web/Movie/index.html

@@ -583,6 +583,8 @@ html, body {
     #topbar-title { display: block; }
     /* Hide these from the bottom controls bar */
     #now-playing-title, #ctrl-list, #ctrl-cast { display: none !important; }
+    /* Push fullscreen to the far right now that the title spacer is gone */
+    #ctrl-fs { margin-left: auto; }
     /* Shrink volume slider to save space */
     #volume-slider { width: 56px; }
     #time-display { font-size: 11px; }
@@ -922,6 +924,123 @@ html, body {
 .ctx-item.ctx-disabled { opacity: 0.3; pointer-events: none; }
 .ctx-icon { width: 16px; text-align: center; flex-shrink: 0; font-style: normal; }
 .ctx-divider { height: 1px; background: rgba(255,255,255,0.08); margin: 3px 0; }
+.ctx-has-sub  { justify-content: space-between; position: relative; }
+.ctx-sub-arrow { font-style: normal; opacity: 0.55; font-size: 15px; }
+
+/* Subtitle submenu — positioned relative to the parent .ctx-has-sub item */
+#ctx-subtitle-sub {
+    display: none;
+    position: absolute;
+    top: 0;
+    left: 100%;
+    margin-left: 0;
+    background: rgba(28,28,30,0.97);
+    backdrop-filter: blur(16px);
+    -webkit-backdrop-filter: blur(16px);
+    border-radius: 10px;
+    padding: 4px 0;
+    min-width: 200px;
+    box-shadow: 0 4px 24px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.08);
+    z-index: 32;
+}
+
+/* Subtitle overlay */
+#subtitle-display {
+    display: none;
+    position: absolute;
+    bottom: 72px;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 90%;
+    text-align: center;
+    pointer-events: none;
+    z-index: 15;
+    color: #fff;
+    font-size: 22px;
+    font-weight: 500;
+    line-height: 1.45;
+    text-shadow:
+        -1px -1px 0 #000,  1px -1px 0 #000,
+        -1px  1px 0 #000,  1px  1px 0 #000,
+         0    2px 6px rgba(0,0,0,0.85);
+}
+
+/* ─── Subtitle settings modal ───────────────────────────────────────────────── */
+#subtitle-settings-modal {
+    display: none;
+    position: absolute;
+    top: 50%; left: 50%;
+    transform: translate(-50%, -50%);
+    z-index: 40;
+    background: rgba(18,18,20,0.97);
+    backdrop-filter: blur(20px);
+    -webkit-backdrop-filter: blur(20px);
+    border-radius: 14px;
+    padding: 20px 22px 18px;
+    width: 340px;
+    box-shadow: 0 8px 40px rgba(0,0,0,0.8), 0 0 0 1px rgba(255,255,255,0.1);
+    color: var(--text);
+}
+#subtitle-settings-modal h3 {
+    font-size: 14px; font-weight: 600;
+    margin-bottom: 14px;
+    display: flex; align-items: center; justify-content: space-between;
+}
+#sset-close {
+    background: none; border: none; cursor: pointer;
+    color: var(--text-sub); font-size: 17px; line-height: 1; padding: 0; outline: none;
+}
+#sset-close:hover { color: var(--text); }
+.sset-row {
+    display: flex; align-items: center; gap: 10px;
+    padding: 8px 0;
+    border-bottom: 1px solid rgba(255,255,255,0.05);
+    font-size: 12px;
+}
+.sset-row:last-child { border-bottom: none; }
+.sset-label { color: var(--text-sub); flex-shrink: 0; width: 56px; }
+.sset-seg {
+    display: flex; background: var(--surface2);
+    border-radius: 7px; padding: 2px; gap: 2px; flex: 1;
+}
+.sset-seg-btn {
+    flex: 1; padding: 4px 0; font-size: 12px; font-family: inherit;
+    border: none; border-radius: 5px; cursor: pointer;
+    background: transparent; color: var(--text-sub);
+    transition: background 0.15s, color 0.15s;
+}
+.sset-seg-btn.active { background: var(--surface); color: var(--text); }
+#sset-size { flex: 1; accent-color: var(--accent); cursor: pointer; }
+#sset-size-val { color: var(--text-sub); font-size: 11px; min-width: 32px; text-align: right; }
+#sset-font {
+    flex: 1; background: var(--surface2); color: var(--text);
+    border: 1px solid rgba(255,255,255,0.08); border-radius: 7px;
+    padding: 5px 8px; font-size: 12px; cursor: pointer; outline: none;
+    appearance: auto;
+}
+.sset-colors { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; flex: 1; }
+.sset-color {
+    width: 22px; height: 22px; border-radius: 50%;
+    border: 2px solid transparent; outline: 2px solid transparent;
+    cursor: pointer; padding: 0; flex-shrink: 0;
+    transition: outline-color 0.15s, transform 0.1s;
+}
+.sset-color:hover  { transform: scale(1.12); }
+.sset-color.active { outline-color: var(--text); transform: scale(1.15); }
+#sset-color-custom {
+    width: 22px; height: 22px; border-radius: 50%;
+    border: 2px solid rgba(255,255,255,0.2); padding: 0;
+    cursor: pointer; background: transparent; flex-shrink: 0;
+}
+#sset-color-custom.active { border-color: var(--text); transform: scale(1.15); }
+#sset-preview {
+    margin-top: 14px;
+    background: rgba(0,0,0,0.65);
+    border-radius: 8px;
+    padding: 14px 12px;
+    text-align: center;
+    line-height: 1.4;
+}
 
 /* ─── Video info modal ───────────────────────────────────────────────────────── */
 #video-info-modal {
@@ -1164,6 +1283,7 @@ html, body {
                 <button class="ctrl-btn" id="ctrl-cast-top" title="Cast to Arozcast" onclick="openCastDialog()"><img src="img/icons/cast_white.svg" alt="" width="18" height="18"></button>
             </div>
             <video id="main-video" preload="metadata"></video>
+            <div id="subtitle-display"></div>
 
             <!-- Resume-from-last-position popup -->
             <div id="resume-popup">
@@ -1192,10 +1312,65 @@ html, body {
                 <div class="ctx-divider"></div>
                 <div class="ctx-item" id="ctx-repeat"><i class="ctx-icon">↺</i>Repeat: Off</div>
                 <div class="ctx-divider"></div>
+                <div class="ctx-item ctx-has-sub" id="ctx-subtitle-parent">
+                    <span><i class="ctx-icon" style="display:inline-block">CC</i>Subtitles</span>
+                    <i class="ctx-sub-arrow">›</i>
+                    <!-- Subtitle submenu — child of parent item for easy relative positioning -->
+                    <div id="ctx-subtitle-sub">
+                        <div class="ctx-item ctx-active" id="ctx-sub-disable"><i class="ctx-icon">✓</i>Disable</div>
+                        <div class="ctx-divider"></div>
+                        <div class="ctx-item" id="ctx-sub-load"><i class="ctx-icon">+</i>Load SRT file…</div>
+                    </div>
+                </div>
+                <div class="ctx-divider"></div>
+                <div class="ctx-item" id="ctx-subtitle-settings"><i class="ctx-icon">⚙</i>Subtitle Settings</div>
                 <div class="ctx-item" id="ctx-props">Video Properties</div>
                 <!-- <div class="ctx-item" id="ctx-stats"><i class="ctx-icon">⧉</i>Streaming Stats</div> -->
             </div>
 
+            <!-- Playback / subtitle settings modal -->
+            <div id="subtitle-settings-modal">
+                <h3>Subtitle Settings<button id="sset-close">✕</button></h3>
+                <div class="sset-row">
+                    <span class="sset-label">Position</span>
+                    <div class="sset-seg">
+                        <button class="sset-seg-btn active" data-val="bottom">Bottom</button>
+                        <button class="sset-seg-btn"        data-val="top">Top</button>
+                    </div>
+                </div>
+                <div class="sset-row">
+                    <span class="sset-label">Size</span>
+                    <input type="range" id="sset-size" min="14" max="52" step="1" value="22">
+                    <span id="sset-size-val">22px</span>
+                </div>
+                <div class="sset-row">
+                    <span class="sset-label">Font</span>
+                    <select id="sset-font">
+                        <option value="system-ui, sans-serif">System Default</option>
+                        <option value="Arial, sans-serif">Arial</option>
+                        <option value="'Helvetica Neue', Helvetica, sans-serif">Helvetica</option>
+                        <option value="Georgia, serif">Georgia</option>
+                        <option value="'Times New Roman', Times, serif">Times New Roman</option>
+                        <option value="'Courier New', Courier, monospace">Courier New</option>
+                        <option value="Verdana, sans-serif">Verdana</option>
+                        <option value="'Trebuchet MS', sans-serif">Trebuchet MS</option>
+                        <option value="Impact, fantasy">Impact</option>
+                    </select>
+                </div>
+                <div class="sset-row">
+                    <span class="sset-label">Color</span>
+                    <div class="sset-colors">
+                        <button class="sset-color active" data-color="#ffffff" style="background:#ffffff" title="White"></button>
+                        <button class="sset-color"        data-color="#ffff00" style="background:#ffff00" title="Yellow"></button>
+                        <button class="sset-color"        data-color="#00ffff" style="background:#00ffff" title="Cyan"></button>
+                        <button class="sset-color"        data-color="#00ff88" style="background:#00ff88" title="Green"></button>
+                        <button class="sset-color"        data-color="#ff8800" style="background:#ff8800" title="Orange"></button>
+                        <input  type="color" id="sset-color-custom" value="#ffffff" title="Custom color">
+                    </div>
+                </div>
+                <div id="sset-preview">Sample subtitle text</div>
+            </div>
+
             <!-- Video info / stats modal -->
             <div id="video-info-modal">
                 <h3><span id="video-info-title">Video Properties</span><button id="video-info-close" onclick="closeVideoInfo()">✕</button></h3>
@@ -2004,6 +2179,8 @@ $(document).ready(function () {
     initKeyboard();
     initSearch();
     initContextMenu();
+    initSubtitleMenu();
+    initSubtitleSettings();
     // Autoplay toggle
     $('#autoplay-check').prop('checked', autoplayEnabled);
     $('#autoplay-check').on('change', function () {
@@ -2855,6 +3032,9 @@ function initContextMenu() {
         $('#ctx-repeat').toggleClass('ctx-active', repeatSingle)
             .html('<i class="ctx-icon">' + (repeatSingle ? '✓' : '↺') + '</i>Repeat: ' + (repeatSingle ? 'On' : 'Off'));
 
+        // Always collapse subtitle submenu on fresh open
+        $('#ctx-subtitle-sub').hide();
+
         // Position menu, clamp inside container
         var rect = this.getBoundingClientRect();
         var x = e.clientX - rect.left;
@@ -2872,7 +3052,7 @@ function initContextMenu() {
     });
     // Dismiss on Escape
     $(document).on('keydown.ctx', function (e) {
-        if (e.key === 'Escape') { $ctx.hide(); closeVideoInfo(); }
+        if (e.key === 'Escape') { $ctx.hide(); closeVideoInfo(); closeSubtitleSettings(); }
     });
 
     $('#ctx-play').on('click', function () {
@@ -2892,6 +3072,7 @@ function initContextMenu() {
     $('#ctx-prev').on('click',   function () { cancelCountdown(); playOffset(-1); $ctx.hide(); });
     $('#ctx-next').on('click',   function () { cancelCountdown(); playOffset(1);  $ctx.hide(); });
     $('#ctx-repeat').on('click', function () { repeatSingle = !repeatSingle;      $ctx.hide(); });
+    $('#ctx-subtitle-settings').on('click', function () { $ctx.hide(); openSubtitleSettings(); });
     $('#ctx-props').on('click',  function () { $ctx.hide(); openVideoInfo('props'); });
     $('#ctx-stats').on('click',  function () { $ctx.hide(); openVideoInfo('stats'); });
 }
@@ -3147,6 +3328,298 @@ function escapeHtml(str) {
 }
 function escapeAttr(str) { return escapeHtml(str); }
 
+// ─── Subtitle settings ────────────────────────────────────────────────────────
+var subtitleSettings = { position: 'bottom', size: 22, font: 'system-ui, sans-serif', color: '#ffffff' };
+
+function loadSubtitleSettings() {
+    try {
+        var s = JSON.parse(localStorage.getItem('movie_subtitle_settings') || 'null');
+        if (s) { Object.assign(subtitleSettings, s); }
+    } catch (e) {}
+}
+
+function saveSubtitleSettings() {
+    localStorage.setItem('movie_subtitle_settings', JSON.stringify(subtitleSettings));
+}
+
+function applySubtitleSettings() {
+    var s = subtitleSettings;
+    var $d = $('#subtitle-display');
+    $d.css({
+        'font-size':   s.size + 'px',
+        'font-family': s.font,
+        'color':       s.color,
+        'bottom':      s.position === 'bottom' ? '72px' : 'auto',
+        'top':         s.position === 'top'    ? '72px' : 'auto',
+        'transform':   'translateX(-50%)'
+    });
+}
+
+function syncSettingsPreview() {
+    var s = subtitleSettings;
+    $('#sset-preview').css({
+        'font-size':   s.size + 'px',
+        'font-family': s.font,
+        'color':       s.color,
+        'text-shadow': '-1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000, 0 2px 6px rgba(0,0,0,0.85)'
+    });
+}
+
+function openSubtitleSettings() {
+    var s = subtitleSettings;
+    // Sync all controls to current state
+    $('.sset-seg-btn').removeClass('active')
+        .filter('[data-val="' + s.position + '"]').addClass('active');
+    $('#sset-size').val(s.size);
+    $('#sset-size-val').text(s.size + 'px');
+    $('#sset-font').val(s.font);
+    var swatchMatch = $('.sset-color[data-color="' + s.color.toLowerCase() + '"]');
+    $('.sset-color, #sset-color-custom').removeClass('active');
+    if (swatchMatch.length) { swatchMatch.addClass('active'); }
+    else                    { $('#sset-color-custom').addClass('active'); }
+    $('#sset-color-custom').val(s.color);
+    syncSettingsPreview();
+    $('#subtitle-settings-modal').show();
+}
+
+function closeSubtitleSettings() {
+    $('#subtitle-settings-modal').hide();
+}
+
+function initSubtitleSettings() {
+    loadSubtitleSettings();
+    applySubtitleSettings();
+
+    $('#sset-close').on('click', closeSubtitleSettings);
+
+    // Position
+    $(document).on('click', '.sset-seg-btn', function () {
+        subtitleSettings.position = $(this).data('val');
+        $('.sset-seg-btn').removeClass('active');
+        $(this).addClass('active');
+        applySubtitleSettings();
+        saveSubtitleSettings();
+    });
+
+    // Size
+    $('#sset-size').on('input', function () {
+        subtitleSettings.size = parseInt($(this).val(), 10);
+        $('#sset-size-val').text(subtitleSettings.size + 'px');
+        applySubtitleSettings();
+        syncSettingsPreview();
+        saveSubtitleSettings();
+    });
+
+    // Font
+    $('#sset-font').on('change', function () {
+        subtitleSettings.font = $(this).val();
+        applySubtitleSettings();
+        syncSettingsPreview();
+        saveSubtitleSettings();
+    });
+
+    // Preset color swatches
+    $(document).on('click', '.sset-color', function () {
+        subtitleSettings.color = $(this).data('color');
+        $('.sset-color, #sset-color-custom').removeClass('active');
+        $(this).addClass('active');
+        $('#sset-color-custom').val(subtitleSettings.color);
+        applySubtitleSettings();
+        syncSettingsPreview();
+        saveSubtitleSettings();
+    });
+
+    // Custom color picker
+    $('#sset-color-custom').on('input', function () {
+        subtitleSettings.color = $(this).val();
+        $('.sset-color').removeClass('active');
+        $('#sset-color-custom').addClass('active');
+        applySubtitleSettings();
+        syncSettingsPreview();
+        saveSubtitleSettings();
+    });
+}
+
+// ─── Subtitle ─────────────────────────────────────────────────────────────────
+var loadedSubtitleFiles = [];   // [{path, name, cues:[{start,end,text}]}]
+var activeSubtitleIndex = -1;   // -1 = disabled
+
+// Window-scoped callback required by ao_module file selector.
+// The selector always delivers [{filename, filepath}, …]; handle gracefully.
+window.movieOnSubtitleFile = function (raw) {
+    console.log('[subtitle] callback fired, raw value:', raw);
+    // Unwrap array
+    var entry = Array.isArray(raw) ? raw[0] : raw;
+    console.log('[subtitle] unwrapped entry:', entry);
+    // Accept either a plain object {filename, filepath} or a bare string path
+    var filePath = (entry && typeof entry === 'object') ? entry.filepath : entry;
+    console.log('[subtitle] resolved filePath:', filePath);
+    if (!filePath || typeof filePath !== 'string') {
+        console.warn('[subtitle] unexpected callback payload — aborting', raw);
+        return;
+    }
+    var url = MEDIA_API + '?file=' + encodeURIComponent(filePath);
+    console.log('[subtitle] fetching:', url);
+    $.ajax({
+        url: url,
+        dataType: 'text',
+        success: function (content) {
+            console.log('[subtitle] file fetched, length:', content.length);
+            var name = filePath.replace(/\\/g, '/').split('/').pop();
+            var cues = parseSrt(content);
+            console.log('[subtitle] parsed cues:', cues.length);
+            var existing = -1;
+            loadedSubtitleFiles.forEach(function (sf, i) {
+                if (sf.path === filePath) { existing = i; }
+            });
+            if (existing >= 0) {
+                loadedSubtitleFiles[existing].cues = cues;
+                activeSubtitleIndex = existing;
+            } else {
+                loadedSubtitleFiles.push({ path: filePath, name: name, cues: cues });
+                activeSubtitleIndex = loadedSubtitleFiles.length - 1;
+            }
+            showToast('Subtitle loaded: ' + name);
+            updateSubtitleSubmenu();
+        },
+        error: function (xhr, status, err) {
+            console.error('[subtitle] fetch failed:', status, err, url);
+            showToast('Failed to load subtitle file');
+        }
+    });
+};
+
+function parseSrtTime(t) {
+    var m = t.match(/(\d+):(\d+):(\d+)[,.](\d+)/);
+    if (!m) { return 0; }
+    return parseInt(m[1]) * 3600 + parseInt(m[2]) * 60 + parseInt(m[3]) + parseInt(m[4]) / 1000;
+}
+
+function parseSrt(content) {
+    var cues = [];
+    var blocks = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim().split(/\n\n+/);
+    blocks.forEach(function (block) {
+        var lines = block.split('\n');
+        var tcIdx = -1;
+        for (var i = 0; i < lines.length; i++) {
+            if (lines[i].indexOf('-->') !== -1) { tcIdx = i; break; }
+        }
+        if (tcIdx < 0) { return; }
+        var parts = lines[tcIdx].split('-->');
+        if (parts.length < 2) { return; }
+        var start = parseSrtTime(parts[0].trim());
+        var end   = parseSrtTime(parts[1].trim().split(' ')[0]);
+        // Strip inline SRT tags like <b>, <i>, <u>, <font>
+        var text  = lines.slice(tcIdx + 1).join('\n').trim()
+                        .replace(/<[^>]+>/g, '');
+        if (text) { cues.push({ start: start, end: end, text: text }); }
+    });
+    return cues;
+}
+
+function updateSubtitleDisplay() {
+    if (activeSubtitleIndex < 0 || !loadedSubtitleFiles[activeSubtitleIndex]) {
+        $('#subtitle-display').hide();
+        return;
+    }
+    var currentTime = document.getElementById('main-video').currentTime;
+    var cues = loadedSubtitleFiles[activeSubtitleIndex].cues;
+    var found = null;
+    for (var i = 0; i < cues.length; i++) {
+        if (currentTime >= cues[i].start && currentTime <= cues[i].end) {
+            found = cues[i]; break;
+        }
+    }
+    if (found) {
+        $('#subtitle-display').html(escapeHtml(found.text).replace(/\n/g, '<br>')).show();
+    } else {
+        $('#subtitle-display').hide();
+    }
+}
+
+function updateSubtitleSubmenu() {
+    // Remove previously injected dynamic entries
+    $('#ctx-subtitle-sub .ctx-sub-dynamic').remove();
+
+    var disabled = (activeSubtitleIndex < 0);
+    $('#ctx-sub-disable')
+        .toggleClass('ctx-active', disabled)
+        .find('.ctx-icon').text(disabled ? '✓' : '');
+
+    if (loadedSubtitleFiles.length > 0) {
+        var $load = $('#ctx-sub-load');
+        $('<div class="ctx-divider ctx-sub-dynamic"></div>').insertBefore($load);
+        loadedSubtitleFiles.forEach(function (sf, idx) {
+            var isActive = (idx === activeSubtitleIndex);
+            $('<div class="ctx-item ctx-sub-dynamic' + (isActive ? ' ctx-active' : '') + '" data-sub-idx="' + idx + '">' +
+                '<i class="ctx-icon">' + (isActive ? '✓' : '') + '</i>' +
+                escapeHtml(sf.name) +
+              '</div>')
+            .on('click', function () {
+                activeSubtitleIndex = parseInt($(this).data('sub-idx'), 10);
+                updateSubtitleSubmenu();
+                $('#player-ctx').hide();
+            })
+            .insertBefore($load);
+        });
+    }
+}
+
+function initSubtitleMenu() {
+    var $ctx    = $('#player-ctx');
+    var $parent = $('#ctx-subtitle-parent');
+    var $sub    = $('#ctx-subtitle-sub');
+
+    var subHideTimer;
+    function cancelSubHide() { clearTimeout(subHideTimer); }
+    function scheduleSubHide() {
+        subHideTimer = setTimeout(function () { $sub.hide(); }, 120);
+    }
+
+    $parent.on('mouseenter', function () {
+        cancelSubHide();
+        // Flip submenu left if it would overflow the video container
+        var ctxRight       = $ctx.offset().left + $ctx.outerWidth();
+        var subWidth       = 200; // min-width; use before it's visible
+        var containerRight = $('#video-container').offset().left + $('#video-container').outerWidth();
+        if (ctxRight + subWidth > containerRight) {
+            $sub.css({ left: 'auto', right: '100%' });
+        } else {
+            $sub.css({ left: '100%', right: 'auto' });
+        }
+        $sub.show();
+    });
+    $parent.on('mouseleave', scheduleSubHide);
+    $sub.on('mouseenter',    cancelSubHide);
+    $sub.on('mouseleave',    scheduleSubHide);
+
+    $('#ctx-sub-disable').on('click', function () {
+        activeSubtitleIndex = -1;
+        $('#subtitle-display').hide();
+        updateSubtitleSubmenu();
+        $ctx.hide();
+    });
+
+    $('#ctx-sub-load').on('click', function () {
+        $ctx.hide();
+        // Default to the folder of the currently playing file
+        var startDir = 'user:/';
+        if (playingIndex >= 0 && currentEpisodes[playingIndex]) {
+            var fp = currentEpisodes[playingIndex].filepath.replace(/\\/g, '/');
+            var slash = fp.lastIndexOf('/');
+            if (slash > 0) { startDir = fp.substring(0, slash); }
+        }
+        ao_module_openFileSelector(
+            window.movieOnSubtitleFile,
+            startDir, 'file', false,
+            { fnameOverride: 'movieOnSubtitleFile', extAllowed: '.srt' }
+        );
+    });
+
+    document.getElementById('main-video')
+        .addEventListener('timeupdate', updateSubtitleDisplay);
+}
+
 var toastTimer;
 function showToast(msg) {
     clearTimeout(toastTimer);