// // ReflectSubscriptionStoreView.swift // Reflect // // Premium subscription experience with multiple theme options. // import SwiftUI import StoreKit import os.log struct ReflectSubscriptionStoreView: View { @Environment(\.dismiss) private var dismiss @EnvironmentObject var iapManager: IAPManager // Read from AppStorage to match current theme, with optional override for previews @AppStorage(UserDefaultsStore.Keys.paywallStyle.rawValue, store: GroupUserDefaults.groupDefaults) private var paywallStyleRaw: Int = 0 var source: String = "unknown" var style: PaywallStyle? private var currentStyle: PaywallStyle { if let override = style { return override } return PaywallStyle(rawValue: paywallStyleRaw) ?? .celestial } var body: some View { SubscriptionStoreView(groupID: IAPManager.subscriptionGroupID) { marketingContent } .subscriptionStoreControlStyle(.prominentPicker) .storeButton(.visible, for: .restorePurchases) .subscriptionStoreButtonLabel(.multiline) .tint(tintColor) .overlay(alignment: .topTrailing) { Button { dismiss() } label: { Image(systemName: "xmark.circle.fill") .font(.title2) .symbolRenderingMode(.hierarchical) .foregroundStyle(.secondary) } .padding(16) .accessibilityLabel("Close") } .onAppear { AppLogger.iap.info("SubscriptionStoreView appeared โ€” source: \(source), productIDs: \(IAPManager.productIdentifiers.sorted().joined(separator: ", ")), groupID: \(IAPManager.subscriptionGroupID)") AppLogger.iap.info("IAPManager state โ€” isLoading: \(iapManager.isLoading), products loaded: \(iapManager.availableProducts.count), state: \(String(describing: iapManager.state))") // Also try loading products directly to log what StoreKit returns Task { do { let products = try await Product.products(for: IAPManager.productIdentifiers) AppLogger.iap.info("Direct Product.products() returned \(products.count) products: \(products.map { "\($0.id) (\($0.displayName))" }.joined(separator: ", "))") } catch { AppLogger.iap.error("Direct Product.products() FAILED: \(error.localizedDescription)") } } AnalyticsManager.shared.trackPaywallViewed(source: source) } .onInAppPurchaseStart { product in AnalyticsManager.shared.trackPurchaseStarted(productId: product.id, source: source) } .onInAppPurchaseCompletion { product, result in switch result { case .success(.success(_)): AnalyticsManager.shared.trackPurchaseCompleted(productId: product.id, source: source) Task { @MainActor in await iapManager.checkSubscriptionStatus() iapManager.trackSubscriptionAnalytics(source: "purchase_success") } dismiss() case .success(.userCancelled): AnalyticsManager.shared.trackPurchaseFailed(productId: product.id, source: source, error: "user_cancelled") case .success(.pending): AnalyticsManager.shared.trackPurchaseFailed(productId: product.id, source: source, error: "pending") case .failure(let error): AnalyticsManager.shared.trackPurchaseFailed(productId: product.id, source: source, error: error.localizedDescription) @unknown default: AnalyticsManager.shared.trackPurchaseFailed(productId: product.id, source: source, error: "unknown_result") } } } @ViewBuilder private var marketingContent: some View { Group { switch currentStyle { case .celestial: CelestialMarketingContent() case .garden: GardenMarketingContent() case .neon: NeonMarketingContent() case .minimal: MinimalMarketingContent() case .zen: ZenMarketingContent() case .editorial: EditorialMarketingContent() case .mixtape: MixtapeMarketingContent() case .heartfelt: HeartfeltMarketingContent() case .luxe: LuxeMarketingContent() case .forecast: ForecastMarketingContent() case .playful: PlayfulMarketingContent() case .journal: JournalMarketingContent() } } .frame(maxWidth: .infinity) } private var tintColor: Color { switch currentStyle { case .celestial: return Color(red: 1.0, green: 0.4, blue: 0.5) case .garden: return Color(red: 0.4, green: 0.75, blue: 0.45) case .neon: return Color(red: 0.0, green: 1.0, blue: 0.8) case .minimal: return Color(red: 0.95, green: 0.6, blue: 0.5) case .zen: return Color(red: 0.4, green: 0.6, blue: 0.5) case .editorial: return Color(red: 0.2, green: 0.2, blue: 0.3) case .mixtape: return Color(red: 0.8, green: 0.5, blue: 0.3) case .heartfelt: return Color(red: 1.0, green: 0.4, blue: 0.5) case .luxe: return Color(red: 0.6, green: 0.7, blue: 0.9) case .forecast: return Color(red: 0.3, green: 0.6, blue: 0.9) case .playful: return Color(red: 0.2, green: 1.0, blue: 0.4) case .journal: return Color(red: 0.6, green: 0.5, blue: 0.4) } } } // MARK: - 1. Celestial Theme (Aurora & Floating Orbs) struct CelestialMarketingContent: View { @State private var animateGradient = false @State private var animateOrbs = false @State private var showContent = false var body: some View { ZStack { CelestialBackground(animate: $animateGradient) VStack(spacing: 0) { EmotionOrbsView(animate: $animateOrbs) .frame(height: 140) .padding(.top, 20) VStack(spacing: 16) { Text("Understand\nYourself Deeper") .font(.system(size: 34, weight: .bold, design: .serif)) .multilineTextAlignment(.center) .foregroundStyle( LinearGradient( colors: [.white, .white.opacity(0.85)], startPoint: .top, endPoint: .bottom ) ) .shadow(color: .black.opacity(0.3), radius: 10, x: 0, y: 5) Text("Your emotions tell a story.\nPremium helps you read it.") .font(.system(size: 16, weight: .medium, design: .rounded)) .multilineTextAlignment(.center) .foregroundColor(.white.opacity(0.7)) .lineSpacing(4) } .padding(.horizontal, 24) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 20) FeatureCardsGrid(style: .celestial) .padding(.top, 32) .padding(.horizontal, 28) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 30) SocialProofBadge(style: .celestial) .padding(.top, 24) .opacity(showContent ? 1 : 0) Spacer().frame(height: 20) } } .onAppear { withAnimation(.easeInOut(duration: 3).repeatForever(autoreverses: true)) { animateGradient = true } withAnimation(.easeInOut(duration: 4).repeatForever(autoreverses: true)) { animateOrbs = true } withAnimation(.easeOut(duration: 0.8).delay(0.2)) { showContent = true } } .onDisappear { animateGradient = false animateOrbs = false } } } // MARK: - 2. Garden Theme (Organic Growth & Blooming) struct GardenMarketingContent: View { @State private var bloomPhase = false @State private var showContent = false @State private var swayPhase = false var body: some View { ZStack { GardenBackground(bloom: $bloomPhase, sway: $swayPhase) VStack(spacing: 0) { // Blooming flower illustration BloomingFlowerView(bloom: $bloomPhase) .frame(height: 160) .padding(.top, 10) VStack(spacing: 16) { Text("Watch Yourself\nBloom") .font(.system(size: 34, weight: .bold, design: .serif)) .multilineTextAlignment(.center) .foregroundStyle( LinearGradient( colors: [ Color(red: 0.95, green: 0.95, blue: 0.9), Color(red: 0.85, green: 0.9, blue: 0.8) ], startPoint: .top, endPoint: .bottom ) ) .shadow(color: .black.opacity(0.2), radius: 8, x: 0, y: 4) Text("Every feeling is a seed.\nPremium helps you grow.") .font(.system(size: 16, weight: .medium, design: .rounded)) .multilineTextAlignment(.center) .foregroundColor(.white.opacity(0.75)) .lineSpacing(4) } .padding(.horizontal, 24) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 20) FeatureCardsGrid(style: .garden) .padding(.top, 28) .padding(.horizontal, 28) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 30) SocialProofBadge(style: .garden) .padding(.top, 24) .opacity(showContent ? 1 : 0) Spacer().frame(height: 20) } } .onAppear { withAnimation(.easeInOut(duration: 4).repeatForever(autoreverses: true)) { bloomPhase = true } withAnimation(.easeInOut(duration: 3).repeatForever(autoreverses: true)) { swayPhase = true } withAnimation(.easeOut(duration: 0.8).delay(0.3)) { showContent = true } } .onDisappear { bloomPhase = false swayPhase = false } } } // MARK: - 3. Neon Theme (Synthwave & Energy) struct NeonMarketingContent: View { @State private var pulsePhase = false @State private var glowPhase = false @State private var showContent = false @State private var scanlineOffset: CGFloat = 0 var body: some View { ZStack { NeonBackground(pulse: $pulsePhase, glow: $glowPhase) // Scanlines overlay NeonScanlines(offset: $scanlineOffset) .opacity(0.03) VStack(spacing: 0) { // Glowing mood meter NeonMoodMeter(pulse: $pulsePhase) .frame(height: 140) .padding(.top, 20) VStack(spacing: 16) { Text("UNLOCK YOUR\nFULL SIGNAL") .font(.system(size: 32, weight: .black, design: .monospaced)) .multilineTextAlignment(.center) .foregroundStyle( LinearGradient( colors: [ Color(red: 0.0, green: 1.0, blue: 0.8), Color(red: 1.0, green: 0.0, blue: 0.8) ], startPoint: .leading, endPoint: .trailing ) ) .shadow(color: Color(red: 0.0, green: 1.0, blue: 0.8).opacity(0.5), radius: 20, x: 0, y: 0) Text("Amplify your emotional intelligence.\nGo premium. Go limitless.") .font(.system(size: 15, weight: .medium, design: .monospaced)) .multilineTextAlignment(.center) .foregroundColor(.white.opacity(0.7)) .lineSpacing(4) } .padding(.horizontal, 24) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 20) FeatureCardsGrid(style: .neon) .padding(.top, 28) .padding(.horizontal, 28) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 30) SocialProofBadge(style: .neon) .padding(.top, 24) .opacity(showContent ? 1 : 0) Spacer().frame(height: 20) } } .onAppear { withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) { pulsePhase = true } withAnimation(.easeInOut(duration: 2).repeatForever(autoreverses: true)) { glowPhase = true } withAnimation(.linear(duration: 8).repeatForever(autoreverses: false)) { scanlineOffset = 400 } withAnimation(.easeOut(duration: 0.6).delay(0.2)) { showContent = true } } .onDisappear { pulsePhase = false glowPhase = false scanlineOffset = 0 } } } // MARK: - 4. Minimal Theme (Clean & Sophisticated) struct MinimalMarketingContent: View { @State private var showContent = false @State private var breathe = false var body: some View { ZStack { MinimalBackground() VStack(spacing: 0) { // Elegant breathing circle MinimalBreathingCircle(breathe: $breathe) .frame(height: 160) .padding(.top, 10) VStack(spacing: 20) { Text("Simply\nKnow Yourself") .font(.system(size: 36, weight: .light, design: .serif)) .italic() .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.2, green: 0.15, blue: 0.1)) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 15) Text("Clarity through simplicity.\nPremium unlocks understanding.") .font(.system(size: 15, weight: .regular, design: .serif)) .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.4, green: 0.35, blue: 0.3)) .lineSpacing(6) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 15) } .padding(.horizontal, 32) FeatureCardsGrid(style: .minimal) .padding(.top, 32) .padding(.horizontal, 32) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 20) SocialProofBadge(style: .minimal) .padding(.top, 28) .opacity(showContent ? 1 : 0) Spacer().frame(height: 20) } } .onAppear { withAnimation(.easeInOut(duration: 4).repeatForever(autoreverses: true)) { breathe = true } withAnimation(.easeOut(duration: 1.0).delay(0.2)) { showContent = true } } .onDisappear { breathe = false } } } // MARK: - 5. Zen Theme (Ink Brushstrokes & Meditation) struct ZenMarketingContent: View { @State private var showContent = false @State private var inkFlow = false var body: some View { ZStack { ZenBackground(inkFlow: $inkFlow) VStack(spacing: 0) { // Zen circle (ensล) ZenEnsoView(animate: $inkFlow) .frame(height: 160) .padding(.top, 10) VStack(spacing: 20) { Text("Find Your\nInner Calm") .font(.system(size: 34, weight: .light, design: .serif)) .italic() .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.95, green: 0.93, blue: 0.88)) .shadow(color: .black.opacity(0.3), radius: 8) Text("Like ink on paper, each mood\nleaves its mark. Premium reveals the pattern.") .font(.system(size: 15, weight: .regular, design: .serif)) .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.8, green: 0.75, blue: 0.7)) .lineSpacing(4) } .padding(.horizontal, 32) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 20) FeatureCardsGrid(style: .zen) .padding(.top, 28) .padding(.horizontal, 28) .opacity(showContent ? 1 : 0) SocialProofBadge(style: .zen) .padding(.top, 24) .opacity(showContent ? 1 : 0) Spacer().frame(height: 20) } } .onAppear { withAnimation(.easeInOut(duration: 4).repeatForever(autoreverses: true)) { inkFlow = true } withAnimation(.easeOut(duration: 1.0).delay(0.3)) { showContent = true } } .onDisappear { inkFlow = false } } } // MARK: - 6. Editorial Theme (Magazine Typography) struct EditorialMarketingContent: View { @State private var showContent = false var body: some View { ZStack { EditorialBackground() VStack(spacing: 0) { // Large typographic element VStack(spacing: 4) { Text("THE") .font(.system(size: 14, weight: .regular, design: .serif)) .tracking(8) .foregroundColor(Color(red: 0.4, green: 0.4, blue: 0.45)) Text("REFLECT") .font(.system(size: 52, weight: .bold, design: .serif)) .tracking(2) .foregroundColor(Color(red: 0.15, green: 0.15, blue: 0.2)) Text("JOURNAL") .font(.system(size: 14, weight: .light, design: .serif)) .tracking(12) .foregroundColor(Color(red: 0.4, green: 0.4, blue: 0.45)) } .padding(.top, 30) .padding(.bottom, 20) VStack(spacing: 16) { Rectangle() .fill(Color(red: 0.2, green: 0.2, blue: 0.25)) .frame(width: 60, height: 1) Text("Premium Edition") .font(.system(size: 13, weight: .medium, design: .serif)) .italic() .foregroundColor(Color(red: 0.5, green: 0.5, blue: 0.55)) Text("Unlock the complete story\nof your emotional landscape.") .font(.system(size: 16, weight: .regular, design: .serif)) .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.3, green: 0.3, blue: 0.35)) .lineSpacing(6) } .padding(.horizontal, 40) .opacity(showContent ? 1 : 0) FeatureCardsGrid(style: .editorial) .padding(.top, 28) .padding(.horizontal, 28) .opacity(showContent ? 1 : 0) SocialProofBadge(style: .editorial) .padding(.top, 24) .opacity(showContent ? 1 : 0) Spacer().frame(height: 20) } } .onAppear { withAnimation(.easeOut(duration: 0.8).delay(0.2)) { showContent = true } } } } // MARK: - 7. Mixtape Theme (Cassette & Retro Analog) struct MixtapeMarketingContent: View { @State private var showContent = false @State private var tapeRotation = false var body: some View { ZStack { MixtapeBackground() VStack(spacing: 0) { // Cassette tape illustration CassetteTapeView(rotating: $tapeRotation) .frame(height: 150) .padding(.top, 15) VStack(spacing: 16) { Text("YOUR MOOD\nMIXTAPE") .font(.system(size: 30, weight: .bold, design: .rounded)) .multilineTextAlignment(.center) .foregroundStyle( LinearGradient( colors: [ Color(red: 1.0, green: 0.85, blue: 0.6), Color(red: 0.9, green: 0.6, blue: 0.4) ], startPoint: .top, endPoint: .bottom ) ) Text("Every feeling is a track.\nPremium gives you the full album.") .font(.system(size: 15, weight: .medium, design: .rounded)) .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.85, green: 0.75, blue: 0.65)) .lineSpacing(4) } .padding(.horizontal, 32) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 20) FeatureCardsGrid(style: .mixtape) .padding(.top, 28) .padding(.horizontal, 28) .opacity(showContent ? 1 : 0) SocialProofBadge(style: .mixtape) .padding(.top, 24) .opacity(showContent ? 1 : 0) Spacer().frame(height: 20) } } .onAppear { withAnimation(.linear(duration: 3).repeatForever(autoreverses: false)) { tapeRotation = true } withAnimation(.easeOut(duration: 0.8).delay(0.2)) { showContent = true } } .onDisappear { tapeRotation = false } } } // MARK: - 8. Heartfelt Theme (Hearts & Emotion) struct HeartfeltMarketingContent: View { @State private var showContent = false @State private var heartbeat = false var body: some View { ZStack { HeartfeltBackground(beat: $heartbeat) VStack(spacing: 0) { // Floating hearts FloatingHeartsView(beat: $heartbeat) .frame(height: 150) .padding(.top, 15) VStack(spacing: 16) { Text("Feel It All") .font(.system(size: 38, weight: .bold, design: .rounded)) .foregroundStyle( LinearGradient( colors: [ Color(red: 1.0, green: 0.6, blue: 0.7), Color(red: 1.0, green: 0.4, blue: 0.5) ], startPoint: .top, endPoint: .bottom ) ) .shadow(color: Color(red: 1.0, green: 0.4, blue: 0.5).opacity(0.3), radius: 15) Text("Your heart knows the way.\nPremium helps you listen.") .font(.system(size: 16, weight: .medium, design: .rounded)) .multilineTextAlignment(.center) .foregroundColor(.white.opacity(0.8)) .lineSpacing(4) } .padding(.horizontal, 32) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 20) FeatureCardsGrid(style: .heartfelt) .padding(.top, 28) .padding(.horizontal, 28) .opacity(showContent ? 1 : 0) SocialProofBadge(style: .heartfelt) .padding(.top, 24) .opacity(showContent ? 1 : 0) Spacer().frame(height: 20) } } .onAppear { withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) { heartbeat = true } withAnimation(.easeOut(duration: 0.8).delay(0.2)) { showContent = true } } .onDisappear { heartbeat = false } } } // MARK: - 9. Luxe Theme (Premium Glass Materials) struct LuxeMarketingContent: View { @State private var showContent = false @State private var shimmer = false var body: some View { ZStack { LuxeBackground(shimmer: $shimmer) VStack(spacing: 0) { // Diamond/gem icon LuxeGemView(shimmer: $shimmer) .frame(height: 150) .padding(.top, 15) VStack(spacing: 16) { Text("ELEVATE YOUR\nEXPERIENCE") .font(.system(size: 28, weight: .semibold, design: .rounded)) .multilineTextAlignment(.center) .foregroundStyle( LinearGradient( colors: [.white, Color(red: 0.85, green: 0.9, blue: 1.0)], startPoint: .top, endPoint: .bottom ) ) .shadow(color: .white.opacity(0.3), radius: 10) Text("Premium refinement for those\nwho expect the finest.") .font(.system(size: 15, weight: .regular, design: .rounded)) .multilineTextAlignment(.center) .foregroundColor(.white.opacity(0.7)) .lineSpacing(4) } .padding(.horizontal, 32) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 20) FeatureCardsGrid(style: .luxe) .padding(.top, 28) .padding(.horizontal, 28) .opacity(showContent ? 1 : 0) SocialProofBadge(style: .luxe) .padding(.top, 24) .opacity(showContent ? 1 : 0) Spacer().frame(height: 20) } } .onAppear { withAnimation(.easeInOut(duration: 2).repeatForever(autoreverses: true)) { shimmer = true } withAnimation(.easeOut(duration: 0.8).delay(0.2)) { showContent = true } } .onDisappear { shimmer = false } } } // MARK: - 10. Forecast Theme (Weather Metaphors) struct ForecastMarketingContent: View { @State private var showContent = false @State private var cloudDrift = false var body: some View { ZStack { ForecastBackground(drift: $cloudDrift) VStack(spacing: 0) { // Weather icons WeatherIconsView(drift: $cloudDrift) .frame(height: 150) .padding(.top, 15) VStack(spacing: 16) { Text("Your Emotional\nForecast") .font(.system(size: 34, weight: .bold, design: .rounded)) .multilineTextAlignment(.center) .foregroundStyle( LinearGradient( colors: [ Color(red: 0.95, green: 0.95, blue: 1.0), Color(red: 0.7, green: 0.85, blue: 1.0) ], startPoint: .top, endPoint: .bottom ) ) Text("Predict your patterns.\nPrepare for any weather.") .font(.system(size: 16, weight: .medium, design: .rounded)) .multilineTextAlignment(.center) .foregroundColor(.white.opacity(0.75)) .lineSpacing(4) } .padding(.horizontal, 32) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 20) FeatureCardsGrid(style: .forecast) .padding(.top, 28) .padding(.horizontal, 28) .opacity(showContent ? 1 : 0) SocialProofBadge(style: .forecast) .padding(.top, 24) .opacity(showContent ? 1 : 0) Spacer().frame(height: 20) } } .onAppear { withAnimation(.easeInOut(duration: 4).repeatForever(autoreverses: true)) { cloudDrift = true } withAnimation(.easeOut(duration: 0.8).delay(0.2)) { showContent = true } } .onDisappear { cloudDrift = false } } } // MARK: - 11. Playful Theme (Vibrant & Fun) struct PlayfulMarketingContent: View { @State private var showContent = false @State private var bounce = false var body: some View { ZStack { PlayfulBackground(bounce: $bounce) VStack(spacing: 0) { // Bouncing emoji faces PlayfulEmojisView(bounce: $bounce) .frame(height: 150) .padding(.top, 15) VStack(spacing: 16) { Text("MOOD UNLOCKED! ๐ŸŽฎ") .font(.system(size: 30, weight: .black, design: .rounded)) .foregroundStyle( LinearGradient( colors: [ Color(red: 0.2, green: 1.0, blue: 0.4), Color(red: 1.0, green: 1.0, blue: 0.2) ], startPoint: .leading, endPoint: .trailing ) ) .shadow(color: Color(red: 0.2, green: 1.0, blue: 0.4).opacity(0.5), radius: 15) Text("Level up your self-awareness.\nPremium = MAX POWER!") .font(.system(size: 15, weight: .bold, design: .rounded)) .multilineTextAlignment(.center) .foregroundColor(.white.opacity(0.85)) .lineSpacing(4) } .padding(.horizontal, 32) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 20) FeatureCardsGrid(style: .playful) .padding(.top, 28) .padding(.horizontal, 28) .opacity(showContent ? 1 : 0) SocialProofBadge(style: .playful) .padding(.top, 24) .opacity(showContent ? 1 : 0) Spacer().frame(height: 20) } } .onAppear { withAnimation(.easeInOut(duration: 0.6).repeatForever(autoreverses: true)) { bounce = true } withAnimation(.easeOut(duration: 0.6).delay(0.1)) { showContent = true } } .onDisappear { bounce = false } } } // MARK: - 12. Journal Theme (Handwritten Paper) struct JournalMarketingContent: View { @State private var showContent = false @State private var pageFlip = false var body: some View { ZStack { JournalBackground() VStack(spacing: 0) { // Stacked paper pages JournalPagesView(flip: $pageFlip) .frame(height: 150) .padding(.top, 15) VStack(spacing: 16) { Text("Your Personal\nDiary") .font(.custom("Georgia", size: 34)) .italic() .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.35, green: 0.25, blue: 0.2)) Text("Every entry tells your story.\nPremium unlocks the full narrative.") .font(.custom("Georgia", size: 15)) .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.5, green: 0.4, blue: 0.35)) .lineSpacing(6) } .padding(.horizontal, 40) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 15) FeatureCardsGrid(style: .journal) .padding(.top, 28) .padding(.horizontal, 28) .opacity(showContent ? 1 : 0) SocialProofBadge(style: .journal) .padding(.top, 24) .opacity(showContent ? 1 : 0) Spacer().frame(height: 20) } } .onAppear { withAnimation(.easeInOut(duration: 3).repeatForever(autoreverses: true)) { pageFlip = true } withAnimation(.easeOut(duration: 1.0).delay(0.2)) { showContent = true } } .onDisappear { pageFlip = false } } } // MARK: - Background Views struct CelestialBackground: View { @Binding var animate: Bool var body: some View { ZStack { LinearGradient( colors: [ Color(red: 0.05, green: 0.05, blue: 0.12), Color(red: 0.08, green: 0.06, blue: 0.15), Color(red: 0.04, green: 0.04, blue: 0.1) ], startPoint: .top, endPoint: .bottom ) EllipticalGradient( colors: [ Color(red: 1.0, green: 0.4, blue: 0.3).opacity(0.4), Color(red: 1.0, green: 0.6, blue: 0.4).opacity(0.2), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.8 ) .frame(width: 400, height: 300) .offset(x: animate ? 30 : -30, y: animate ? -50 : -80) .blur(radius: 60) EllipticalGradient( colors: [ Color(red: 0.4, green: 0.3, blue: 0.9).opacity(0.3), Color(red: 0.3, green: 0.5, blue: 0.8).opacity(0.15), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.7 ) .frame(width: 350, height: 250) .offset(x: animate ? -40 : 20, y: animate ? 100 : 60) .blur(radius: 50) EllipticalGradient( colors: [ Color(red: 0.9, green: 0.3, blue: 0.5).opacity(0.25), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.6 ) .frame(width: 300, height: 200) .offset(x: animate ? 60 : -20, y: animate ? -20 : 40) .blur(radius: 40) } .ignoresSafeArea() } } struct GardenBackground: View { @Binding var bloom: Bool @Binding var sway: Bool var body: some View { ZStack { // Deep forest gradient LinearGradient( colors: [ Color(red: 0.05, green: 0.12, blue: 0.08), Color(red: 0.08, green: 0.18, blue: 0.1), Color(red: 0.04, green: 0.1, blue: 0.06) ], startPoint: .top, endPoint: .bottom ) // Soft green glow EllipticalGradient( colors: [ Color(red: 0.3, green: 0.7, blue: 0.4).opacity(0.25), Color(red: 0.2, green: 0.5, blue: 0.3).opacity(0.1), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.8 ) .frame(width: 400, height: 400) .offset(y: bloom ? -20 : 20) .blur(radius: 80) // Warm accent EllipticalGradient( colors: [ Color(red: 1.0, green: 0.8, blue: 0.5).opacity(0.15), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.5 ) .frame(width: 300, height: 200) .offset(x: sway ? 40 : -40, y: -100) .blur(radius: 60) // Floating leaves particles ForEach(0..<8, id: \.self) { i in LeafParticle(index: i, sway: sway) } } .ignoresSafeArea() } } struct LeafParticle: View { let index: Int let sway: Bool var body: some View { Circle() .fill(Color(red: 0.4, green: 0.7, blue: 0.4).opacity(0.15)) .frame(width: CGFloat.random(in: 4...12), height: CGFloat.random(in: 4...12)) .offset( x: CGFloat(index * 40 - 140) + (sway ? 10 : -10), y: CGFloat(index * 30 - 100) ) .blur(radius: 2) } } struct NeonBackground: View { @Binding var pulse: Bool @Binding var glow: Bool var body: some View { ZStack { // Deep dark base Color(red: 0.02, green: 0.02, blue: 0.05) // Grid lines NeonGrid() .opacity(0.3) // Cyan glow EllipticalGradient( colors: [ Color(red: 0.0, green: 1.0, blue: 0.8).opacity(pulse ? 0.3 : 0.15), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.6 ) .frame(width: 400, height: 300) .offset(y: -80) .blur(radius: 60) // Magenta glow EllipticalGradient( colors: [ Color(red: 1.0, green: 0.0, blue: 0.8).opacity(glow ? 0.25 : 0.1), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.5 ) .frame(width: 350, height: 250) .offset(x: 50, y: 100) .blur(radius: 50) } .ignoresSafeArea() } } struct NeonGrid: View { var body: some View { Canvas { context, size in let gridSpacing: CGFloat = 30 let lineColor = Color(red: 0.0, green: 0.8, blue: 0.8).opacity(0.15) // Horizontal lines for y in stride(from: 0, to: size.height, by: gridSpacing) { var path = Path() path.move(to: CGPoint(x: 0, y: y)) path.addLine(to: CGPoint(x: size.width, y: y)) context.stroke(path, with: .color(lineColor), lineWidth: 0.5) } // Vertical lines for x in stride(from: 0, to: size.width, by: gridSpacing) { var path = Path() path.move(to: CGPoint(x: x, y: 0)) path.addLine(to: CGPoint(x: x, y: size.height)) context.stroke(path, with: .color(lineColor), lineWidth: 0.5) } } } } struct NeonScanlines: View { @Binding var offset: CGFloat var body: some View { GeometryReader { geo in ForEach(0..<20, id: \.self) { i in Rectangle() .fill(Color.white) .frame(height: 1) .offset(y: CGFloat(i * 20) + offset.truncatingRemainder(dividingBy: 400)) } } } } struct MinimalBackground: View { var body: some View { ZStack { // Warm cream gradient LinearGradient( colors: [ Color(red: 0.98, green: 0.96, blue: 0.92), Color(red: 0.95, green: 0.93, blue: 0.88), Color(red: 0.92, green: 0.90, blue: 0.85) ], startPoint: .top, endPoint: .bottom ) // Subtle warm accent EllipticalGradient( colors: [ Color(red: 0.95, green: 0.85, blue: 0.75).opacity(0.4), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.6 ) .frame(width: 500, height: 400) .offset(y: -50) .blur(radius: 100) } .ignoresSafeArea() } } struct ZenBackground: View { @Binding var inkFlow: Bool var body: some View { ZStack { // Dark paper texture LinearGradient( colors: [ Color(red: 0.12, green: 0.1, blue: 0.08), Color(red: 0.08, green: 0.07, blue: 0.06), Color(red: 0.1, green: 0.08, blue: 0.06) ], startPoint: .top, endPoint: .bottom ) // Subtle ink wash EllipticalGradient( colors: [ Color(red: 0.3, green: 0.4, blue: 0.35).opacity(inkFlow ? 0.2 : 0.1), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.7 ) .frame(width: 400, height: 400) .blur(radius: 80) } .ignoresSafeArea() } } struct EditorialBackground: View { var body: some View { ZStack { // Clean off-white LinearGradient( colors: [ Color(red: 0.98, green: 0.97, blue: 0.95), Color(red: 0.96, green: 0.95, blue: 0.93), Color(red: 0.94, green: 0.93, blue: 0.91) ], startPoint: .top, endPoint: .bottom ) } .ignoresSafeArea() } } struct MixtapeBackground: View { var body: some View { ZStack { // Warm brown gradient LinearGradient( colors: [ Color(red: 0.15, green: 0.1, blue: 0.08), Color(red: 0.12, green: 0.08, blue: 0.06), Color(red: 0.1, green: 0.07, blue: 0.05) ], startPoint: .top, endPoint: .bottom ) // Warm orange glow EllipticalGradient( colors: [ Color(red: 0.8, green: 0.4, blue: 0.2).opacity(0.15), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.6 ) .frame(width: 400, height: 300) .offset(y: -50) .blur(radius: 60) } .ignoresSafeArea() } } struct HeartfeltBackground: View { @Binding var beat: Bool var body: some View { ZStack { // Warm pink gradient LinearGradient( colors: [ Color(red: 0.15, green: 0.05, blue: 0.08), Color(red: 0.12, green: 0.04, blue: 0.06), Color(red: 0.1, green: 0.03, blue: 0.05) ], startPoint: .top, endPoint: .bottom ) // Pink glow EllipticalGradient( colors: [ Color(red: 1.0, green: 0.3, blue: 0.5).opacity(beat ? 0.25 : 0.15), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.7 ) .frame(width: 400, height: 400) .blur(radius: 80) } .ignoresSafeArea() } } struct LuxeBackground: View { @Binding var shimmer: Bool var body: some View { ZStack { // Deep blue-black LinearGradient( colors: [ Color(red: 0.06, green: 0.08, blue: 0.12), Color(red: 0.04, green: 0.05, blue: 0.08), Color(red: 0.03, green: 0.04, blue: 0.06) ], startPoint: .top, endPoint: .bottom ) // Subtle blue shimmer EllipticalGradient( colors: [ Color(red: 0.4, green: 0.5, blue: 0.8).opacity(shimmer ? 0.2 : 0.1), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.6 ) .frame(width: 400, height: 300) .offset(y: shimmer ? -30 : -60) .blur(radius: 60) } .ignoresSafeArea() } } struct ForecastBackground: View { @Binding var drift: Bool var body: some View { ZStack { // Sky gradient LinearGradient( colors: [ Color(red: 0.15, green: 0.25, blue: 0.4), Color(red: 0.1, green: 0.18, blue: 0.3), Color(red: 0.08, green: 0.12, blue: 0.2) ], startPoint: .top, endPoint: .bottom ) // Cloud-like glow EllipticalGradient( colors: [ Color.white.opacity(0.1), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.5 ) .frame(width: 350, height: 200) .offset(x: drift ? 30 : -30, y: -80) .blur(radius: 50) } .ignoresSafeArea() } } struct PlayfulBackground: View { @Binding var bounce: Bool var body: some View { ZStack { // Deep purple-black LinearGradient( colors: [ Color(red: 0.08, green: 0.05, blue: 0.12), Color(red: 0.05, green: 0.03, blue: 0.08), Color(red: 0.04, green: 0.02, blue: 0.06) ], startPoint: .top, endPoint: .bottom ) // Green glow EllipticalGradient( colors: [ Color(red: 0.2, green: 1.0, blue: 0.4).opacity(bounce ? 0.2 : 0.1), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.5 ) .frame(width: 300, height: 300) .offset(x: -50, y: -50) .blur(radius: 60) // Yellow glow EllipticalGradient( colors: [ Color(red: 1.0, green: 1.0, blue: 0.2).opacity(0.1), Color.clear ], center: .center, startRadiusFraction: 0, endRadiusFraction: 0.4 ) .frame(width: 250, height: 250) .offset(x: 60, y: 100) .blur(radius: 50) } .ignoresSafeArea() } } struct JournalBackground: View { var body: some View { ZStack { // Warm paper LinearGradient( colors: [ Color(red: 1.0, green: 0.98, blue: 0.94), Color(red: 0.98, green: 0.95, blue: 0.88), Color(red: 0.95, green: 0.92, blue: 0.85) ], startPoint: .top, endPoint: .bottom ) // Paper texture lines VStack(spacing: 28) { ForEach(0..<20, id: \.self) { _ in Rectangle() .fill(Color(red: 0.85, green: 0.82, blue: 0.78).opacity(0.3)) .frame(height: 1) } } .padding(.horizontal, 40) } .ignoresSafeArea() } } // MARK: - Decorative Elements struct EmotionOrbsView: View { @Binding var animate: Bool private let emotions: [(color: Color, size: CGFloat, xOffset: CGFloat, yOffset: CGFloat)] = [ (Color(red: 1.0, green: 0.8, blue: 0.3), 56, -90, 20), (Color(red: 0.4, green: 0.8, blue: 0.6), 44, -30, -30), (Color(red: 1.0, green: 0.5, blue: 0.5), 52, 40, 10), (Color(red: 0.6, green: 0.5, blue: 0.9), 40, 95, -20), (Color(red: 0.3, green: 0.7, blue: 1.0), 36, 60, 50), ] var body: some View { ZStack { ForEach(0.. [Color] { let colorSets: [[[Color]]] = [ // 0: Celestial [ [Color(red: 1.0, green: 0.6, blue: 0.4), Color(red: 1.0, green: 0.4, blue: 0.3)], [Color(red: 0.4, green: 0.7, blue: 0.9), Color(red: 0.3, green: 0.5, blue: 0.8)], [Color(red: 0.6, green: 0.8, blue: 0.5), Color(red: 0.4, green: 0.7, blue: 0.4)], [Color(red: 0.8, green: 0.5, blue: 0.9), Color(red: 0.6, green: 0.3, blue: 0.8)] ], // 1: Garden [ [Color(red: 0.6, green: 0.8, blue: 0.5), Color(red: 0.4, green: 0.7, blue: 0.4)], [Color(red: 1.0, green: 0.7, blue: 0.7), Color(red: 0.9, green: 0.5, blue: 0.5)], [Color(red: 0.7, green: 0.6, blue: 0.9), Color(red: 0.5, green: 0.4, blue: 0.7)], [Color(red: 1.0, green: 0.85, blue: 0.5), Color(red: 0.9, green: 0.7, blue: 0.3)] ], // 2: Neon [ [Color(red: 0.0, green: 1.0, blue: 0.8), Color(red: 0.0, green: 0.7, blue: 0.6)], [Color(red: 1.0, green: 0.0, blue: 0.8), Color(red: 0.7, green: 0.0, blue: 0.6)], [Color(red: 1.0, green: 1.0, blue: 0.0), Color(red: 0.8, green: 0.8, blue: 0.0)], [Color(red: 0.5, green: 0.0, blue: 1.0), Color(red: 0.3, green: 0.0, blue: 0.7)] ], // 3: Minimal [ [Color(red: 0.85, green: 0.7, blue: 0.65), Color(red: 0.75, green: 0.6, blue: 0.55)], [Color(red: 0.7, green: 0.65, blue: 0.6), Color(red: 0.6, green: 0.55, blue: 0.5)], [Color(red: 0.8, green: 0.65, blue: 0.6), Color(red: 0.7, green: 0.55, blue: 0.5)], [Color(red: 0.75, green: 0.7, blue: 0.65), Color(red: 0.65, green: 0.6, blue: 0.55)] ], // 4: Zen [ [Color(red: 0.5, green: 0.55, blue: 0.5), Color(red: 0.4, green: 0.45, blue: 0.4)], [Color(red: 0.55, green: 0.5, blue: 0.45), Color(red: 0.45, green: 0.4, blue: 0.35)], [Color(red: 0.5, green: 0.5, blue: 0.5), Color(red: 0.4, green: 0.4, blue: 0.4)], [Color(red: 0.55, green: 0.55, blue: 0.5), Color(red: 0.45, green: 0.45, blue: 0.4)] ], // 5: Editorial [ [Color(red: 0.4, green: 0.4, blue: 0.45), Color(red: 0.3, green: 0.3, blue: 0.35)], [Color(red: 0.45, green: 0.4, blue: 0.4), Color(red: 0.35, green: 0.3, blue: 0.3)], [Color(red: 0.4, green: 0.45, blue: 0.45), Color(red: 0.3, green: 0.35, blue: 0.35)], [Color(red: 0.45, green: 0.45, blue: 0.4), Color(red: 0.35, green: 0.35, blue: 0.3)] ], // 6: Mixtape [ [Color(red: 0.8, green: 0.5, blue: 0.3), Color(red: 0.6, green: 0.35, blue: 0.2)], [Color(red: 0.7, green: 0.45, blue: 0.25), Color(red: 0.5, green: 0.3, blue: 0.15)], [Color(red: 0.85, green: 0.55, blue: 0.35), Color(red: 0.65, green: 0.4, blue: 0.25)], [Color(red: 0.75, green: 0.5, blue: 0.3), Color(red: 0.55, green: 0.35, blue: 0.2)] ], // 7: Heartfelt [ [Color(red: 1.0, green: 0.5, blue: 0.6), Color(red: 0.9, green: 0.4, blue: 0.5)], [Color(red: 1.0, green: 0.6, blue: 0.65), Color(red: 0.9, green: 0.5, blue: 0.55)], [Color(red: 1.0, green: 0.45, blue: 0.55), Color(red: 0.9, green: 0.35, blue: 0.45)], [Color(red: 1.0, green: 0.55, blue: 0.6), Color(red: 0.9, green: 0.45, blue: 0.5)] ], // 8: Luxe [ [Color(red: 0.7, green: 0.8, blue: 1.0), Color(red: 0.5, green: 0.6, blue: 0.8)], [Color(red: 0.8, green: 0.85, blue: 0.95), Color(red: 0.6, green: 0.65, blue: 0.75)], [Color(red: 0.75, green: 0.8, blue: 0.9), Color(red: 0.55, green: 0.6, blue: 0.7)], [Color(red: 0.65, green: 0.75, blue: 0.95), Color(red: 0.45, green: 0.55, blue: 0.75)] ], // 9: Forecast [ [Color(red: 0.4, green: 0.7, blue: 1.0), Color(red: 0.3, green: 0.5, blue: 0.8)], [Color(red: 1.0, green: 0.85, blue: 0.3), Color(red: 0.8, green: 0.65, blue: 0.2)], [Color(red: 0.5, green: 0.8, blue: 0.9), Color(red: 0.4, green: 0.6, blue: 0.7)], [Color(red: 0.9, green: 0.6, blue: 0.4), Color(red: 0.7, green: 0.4, blue: 0.3)] ], // 10: Playful [ [Color(red: 0.2, green: 1.0, blue: 0.4), Color(red: 0.1, green: 0.8, blue: 0.3)], [Color(red: 1.0, green: 1.0, blue: 0.2), Color(red: 0.8, green: 0.8, blue: 0.1)], [Color(red: 1.0, green: 0.4, blue: 0.2), Color(red: 0.8, green: 0.3, blue: 0.1)], [Color(red: 0.6, green: 0.2, blue: 1.0), Color(red: 0.4, green: 0.1, blue: 0.8)] ], // 11: Journal [ [Color(red: 0.6, green: 0.45, blue: 0.35), Color(red: 0.5, green: 0.35, blue: 0.25)], [Color(red: 0.55, green: 0.4, blue: 0.3), Color(red: 0.45, green: 0.3, blue: 0.2)], [Color(red: 0.5, green: 0.45, blue: 0.4), Color(red: 0.4, green: 0.35, blue: 0.3)], [Color(red: 0.55, green: 0.45, blue: 0.35), Color(red: 0.45, green: 0.35, blue: 0.25)] ] ] let safeIndex = min(style.rawValue, colorSets.count - 1) return colorSets[safeIndex][index % 4] } } // MARK: - Preview #Preview("Celestial") { ReflectSubscriptionStoreView(style: .celestial) .environmentObject(IAPManager()) .preferredColorScheme(.dark) } #Preview("Garden") { ReflectSubscriptionStoreView(style: .garden) .environmentObject(IAPManager()) .preferredColorScheme(.dark) } #Preview("Neon") { ReflectSubscriptionStoreView(style: .neon) .environmentObject(IAPManager()) .preferredColorScheme(.dark) } #Preview("Minimal") { ReflectSubscriptionStoreView(style: .minimal) .environmentObject(IAPManager()) .preferredColorScheme(.light) }