|
|
@@ -7,28 +7,27 @@ struct PaywallView: View {
|
|
|
|
|
|
var body: some View {
|
|
|
GeometryReader { proxy in
|
|
|
- let availableWidth = max(320, proxy.size.width - 24)
|
|
|
+ let horizontalInset = min(max(proxy.size.width * 0.03, 12), 34)
|
|
|
+ let availableWidth = max(320, proxy.size.width - (horizontalInset * 2))
|
|
|
let availableHeight = max(420, proxy.size.height - 24)
|
|
|
let baseWidth: CGFloat = 860
|
|
|
- let baseContentHeight: CGFloat = 610
|
|
|
- let topChromeHeight: CGFloat = 44
|
|
|
- let totalBaseHeight = baseContentHeight + topChromeHeight
|
|
|
- let scale = min(1, availableWidth / baseWidth, availableHeight / totalBaseHeight)
|
|
|
+ let contentWidth = min(baseWidth, availableWidth)
|
|
|
+ let heightScale = min(max(availableHeight / 680, 0.82), 1.2)
|
|
|
+ let verticalSpacing: CGFloat = 18
|
|
|
+ let topPadding: CGFloat = 10
|
|
|
+ let featureHeight = max(72, min(108, 84 * heightScale))
|
|
|
+ let planHeight = max(150, min(210, 170 * heightScale))
|
|
|
|
|
|
VStack(spacing: 0) {
|
|
|
- VStack(spacing: 18) {
|
|
|
+ VStack(spacing: verticalSpacing) {
|
|
|
titleSection
|
|
|
- featuresSection
|
|
|
- plansSection
|
|
|
+ featuresSection(featureHeight: featureHeight)
|
|
|
+ plansSection(planHeight: planHeight)
|
|
|
ctaSection
|
|
|
- Spacer(minLength: 0)
|
|
|
legalSection
|
|
|
}
|
|
|
- .frame(width: baseWidth, height: baseContentHeight, alignment: .top)
|
|
|
- .scaleEffect(scale, anchor: .top)
|
|
|
- .padding(.top, 10)
|
|
|
-
|
|
|
- Spacer(minLength: 0)
|
|
|
+ .frame(width: contentWidth, alignment: .top)
|
|
|
+ .padding(.top, topPadding)
|
|
|
}
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
|
|
.background(Color.white.ignoresSafeArea())
|
|
|
@@ -57,20 +56,21 @@ struct PaywallView: View {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private var featuresSection: some View {
|
|
|
+ private func featuresSection(featureHeight: CGFloat) -> some View {
|
|
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 14), count: 3), spacing: 14) {
|
|
|
ForEach(viewModel.features) { feature in
|
|
|
- PaywallFeatureBox(feature: feature)
|
|
|
+ PaywallFeatureBox(feature: feature, minHeight: featureHeight)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private var plansSection: some View {
|
|
|
+ private func plansSection(planHeight: CGFloat) -> some View {
|
|
|
HStack(spacing: 14) {
|
|
|
ForEach(PaywallPlan.allCases) { plan in
|
|
|
PaywallPlanCardView(
|
|
|
plan: plan,
|
|
|
isSelected: viewModel.selectedPlan == plan,
|
|
|
+ minHeight: planHeight,
|
|
|
onSelect: { viewModel.selectPlan(plan) }
|
|
|
)
|
|
|
}
|
|
|
@@ -146,6 +146,7 @@ struct PaywallView: View {
|
|
|
|
|
|
private struct PaywallFeatureBox: View {
|
|
|
let feature: PaywallFeature
|
|
|
+ let minHeight: CGFloat
|
|
|
@State private var isHovered = false
|
|
|
|
|
|
var body: some View {
|
|
|
@@ -175,7 +176,7 @@ private struct PaywallFeatureBox: View {
|
|
|
}
|
|
|
.padding(.horizontal, 12)
|
|
|
.padding(.vertical, 12)
|
|
|
- .frame(maxWidth: .infinity, minHeight: 84, maxHeight: 84, alignment: .topLeading)
|
|
|
+ .frame(maxWidth: .infinity, minHeight: minHeight, alignment: .topLeading)
|
|
|
.background(isHovered ? AppTheme.tealLight.opacity(0.55) : AppTheme.cardBackground)
|
|
|
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
|
|
|
.overlay(
|
|
|
@@ -192,6 +193,7 @@ private struct PaywallFeatureBox: View {
|
|
|
private struct PaywallPlanCardView: View {
|
|
|
let plan: PaywallPlan
|
|
|
let isSelected: Bool
|
|
|
+ let minHeight: CGFloat
|
|
|
let onSelect: () -> Void
|
|
|
@State private var isHovered = false
|
|
|
|
|
|
@@ -265,7 +267,7 @@ private struct PaywallPlanCardView: View {
|
|
|
RoundedRectangle(cornerRadius: 0)
|
|
|
)
|
|
|
}
|
|
|
- .frame(maxWidth: .infinity, minHeight: 170, alignment: .topLeading)
|
|
|
+ .frame(maxWidth: .infinity, minHeight: minHeight, alignment: .topLeading)
|
|
|
.background((isSelected || isHovered) ? AppTheme.tealLight.opacity(isSelected ? 0.28 : 0.16) : AppTheme.cardBackground)
|
|
|
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
|
|
|
.overlay(
|