Нема описа

PremiumPlansWindowController.swift 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. import Cocoa
  2. final class PremiumPlansWindowController: NSWindowController {
  3. init() {
  4. let viewController = PremiumPlansViewController()
  5. let window = NSWindow(contentViewController: viewController)
  6. window.title = "Premium Plans"
  7. window.styleMask = [.titled, .closable, .miniaturizable, .resizable]
  8. window.setContentSize(NSSize(width: 1160, height: 760))
  9. window.minSize = NSSize(width: 980, height: 680)
  10. window.center()
  11. super.init(window: window)
  12. }
  13. @available(*, unavailable)
  14. required init?(coder: NSCoder) {
  15. nil
  16. }
  17. }
  18. private final class PremiumPlansViewController: NSViewController {
  19. private struct Plan {
  20. let id: String
  21. let title: String
  22. let subtitle: String
  23. let price: String
  24. let period: String
  25. let billedPill: String
  26. let billedLine: String
  27. let crossedPrice: String?
  28. let savingsText: String?
  29. let features: [String]
  30. let iconName: String
  31. let iconTint: NSColor
  32. let highlight: Bool
  33. }
  34. private enum Theme {
  35. static let pageStart = NSColor(srgbRed: 249 / 255, green: 252 / 255, blue: 255 / 255, alpha: 1)
  36. static let pageEnd = NSColor(srgbRed: 238 / 255, green: 244 / 255, blue: 255 / 255, alpha: 1)
  37. static let cardBackground = NSColor.white
  38. static let primaryText = NSColor(srgbRed: 27 / 255, green: 38 / 255, blue: 79 / 255, alpha: 1)
  39. static let secondaryText = NSColor(srgbRed: 108 / 255, green: 120 / 255, blue: 157 / 255, alpha: 1)
  40. static let cardBorder = NSColor(srgbRed: 198 / 255, green: 216 / 255, blue: 255 / 255, alpha: 1)
  41. static let accent = NSColor(srgbRed: 55 / 255, green: 128 / 255, blue: 255 / 255, alpha: 1)
  42. static let accentHover = NSColor(srgbRed: 38 / 255, green: 108 / 255, blue: 232 / 255, alpha: 1)
  43. static let mutedButtonFill = NSColor(srgbRed: 238 / 255, green: 243 / 255, blue: 252 / 255, alpha: 1)
  44. static let bottomStrip = NSColor(srgbRed: 244 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1)
  45. static let divider = NSColor(srgbRed: 218 / 255, green: 228 / 255, blue: 247 / 255, alpha: 1)
  46. static let successText = NSColor(srgbRed: 21 / 255, green: 154 / 255, blue: 220 / 255, alpha: 1)
  47. static let iconTint = NSColor(srgbRed: 47 / 255, green: 136 / 255, blue: 255 / 255, alpha: 1)
  48. }
  49. private let plans: [Plan] = [
  50. Plan(
  51. id: "weekly",
  52. title: "Weekly",
  53. subtitle: "Flexible and commitment-free",
  54. price: "$9.99",
  55. period: "/ week",
  56. billedPill: "",
  57. billedLine: "",
  58. crossedPrice: nil,
  59. savingsText: nil,
  60. features: [
  61. "All premium features",
  62. "Perfect for short-term goals",
  63. "Cancel anytime"
  64. ],
  65. iconName: "paperplane.fill",
  66. iconTint: Theme.iconTint,
  67. highlight: false
  68. ),
  69. Plan(
  70. id: "monthly",
  71. title: "Monthly",
  72. subtitle: "Balanced for regular productivity",
  73. price: "$19.99",
  74. period: "/ month",
  75. billedPill: "Most Popular",
  76. billedLine: "",
  77. crossedPrice: nil,
  78. savingsText: nil,
  79. features: [
  80. "All premium features",
  81. "Best value for regular users",
  82. "Priority support"
  83. ],
  84. iconName: "bolt.fill",
  85. iconTint: Theme.accent,
  86. highlight: true
  87. ),
  88. Plan(
  89. id: "yearly",
  90. title: "Yearly",
  91. subtitle: "Best value for long-term users",
  92. price: "$39.99",
  93. period: "/ year",
  94. billedPill: "3-day free trial",
  95. billedLine: "",
  96. crossedPrice: nil,
  97. savingsText: nil,
  98. features: [
  99. "All premium features",
  100. "Lowest effective monthly cost",
  101. "Ideal for long-term use"
  102. ],
  103. iconName: "crown.fill",
  104. iconTint: Theme.successText,
  105. highlight: false
  106. )
  107. ]
  108. private let pageGradient = CAGradientLayer()
  109. private lazy var popularGradient: CAGradientLayer = {
  110. let layer = CAGradientLayer()
  111. layer.colors = [
  112. NSColor(srgbRed: 189 / 255, green: 52 / 255, blue: 255 / 255, alpha: 1).cgColor,
  113. NSColor(srgbRed: 73 / 255, green: 153 / 255, blue: 255 / 255, alpha: 1).cgColor
  114. ]
  115. layer.startPoint = CGPoint(x: 0, y: 0.5)
  116. layer.endPoint = CGPoint(x: 1, y: 0.5)
  117. return layer
  118. }()
  119. override func viewDidLayout() {
  120. super.viewDidLayout()
  121. pageGradient.frame = view.bounds
  122. popularGradient.frame = CGRect(x: 0, y: 0, width: 160, height: 22)
  123. }
  124. override func loadView() {
  125. view = NSView()
  126. view.wantsLayer = true
  127. pageGradient.colors = [Theme.pageStart.cgColor, Theme.pageEnd.cgColor]
  128. pageGradient.startPoint = CGPoint(x: 0, y: 1)
  129. pageGradient.endPoint = CGPoint(x: 1, y: 0)
  130. view.layer?.addSublayer(pageGradient)
  131. setupLayout()
  132. }
  133. private func setupLayout() {
  134. let crownIcon = NSImageView()
  135. crownIcon.translatesAutoresizingMaskIntoConstraints = false
  136. crownIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 18, weight: .semibold)
  137. crownIcon.image = NSImage(systemSymbolName: "crown.fill", accessibilityDescription: nil)
  138. crownIcon.contentTintColor = NSColor(srgbRed: 254 / 255, green: 214 / 255, blue: 92 / 255, alpha: 1)
  139. let title = NSTextField(labelWithString: "Upgrade to Pro")
  140. title.font = .systemFont(ofSize: 52, weight: .bold)
  141. title.textColor = Theme.primaryText
  142. title.alignment = .center
  143. let subtitle = NSTextField(labelWithString: "Unlock unlimited access to premium tools and boost your productivity.")
  144. subtitle.font = .systemFont(ofSize: 15, weight: .medium)
  145. subtitle.textColor = Theme.secondaryText
  146. subtitle.alignment = .center
  147. let cardsRow = NSStackView(views: plans.map(makePricingCard(_:)))
  148. cardsRow.orientation = .horizontal
  149. cardsRow.spacing = 14
  150. cardsRow.alignment = .top
  151. cardsRow.distribution = .fillEqually
  152. cardsRow.translatesAutoresizingMaskIntoConstraints = false
  153. let trustRow = makeTrustRow()
  154. let footerRow = makeFooterRow()
  155. let root = NSStackView(views: [crownIcon, title, subtitle, cardsRow, trustRow, footerRow])
  156. root.orientation = .vertical
  157. root.spacing = 18
  158. root.alignment = .centerX
  159. root.translatesAutoresizingMaskIntoConstraints = false
  160. view.addSubview(root)
  161. NSLayoutConstraint.activate([
  162. root.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24),
  163. root.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24),
  164. root.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
  165. root.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -16),
  166. cardsRow.widthAnchor.constraint(equalTo: root.widthAnchor),
  167. cardsRow.heightAnchor.constraint(equalToConstant: 420),
  168. trustRow.widthAnchor.constraint(equalTo: root.widthAnchor),
  169. footerRow.widthAnchor.constraint(equalTo: root.widthAnchor),
  170. crownIcon.heightAnchor.constraint(equalToConstant: 20)
  171. ])
  172. }
  173. private func makePricingCard(_ plan: Plan) -> NSView {
  174. let card = NSView()
  175. card.translatesAutoresizingMaskIntoConstraints = false
  176. card.wantsLayer = true
  177. card.layer?.backgroundColor = Theme.cardBackground.cgColor
  178. card.layer?.cornerRadius = 16
  179. card.layer?.borderWidth = plan.highlight ? 2 : 1
  180. card.layer?.borderColor = (plan.highlight ? Theme.accent : Theme.cardBorder).cgColor
  181. let iconWell = NSView()
  182. iconWell.translatesAutoresizingMaskIntoConstraints = false
  183. iconWell.wantsLayer = true
  184. iconWell.layer?.cornerRadius = 10
  185. iconWell.layer?.backgroundColor = Theme.bottomStrip.cgColor
  186. iconWell.widthAnchor.constraint(equalToConstant: 24).isActive = true
  187. iconWell.heightAnchor.constraint(equalToConstant: 24).isActive = true
  188. let icon = NSImageView()
  189. icon.translatesAutoresizingMaskIntoConstraints = false
  190. icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 11, weight: .bold)
  191. icon.image = NSImage(systemSymbolName: plan.iconName, accessibilityDescription: nil)
  192. icon.contentTintColor = plan.iconTint
  193. iconWell.addSubview(icon)
  194. NSLayoutConstraint.activate([
  195. icon.centerXAnchor.constraint(equalTo: iconWell.centerXAnchor),
  196. icon.centerYAnchor.constraint(equalTo: iconWell.centerYAnchor)
  197. ])
  198. let titleLabel = NSTextField(labelWithString: plan.title)
  199. titleLabel.font = .systemFont(ofSize: 44, weight: .bold)
  200. titleLabel.textColor = Theme.primaryText
  201. titleLabel.alignment = .center
  202. let subtitleLabel = NSTextField(labelWithString: plan.subtitle)
  203. subtitleLabel.font = .systemFont(ofSize: 13, weight: .medium)
  204. subtitleLabel.textColor = Theme.secondaryText
  205. subtitleLabel.alignment = .center
  206. let billingPill = pillLabel(text: plan.billedPill, tint: Theme.bottomStrip, textColor: Theme.iconTint)
  207. billingPill.isHidden = plan.billedPill.isEmpty
  208. let priceLabel = NSTextField(labelWithString: plan.price)
  209. priceLabel.font = .systemFont(ofSize: 40, weight: .bold)
  210. priceLabel.textColor = Theme.primaryText
  211. let periodLabel = NSTextField(labelWithString: plan.period)
  212. periodLabel.font = .systemFont(ofSize: 30, weight: .semibold)
  213. periodLabel.textColor = Theme.secondaryText
  214. let priceRow = NSStackView(views: [priceLabel, periodLabel])
  215. priceRow.orientation = .horizontal
  216. priceRow.spacing = 4
  217. priceRow.alignment = .firstBaseline
  218. let billingLabel = NSTextField(labelWithString: plan.billedLine)
  219. billingLabel.font = .systemFont(ofSize: 13, weight: .medium)
  220. billingLabel.textColor = Theme.secondaryText
  221. billingLabel.isHidden = plan.billedLine.isEmpty
  222. let inlinePriceInfo = inlinePriceInfoLabel(oldPrice: plan.crossedPrice, newPrice: plan.savingsText)
  223. inlinePriceInfo.isHidden = (plan.crossedPrice == nil || plan.savingsText == nil)
  224. let divider = NSBox()
  225. divider.boxType = .separator
  226. divider.translatesAutoresizingMaskIntoConstraints = false
  227. divider.borderColor = Theme.divider
  228. let featuresStack = NSStackView(views: plan.features.map(makeFeatureRow(_:)))
  229. featuresStack.orientation = .vertical
  230. featuresStack.spacing = 9
  231. featuresStack.alignment = .leading
  232. let selectButton = NSButton(title: "Get \(plan.title)", target: self, action: #selector(didTapSelectPlan))
  233. selectButton.identifier = NSUserInterfaceItemIdentifier(plan.id)
  234. selectButton.isBordered = false
  235. selectButton.bezelStyle = .rounded
  236. selectButton.font = .systemFont(ofSize: 15, weight: .bold)
  237. selectButton.contentTintColor = plan.highlight ? .white : Theme.primaryText
  238. selectButton.wantsLayer = true
  239. selectButton.layer?.cornerRadius = 12
  240. selectButton.layer?.borderWidth = 1
  241. selectButton.layer?.borderColor = (plan.highlight ? Theme.accent : Theme.divider).cgColor
  242. selectButton.layer?.backgroundColor = (plan.highlight
  243. ? NSColor(srgbRed: 189 / 255, green: 52 / 255, blue: 255 / 255, alpha: 1)
  244. : Theme.mutedButtonFill).cgColor
  245. selectButton.translatesAutoresizingMaskIntoConstraints = false
  246. selectButton.heightAnchor.constraint(equalToConstant: 58).isActive = true
  247. let spacer = NSView()
  248. spacer.setContentHuggingPriority(.defaultLow, for: .vertical)
  249. let content = NSStackView(views: [iconWell, titleLabel, subtitleLabel, billingPill, priceRow, billingLabel, inlinePriceInfo, divider, featuresStack, spacer, selectButton])
  250. content.orientation = .vertical
  251. content.spacing = 10
  252. content.alignment = .centerX
  253. content.translatesAutoresizingMaskIntoConstraints = false
  254. card.addSubview(content)
  255. NSLayoutConstraint.activate([
  256. divider.widthAnchor.constraint(equalTo: content.widthAnchor),
  257. content.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 18),
  258. content.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -18),
  259. content.topAnchor.constraint(equalTo: card.topAnchor, constant: 14),
  260. content.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -16),
  261. selectButton.widthAnchor.constraint(equalTo: content.widthAnchor)
  262. ])
  263. if plan.highlight {
  264. let badgeHost = NSView()
  265. badgeHost.translatesAutoresizingMaskIntoConstraints = false
  266. badgeHost.wantsLayer = true
  267. badgeHost.layer?.cornerRadius = 14
  268. badgeHost.layer?.masksToBounds = true
  269. badgeHost.layer?.addSublayer(popularGradient)
  270. let sparkle = NSImageView()
  271. sparkle.translatesAutoresizingMaskIntoConstraints = false
  272. sparkle.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 10, weight: .semibold)
  273. sparkle.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: nil)
  274. sparkle.contentTintColor = .white
  275. let badge = NSTextField(labelWithString: "Most Popular")
  276. badge.font = .systemFont(ofSize: 12, weight: .bold)
  277. badge.textColor = .white
  278. badgeHost.addSubview(sparkle)
  279. badgeHost.addSubview(badge)
  280. card.addSubview(badgeHost)
  281. NSLayoutConstraint.activate([
  282. badgeHost.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -14),
  283. badgeHost.topAnchor.constraint(equalTo: card.topAnchor, constant: 10),
  284. badgeHost.widthAnchor.constraint(equalToConstant: 160),
  285. badgeHost.heightAnchor.constraint(equalToConstant: 22),
  286. sparkle.leadingAnchor.constraint(equalTo: badgeHost.leadingAnchor, constant: 14),
  287. sparkle.centerYAnchor.constraint(equalTo: badgeHost.centerYAnchor),
  288. badge.leadingAnchor.constraint(equalTo: sparkle.trailingAnchor, constant: 6),
  289. badge.centerYAnchor.constraint(equalTo: badgeHost.centerYAnchor)
  290. ])
  291. }
  292. return card
  293. }
  294. private func makeFeatureRow(_ text: String) -> NSView {
  295. let icon = NSImageView()
  296. icon.translatesAutoresizingMaskIntoConstraints = false
  297. icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 11, weight: .bold)
  298. icon.image = NSImage(systemSymbolName: "checkmark.circle.fill", accessibilityDescription: nil)
  299. icon.contentTintColor = Theme.iconTint
  300. icon.widthAnchor.constraint(equalToConstant: 14).isActive = true
  301. let label = NSTextField(labelWithString: text)
  302. label.font = .systemFont(ofSize: 16, weight: .semibold)
  303. label.textColor = Theme.primaryText
  304. let row = NSStackView(views: [icon, label])
  305. row.orientation = .horizontal
  306. row.spacing = 8
  307. row.alignment = .centerY
  308. row.distribution = .fill
  309. return row
  310. }
  311. private func inlinePriceInfoLabel(oldPrice: String?, newPrice: String?) -> NSTextField {
  312. guard let oldPrice, let newPrice else {
  313. return NSTextField(labelWithString: "")
  314. }
  315. let full = NSMutableAttributedString()
  316. let oldAttributes: [NSAttributedString.Key: Any] = [
  317. .font: NSFont.systemFont(ofSize: 12, weight: .semibold),
  318. .foregroundColor: Theme.secondaryText,
  319. .strikethroughStyle: NSUnderlineStyle.single.rawValue
  320. ]
  321. let newAttributes: [NSAttributedString.Key: Any] = [
  322. .font: NSFont.systemFont(ofSize: 12, weight: .bold),
  323. .foregroundColor: Theme.successText
  324. ]
  325. full.append(NSAttributedString(string: "\(oldPrice) ", attributes: oldAttributes))
  326. full.append(NSAttributedString(string: newPrice, attributes: newAttributes))
  327. let label = NSTextField(labelWithAttributedString: full)
  328. return label
  329. }
  330. private func pillLabel(text: String, tint: NSColor, textColor: NSColor) -> NSTextField {
  331. let pill = NSTextField(labelWithString: text)
  332. pill.font = .systemFont(ofSize: 10, weight: .semibold)
  333. pill.textColor = textColor
  334. pill.alignment = .center
  335. pill.wantsLayer = true
  336. pill.layer?.backgroundColor = tint.cgColor
  337. pill.layer?.cornerRadius = 9
  338. pill.translatesAutoresizingMaskIntoConstraints = false
  339. pill.heightAnchor.constraint(equalToConstant: 18).isActive = true
  340. pill.widthAnchor.constraint(greaterThanOrEqualToConstant: 95).isActive = true
  341. return pill
  342. }
  343. private func makeTrustRow() -> NSView {
  344. let badges = NSStackView(views: [
  345. trustBadge(icon: "shield.fill", title: "Secure Payments", subtitle: "Your payment is 100% secure."),
  346. trustBadge(icon: "arrow.counterclockwise", title: "Cancel Anytime", subtitle: "No commitment, cancel anytime."),
  347. trustBadge(icon: "headphones", title: "24/7 Support", subtitle: "We're here to help you anytime."),
  348. trustBadge(icon: "lock.fill", title: "Privacy First", subtitle: "Your data is safe with us.")
  349. ])
  350. badges.orientation = .horizontal
  351. badges.alignment = .centerY
  352. badges.distribution = .fillEqually
  353. badges.spacing = 12
  354. badges.edgeInsets = NSEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
  355. badges.translatesAutoresizingMaskIntoConstraints = false
  356. badges.wantsLayer = true
  357. badges.layer?.backgroundColor = Theme.bottomStrip.cgColor
  358. badges.layer?.borderColor = Theme.divider.cgColor
  359. badges.layer?.borderWidth = 1
  360. badges.layer?.cornerRadius = 10
  361. badges.setHuggingPriority(.defaultLow, for: .horizontal)
  362. badges.heightAnchor.constraint(equalToConstant: 72).isActive = true
  363. return badges
  364. }
  365. private func makeFooterRow() -> NSView {
  366. let items = [
  367. "Manage Subscription",
  368. "Restore Purchase",
  369. "Privacy Policy",
  370. "Terms of Services",
  371. "Support"
  372. ]
  373. let cells = items.enumerated().map { index, text in
  374. footerCell(text: text, showsTrailingDivider: index < items.count - 1)
  375. }
  376. let links = NSStackView(views: cells)
  377. links.orientation = .horizontal
  378. links.distribution = .fillEqually
  379. links.spacing = 0
  380. links.alignment = .centerY
  381. links.translatesAutoresizingMaskIntoConstraints = false
  382. return links
  383. }
  384. private func footerCell(text: String, showsTrailingDivider: Bool) -> NSView {
  385. let container = NSView()
  386. container.translatesAutoresizingMaskIntoConstraints = false
  387. let label = footerLink(text)
  388. label.translatesAutoresizingMaskIntoConstraints = false
  389. label.alignment = .center
  390. container.addSubview(label)
  391. var constraints = [
  392. label.centerXAnchor.constraint(equalTo: container.centerXAnchor),
  393. label.centerYAnchor.constraint(equalTo: container.centerYAnchor)
  394. ]
  395. if showsTrailingDivider {
  396. let divider = footerDivider()
  397. container.addSubview(divider)
  398. constraints.append(contentsOf: [
  399. divider.trailingAnchor.constraint(equalTo: container.trailingAnchor),
  400. divider.centerYAnchor.constraint(equalTo: container.centerYAnchor)
  401. ])
  402. }
  403. NSLayoutConstraint.activate(constraints)
  404. return container
  405. }
  406. private func trustBadge(icon: String, title: String, subtitle: String) -> NSView {
  407. let image = NSImageView()
  408. image.translatesAutoresizingMaskIntoConstraints = false
  409. image.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 12, weight: .semibold)
  410. image.image = NSImage(systemSymbolName: icon, accessibilityDescription: nil)
  411. image.contentTintColor = Theme.primaryText
  412. let titleLabel = NSTextField(labelWithString: title)
  413. titleLabel.font = .systemFont(ofSize: 12, weight: .bold)
  414. titleLabel.textColor = Theme.primaryText
  415. let subtitleLabel = NSTextField(labelWithString: subtitle)
  416. subtitleLabel.font = .systemFont(ofSize: 10, weight: .medium)
  417. subtitleLabel.textColor = Theme.secondaryText
  418. let textStack = NSStackView(views: [titleLabel, subtitleLabel])
  419. textStack.orientation = .vertical
  420. textStack.spacing = 2
  421. textStack.alignment = .leading
  422. let stack = NSStackView(views: [image, textStack])
  423. stack.orientation = .horizontal
  424. stack.spacing = 8
  425. stack.alignment = .leading
  426. stack.edgeInsets = NSEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
  427. stack.wantsLayer = true
  428. stack.layer?.backgroundColor = NSColor.clear.cgColor
  429. return stack
  430. }
  431. private func footerLink(_ text: String) -> NSTextField {
  432. let label = NSTextField(labelWithString: text)
  433. label.font = .systemFont(ofSize: 12, weight: .medium)
  434. label.textColor = Theme.secondaryText
  435. return label
  436. }
  437. private func footerDivider() -> NSBox {
  438. let divider = NSBox()
  439. divider.boxType = .separator
  440. divider.borderColor = Theme.divider
  441. divider.translatesAutoresizingMaskIntoConstraints = false
  442. divider.widthAnchor.constraint(equalToConstant: 1).isActive = true
  443. divider.heightAnchor.constraint(equalToConstant: 14).isActive = true
  444. return divider
  445. }
  446. @objc private func didTapSelectPlan(_ sender: NSButton) {
  447. sender.layer?.backgroundColor = Theme.accentHover.cgColor
  448. let selectedPlan = sender.identifier?.rawValue ?? sender.title
  449. let alert = NSAlert()
  450. alert.messageText = "Premium checkout coming soon"
  451. alert.informativeText = "Plan selected: \(selectedPlan.capitalized). Payment flow can be connected next."
  452. alert.alertStyle = .informational
  453. alert.addButton(withTitle: "OK")
  454. if let window = view.window {
  455. alert.beginSheetModal(for: window)
  456. } else {
  457. alert.runModal()
  458. }
  459. }
  460. }