|
|
@@ -2,9 +2,9 @@
|
|
2
|
2
|
// CVMakerPageView.swift
|
|
3
|
3
|
// App for Indeed
|
|
4
|
4
|
//
|
|
5
|
|
-// Template gallery for the CV Maker sidebar destination. Light-theme rendering
|
|
6
|
|
-// inspired by a dark reference UI: page header, category toggle, style chips,
|
|
7
|
|
-// 4-column thumbnail grid and a sticky bottom CTA.
|
|
|
5
|
+// Template gallery for the CV Maker sidebar destination: page header, category
|
|
|
6
|
+// toggle, style chips, thumbnail grid, and a sticky bottom CTA. Follows the
|
|
|
7
|
+// active dashboard light / dark appearance via `AppDashboardTheme`.
|
|
8
|
8
|
//
|
|
9
|
9
|
|
|
10
|
10
|
import Cocoa
|
|
|
@@ -554,41 +554,38 @@ enum CVTemplateCatalog {
|
|
554
|
554
|
/// bottom CTA. Hosts inside the same `nonHomeHost` slot as Saved Jobs/Settings.
|
|
555
|
555
|
final class CVMakerPageView: NSView {
|
|
556
|
556
|
|
|
557
|
|
- /// Light-theme palette aligned with the rest of the dashboard (brand blue + neutral grays on white).
|
|
558
|
557
|
private enum Palette {
|
|
559
|
|
- static let pageBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
|
|
560
|
|
- static let mutedSurface = NSColor(srgbRed: 247 / 255, green: 247 / 255, blue: 247 / 255, alpha: 1)
|
|
561
|
|
- static let chipRestFill = NSColor(srgbRed: 244 / 255, green: 246 / 255, blue: 250 / 255, alpha: 1)
|
|
562
|
|
- static let chipBorder = NSColor(srgbRed: 222 / 255, green: 226 / 255, blue: 233 / 255, alpha: 1)
|
|
563
|
|
- static let chipHoverFill = NSColor(srgbRed: 236 / 255, green: 240 / 255, blue: 246 / 255, alpha: 1)
|
|
564
|
|
- static let chipBadgeBackground = NSColor(srgbRed: 233 / 255, green: 236 / 255, blue: 241 / 255, alpha: 1)
|
|
565
|
|
- static let chipBadgeText = NSColor(srgbRed: 90 / 255, green: 102 / 255, blue: 121 / 255, alpha: 1)
|
|
566
|
|
- static let activeChipBackground = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
|
|
567
|
|
- static let activeChipHover = NSColor(srgbRed: 28 / 255, green: 70 / 255, blue: 140 / 255, alpha: 1)
|
|
568
|
|
- static let activeChipText = NSColor.white
|
|
569
|
|
- static let activeChipBadgeBackground = NSColor.white.withAlphaComponent(0.22)
|
|
570
|
|
- static let activeChipBadgeText = NSColor.white
|
|
571
|
|
- static let primaryText = NSColor(srgbRed: 31 / 255, green: 41 / 255, blue: 55 / 255, alpha: 1)
|
|
572
|
|
- static let secondaryText = NSColor(srgbRed: 100 / 255, green: 116 / 255, blue: 139 / 255, alpha: 1)
|
|
573
|
|
- static let cardBorder = NSColor(srgbRed: 216 / 255, green: 223 / 255, blue: 233 / 255, alpha: 1)
|
|
574
|
|
- static let cardBorderHover = NSColor(srgbRed: 178 / 255, green: 196 / 255, blue: 225 / 255, alpha: 1)
|
|
575
|
|
- static let cardBorderSelected = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
|
|
576
|
|
- static let cardFooter = NSColor(srgbRed: 250 / 255, green: 251 / 255, blue: 253 / 255, alpha: 1)
|
|
577
|
|
- static let previewSurface = NSColor(srgbRed: 252 / 255, green: 252 / 255, blue: 252 / 255, alpha: 1)
|
|
578
|
|
- static let previewPaper = NSColor.white
|
|
579
|
|
- static let previewSidebarTint = NSColor(srgbRed: 244 / 255, green: 246 / 255, blue: 250 / 255, alpha: 1)
|
|
580
|
|
- static let previewInk = NSColor(srgbRed: 38 / 255, green: 50 / 255, blue: 71 / 255, alpha: 1)
|
|
581
|
|
- static let previewMuted = NSColor(srgbRed: 165 / 255, green: 175 / 255, blue: 192 / 255, alpha: 1)
|
|
582
|
|
- static let previewAccentRed = NSColor(srgbRed: 207 / 255, green: 67 / 255, blue: 50 / 255, alpha: 1)
|
|
583
|
|
- static let previewAccentBlue = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
|
|
584
|
|
- static let ctaBackground = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
|
|
585
|
|
- static let ctaHover = NSColor(srgbRed: 28 / 255, green: 70 / 255, blue: 140 / 255, alpha: 1)
|
|
586
|
|
- static let ctaText = NSColor.white
|
|
587
|
|
- static let selectionGlow = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.55)
|
|
588
|
|
- static let gradientTop = NSColor(srgbRed: 250 / 255, green: 252 / 255, blue: 1, alpha: 1)
|
|
589
|
|
- static let gradientBottom = NSColor(srgbRed: 236 / 255, green: 244 / 255, blue: 1, alpha: 1)
|
|
|
558
|
+ static var primaryText: NSColor { AppDashboardTheme.primaryText }
|
|
|
559
|
+ static var secondaryText: NSColor { AppDashboardTheme.secondaryText }
|
|
|
560
|
+ static var cardBackground: NSColor { AppDashboardTheme.cardBackground }
|
|
|
561
|
+ static var cardBorder: NSColor { AppDashboardTheme.border }
|
|
|
562
|
+ static var cardBorderHover: NSColor { AppDashboardTheme.cvMakerCardBorderHover }
|
|
|
563
|
+ static var cardBorderSelected: NSColor { AppDashboardTheme.brandBlue }
|
|
|
564
|
+ static var cardFooter: NSColor { AppDashboardTheme.cvMakerCardFooter }
|
|
|
565
|
+ static var previewSurface: NSColor { AppDashboardTheme.cvMakerPreviewSurface }
|
|
|
566
|
+ static var previewPaper: NSColor { NSColor.white }
|
|
|
567
|
+ static var previewSidebarTint: NSColor { AppDashboardTheme.cvMakerPreviewSidebarTint }
|
|
|
568
|
+ static var previewInk: NSColor {
|
|
|
569
|
+ NSColor(srgbRed: 38 / 255, green: 50 / 255, blue: 71 / 255, alpha: 1)
|
|
|
570
|
+ }
|
|
|
571
|
+ static var previewMuted: NSColor {
|
|
|
572
|
+ NSColor(srgbRed: 165 / 255, green: 175 / 255, blue: 192 / 255, alpha: 1)
|
|
|
573
|
+ }
|
|
|
574
|
+ static var previewAccentRed: NSColor {
|
|
|
575
|
+ NSColor(srgbRed: 207 / 255, green: 67 / 255, blue: 50 / 255, alpha: 1)
|
|
|
576
|
+ }
|
|
|
577
|
+ static var previewAccentBlue: NSColor { AppDashboardTheme.brandBlue }
|
|
|
578
|
+ static var ctaBackground: NSColor { AppDashboardTheme.brandBlue }
|
|
|
579
|
+ static var ctaHover: NSColor { AppDashboardTheme.brandBlueHover }
|
|
|
580
|
+ static var ctaText: NSColor { AppDashboardTheme.proCTAText }
|
|
|
581
|
+ static var selectionGlow: NSColor { AppDashboardTheme.cvMakerSelectionGlow }
|
|
|
582
|
+ static var gradientTop: NSColor { AppDashboardTheme.cvMakerPageGradientTop }
|
|
|
583
|
+ static var gradientBottom: NSColor { AppDashboardTheme.cvMakerPageGradientBottom }
|
|
|
584
|
+ static var filterChromeBorder: NSColor { AppDashboardTheme.cvMakerFilterChromeBorder }
|
|
590
|
585
|
}
|
|
591
|
586
|
|
|
|
587
|
+ private var appearanceObserver: NSObjectProtocol?
|
|
|
588
|
+
|
|
592
|
589
|
private let pageGradientLayer = CAGradientLayer()
|
|
593
|
590
|
private let filterChrome = NSVisualEffectView()
|
|
594
|
591
|
private let filterStack = NSStackView()
|
|
|
@@ -650,6 +647,20 @@ final class CVMakerPageView: NSView {
|
|
650
|
647
|
reloadTemplateGrid()
|
|
651
|
648
|
updateSelectedChipStates()
|
|
652
|
649
|
beginLoadingAICatalogIfPossible()
|
|
|
650
|
+ appearanceObserver = NotificationCenter.default.addObserver(
|
|
|
651
|
+ forName: AppAppearanceManager.didChangeNotification,
|
|
|
652
|
+ object: nil,
|
|
|
653
|
+ queue: .main
|
|
|
654
|
+ ) { [weak self] _ in
|
|
|
655
|
+ self?.applyCurrentAppearance()
|
|
|
656
|
+ }
|
|
|
657
|
+ applyCurrentAppearance()
|
|
|
658
|
+ }
|
|
|
659
|
+
|
|
|
660
|
+ deinit {
|
|
|
661
|
+ if let appearanceObserver {
|
|
|
662
|
+ NotificationCenter.default.removeObserver(appearanceObserver)
|
|
|
663
|
+ }
|
|
653
|
664
|
}
|
|
654
|
665
|
|
|
655
|
666
|
@available(*, unavailable)
|
|
|
@@ -657,32 +668,47 @@ final class CVMakerPageView: NSView {
|
|
657
|
668
|
fatalError("init(coder:) has not been implemented")
|
|
658
|
669
|
}
|
|
659
|
670
|
|
|
|
671
|
+ override func viewDidChangeEffectiveAppearance() {
|
|
|
672
|
+ super.viewDidChangeEffectiveAppearance()
|
|
|
673
|
+ applyCurrentAppearance()
|
|
|
674
|
+ }
|
|
|
675
|
+
|
|
660
|
676
|
override func layout() {
|
|
661
|
677
|
super.layout()
|
|
662
|
678
|
pageGradientLayer.frame = bounds
|
|
663
|
679
|
layoutGridCardsIfNeeded()
|
|
664
|
680
|
}
|
|
665
|
681
|
|
|
|
682
|
+ func applyCurrentAppearance() {
|
|
|
683
|
+ pageGradientLayer.colors = [Palette.gradientBottom.cgColor, Palette.gradientTop.cgColor]
|
|
|
684
|
+ filterChrome.layer?.borderColor = Palette.filterChromeBorder.cgColor
|
|
|
685
|
+ titleLabel.textColor = Palette.primaryText
|
|
|
686
|
+ subtitleLabel.textColor = Palette.secondaryText
|
|
|
687
|
+ styleCTAButton(ctaButton)
|
|
|
688
|
+ for chip in groupTabButtons.values { chip.applyCurrentAppearance() }
|
|
|
689
|
+ for chip in familyChipButtons.values { chip.applyCurrentAppearance() }
|
|
|
690
|
+ reloadTemplateGrid()
|
|
|
691
|
+ updateSelectedChipStates()
|
|
|
692
|
+ }
|
|
|
693
|
+
|
|
666
|
694
|
// MARK: Setup
|
|
667
|
695
|
|
|
668
|
696
|
private func configureLayout() {
|
|
669
|
697
|
wantsLayer = true
|
|
670
|
698
|
layer?.backgroundColor = NSColor.clear.cgColor
|
|
671
|
699
|
|
|
672
|
|
- pageGradientLayer.colors = [Palette.gradientBottom.cgColor, Palette.gradientTop.cgColor]
|
|
673
|
700
|
pageGradientLayer.locations = [0, 1] as [NSNumber]
|
|
674
|
701
|
pageGradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
|
|
675
|
702
|
pageGradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
|
|
676
|
703
|
layer?.insertSublayer(pageGradientLayer, at: 0)
|
|
677
|
704
|
|
|
678
|
705
|
filterChrome.translatesAutoresizingMaskIntoConstraints = false
|
|
679
|
|
- filterChrome.material = .sidebar
|
|
|
706
|
+ filterChrome.material = .contentBackground
|
|
680
|
707
|
filterChrome.blendingMode = .withinWindow
|
|
681
|
708
|
filterChrome.state = .active
|
|
682
|
709
|
filterChrome.wantsLayer = true
|
|
683
|
710
|
filterChrome.layer?.cornerRadius = 18
|
|
684
|
711
|
filterChrome.layer?.borderWidth = 1
|
|
685
|
|
- filterChrome.layer?.borderColor = NSColor.white.withAlphaComponent(0.65).cgColor
|
|
686
|
712
|
|
|
687
|
713
|
filterStack.orientation = .vertical
|
|
688
|
714
|
filterStack.spacing = 12
|
|
|
@@ -975,6 +1001,7 @@ final class CVMakerPageView: NSView {
|
|
975
|
1001
|
borderHover: Palette.cardBorderHover,
|
|
976
|
1002
|
borderSelected: Palette.cardBorderSelected,
|
|
977
|
1003
|
selectionGlow: Palette.selectionGlow,
|
|
|
1004
|
+ cardShellBackground: Palette.cardBackground,
|
|
978
|
1005
|
footerBackground: Palette.cardFooter,
|
|
979
|
1006
|
previewSurface: Palette.previewSurface,
|
|
980
|
1007
|
previewPaper: Palette.previewPaper,
|
|
|
@@ -1121,18 +1148,22 @@ private final class CVChipButton: NSView {
|
|
1121
|
1148
|
private var trackingArea: NSTrackingArea?
|
|
1122
|
1149
|
|
|
1123
|
1150
|
private enum Palette {
|
|
1124
|
|
- static let restFill = NSColor.white
|
|
1125
|
|
- static let restBorder = NSColor(srgbRed: 222 / 255, green: 226 / 255, blue: 233 / 255, alpha: 1)
|
|
1126
|
|
- static let hoverFill = NSColor(srgbRed: 248 / 255, green: 250 / 255, blue: 252 / 255, alpha: 1)
|
|
1127
|
|
- static let activeFill = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
|
|
1128
|
|
- static let activeFillHover = NSColor(srgbRed: 28 / 255, green: 70 / 255, blue: 140 / 255, alpha: 1)
|
|
1129
|
|
- static let activeBorder = NSColor(srgbRed: 28 / 255, green: 70 / 255, blue: 140 / 255, alpha: 1)
|
|
1130
|
|
- static let restText = NSColor(srgbRed: 71 / 255, green: 85 / 255, blue: 105 / 255, alpha: 1)
|
|
1131
|
|
- static let activeText = NSColor.white
|
|
1132
|
|
- static let restBadge = NSColor(srgbRed: 230 / 255, green: 234 / 255, blue: 240 / 255, alpha: 1)
|
|
1133
|
|
- static let restBadgeText = NSColor(srgbRed: 100 / 255, green: 116 / 255, blue: 139 / 255, alpha: 1)
|
|
1134
|
|
- static let activeBadge = NSColor.white.withAlphaComponent(0.22)
|
|
1135
|
|
- static let activeBadgeText = NSColor.white
|
|
|
1151
|
+ static var restFill: NSColor { AppDashboardTheme.cvMakerChipRestFill }
|
|
|
1152
|
+ static var restBorder: NSColor { AppDashboardTheme.cvMakerChipRestBorder }
|
|
|
1153
|
+ static var hoverFill: NSColor { AppDashboardTheme.cvMakerChipHoverFill }
|
|
|
1154
|
+ static var activeFill: NSColor { AppDashboardTheme.brandBlue }
|
|
|
1155
|
+ static var activeFillHover: NSColor { AppDashboardTheme.brandBlueHover }
|
|
|
1156
|
+ static var activeBorder: NSColor { AppDashboardTheme.brandBlueHover }
|
|
|
1157
|
+ static var restText: NSColor { AppDashboardTheme.primaryText }
|
|
|
1158
|
+ static var activeText: NSColor { AppDashboardTheme.proCTAText }
|
|
|
1159
|
+ static var restBadge: NSColor { AppDashboardTheme.cvMakerChipBadgeBackground }
|
|
|
1160
|
+ static var restBadgeText: NSColor { AppDashboardTheme.cvMakerChipBadgeText }
|
|
|
1161
|
+ static var activeBadge: NSColor { NSColor.white.withAlphaComponent(0.22) }
|
|
|
1162
|
+ static var activeBadgeText: NSColor { AppDashboardTheme.proCTAText }
|
|
|
1163
|
+ }
|
|
|
1164
|
+
|
|
|
1165
|
+ func applyCurrentAppearance() {
|
|
|
1166
|
+ applyState()
|
|
1136
|
1167
|
}
|
|
1137
|
1168
|
|
|
1138
|
1169
|
private static let symbolSide: CGFloat = 18
|
|
|
@@ -1331,6 +1362,7 @@ private final class CVTemplateCard: NSView {
|
|
1331
|
1362
|
private let template: CVTemplate
|
|
1332
|
1363
|
private let palette: CVTemplateCardPalette
|
|
1333
|
1364
|
private let previewSurface = NSView()
|
|
|
1365
|
+ private let footerView = NSView()
|
|
1334
|
1366
|
private let preview: CVTemplatePreviewView
|
|
1335
|
1367
|
private let nameLabel = NSTextField(labelWithString: "")
|
|
1336
|
1368
|
private let categoryLabel = NSTextField(labelWithString: "")
|
|
|
@@ -1346,7 +1378,7 @@ private final class CVTemplateCard: NSView {
|
|
1346
|
1378
|
wantsLayer = true
|
|
1347
|
1379
|
layer?.masksToBounds = false
|
|
1348
|
1380
|
layer?.cornerRadius = 24
|
|
1349
|
|
- layer?.backgroundColor = NSColor.white.cgColor
|
|
|
1381
|
+ layer?.backgroundColor = palette.cardShellBackground.cgColor
|
|
1350
|
1382
|
translatesAutoresizingMaskIntoConstraints = false
|
|
1351
|
1383
|
heightAnchor.constraint(equalToConstant: Self.layoutHeight).isActive = true
|
|
1352
|
1384
|
|
|
|
@@ -1379,20 +1411,19 @@ private final class CVTemplateCard: NSView {
|
|
1379
|
1411
|
footerStack.alignment = .leading
|
|
1380
|
1412
|
footerStack.translatesAutoresizingMaskIntoConstraints = false
|
|
1381
|
1413
|
|
|
1382
|
|
- let footer = NSView()
|
|
1383
|
|
- footer.translatesAutoresizingMaskIntoConstraints = false
|
|
1384
|
|
- footer.wantsLayer = true
|
|
1385
|
|
- footer.layer?.backgroundColor = palette.footerBackground.cgColor
|
|
1386
|
|
- footer.addSubview(footerStack)
|
|
|
1414
|
+ footerView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
1415
|
+ footerView.wantsLayer = true
|
|
|
1416
|
+ footerView.layer?.backgroundColor = palette.footerBackground.cgColor
|
|
|
1417
|
+ footerView.addSubview(footerStack)
|
|
1387
|
1418
|
NSLayoutConstraint.activate([
|
|
1388
|
|
- footerStack.leadingAnchor.constraint(equalTo: footer.leadingAnchor, constant: 16),
|
|
1389
|
|
- footerStack.trailingAnchor.constraint(lessThanOrEqualTo: footer.trailingAnchor, constant: -16),
|
|
1390
|
|
- footerStack.topAnchor.constraint(equalTo: footer.topAnchor, constant: 13),
|
|
1391
|
|
- footerStack.bottomAnchor.constraint(equalTo: footer.bottomAnchor, constant: -13)
|
|
|
1419
|
+ footerStack.leadingAnchor.constraint(equalTo: footerView.leadingAnchor, constant: 16),
|
|
|
1420
|
+ footerStack.trailingAnchor.constraint(lessThanOrEqualTo: footerView.trailingAnchor, constant: -16),
|
|
|
1421
|
+ footerStack.topAnchor.constraint(equalTo: footerView.topAnchor, constant: 13),
|
|
|
1422
|
+ footerStack.bottomAnchor.constraint(equalTo: footerView.bottomAnchor, constant: -13)
|
|
1392
|
1423
|
])
|
|
1393
|
1424
|
|
|
1394
|
1425
|
addSubview(previewSurface)
|
|
1395
|
|
- addSubview(footer)
|
|
|
1426
|
+ addSubview(footerView)
|
|
1396
|
1427
|
|
|
1397
|
1428
|
NSLayoutConstraint.activate([
|
|
1398
|
1429
|
previewSurface.topAnchor.constraint(equalTo: topAnchor),
|
|
|
@@ -1405,10 +1436,10 @@ private final class CVTemplateCard: NSView {
|
|
1405
|
1436
|
preview.trailingAnchor.constraint(equalTo: previewSurface.trailingAnchor, constant: -16),
|
|
1406
|
1437
|
preview.bottomAnchor.constraint(equalTo: previewSurface.bottomAnchor, constant: -14),
|
|
1407
|
1438
|
|
|
1408
|
|
- footer.topAnchor.constraint(equalTo: previewSurface.bottomAnchor),
|
|
1409
|
|
- footer.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
1410
|
|
- footer.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
1411
|
|
- footer.bottomAnchor.constraint(equalTo: bottomAnchor)
|
|
|
1439
|
+ footerView.topAnchor.constraint(equalTo: previewSurface.bottomAnchor),
|
|
|
1440
|
+ footerView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
|
1441
|
+ footerView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
|
1442
|
+ footerView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
|
1412
|
1443
|
])
|
|
1413
|
1444
|
applyChrome()
|
|
1414
|
1445
|
}
|
|
|
@@ -1499,6 +1530,11 @@ private final class CVTemplateCard: NSView {
|
|
1499
|
1530
|
} else {
|
|
1500
|
1531
|
borderColor = palette.border
|
|
1501
|
1532
|
}
|
|
|
1533
|
+ layer?.backgroundColor = palette.cardShellBackground.cgColor
|
|
|
1534
|
+ previewSurface.layer?.backgroundColor = palette.previewSurface.cgColor
|
|
|
1535
|
+ footerView.layer?.backgroundColor = palette.footerBackground.cgColor
|
|
|
1536
|
+ nameLabel.textColor = palette.primaryText
|
|
|
1537
|
+ categoryLabel.textColor = palette.secondaryText
|
|
1502
|
1538
|
layer?.borderColor = borderColor.cgColor
|
|
1503
|
1539
|
layer?.borderWidth = uniformBorder
|
|
1504
|
1540
|
layer?.shadowColor = NSColor.black.cgColor
|