Explorar o código

Add home-style hover interactions across paywall components.

Apply consistent hover feedback to the CTA button, plan cards, and footer links so the paywall matches the interactive feel of the home page while preserving selected plan emphasis.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 hai 1 mes
pai
achega
ce69050191
Modificáronse 1 ficheiros con 52 adicións e 23 borrados
  1. 52 23
      gramora/Views/PaywallView.swift

+ 52 - 23
gramora/Views/PaywallView.swift

@@ -3,6 +3,7 @@ import SwiftUI
 struct PaywallView: View {
     let onClose: () -> Void
     @StateObject private var viewModel = PaywallViewModel()
+    @State private var isCTAHovered = false
 
     var body: some View {
         GeometryReader { proxy in
@@ -93,7 +94,7 @@ struct PaywallView: View {
                     Spacer()
                 }
                 .padding(.vertical, 14)
-                .background(Color(red: 0.03, green: 0.62, blue: 0.48))
+                .background(isCTAHovered ? Color(red: 0.02, green: 0.56, blue: 0.43) : Color(red: 0.03, green: 0.62, blue: 0.48))
                 .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
                 .overlay(alignment: .trailing) {
                     Image(systemName: "chevron.right.2")
@@ -102,7 +103,15 @@ struct PaywallView: View {
                         .padding(.trailing, 24)
                 }
             }
+            .scaleEffect(isCTAHovered ? 1.01 : 1.0)
+            .shadow(
+                color: isCTAHovered ? AppTheme.primaryShadow.opacity(0.32) : .clear,
+                radius: isCTAHovered ? 8 : 0,
+                y: isCTAHovered ? 3 : 0
+            )
+            .animation(.easeOut(duration: 0.15), value: isCTAHovered)
             .buttonStyle(.plain)
+            .onHover { isCTAHovered = $0 }
 
             Text("No commitment, cancel anytime.")
                 .font(.system(size: 13, weight: .semibold))
@@ -119,10 +128,10 @@ struct PaywallView: View {
                 .lineSpacing(2)
 
             HStack(spacing: 0) {
-                footerLink("Continue with free plan", action: onClose)
-                footerLink("Privacy Policy")
-                footerLink("Support")
-                footerLink("Terms of Services")
+                PaywallFooterLinkView(title: "Continue with free plan", action: onClose)
+                PaywallFooterLinkView(title: "Privacy Policy")
+                PaywallFooterLinkView(title: "Support")
+                PaywallFooterLinkView(title: "Terms of Services")
             }
             .frame(height: 44)
             .frame(maxWidth: .infinity)
@@ -131,19 +140,6 @@ struct PaywallView: View {
         }
     }
 
-    private func footerLink(_ title: String, action: @escaping () -> Void = {}) -> some View {
-        Button(action: action) {
-            Text(title)
-                .font(.system(size: 14, weight: .semibold))
-                .foregroundStyle(Color(red: 0.59, green: 0.62, blue: 0.68))
-                .lineLimit(1)
-                .minimumScaleFactor(0.75)
-                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
-                .contentShape(Rectangle())
-        }
-        .frame(maxWidth: .infinity, maxHeight: .infinity)
-        .buttonStyle(.plain)
-    }
 }
 
 private struct PaywallFeatureBox: View {
@@ -195,6 +191,7 @@ private struct PaywallPlanCardView: View {
     let plan: PaywallPlan
     let isSelected: Bool
     let onSelect: () -> Void
+    @State private var isHovered = false
 
     var body: some View {
         Button(action: onSelect) {
@@ -267,21 +264,53 @@ private struct PaywallPlanCardView: View {
                 )
             }
             .frame(maxWidth: .infinity, minHeight: 170, alignment: .topLeading)
-            .background(isSelected ? AppTheme.tealLight.opacity(0.28) : AppTheme.cardBackground)
+            .background((isSelected || isHovered) ? AppTheme.tealLight.opacity(isSelected ? 0.28 : 0.16) : AppTheme.cardBackground)
             .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
             .overlay(
                 RoundedRectangle(cornerRadius: 16, style: .continuous)
                     .stroke(
-                        isSelected ? AppTheme.toolbarBorderHover : AppTheme.border,
+                        (isSelected || isHovered) ? AppTheme.toolbarBorderHover : AppTheme.border,
                         lineWidth: isSelected ? 2 : 1
                     )
             )
             .shadow(
-                color: isSelected ? AppTheme.primaryShadow.opacity(0.35) : .clear,
-                radius: isSelected ? 6 : 0,
-                y: isSelected ? 2 : 0
+                color: (isSelected || isHovered) ? AppTheme.primaryShadow.opacity(isSelected ? 0.35 : 0.22) : .clear,
+                radius: (isSelected || isHovered) ? (isSelected ? 6 : 4) : 0,
+                y: (isSelected || isHovered) ? 2 : 0
             )
+            .scaleEffect(isHovered && !isSelected ? 1.008 : 1.0)
+            .animation(.easeOut(duration: 0.15), value: isHovered)
         }
         .buttonStyle(.plain)
+        .onHover { isHovered = $0 }
+    }
+}
+
+private struct PaywallFooterLinkView: View {
+    let title: String
+    let action: () -> Void
+    @State private var isHovered = false
+
+    init(title: String, action: @escaping () -> Void = {}) {
+        self.title = title
+        self.action = action
+    }
+
+    var body: some View {
+        Button(action: action) {
+            Text(title)
+                .font(.system(size: 14, weight: .semibold))
+                .foregroundStyle(isHovered ? Color(red: 0.47, green: 0.51, blue: 0.57) : Color(red: 0.59, green: 0.62, blue: 0.68))
+                .lineLimit(1)
+                .minimumScaleFactor(0.75)
+                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
+                .contentShape(Rectangle())
+        }
+        .frame(maxWidth: .infinity, maxHeight: .infinity)
+        .background(isHovered ? AppTheme.cardBackground : Color.clear)
+        .scaleEffect(isHovered ? 1.01 : 1.0)
+        .animation(.easeOut(duration: 0.15), value: isHovered)
+        .buttonStyle(.plain)
+        .onHover { isHovered = $0 }
     }
 }