import SwiftUI /// Das Aufmacher-Bild oben auf der Startseite. /// /// Die Aktionsknöpfe kommen von außen, weil ein Film „Abspielen" und /// „Herunterladen" braucht, eine angefangene Folge dagegen „Weiterschauen". /// Der Baustein legt nur Bild, Verlauf und Textsatz fest. public struct HeroBanner: View { private let title: String private let metadata: String? private let summary: String? private let artworkURL: URL? private let actions: Actions public init( title: String, metadata: String? = nil, summary: String? = nil, artworkURL: URL?, @ViewBuilder actions: () -> Actions ) { self.title = title self.metadata = metadata self.summary = summary self.artworkURL = artworkURL self.actions = actions() } public var body: some View { FlixyArtwork(url: artworkURL, aspectRatio: 3.0 / 4.0) .overlay(alignment: .bottom) { // Der Verlauf endet in der Hintergrundfarbe der Seite, nicht in // Schwarz: sonst entsteht genau dort, wo der Aufmacher in die // erste Reihe übergeht, eine sichtbare Kante. LinearGradient( stops: [ .init(color: .clear, location: 0), .init(color: Color.flixyCanvas.opacity(0.75), location: 0.45), .init(color: Color.flixyCanvas, location: 1), ], startPoint: .top, endPoint: .bottom ) .frame(maxHeight: .infinity, alignment: .bottom) .allowsHitTesting(false) } .overlay(alignment: .bottomLeading) { VStack(alignment: .leading, spacing: FlixySpacing.snug) { Text(title) .font(.flixyHero) .foregroundStyle(Color.flixyTextPrimary) .lineLimit(2) if let metadata { Text(metadata) .font(.flixyCaption) .foregroundStyle(Color.flixyTextSecondary) } if let summary { Text(summary) .font(.flixyCaption) .foregroundStyle(Color.flixyTextSecondary) .lineLimit(2) } HStack(spacing: FlixySpacing.regular) { actions } .padding(.top, FlixySpacing.tight) } .padding(.horizontal, FlixySpacing.screenMargin) .padding(.bottom, FlixySpacing.loose) } } }