diff --git a/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/desktop/NativePlayerBridge.kt b/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/desktop/NativePlayerBridge.kt index 10517c167..149078b77 100644 --- a/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/desktop/NativePlayerBridge.kt +++ b/composeApp/src/desktopMain/kotlin/com/nuvio/app/features/player/desktop/NativePlayerBridge.kt @@ -197,14 +197,20 @@ internal object NativePlayerBridge { private fun exportControlsPageAssets(): ControlsPageAssets { val root = File(System.getProperty("java.io.tmpdir"), "nuvio-player-ui").apply { mkdirs() } val fontsDir = root.resolve("fonts").apply { mkdirs() } - val htmlResource = "/player-ui/controls.html" - val html = NativePlayerBridge::class.java.getResourceAsStream(htmlResource) - ?.bufferedReader() - ?.use { it.readText() } - ?: error("Missing native player controls resource: $htmlResource") - val resolvedHtml = html.replace("/* __NUVIO_PLAYER_FONT_FACES__ */", nativePlayerFontFaces()) val htmlFile = root.resolve("controls.html") - writeTextIfChanged(htmlFile, resolvedHtml) + writeTextIfChanged( + target = htmlFile, + text = readTextResource("/player-ui/controls.html"), + ) + writeTextIfChanged( + target = root.resolve("controls.css"), + text = readTextResource("/player-ui/controls.css") + .replace("/* __NUVIO_PLAYER_FONT_FACES__ */", nativePlayerFontFaces()), + ) + copyResourceIfChanged( + resource = "/player-ui/controls.js", + target = root.resolve("controls.js"), + ) copyResourceIfChanged( resource = "/composeResources/nuvio.composeapp.generated.resources/font/jetbrains_sans_regular.ttf", target = fontsDir.resolve("jetbrains_sans_regular.ttf"), @@ -247,6 +253,12 @@ internal object NativePlayerBridge { } """.trimIndent() + private fun readTextResource(resource: String): String = + NativePlayerBridge::class.java.getResourceAsStream(resource) + ?.bufferedReader(Charsets.UTF_8) + ?.use { it.readText() } + ?: error("Missing native player controls resource: $resource") + private fun writeTextIfChanged(target: File, text: String) { val bytes = text.toByteArray(Charsets.UTF_8) if (target.exists() && target.readBytes().contentEquals(bytes)) return diff --git a/composeApp/src/desktopMain/resources/player-ui/controls.css b/composeApp/src/desktopMain/resources/player-ui/controls.css new file mode 100644 index 000000000..4cd77ce50 --- /dev/null +++ b/composeApp/src/desktopMain/resources/player-ui/controls.css @@ -0,0 +1,1883 @@ +/* __NUVIO_PLAYER_FONT_FACES__ */ + +:root { + --hp: 20px; + --vp: 16px; + --title: 18px; + --episode: 14px; + --meta: 12px; + --center-gap: 56px; + --center-lift: 10px; + --slider-bottom: 16px; + --slider-touch: 22px; + --slider-scale: .82; + --overlay-bottom: calc(var(--slider-bottom) + var(--slider-touch) + 98px); + --time: 12px; + --header-icon: 20px; + --header-button: 36px; + --side-pad: 10px; + --side-icon: 26px; + --play-pad: 13px; + --play-icon: 34px; + --motion-fast: 120ms; + --motion-standard: 180ms; + --motion-panel: 220ms; + --ease-out: cubic-bezier(.2, .8, .2, 1); + --theme-accent: #2f6fed; + --theme-accent-strong: #3c7bff; + --theme-on-accent: #fff; + --theme-focus: #9ecaff; + --theme-selected-surface: #26384f; + --theme-selected-surface-hover: #2d4565; + --theme-selected-ring: rgba(47, 111, 237, .35); + --theme-timeline-fill: #fff; + --theme-timeline-track: rgba(255, 255, 255, .28); + --theme-buffering: #fff; + --theme-buffering-track: rgba(255, 255, 255, .28); + --theme-control-foreground: #fff; + color-scheme: dark; +} + +@media (min-width: 768px) { + :root { + --hp: 20px; + --vp: 16px; + --title: 22px; + --episode: 14px; + --meta: 12px; + --center-gap: 72px; + --center-lift: 14px; + --slider-bottom: 20px; + --slider-touch: 24px; + --slider-scale: .78; + --time: 12px; + --header-icon: 20px; + --header-button: 36px; + --side-pad: 12px; + --side-icon: 30px; + --play-pad: 15px; + --play-icon: 38px; + } +} + +@media (min-width: 1024px) { + :root { + --hp: 24px; + --vp: 20px; + --title: 24px; + --episode: 15px; + --meta: 13px; + --center-gap: 88px; + --center-lift: 18px; + --slider-bottom: 24px; + --slider-touch: 26px; + --slider-scale: .74; + --time: 13px; + --header-icon: 22px; + --header-button: 38px; + --side-pad: 13px; + --side-icon: 32px; + --play-pad: 16px; + --play-icon: 42px; + } +} + +@media (min-width: 1440px) { + :root { + --hp: 28px; + --vp: 24px; + --title: 28px; + --episode: 16px; + --meta: 14px; + --center-gap: 112px; + --center-lift: 24px; + --slider-bottom: 28px; + --slider-touch: 28px; + --slider-scale: .72; + --time: 14px; + --header-icon: 24px; + --header-button: 40px; + --side-pad: 14px; + --side-icon: 34px; + --play-pad: 18px; + --play-icon: 44px; + } +} + +* { + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; +} + +html, +body { + width: 100%; + height: 100%; + margin: 0; + overflow: hidden; + background: transparent; + color: #fff; + font-family: "Nuvio JetBrains Sans", "JetBrains Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + letter-spacing: 0; + user-select: none; +} + +button { + appearance: none; + border: 0; + padding: 0; + margin: 0; + color: inherit; + background: transparent; + font: inherit; + cursor: pointer; + touch-action: manipulation; +} + +button:disabled { + cursor: default; +} + +.player { + position: relative; + width: 100%; + height: 100%; +} + +.player:focus { + outline: none; +} + +.top-gradient, +.bottom-gradient, +.metadata, +.parental-guide, +.skip-prompt, +.next-episode-card, +.header-actions, +.center-controls, +.progress { + transition: opacity 160ms ease; +} + +.player.chrome-hidden .top-gradient, +.player.chrome-hidden .bottom-gradient, +.player.chrome-hidden .metadata, +.player.chrome-hidden .header-actions, +.player.chrome-hidden .center-controls, +.player.chrome-hidden .progress, +.player.locked .metadata, +.player.locked .parental-guide, +.player.locked .header-actions, +.player.locked .center-controls { + opacity: 0; + pointer-events: none; +} + +.player.opening-active .top-gradient, +.player.opening-active .bottom-gradient, +.player.opening-active .parental-guide, +.player.opening-active .skip-prompt, +.player.opening-active .next-episode-card, +.player.opening-active .content { + visibility: hidden; + opacity: 0; + pointer-events: none; + transition: none !important; +} + +.player.locked.locked-visible .bottom-gradient, +.player.locked.locked-visible .progress { + opacity: 1; + pointer-events: none; +} + +[hidden] { + display: none !important; +} + +.next-episode-thumb, +.episode-thumb { + isolation: isolate; +} + +.next-episode-thumb::before, +.episode-thumb::before, +.opening-overlay::before { + content: ""; + position: absolute; + inset: 0; + z-index: 0; + pointer-events: none; + background: + linear-gradient(100deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .14) 48%, rgba(255, 255, 255, 0) 92%), + linear-gradient(135deg, rgba(255, 255, 255, .09), rgba(255, 255, 255, .028)); + background-size: 260% 100%, 100% 100%; + opacity: 0; + transform: translateZ(0); + transition: opacity 260ms ease; +} + +.opening-overlay::before { + z-index: 1; +} + +.next-episode-thumb.image-loading::before, +.episode-thumb.image-loading::before { + opacity: .78; + animation: image-shimmer 1600ms ease-in-out infinite; +} + +.opening-overlay.visible:not(.image-loaded)::before { + opacity: .68; + animation: image-shimmer 3200ms ease-in-out infinite; +} + +.next-episode-thumb img, +.episode-thumb img, +.opening-artwork, +.opening-logo { + opacity: 0; + filter: blur(14px) saturate(.92); + transform: scale(1.035); + transform-origin: center; + will-change: opacity, filter, transform; + transition: + opacity 260ms ease, + filter 520ms var(--ease-out), + transform 620ms var(--ease-out); +} + +.next-episode-thumb img.image-loaded, +.episode-thumb img.image-loaded, +.opening-artwork.image-loaded, +.opening-logo.image-loaded { + opacity: 1; + filter: blur(0) saturate(1); + transform: scale(1); +} + +.image-error::before { + opacity: .65; + animation: none; + background: + radial-gradient(circle at 30% 24%, rgba(255, 255, 255, .12), rgba(255, 255, 255, 0) 34%), + linear-gradient(135deg, rgba(255, 255, 255, .08), rgba(255, 255, 255, .03)); +} + +@keyframes image-shimmer { + from { background-position: 190% 0, 0 0; } + to { background-position: -90% 0, 0 0; } +} + +@keyframes opening-artwork-drift { + from { transform: scale(1.015) translate3d(-.35%, -.25%, 0); } + to { transform: scale(1.04) translate3d(.35%, .25%, 0); } +} + +.top-gradient, +.bottom-gradient { + position: absolute; + left: 0; + right: 0; + pointer-events: none; +} + +.top-gradient { + top: 0; + height: 160px; + background: linear-gradient(to bottom, rgba(0, 0, 0, .7), rgba(0, 0, 0, 0)); +} + +.bottom-gradient { + bottom: 0; + height: 220px; + background: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, .7)); +} + +.content { + position: absolute; + inset: 0; + padding-inline: var(--hp); +} + +.header { + position: absolute; + top: calc(var(--slider-bottom) + env(safe-area-inset-top)); + left: var(--hp); + right: var(--hp); + display: flex; + justify-content: flex-end; + align-items: flex-start; + gap: 18px; +} + +.metadata { + min-width: 0; + display: flex; + flex-direction: column; + gap: 0; + opacity: 1; + padding: 0 14px 10px; +} + +.title { + max-width: min(760px, 100%); + font-size: var(--title); + line-height: 1.16; + font-weight: 700; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.episode { + max-width: min(760px, 100%); + margin-top: 4px; + font-size: var(--episode); + line-height: 1.3; + color: rgba(255, 255, 255, .9); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.meta-row { + display: flex; + align-items: center; + gap: 8px; + max-width: min(760px, 100%); + min-width: 0; + max-height: 0; + margin-top: 0; + opacity: 0; + overflow: hidden; + pointer-events: none; + transform: translateY(6px); + transition: + opacity 180ms ease, + max-height 180ms ease, + margin-top 180ms ease, + transform 180ms var(--ease-out); +} + +.player.source-visible .meta-row { + max-height: 24px; + margin-top: 6px; + opacity: 1; + pointer-events: auto; + transform: translateY(0); +} + +.stream-title, +.provider { + min-width: 0; + font-size: var(--meta); + line-height: 1.25; + color: rgba(255, 255, 255, .7); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.provider { + font-style: italic; +} + +.parental-guide { + position: absolute; + top: calc(var(--slider-bottom) + env(safe-area-inset-top)); + left: var(--hp); + z-index: 5; + display: flex; + align-items: flex-start; + opacity: 0; + pointer-events: none; + transition: opacity 300ms ease; +} + +.player.parental-visible .parental-guide { + opacity: 1; +} + +.parental-guide-line { + width: 3px; + height: 0; + border-radius: 1px; + background: var(--theme-accent); + transform: scaleY(0); + transform-origin: top; + transition: transform 400ms var(--ease-out); +} + +.player.parental-line-visible .parental-guide-line { + transform: scaleY(1); +} + +.parental-guide-list { + display: flex; + flex-direction: column; + gap: 2px; + padding-left: 10px; +} + +.parental-guide-row { + height: 18px; + display: flex; + align-items: center; + opacity: 0; + transform: translateY(2px); + transition: + opacity 200ms ease, + transform 200ms var(--ease-out); + white-space: nowrap; +} + +.parental-guide-row.visible { + opacity: 1; + transform: translateY(0); +} + +.parental-guide-label, +.parental-guide-separator, +.parental-guide-severity { + font-size: 11px; + line-height: 18px; +} + +.parental-guide-label { + color: rgba(255, 255, 255, .85); + font-weight: 600; +} + +.parental-guide-separator { + color: rgba(255, 255, 255, .4); +} + +.parental-guide-severity { + color: rgba(255, 255, 255, .5); +} + +.skip-prompt { + position: absolute; + left: var(--hp); + bottom: calc(var(--overlay-bottom) + env(safe-area-inset-bottom)); + z-index: 7; + min-width: max-content; + display: flex; + flex-direction: column; + overflow: hidden; + border-radius: 16px; + background: rgba(30, 30, 30, .85); + color: #fff; + opacity: 0; + pointer-events: none; + transform: scale(.8); + transform-origin: left bottom; + transition: + opacity 300ms ease, + transform 300ms var(--ease-out); +} + +.skip-prompt.visible { + opacity: 1; + pointer-events: auto; + transform: scale(1); +} + +.skip-prompt-row { + display: flex; + align-items: center; + padding: 12px 18px; +} + +.skip-prompt svg { + width: 20px; + height: 20px; + flex: 0 0 auto; +} + +.skip-prompt-label { + padding-left: 8px; + font-size: 14px; + line-height: 1.2; + white-space: nowrap; +} + +.skip-progress-track { + display: none; + width: 100%; + height: 3px; + overflow: hidden; + border-radius: 0 0 16px 16px; + background: rgba(255, 255, 255, .15); +} + +.skip-prompt.show-progress .skip-progress-track { + display: block; +} + +.skip-progress-fill { + width: 0%; + height: 100%; + background: rgba(30, 30, 30, .85); +} + +.next-episode-card { + position: absolute; + right: var(--hp); + bottom: calc(var(--overlay-bottom) + env(safe-area-inset-bottom)); + z-index: 7; + width: min(292px, calc(100vw - (var(--hp) * 2))); + display: flex; + align-items: center; + padding: 8px 9px; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, .12); + background: rgba(25, 25, 25, .89); + color: #fff; + opacity: 0; + pointer-events: none; + transform: translateX(50%) scale(.98); + transition: + opacity 220ms ease, + transform 260ms var(--ease-out); +} + +.next-episode-card.visible { + opacity: 1; + pointer-events: auto; + transform: translateX(0) scale(1); +} + +.next-episode-card:not(.playable) { + cursor: default; +} + +.next-episode-thumb { + position: relative; + width: 78px; + height: 44px; + flex: 0 0 auto; + overflow: hidden; + border-radius: 9px; + background: rgba(255, 255, 255, .08); +} + +.next-episode-thumb img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.next-episode-card:not(.has-thumb) .next-episode-thumb img { + display: none; +} + +.next-episode-thumb::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, .32)); +} + +.next-episode-copy { + min-width: 0; + flex: 1 1 auto; + padding-left: 8px; +} + +.next-episode-header { + display: block; + color: rgba(255, 255, 255, .8); + font-size: 10px; + line-height: 1.25; + font-weight: 500; +} + +.next-episode-title { + display: block; + margin-top: 2px; + color: #fff; + font-size: 12px; + line-height: 1.25; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.next-episode-status { + display: block; + margin-top: 2px; + color: rgba(255, 255, 255, .78); + font-size: 10px; + line-height: 1.25; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.next-episode-action { + display: flex; + align-items: center; + gap: 3px; + flex: 0 0 auto; + margin-left: 4px; + padding: 5px 8px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, .2); + color: #fff; + font-size: 11px; + line-height: 1.2; + white-space: nowrap; +} + +.next-episode-card:not(.playable) .next-episode-action { + color: rgba(255, 255, 255, .72); +} + +.next-episode-action svg { + width: 13px; + height: 13px; + color: inherit; +} + +.header-actions { + display: flex; + gap: 10px; + align-items: center; + flex: 0 0 auto; +} + +.header-button { + width: var(--header-button); + height: var(--header-button); + display: grid; + place-items: center; + border-radius: 999px; + background: rgba(0, 0, 0, .35); +} + +.header-button, +.side-control, +.play-control, +.skip-prompt, +.next-episode-card, +.locked-button, +.action, +.panel-button, +.filter-chip, +.capture-button, +.subtitle-tab, +.round-step, +.swatch, +.sync-cue, +.track-row, +.segment-button { + transform: translateZ(0); + transition: + transform var(--motion-fast) var(--ease-out), + background-color var(--motion-standard) ease, + border-color var(--motion-standard) ease, + color var(--motion-standard) ease, + opacity var(--motion-standard) ease, + box-shadow var(--motion-standard) ease; + will-change: transform; +} + +.header-button:hover, +.locked-button:hover { + background: rgba(255, 255, 255, .14); +} + +.side-control:hover, +.play-control:hover, +.action:hover { + background: rgba(255, 255, 255, .12); +} + +.header-button:active, +.header-button.is-pressed, +.skip-prompt:active, +.skip-prompt.is-pressed, +.next-episode-card.playable:active, +.next-episode-card.playable.is-pressed, +.locked-button:active, +.locked-button.is-pressed, +.side-control:active, +.side-control.is-pressed, +.action:active, +.action.is-pressed { + transform: scale(.94); +} + +.play-control:active, +.play-control.is-pressed { + transform: scale(.91); +} + +.header-button svg { + width: var(--header-icon); + height: var(--header-icon); +} + +.center-controls { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, calc(-50% - var(--center-lift))); + display: flex; + align-items: center; + gap: var(--center-gap); +} + +.center-status { + position: absolute; + left: 50%; + top: 50%; + width: 48px; + height: 48px; + display: grid; + place-items: center; + opacity: 0; + pointer-events: none; + transform: translate(-50%, calc(-50% - var(--center-lift) + 10px)) scale(.98); + transition: + opacity var(--motion-standard) ease, + transform var(--motion-standard) var(--ease-out); + will-change: opacity, transform; +} + +.center-status.visible { + opacity: 1; + transform: translate(-50%, calc(-50% - var(--center-lift))) scale(1); +} + +.center-spinner { + width: 42px; + height: 42px; + border-radius: 999px; + border: 3px solid var(--theme-buffering-track); + border-top-color: var(--theme-buffering); + animation: center-spin 760ms linear infinite; +} + +.opening-overlay { + position: absolute; + inset: 0; + z-index: 20; + display: grid; + place-items: center; + overflow: hidden; + background: rgba(0, 0, 0, .85); + opacity: 0; + pointer-events: none; + transition: opacity 180ms ease; +} + +.opening-overlay.visible { + opacity: 1; + pointer-events: auto; +} + +.opening-back { + position: absolute; + top: calc(20px + env(safe-area-inset-top)); + right: calc(var(--hp) + 20px); + z-index: 3; + width: 44px; + height: 44px; + display: grid; + place-items: center; + border-radius: 999px; + background: rgba(0, 0, 0, .3); +} + +.opening-back:hover { + background: rgba(255, 255, 255, .14); +} + +.opening-back:active, +.opening-back.is-pressed { + transform: scale(.94); +} + +.opening-back svg { + width: 24px; + height: 24px; +} + +.opening-artwork { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +.opening-overlay.has-artwork.image-loading::before { + z-index: 1; +} + +.opening-overlay.has-artwork .opening-artwork.image-loaded { + animation: opening-artwork-drift 14000ms ease-in-out infinite alternate; +} + +.opening-scrim { + position: absolute; + inset: 0; + background: linear-gradient( + to bottom, + rgba(0, 0, 0, .3), + rgba(0, 0, 0, .6), + rgba(0, 0, 0, .8), + rgba(0, 0, 0, .9) + ); + opacity: 0; +} + +.opening-overlay.has-artwork .opening-scrim { + opacity: 1; +} + +.opening-content { + position: relative; + z-index: 2; + width: min(100%, 720px); + min-height: 260px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 24px; + text-align: center; + opacity: 1; +} + +.opening-logo-slot { + position: relative; + width: 300px; + max-width: min(78vw, 300px); + height: 180px; +} + +.opening-logo { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: contain; + transform-origin: center; + animation: opening-logo-pulse 2000ms linear infinite alternate !important; +} + +.opening-logo-base { + opacity: 0; +} + +.opening-logo-fill-clip { + position: absolute; + inset: 0 auto 0 0; + width: 0%; + overflow: hidden; +} + +.opening-logo-fill { + width: 300px; + max-width: min(78vw, 300px); + height: 180px; + object-fit: contain; +} + +.opening-title { + max-width: min(84vw, 680px); + margin: 0; + color: #fff; + font-size: 42px; + line-height: 1.12; + font-weight: 800; + text-align: center; + opacity: 1; + -webkit-text-fill-color: #fff; + text-shadow: 0 2px 22px rgba(0, 0, 0, .42); + transform-origin: center; + will-change: transform; + animation: opening-logo-pulse 2000ms linear infinite alternate !important; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.opening-spinner { + width: 54px; + height: 54px; + border-radius: 999px; + border: 3px solid var(--theme-buffering-track); + border-top-color: var(--theme-buffering); + animation: center-spin 760ms linear infinite; +} + +.opening-status { + width: 100%; + min-height: 40px; + margin-top: 16px; + display: grid; + place-items: center; + color: rgba(255, 255, 255, .72); + font-size: 13px; + line-height: 1.35; + font-weight: 600; + text-align: center; +} + +.opening-status span { + max-width: min(84vw, 620px); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.opening-progress-track { + width: 240px; + height: 4px; + margin-top: 10px; + overflow: hidden; + border-radius: 2px; + background: rgba(255, 255, 255, .2); +} + +.opening-progress-bar { + width: 0%; + height: 100%; + border-radius: inherit; + background: var(--theme-timeline-fill); + transition: width 900ms cubic-bezier(.4, 0, .2, 1); +} + +@keyframes opening-logo-pulse { + to { + transform: scale(1.04); + } +} + +@keyframes center-spin { + to { + transform: rotate(360deg); + } +} + +.side-control, +.play-control { + display: grid; + place-items: center; + border-radius: 999px; +} + +.side-control { + padding: var(--side-pad); +} + +.play-control { + padding: var(--play-pad); +} + +.side-control svg { + width: var(--side-icon); + height: var(--side-icon); +} + +.play-control svg { + width: var(--play-icon); + height: var(--play-icon); +} + +.progress { + position: absolute; + left: var(--hp); + right: var(--hp); + bottom: var(--slider-bottom); + display: flex; + flex-direction: column; +} + +.locked-overlay { + position: absolute; + inset: 0; + display: none; + place-items: center; + text-align: center; + pointer-events: none; +} + +.player.locked.locked-visible .locked-overlay { + display: grid; +} + +.locked-content { + display: grid; + justify-items: center; + gap: 12px; + pointer-events: auto; +} + +.locked-button { + width: 78px; + height: 78px; + display: grid; + place-items: center; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, .18); + background: rgba(0, 0, 0, .52); +} + +.locked-button svg { + width: 34px; + height: 34px; +} + +.locked-label { + font-size: 15px; + line-height: 1.25; + font-weight: 600; + color: rgba(255, 255, 255, .92); +} + +.scrub { + width: calc(100% - 28px); + height: var(--slider-touch); + transform: scaleY(var(--slider-scale)); + transform-origin: center; + margin: 0 14px; + appearance: none; + background: transparent; +} + +.scrub::-webkit-slider-runnable-track { + height: 4px; + border-radius: 999px; + background: + linear-gradient(to right, var(--theme-timeline-fill) 0 var(--progress, 0%), var(--theme-timeline-track) var(--progress, 0%) 100%); +} + +.scrub::-webkit-slider-thumb { + appearance: none; + width: 14px; + height: 14px; + margin-top: -5px; + border-radius: 999px; + background: var(--theme-control-foreground); + box-shadow: 0 0 0 1px rgba(255, 255, 255, .18); + transition: + transform var(--motion-fast) var(--ease-out), + box-shadow var(--motion-standard) ease; +} + +.scrub:active::-webkit-slider-thumb { + transform: scale(1.18); + box-shadow: 0 0 0 6px rgba(255, 255, 255, .16); +} + +.time-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 14px 8px; +} + +.time-pill { + border-radius: 12px; + padding: 4px 10px; + border: 1px solid rgba(255, 255, 255, .2); + background: rgba(0, 0, 0, .5); + font-size: var(--time); + line-height: 1.25; + font-weight: 500; + font-variant-numeric: tabular-nums; +} + +.action-row { + display: flex; + justify-content: center; + min-width: 0; +} + +.action-pill { + max-width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: 0; + padding: 2px 4px; + border: 1px solid rgba(255, 255, 255, .2); + border-radius: 24px; + background: rgba(0, 0, 0, .5); + overflow: hidden; +} + +.action { + min-width: 0; + display: flex; + align-items: center; + gap: 8px; + padding: 12px; + border-radius: 22px; + color: #fff; + white-space: nowrap; +} + +.action svg { + width: 18px; + height: 18px; + flex: 0 0 auto; +} + +.action span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + line-height: 1.2; + font-weight: 500; +} + +.modal-layer { + position: absolute; + inset: 0; + z-index: 30; + display: grid; + place-items: center; + padding: 24px; + background-color: rgba(0, 0, 0, .54); + opacity: 0; + pointer-events: none; + transition: opacity var(--motion-standard) ease; +} + +.modal-layer.modal-visible { + opacity: 1; + pointer-events: auto; +} + +.modal-layer.modal-closing { + opacity: 0; + pointer-events: none; +} + +.track-panel { + width: min(90vw, 420px); + max-height: min(90vh, 600px); + display: flex; + flex-direction: column; + overflow: hidden; + border-radius: 24px; + border: 1px solid rgba(255, 255, 255, .16); + background: #16171d; + color: #fff; + box-shadow: 0 20px 70px rgba(0, 0, 0, .45); + opacity: 0; + transform: translateY(14px) scale(.985); + transition: + transform var(--motion-panel) var(--ease-out), + opacity var(--motion-standard) ease; + will-change: transform, opacity; +} + +.modal-layer.modal-visible .track-panel { + opacity: 1; + transform: translateY(0) scale(1); +} + +.modal-layer.modal-closing .track-panel { + opacity: 0; + transform: translateY(10px) scale(.99); +} + +.track-panel.wide { + width: min(94vw, 860px); + max-height: min(92vh, 760px); +} + +.track-panel.submit { + width: min(92vw, 520px); +} + +.track-header { + padding: 20px; + font-size: 18px; + line-height: 1.25; + font-weight: 700; +} + +.track-header-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 18px 20px 14px; +} + +.track-title { + min-width: 0; + font-size: 18px; + line-height: 1.25; + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.panel-actions { + display: flex; + align-items: center; + gap: 8px; + flex: 0 0 auto; +} + +.panel-button, +.filter-chip, +.capture-button { + min-height: 32px; + padding: 7px 12px; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, .12); + background: rgba(255, 255, 255, .08); + color: rgba(255, 255, 255, .72); + font-size: 12px; + line-height: 1.2; + font-weight: 600; + white-space: nowrap; +} + +.panel-button:hover:not(:disabled), +.filter-chip:hover:not(:disabled), +.capture-button:hover:not(:disabled), +.subtitle-tab:hover:not(:disabled), +.round-step:hover:not(:disabled), +.segment-button:hover:not(:disabled) { + background: rgba(255, 255, 255, .13); + color: rgba(255, 255, 255, .9); +} + +.panel-button:active:not(:disabled), +.panel-button.is-pressed:not(:disabled), +.filter-chip:active:not(:disabled), +.filter-chip.is-pressed:not(:disabled), +.capture-button:active:not(:disabled), +.capture-button.is-pressed:not(:disabled), +.subtitle-tab:active:not(:disabled), +.subtitle-tab.is-pressed:not(:disabled), +.round-step:active:not(:disabled), +.round-step.is-pressed:not(:disabled), +.segment-button:active:not(:disabled), +.segment-button.is-pressed:not(:disabled), +.swatch:active:not(:disabled), +.swatch.is-pressed:not(:disabled), +.sync-cue:active:not(:disabled), +.sync-cue.is-pressed:not(:disabled) { + transform: scale(.96); +} + +.panel-button.primary, +.filter-chip.selected, +.segment-button.selected { + background: var(--theme-accent); + color: var(--theme-on-accent); + border-color: rgba(255, 255, 255, .18); +} + +.panel-button.primary:hover:not(:disabled), +.filter-chip.selected:hover:not(:disabled), +.segment-button.selected:hover:not(:disabled) { + background: var(--theme-accent-strong); +} + +.filter-row { + display: flex; + gap: 8px; + overflow-x: auto; + padding: 0 20px 14px; + flex: 0 0 auto; +} + +.subtitle-tabs { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; + padding: 0 20px 16px; +} + +.subtitle-tab { + min-width: 0; + min-height: 34px; + padding: 8px 10px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, .12); + background: rgba(255, 255, 255, .08); + color: rgba(255, 255, 255, .72); + font-size: 12px; + line-height: 1.2; + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.subtitle-tab.selected { + border-radius: 12px; + background: var(--theme-accent); + color: var(--theme-on-accent); +} + +.style-panel { + display: flex; + flex-direction: column; + gap: 14px; + overflow: auto; + padding: 0 20px 20px; +} + +.style-card { + display: flex; + flex-direction: column; + gap: 13px; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, .12); + background: rgba(255, 255, 255, .07); + padding: 14px; +} + +.style-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-width: 0; +} + +.style-label { + min-width: 0; + color: rgba(255, 255, 255, .72); + font-size: 13px; + line-height: 1.25; + font-weight: 700; +} + +.stepper { + display: flex; + align-items: center; + gap: 8px; + flex: 0 0 auto; +} + +.stepper-value { + min-width: 64px; + text-align: center; + color: rgba(255, 255, 255, .92); + font-size: 13px; + line-height: 1.2; + font-weight: 800; + font-variant-numeric: tabular-nums; +} + +.round-step { + width: 30px; + height: 30px; + display: grid; + place-items: center; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, .12); + background: rgba(255, 255, 255, .08); + color: #fff; + font-size: 18px; + font-weight: 800; + line-height: 1; +} + +.swatch-section { + display: flex; + flex-direction: column; + gap: 8px; +} + +.swatch-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.swatch { + width: 24px; + height: 24px; + border-radius: 999px; + border: 2px solid rgba(255, 255, 255, .28); + background: var(--swatch); +} + +.swatch.transparent { + background: + linear-gradient(45deg, rgba(255,255,255,.16) 25%, transparent 25% 75%, rgba(255,255,255,.16) 75%), + linear-gradient(45deg, rgba(255,255,255,.16) 25%, transparent 25% 75%, rgba(255,255,255,.16) 75%); + background-position: 0 0, 6px 6px; + background-size: 12px 12px; +} + +.swatch.selected { + border-color: var(--theme-focus); + box-shadow: 0 0 0 2px var(--theme-selected-ring); +} + +.auto-sync-card { + display: flex; + flex-direction: column; + gap: 8px; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, .1); + background: rgba(0, 0, 0, .22); + padding: 12px; +} + +.sync-cue { + display: flex; + align-items: flex-start; + gap: 8px; + width: 100%; + padding: 8px; + border-radius: 8px; + background: rgba(255, 255, 255, .08); + text-align: left; +} + +.sync-time { + flex: 0 0 auto; + color: var(--theme-focus); + font-size: 11px; + line-height: 1.25; + font-weight: 800; + font-variant-numeric: tabular-nums; +} + +.sync-text { + min-width: 0; + color: rgba(255, 255, 255, .82); + font-size: 12px; + line-height: 1.3; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.track-list { + display: flex; + flex-direction: column; + gap: 10px; + flex: 1 1 auto; + overflow: auto; + padding: 4px 20px 20px; + min-height: 0; +} + +#episodeListView, +#episodeStreamsView { + min-height: 0; + flex: 1 1 auto; + flex-direction: column; +} + +#episodeListView:not([hidden]), +#episodeStreamsView:not([hidden]) { + display: flex; +} + +.track-list.virtualized { + display: block; + gap: 0; + contain: content; +} + +.source-virtual-spacer { + position: relative; + width: 100%; +} + +.source-virtual-row { + position: absolute; + left: 0; + right: 0; + top: 0; +} + +.track-row { + width: 100%; + min-height: 42px; + display: flex; + flex: 0 0 auto; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 12px; + border-radius: 12px; + background: rgba(255, 255, 255, .08); + color: rgba(255, 255, 255, .92); + text-align: left; +} + +.track-row.stream-row, +.track-row.episode-row { + align-items: stretch; + justify-content: flex-start; + min-height: 72px; +} + +.track-row.source-row { + min-height: 0; + align-items: flex-start; +} + +.track-row.disabled { + opacity: .45; +} + +.track-row:hover:not(:disabled) { + background: rgba(255, 255, 255, .13); +} + +.track-row:active:not(:disabled), +.track-row.is-pressed:not(:disabled) { + transform: scale(.985); +} + +.track-row.selected { + background: var(--theme-selected-surface); + color: var(--theme-control-foreground); + font-weight: 700; +} + +.track-row.selected:hover:not(:disabled) { + background: var(--theme-selected-surface-hover); +} + +.track-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 15px; + line-height: 1.25; +} + +.track-copy, +.episode-copy { + min-width: 0; + display: flex; + flex: 1 1 auto; + flex-direction: column; + gap: 4px; +} + +.track-row-top, +.episode-row-top { + min-width: 0; + display: flex; + align-items: center; + gap: 8px; + flex: 0 0 auto; +} + +.stream-name, +.episode-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 14px; + line-height: 1.25; + font-weight: 700; +} + +.stream-subtitle, +.episode-overview, +.stream-addon { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + color: rgba(255, 255, 255, .64); + font-size: 12px; + line-height: 1.3; +} + +.stream-subtitle { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + white-space: normal; +} + +.episode-overview { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + white-space: normal; +} + +.stream-addon { + color: rgba(255, 255, 255, .5); + font-style: italic; + white-space: nowrap; +} + +.source-row .stream-name, +.source-row .stream-subtitle, +.source-row .stream-addon { + display: block; + overflow: visible; + overflow-wrap: anywhere; + text-overflow: clip; + white-space: normal; + -webkit-line-clamp: unset; + -webkit-box-orient: initial; +} + +.episode-row .episode-row-top { + flex-wrap: wrap; +} + +.episode-row .episode-name, +.episode-row .episode-overview { + display: block; + overflow: visible; + overflow-wrap: anywhere; + text-overflow: clip; + white-space: normal; + -webkit-line-clamp: unset; + -webkit-box-orient: initial; +} + +.status-chip, +.episode-code { + flex: 0 0 auto; + border-radius: 999px; + padding: 3px 7px; + font-size: 10px; + line-height: 1.1; + font-weight: 800; + letter-spacing: 0; + background: rgba(255, 255, 255, .12); + color: rgba(255, 255, 255, .68); +} + +.status-chip { + background: var(--theme-accent); + color: var(--theme-on-accent); +} + +.episode-thumb { + position: relative; + width: 82px; + height: 48px; + flex: 0 0 auto; + overflow: hidden; + border-radius: 10px; + background: rgba(255, 255, 255, .08); +} + +.episode-thumb img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.track-check { + width: 18px; + height: 18px; + flex: 0 0 auto; + color: var(--theme-focus); + opacity: 0; +} + +.track-row.selected .track-check { + opacity: 1; +} + +button:focus-visible, +.scrub:focus-visible { + outline: 2px solid rgba(158, 202, 255, .95); + outline-offset: 3px; +} + +.track-empty { + padding: 36px 12px 44px; + color: rgba(255, 255, 255, .64); + font-size: 14px; + line-height: 1.35; + text-align: center; +} + +.intro-form { + display: flex; + flex-direction: column; + gap: 16px; + overflow: auto; + padding: 0 20px 20px; +} + +.form-label { + color: rgba(255, 255, 255, .64); + font-size: 11px; + line-height: 1.2; + font-weight: 800; +} + +.segment-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; +} + +.segment-button { + min-width: 0; + min-height: 42px; + padding: 9px 8px; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, .12); + background: rgba(255, 255, 255, .08); + color: rgba(255, 255, 255, .72); + font-size: 13px; + line-height: 1.2; + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; +} + +.time-field-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; + align-items: end; +} + +.time-field-wrap { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; +} + +.time-input { + width: 100%; + min-height: 46px; + border: 1px solid rgba(255, 255, 255, .12); + border-radius: 12px; + padding: 10px 12px; + background: rgba(255, 255, 255, .08); + color: #fff; + font: inherit; + font-size: 16px; + outline: none; +} + +.submit-status { + min-height: 18px; + color: #ffb4b4; + font-size: 12px; + line-height: 1.35; +} + +.consent-body { + overflow: auto; + padding: 0 20px 20px; + color: rgba(255, 255, 255, .74); + font-size: 13px; + line-height: 1.45; + white-space: pre-line; +} + +.consent-body + .modal-actions { + padding: 0 20px 20px; +} + +.modal-actions { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); + gap: 10px; +} + +@media (max-width: 760px) { + .header { + left: var(--hp); + right: var(--hp); + } + + .title, + .episode, + .meta-row { + max-width: 100%; + } + + .progress { + left: var(--hp); + right: var(--hp); + } + + .action.optional { + display: none; + } + + .modal-layer { + padding: 14px; + } + + .episode-thumb { + display: none; + } +} + +@media (prefers-reduced-motion: reduce) { + .top-gradient, + .bottom-gradient, + .metadata, + .header-actions, + .center-controls, + .center-status, + .skip-prompt, + .next-episode-card, + .skip-progress-fill, + .progress, + .opening-overlay, + .opening-progress-bar, + .opening-artwork, + .opening-logo, + .next-episode-thumb img, + .episode-thumb img, + .image-loading::before, + .modal-layer, + .track-panel, + .header-button, + .side-control, + .play-control, + .locked-button, + .action, + .panel-button, + .filter-chip, + .capture-button, + .subtitle-tab, + .round-step, + .swatch, + .sync-cue, + .track-row, + .segment-button { + transition-duration: 1ms; + animation-duration: 1ms; + } +} + +svg { + display: block; + fill: currentColor; + stroke: none; +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/resources/player-ui/controls.html b/composeApp/src/desktopMain/resources/player-ui/controls.html index f45736d52..134a30ad3 100644 --- a/composeApp/src/desktopMain/resources/player-ui/controls.html +++ b/composeApp/src/desktopMain/resources/player-ui/controls.html @@ -3,1891 +3,7 @@ - +