diff --git a/README.md b/README.md index cc18fb6..fa2c85c 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ Terebi reports facts, not watched status. A consumer such as Tubular decides com The layout follows [IINA's video-first window](https://iina.io/img/screenshot.png) and the supplied screenshots: a 32-point transparent titlebar with native traffic lights and file icon, plus a frosted 440 × 68-point floating controller. Conjure-inspired **Sunset** styling uses translucent dark-brown glass, warm cream primary text, sand secondary text, and a fixed orange accent for the volume fill, active pin, and drop target. Native slider thumbs and buffered ranges share the palette; warning symbols retain their semantic yellow. Shared vertical padding and an optical offset for symbol/slider insets balance the controller's visible top and bottom margins in both play and pause states. The controller starts centered with a four-percent bottom inset and keeps its relative position when the window changes size. -The empty player uses the same tinted HUD-glass material, faint warm outlines, 12-point rounded corners, and Sunset palette in a centered welcome card. File and YouTube opening actions are separate, equally sized buttons with a 10-point gap and subtle orange fills. The YouTube button reveals and focuses an inline URL field below the buttons, spanning their combined width including the gap; Return or the arrow button submits through the existing YouTube URL validation. Other URL-opening entry points retain their dialog. The card fits the minimum window, and the full window still accepts media-file and YouTube-link drops. +The empty player uses the same tinted HUD-glass material, faint warm outlines, 12-point rounded corners, and Sunset palette in a centered welcome card. File and YouTube opening actions are separate, equally sized buttons with a 10-point gap, subtle orange fills, and matching half-point sand outlines. The YouTube button toggles an outlined inline URL field below the buttons, spanning their combined width including the gap. Showing it focuses the field; hiding it releases text-input focus and preserves the draft. The field slides/fades in and out while the card adjusts over 220 ms with ease-in/out timing; Reduce Motion disables the animation. Return or the arrow button submits through the existing YouTube URL validation. Other URL-opening entry points retain their dialog. The card fits the minimum window, and the full window still accepts media-file and YouTube-link drops. With no media open, the full window uses native behind-window HUD glass with a 35%-opaque brown tint. Static, cached amber/rust radial gradients add ambient color only to the empty screen; they disappear during loading or errors, and are never drawn over video or playback chrome. Glass title/control/error surfaces use the same brown tint with half-point sand outlines. Decorative drawing does not intercept input. The idle Metal surface stays attached but hidden; opening media restores a solid black background and letterboxing without tinting the video, and closing it restores the ambient frosted background. macOS controls the material’s appearance, including Reduce Transparency. diff --git a/Sources/App/TerebiApp.swift b/Sources/App/TerebiApp.swift index a8e7856..5504c61 100644 --- a/Sources/App/TerebiApp.swift +++ b/Sources/App/TerebiApp.swift @@ -1265,6 +1265,14 @@ private struct PlayerContent: View { @State private var controlOffset = CGSize.zero @State private var dragOrigin = CGSize.zero + private func toggleYouTubeInput() { + if showsYouTubeInput { youTubeInputFocused = false } + withAnimation(reduceMotion ? nil : .easeInOut(duration: 0.22)) { + showsYouTubeInput.toggle() + } + if showsYouTubeInput { youTubeInputFocused = true } + } + private var showsBufferReadout: Bool { model.isNetworkMedia && model.showsBufferInfo } private var controlHeight: CGFloat { showsBufferReadout ? 88 : 68 } private var compactControlHeight: CGFloat { showsBufferReadout ? 52 : 40 } @@ -1551,16 +1559,19 @@ private struct PlayerContent: View { .contentShape(Rectangle()) } .background(Color(SunsetPalette.actionFill), in: RoundedRectangle(cornerRadius: 8)) - Button { - showsYouTubeInput = true - youTubeInputFocused = true - } label: { + .overlay(RoundedRectangle(cornerRadius: 8) + .strokeBorder(Color(SunsetPalette.outline), lineWidth: 0.5) + .allowsHitTesting(false)) + Button(action: toggleYouTubeInput) { Label("YouTube URL…", systemImage: "link") .frame(maxWidth: .infinity, minHeight: 40) .contentShape(Rectangle()) } .accessibilityLabel("Open YouTube URL…") .background(Color(SunsetPalette.actionFill), in: RoundedRectangle(cornerRadius: 8)) + .overlay(RoundedRectangle(cornerRadius: 8) + .strokeBorder(Color(SunsetPalette.outline), lineWidth: 0.5) + .allowsHitTesting(false)) } .font(.system(size: 13, weight: .medium)) .foregroundStyle(Color(SunsetPalette.text)) @@ -1592,6 +1603,10 @@ private struct PlayerContent: View { .padding(.horizontal, 12) .frame(height: 40) .background(Color(SunsetPalette.actionFill), in: RoundedRectangle(cornerRadius: 8)) + .overlay(RoundedRectangle(cornerRadius: 8) + .strokeBorder(Color(SunsetPalette.outline), lineWidth: 0.5) + .allowsHitTesting(false)) + .transition(reduceMotion ? .opacity : .move(edge: .top).combined(with: .opacity)) .disabled(!model.canOpen) } Text("Drop a media file or YouTube link here")