Explorar o código

Polish paywall layout spacing and section structure.

Anchor the close button independently, rebalance vertical spacing, remove extra hero/benefits gaps, and add a trust row so the paywall uses space more intentionally and looks more professional.

Made-with: Cursor
huzaifahayat12 hai 6 horas
pai
achega
48182e7586
Modificáronse 1 ficheiros con 38 adicións e 31 borrados
  1. 38 31
      classroom_app/ViewController.swift

+ 38 - 31
classroom_app/ViewController.swift

@@ -2637,7 +2637,7 @@ private extension ViewController {
2637
         let contentStack = NSStackView()
2637
         let contentStack = NSStackView()
2638
         contentStack.translatesAutoresizingMaskIntoConstraints = false
2638
         contentStack.translatesAutoresizingMaskIntoConstraints = false
2639
         contentStack.orientation = .vertical
2639
         contentStack.orientation = .vertical
2640
-        contentStack.spacing = 10
2640
+        contentStack.spacing = 12
2641
         contentStack.distribution = .fill
2641
         contentStack.distribution = .fill
2642
         contentStack.alignment = .centerX
2642
         contentStack.alignment = .centerX
2643
         panel.addSubview(contentStack)
2643
         panel.addSubview(contentStack)
@@ -2650,9 +2650,6 @@ private extension ViewController {
2650
         topRow.distribution = .fill
2650
         topRow.distribution = .fill
2651
         topRow.spacing = 10
2651
         topRow.spacing = 10
2652
         topRow.addArrangedSubview(textLabel("Classroom Pro", font: NSFont.systemFont(ofSize: 27, weight: .bold), color: palette.textPrimary))
2652
         topRow.addArrangedSubview(textLabel("Classroom Pro", font: NSFont.systemFont(ofSize: 27, weight: .bold), color: palette.textPrimary))
2653
-        let topSpacer = NSView()
2654
-        topSpacer.translatesAutoresizingMaskIntoConstraints = false
2655
-        topRow.addArrangedSubview(topSpacer)
2656
         let closeButton = HoverButton(title: "✕", target: self, action: #selector(closePaywallClicked(_:)))
2653
         let closeButton = HoverButton(title: "✕", target: self, action: #selector(closePaywallClicked(_:)))
2657
         closeButton.translatesAutoresizingMaskIntoConstraints = false
2654
         closeButton.translatesAutoresizingMaskIntoConstraints = false
2658
         closeButton.isBordered = false
2655
         closeButton.isBordered = false
@@ -2674,15 +2671,10 @@ private extension ViewController {
2674
             closeButton.layer?.backgroundColor = (hovering ? hover : base).cgColor
2671
             closeButton.layer?.backgroundColor = (hovering ? hover : base).cgColor
2675
             closeButton.contentTintColor = hovering ? (self.darkModeEnabled ? .white : self.palette.textPrimary) : self.palette.textSecondary
2672
             closeButton.contentTintColor = hovering ? (self.darkModeEnabled ? .white : self.palette.textPrimary) : self.palette.textSecondary
2676
         }
2673
         }
2677
-        topRow.addArrangedSubview(closeButton)
2674
+        panel.addSubview(closeButton)
2678
         contentStack.addArrangedSubview(topRow)
2675
         contentStack.addArrangedSubview(topRow)
2679
         topRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
2676
         topRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
2680
 
2677
 
2681
-        let heroTopSpacer = NSView()
2682
-        heroTopSpacer.translatesAutoresizingMaskIntoConstraints = false
2683
-        heroTopSpacer.heightAnchor.constraint(equalToConstant: 18).isActive = true
2684
-        contentStack.addArrangedSubview(heroTopSpacer)
2685
-
2686
         let hero = roundedContainer(cornerRadius: 16, color: palette.sectionCard)
2678
         let hero = roundedContainer(cornerRadius: 16, color: palette.sectionCard)
2687
         hero.translatesAutoresizingMaskIntoConstraints = false
2679
         hero.translatesAutoresizingMaskIntoConstraints = false
2688
         styleSurface(hero, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
2680
         styleSurface(hero, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
@@ -2710,11 +2702,6 @@ private extension ViewController {
2710
             heroSubtitle.bottomAnchor.constraint(equalTo: hero.bottomAnchor, constant: -16)
2702
             heroSubtitle.bottomAnchor.constraint(equalTo: hero.bottomAnchor, constant: -16)
2711
         ])
2703
         ])
2712
 
2704
 
2713
-        let benefitsTopSpacer = NSView()
2714
-        benefitsTopSpacer.translatesAutoresizingMaskIntoConstraints = false
2715
-        benefitsTopSpacer.heightAnchor.constraint(equalToConstant: 40).isActive = true
2716
-        contentStack.addArrangedSubview(benefitsTopSpacer)
2717
-
2718
         let benefitsRow = NSStackView()
2705
         let benefitsRow = NSStackView()
2719
         benefitsRow.translatesAutoresizingMaskIntoConstraints = false
2706
         benefitsRow.translatesAutoresizingMaskIntoConstraints = false
2720
         benefitsRow.orientation = .horizontal
2707
         benefitsRow.orientation = .horizontal
@@ -2728,11 +2715,6 @@ private extension ViewController {
2728
         contentStack.addArrangedSubview(benefitsRow)
2715
         contentStack.addArrangedSubview(benefitsRow)
2729
         benefitsRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
2716
         benefitsRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
2730
 
2717
 
2731
-        let midTopSpacer = NSView()
2732
-        midTopSpacer.translatesAutoresizingMaskIntoConstraints = false
2733
-        midTopSpacer.heightAnchor.constraint(equalToConstant: 6).isActive = true
2734
-        contentStack.addArrangedSubview(midTopSpacer)
2735
-
2736
         let plansRow = NSStackView()
2718
         let plansRow = NSStackView()
2737
         plansRow.translatesAutoresizingMaskIntoConstraints = false
2719
         plansRow.translatesAutoresizingMaskIntoConstraints = false
2738
         plansRow.orientation = .horizontal
2720
         plansRow.orientation = .horizontal
@@ -2784,10 +2766,17 @@ private extension ViewController {
2784
         plansRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
2766
         plansRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
2785
         updatePaywallPlanSelection()
2767
         updatePaywallPlanSelection()
2786
 
2768
 
2787
-        let midBottomSpacer = NSView()
2788
-        midBottomSpacer.translatesAutoresizingMaskIntoConstraints = false
2789
-        midBottomSpacer.heightAnchor.constraint(equalToConstant: 8).isActive = true
2790
-        contentStack.addArrangedSubview(midBottomSpacer)
2769
+        let trustRow = NSStackView()
2770
+        trustRow.translatesAutoresizingMaskIntoConstraints = false
2771
+        trustRow.orientation = .horizontal
2772
+        trustRow.spacing = 8
2773
+        trustRow.distribution = .fillEqually
2774
+        trustRow.alignment = .centerY
2775
+        trustRow.addArrangedSubview(paywallMetaItem(title: "Cancel anytime", subtitle: "No lock-in"))
2776
+        trustRow.addArrangedSubview(paywallMetaItem(title: "Instant access", subtitle: "Unlock all tools"))
2777
+        trustRow.addArrangedSubview(paywallMetaItem(title: "Secure billing", subtitle: "Handled by Apple"))
2778
+        contentStack.addArrangedSubview(trustRow)
2779
+        trustRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
2791
 
2780
 
2792
         let offer = textLabel(paywallOfferText(for: selectedPremiumPlan), font: NSFont.systemFont(ofSize: 13, weight: .semibold), color: palette.textPrimary)
2781
         let offer = textLabel(paywallOfferText(for: selectedPremiumPlan), font: NSFont.systemFont(ofSize: 13, weight: .semibold), color: palette.textPrimary)
2793
         offer.alignment = .center
2782
         offer.alignment = .center
@@ -2843,20 +2832,17 @@ private extension ViewController {
2843
             secure.bottomAnchor.constraint(equalTo: secureWrap.bottomAnchor, constant: -4)
2832
             secure.bottomAnchor.constraint(equalTo: secureWrap.bottomAnchor, constant: -4)
2844
         ])
2833
         ])
2845
 
2834
 
2846
-        let footerTopSpacer = NSView()
2847
-        footerTopSpacer.translatesAutoresizingMaskIntoConstraints = false
2848
-        contentStack.addArrangedSubview(footerTopSpacer)
2849
-        footerTopSpacer.heightAnchor.constraint(equalToConstant: 4).isActive = true
2850
-
2851
         let footer = paywallFooterLinks()
2835
         let footer = paywallFooterLinks()
2852
         contentStack.addArrangedSubview(footer)
2836
         contentStack.addArrangedSubview(footer)
2853
         footer.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
2837
         footer.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
2854
 
2838
 
2855
         NSLayoutConstraint.activate([
2839
         NSLayoutConstraint.activate([
2840
+            closeButton.topAnchor.constraint(equalTo: panel.topAnchor, constant: 18),
2841
+            closeButton.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -18),
2856
             contentStack.centerXAnchor.constraint(equalTo: panel.centerXAnchor),
2842
             contentStack.centerXAnchor.constraint(equalTo: panel.centerXAnchor),
2857
             contentStack.widthAnchor.constraint(equalToConstant: paywallLayoutWidth),
2843
             contentStack.widthAnchor.constraint(equalToConstant: paywallLayoutWidth),
2858
-            contentStack.topAnchor.constraint(equalTo: panel.topAnchor, constant: 52),
2859
-            contentStack.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -10)
2844
+            contentStack.topAnchor.constraint(equalTo: panel.topAnchor, constant: 80),
2845
+            contentStack.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -12)
2860
         ])
2846
         ])
2861
 
2847
 
2862
         refreshPaywallStoreUI()
2848
         refreshPaywallStoreUI()
@@ -3044,6 +3030,27 @@ private extension ViewController {
3044
         return stack
3030
         return stack
3045
     }
3031
     }
3046
 
3032
 
3033
+    func paywallMetaItem(title: String, subtitle: String) -> NSView {
3034
+        let card = roundedContainer(cornerRadius: 12, color: palette.inputBackground)
3035
+        card.translatesAutoresizingMaskIntoConstraints = false
3036
+        card.heightAnchor.constraint(equalToConstant: 48).isActive = true
3037
+        styleSurface(card, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
3038
+
3039
+        let titleLabel = textLabel(title, font: NSFont.systemFont(ofSize: 11, weight: .semibold), color: palette.textPrimary)
3040
+        let subtitleLabel = textLabel(subtitle, font: NSFont.systemFont(ofSize: 10, weight: .medium), color: palette.textSecondary)
3041
+        card.addSubview(titleLabel)
3042
+        card.addSubview(subtitleLabel)
3043
+
3044
+        NSLayoutConstraint.activate([
3045
+            titleLabel.centerXAnchor.constraint(equalTo: card.centerXAnchor),
3046
+            titleLabel.topAnchor.constraint(equalTo: card.topAnchor, constant: 8),
3047
+            subtitleLabel.centerXAnchor.constraint(equalTo: card.centerXAnchor),
3048
+            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 3)
3049
+        ])
3050
+
3051
+        return card
3052
+    }
3053
+
3047
     func paywallBenefitItem(icon: String, text: String) -> NSView {
3054
     func paywallBenefitItem(icon: String, text: String) -> NSView {
3048
         let card = HoverTrackingView()
3055
         let card = HoverTrackingView()
3049
         card.translatesAutoresizingMaskIntoConstraints = false
3056
         card.translatesAutoresizingMaskIntoConstraints = false