diff --git a/Flixy/Views/Home/HeroArtwork.swift b/Flixy/Views/Home/HeroArtwork.swift index b731856..d754b5e 100644 --- a/Flixy/Views/Home/HeroArtwork.swift +++ b/Flixy/Views/Home/HeroArtwork.swift @@ -17,25 +17,30 @@ import MediaKit /// Adresse, der Zwischenspeicher trifft daneben und der Aufmacher blendet auf /// eine graue Fläche über. enum HeroArtwork { - /// Die Maße, in denen das Hintergrundbild angefordert wird. + /// Die Maße, in denen das Bild angefordert wird. /// - /// Groß genug für ein breites Fenster, und im Seitenverhältnis des - /// Rahmens, damit nichts beschnitten werden muss. + /// Im Seitenverhältnis des Rahmens, in dem es landet — sonst schneidet der + /// Server zu, und man sieht einen Streifen aus der Mitte. static func size(compact: Bool) -> (width: Int, height: Int) { - compact ? (900, 1200) : (1920, 1080) + compact ? (600, 900) : (1920, 1080) } static func aspectRatio(compact: Bool) -> CGFloat { - compact ? FlixyAspectRatio.heroCompact : FlixyAspectRatio.hero + compact ? FlixyAspectRatio.poster : FlixyAspectRatio.hero } + /// Auf einem schmalen Bildschirm das **Plakat**, sonst das Hintergrundbild. + /// + /// Ein Hintergrundbild ist 16:9. Hochkant gezeigt fehlen ihm links und + /// rechts je ein Viertel, und zu sehen ist ein Ausschnitt aus der Mitte — + /// vom Bild bleibt wenig übrig. Ein Plakat ist von vornherein hochkant; + /// es passt in den Rahmen, ohne dass etwas weggeschnitten werden muss. static func backdropURL(for item: MediaItem, backend: any MediaBackend, compact: Bool) -> URL? { let size = size(compact: compact) - return backend.artworkURL( - path: item.art ?? item.seriesArt ?? item.thumb, - width: size.width, - height: size.height - ) + let path = compact + ? (item.posterArtworkPath ?? item.art) + : (item.art ?? item.seriesArt ?? item.thumb) + return backend.artworkURL(path: path, width: size.width, height: size.height) } /// Der freigestellte Schriftzug — ohne Bild-Transcoder. diff --git a/Flixy/Views/Home/HeroCarousel.swift b/Flixy/Views/Home/HeroCarousel.swift index 361001e..7284a97 100644 --- a/Flixy/Views/Home/HeroCarousel.swift +++ b/Flixy/Views/Home/HeroCarousel.swift @@ -88,13 +88,12 @@ struct HeroCarousel: View { // Die Höhe: fast das ganze Fenster. // // Ein Aufmacher, der nur ein Drittel hoch ist, sieht aus wie ein - // breites Vorschaubild. Die zwölf Prozent, die übrig bleiben, - // sind Absicht — dort lugt die erste Reihe herein und sagt, dass - // es weitergeht. + // breites Vorschaubild. So hoch wie die Seitenleiste soll er sein, + // und die ist so hoch wie das Fenster. // // Auf einem schmalen Gerät nicht: dort ist der Aufmacher hochkant // und rechnet sich seine Höhe aus der Breite aus. - .modifier(HeroHeight(fraction: sizeClass == .compact ? nil : 0.88)) + .modifier(HeroHeight(fraction: sizeClass == .compact ? nil : 1)) // **Kein** Seitenverhältnis hier: der Aufmacher bringt sein // eigenes mit, und eines darüber legte ihn auf die Breite des // Inhaltsbereichs fest. Weil er nach links unter die Seitenleiste diff --git a/Flixy/Views/Navigation/FlixyNavigationBar.swift b/Flixy/Views/Navigation/FlixyNavigationBar.swift index 5e8842d..50f3a50 100644 --- a/Flixy/Views/Navigation/FlixyNavigationBar.swift +++ b/Flixy/Views/Navigation/FlixyNavigationBar.swift @@ -76,9 +76,9 @@ struct FlixySidebar: View { shape.strokeBorder( LinearGradient( stops: [ - .init(color: .white.opacity(0.55), location: 0), - .init(color: .white.opacity(0.10), location: 0.5), - .init(color: .white.opacity(0.30), location: 1), + .init(color: .white.opacity(0.30), location: 0), + .init(color: .white.opacity(0.05), location: 0.5), + .init(color: .white.opacity(0.16), location: 1), ], startPoint: .topLeading, endPoint: .bottomTrailing @@ -96,16 +96,24 @@ struct FlixySidebar: View { VStack(alignment: .leading, spacing: 0) { header - ScrollView { - VStack(alignment: .leading, spacing: FlixySpacing.regular) { - ForEach(groups) { group in - section(group) - } + // Ohne Bildlaufansicht. + // + // Sie war der helle Streifen an der rechten Kante: ihr Anzeiger, + // zwölf Punkte breit, in gleichbleibender Helligkeit, genau so + // hoch wie die Liste. `.scrollIndicators(.never)` half nicht — auf + // dem Mac zeichnet das System ihn trotzdem. + // + // Gebraucht wird sie ohnehin nicht: die Einträge sind wenige und + // passen. Sollten es einmal so viele Mediatheken werden, dass sie + // nicht passen, ist das eine eigene Aufgabe — und dann mit + // abgeschaltetem Anzeiger von Hand. + VStack(alignment: .leading, spacing: FlixySpacing.regular) { + ForEach(groups) { group in + section(group) } - .padding(.horizontal, FlixySpacing.snug) - .padding(.bottom, FlixySpacing.regular) } - .scrollIndicators(.never) + .padding(.horizontal, FlixySpacing.snug) + .padding(.bottom, FlixySpacing.regular) Spacer(minLength: 0) diff --git a/Packages/FlixyKit/Sources/FlixyDesign/Components/HeroBanner.swift b/Packages/FlixyKit/Sources/FlixyDesign/Components/HeroBanner.swift index e12f58d..56c9319 100644 --- a/Packages/FlixyKit/Sources/FlixyDesign/Components/HeroBanner.swift +++ b/Packages/FlixyKit/Sources/FlixyDesign/Components/HeroBanner.swift @@ -126,18 +126,6 @@ public struct HeroBanner: View { public var body: some View { FlixyArtwork(url: artworkURL, aspectRatio: aspectRatio, fillsFrame: fillsAvailableSpace) - // Bis unter die Leiste und an den Fensterrand. - // - // Hier stand `backgroundExtensionEffect()`. Der spiegelt den - // Bildrand und zeichnet ihn weichgezeichnet weiter — und je größer - // die Fläche wurde, die er füllen musste, desto mehr übernahm der - // Spiegel das Bild. Über die Seitenleiste hinaus war er nicht mehr - // Fortsetzung, sondern Hauptsache; auf dem iPhone legte er sich - // ohne jede Seitenleiste über den Statusbalken. - // - // Ein negativer Rand tut dasselbe ehrlicher: das Bild **selbst** - // läuft weiter. Eine Fläche, ein Ausschnitt, keine Naht. - .padding(.leading, -chromeInset) .overlay(alignment: .bottom) { // Der Verlauf endet in der Hintergrundfarbe der Seite, nicht in // Schwarz: sonst entsteht genau dort, wo der Aufmacher in die @@ -154,6 +142,24 @@ public struct HeroBanner: View { .frame(maxHeight: .infinity, alignment: .bottom) .allowsHitTesting(false) } + // Erst **hier** nach links ausbrechen, nach dem Verlauf und vor + // der Schrift. + // + // Ein negativer Rand dehnt zwar, was er umschließt, meldet nach + // außen aber die alte Breite. Stand er ganz oben, legten sich + // Verlauf und Schrift deshalb nur über den alten Bereich — und die + // zwölf Punkte daneben blieben ohne Verlauf und damit hell. Das war + // der leuchtende Streifen an der Kante der Seitenleiste. + // + // In dieser Reihenfolge stimmt beides: der Verlauf wird mitgedehnt + // und deckt bis zum Rand, die Schrift bleibt auf der gemeldeten + // Breite und fängt damit von selbst dort an, wo auch die Reihen + // darunter anfangen. + // + // (Hier stand einmal `backgroundExtensionEffect()`. Der spiegelte + // den Bildrand, statt das Bild weiterlaufen zu lassen — je größer + // die Fläche, desto mehr übernahm der Spiegel das Bild.) + .padding(.leading, -chromeInset) .overlay(alignment: .bottomLeading) { GeometryReader { geometry in let scale = scale(for: geometry.size.width)