Преглед изворни кода

Redesign CV template gallery with realistic previews and per-template visuals

- Add CVTemplateMiniPreview: mini résumé rendering (professional, modern rail/split/classic, minimal, executive serif, creative) with shared demo copy
- Extend CVTemplate with theme RGB, layoutType, category; paper tint and idVariant-driven layout variation so cards differ within each family
- Refresh CVMakerPageView: gradient page, glass filter chrome, responsive grid, premium cards (shadows, selection glow, tap scale, Preview overlay)
- Hash id + layout + accent + headline for stable silhouette index across catalog rows

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 пре 3 недеља
родитељ
комит
b9d9a1505b
2 измењених фајлова са 1288 додато и 398 уклоњено
  1. 247 398
      App for Indeed/Views/CVMakerPageView.swift
  2. 1041 0
      App for Indeed/Views/CVTemplateMiniPreview.swift

+ 247 - 398
App for Indeed/Views/CVMakerPageView.swift

@@ -8,6 +8,7 @@
8 8
 //
9 9
 
10 10
 import Cocoa
11
+import QuartzCore
11 12
 
12 13
 // MARK: - Data model
13 14
 
@@ -37,6 +38,21 @@ enum CVDesignFamily: String, CaseIterable, Hashable {
37 38
     }
38 39
 }
39 40
 
41
+/// High-level layout bucket for catalog metadata and filtering.
42
+enum CVTemplateLayoutType: String, Hashable {
43
+    case atsSingleColumn
44
+    case twoColumnSidebarLeading
45
+    case twoColumnSidebarTrailing
46
+
47
+    var gallerySubtitle: String {
48
+        switch self {
49
+        case .atsSingleColumn: return "ATS layout"
50
+        case .twoColumnSidebarLeading: return "Sidebar left"
51
+        case .twoColumnSidebarTrailing: return "Sidebar right"
52
+        }
53
+    }
54
+}
55
+
40 56
 /// Visual recipe used by the mini preview renderer so every template can vary
41 57
 /// the headline style, accent line, and sidebar layout without bespoke views.
42 58
 struct CVTemplate: Hashable {
@@ -77,6 +93,92 @@ struct CVTemplate: Hashable {
77 93
     let accent: Accent
78 94
     let layout: Layout
79 95
     let sectionLabelStyle: SectionLabelStyle
96
+    /// sRGB accent used for headers, tags, and sidebar tints in the mini preview.
97
+    let themeRed: CGFloat
98
+    let themeGreen: CGFloat
99
+    let themeBlue: CGFloat
100
+
101
+    /// Shown on cards; mirrors the design family in this build.
102
+    var category: String { family.title }
103
+
104
+    var layoutType: CVTemplateLayoutType {
105
+        switch layout {
106
+        case .singleColumn: return .atsSingleColumn
107
+        case .twoColumn(sidebar: .leading, _): return .twoColumnSidebarLeading
108
+        case .twoColumn(sidebar: .trailing, _): return .twoColumnSidebarTrailing
109
+        }
110
+    }
111
+
112
+    var themeColor: NSColor {
113
+        NSColor(srgbRed: themeRed, green: themeGreen, blue: themeBlue, alpha: 1)
114
+    }
115
+
116
+    /// Optional bundle image name; `nil` means render a live vector/text preview.
117
+    var previewImageAssetName: String? { nil }
118
+
119
+    init(
120
+        id: String,
121
+        name: String,
122
+        family: CVDesignFamily,
123
+        headline: Headline,
124
+        accent: Accent,
125
+        layout: Layout,
126
+        sectionLabelStyle: SectionLabelStyle,
127
+        themeRed: CGFloat? = nil,
128
+        themeGreen: CGFloat? = nil,
129
+        themeBlue: CGFloat? = nil
130
+    ) {
131
+        self.id = id
132
+        self.name = name
133
+        self.family = family
134
+        self.headline = headline
135
+        self.accent = accent
136
+        self.layout = layout
137
+        self.sectionLabelStyle = sectionLabelStyle
138
+        if let tr = themeRed, let tg = themeGreen, let tb = themeBlue {
139
+            self.themeRed = tr
140
+            self.themeGreen = tg
141
+            self.themeBlue = tb
142
+        } else {
143
+            let rgb = Self.resolvedThemeRGB(family: family, id: id)
144
+            self.themeRed = rgb.0
145
+            self.themeGreen = rgb.1
146
+            self.themeBlue = rgb.2
147
+        }
148
+    }
149
+
150
+    private static func resolvedThemeRGB(family: CVDesignFamily, id: String) -> (CGFloat, CGFloat, CGFloat) {
151
+        var hash: UInt64 = 1469598103934665603
152
+        for b in id.utf8 {
153
+            hash ^= UInt64(b)
154
+            hash &*= 1_099_511_628_211
155
+        }
156
+        let t = Double(hash % 1000) / 1000.0
157
+        switch family {
158
+        case .professional:
159
+            let r = 0.12 + t * 0.06
160
+            let g = 0.32 + t * 0.08
161
+            let b = 0.58 + t * 0.12
162
+            return (r, g, b)
163
+        case .modern:
164
+            let r = 0.0 + t * 0.08
165
+            let g = 0.45 + t * 0.12
166
+            let bl = 0.85 + t * 0.1
167
+            return (min(r, 1), min(g, 1), min(bl, 1))
168
+        case .minimal:
169
+            return (0.45 + t * 0.05, 0.48 + t * 0.04, 0.55 + t * 0.06)
170
+        case .executive:
171
+            let r = 0.08 + t * 0.06
172
+            let g = 0.12 + t * 0.05
173
+            let b = 0.22 + t * 0.08
174
+            return (r, g, b)
175
+        case .creative:
176
+            let r = 0.25 + t * 0.2
177
+            let g = 0.35 + t * 0.15
178
+            let b = 0.72 + t * 0.15
179
+            return (min(r, 1), min(g, 1), min(b, 1))
180
+        }
181
+    }
80 182
 }
81 183
 
82 184
 // MARK: - Catalog
@@ -403,12 +505,17 @@ final class CVMakerPageView: NSView {
403 505
         static let ctaHover = NSColor(srgbRed: 28 / 255, green: 70 / 255, blue: 140 / 255, alpha: 1)
404 506
         static let ctaText = NSColor.white
405 507
         static let overlayTint = NSColor.black.withAlphaComponent(0.45)
508
+        static let selectionGlow = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.55)
509
+        static let gradientTop = NSColor(srgbRed: 250 / 255, green: 252 / 255, blue: 1, alpha: 1)
510
+        static let gradientBottom = NSColor(srgbRed: 236 / 255, green: 244 / 255, blue: 1, alpha: 1)
406 511
     }
407 512
 
408
-    private static let columns: Int = 4
513
+    private let pageGradientLayer = CAGradientLayer()
514
+    private let filterChrome = NSVisualEffectView()
515
+    private let filterStack = NSStackView()
409 516
 
410 517
     private let titleLabel = NSTextField(labelWithString: "Templates")
411
-    private let subtitleLabel = NSTextField(labelWithString: "Browse and select a template for your CV")
518
+    private let subtitleLabel = NSTextField(labelWithString: "Polished layouts with live previews — pick a style that fits your story.")
412 519
     private let groupTabsRow = NSStackView()
413 520
     private let familyChipsRow = NSStackView()
414 521
     private let scrollView = NSScrollView()
@@ -425,6 +532,8 @@ final class CVMakerPageView: NSView {
425 532
     private var familyChipButtons: [CVDesignFamily?: CVChipButton] = [:]
426 533
     private var templateCardsByID: [String: CVTemplateCard] = [:]
427 534
 
535
+    private var appliedGridColumnCount: Int = 0
536
+
428 537
     override init(frame frameRect: NSRect) {
429 538
         super.init(frame: frameRect)
430 539
         configureLayout()
@@ -441,7 +550,7 @@ final class CVMakerPageView: NSView {
441 550
 
442 551
     override func layout() {
443 552
         super.layout()
444
-        // Re-measure the grid in case the container width changed (window resize).
553
+        pageGradientLayer.frame = bounds
445 554
         layoutGridCardsIfNeeded()
446 555
     }
447 556
 
@@ -449,7 +558,36 @@ final class CVMakerPageView: NSView {
449 558
 
450 559
     private func configureLayout() {
451 560
         wantsLayer = true
452
-        layer?.backgroundColor = Palette.pageBackground.cgColor
561
+        layer?.backgroundColor = NSColor.clear.cgColor
562
+
563
+        pageGradientLayer.colors = [Palette.gradientBottom.cgColor, Palette.gradientTop.cgColor]
564
+        pageGradientLayer.locations = [0, 1] as [NSNumber]
565
+        pageGradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
566
+        pageGradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
567
+        layer?.insertSublayer(pageGradientLayer, at: 0)
568
+
569
+        filterChrome.translatesAutoresizingMaskIntoConstraints = false
570
+        filterChrome.material = .sidebar
571
+        filterChrome.blendingMode = .withinWindow
572
+        filterChrome.state = .active
573
+        filterChrome.wantsLayer = true
574
+        filterChrome.layer?.cornerRadius = 18
575
+        filterChrome.layer?.borderWidth = 1
576
+        filterChrome.layer?.borderColor = NSColor.white.withAlphaComponent(0.65).cgColor
577
+
578
+        filterStack.orientation = .vertical
579
+        filterStack.spacing = 12
580
+        filterStack.alignment = .leading
581
+        filterStack.translatesAutoresizingMaskIntoConstraints = false
582
+        filterStack.addArrangedSubview(groupTabsRow)
583
+        filterStack.addArrangedSubview(familyChipsRow)
584
+        filterChrome.addSubview(filterStack)
585
+        NSLayoutConstraint.activate([
586
+            filterStack.leadingAnchor.constraint(equalTo: filterChrome.leadingAnchor, constant: 14),
587
+            filterStack.trailingAnchor.constraint(equalTo: filterChrome.trailingAnchor, constant: -14),
588
+            filterStack.topAnchor.constraint(equalTo: filterChrome.topAnchor, constant: 12),
589
+            filterStack.bottomAnchor.constraint(equalTo: filterChrome.bottomAnchor, constant: -12)
590
+        ])
453 591
 
454 592
         titleLabel.font = .systemFont(ofSize: 22, weight: .bold)
455 593
         titleLabel.textColor = Palette.primaryText
@@ -478,7 +616,7 @@ final class CVMakerPageView: NSView {
478 616
         familyChipsRow.translatesAutoresizingMaskIntoConstraints = false
479 617
 
480 618
         gridStack.orientation = .vertical
481
-        gridStack.spacing = 16
619
+        gridStack.spacing = 26
482 620
         gridStack.alignment = .leading
483 621
         gridStack.distribution = .fill
484 622
         gridStack.translatesAutoresizingMaskIntoConstraints = false
@@ -512,8 +650,7 @@ final class CVMakerPageView: NSView {
512 650
         ctaButton.action = #selector(didTapUseTemplate)
513 651
 
514 652
         addSubview(headerStack)
515
-        addSubview(groupTabsRow)
516
-        addSubview(familyChipsRow)
653
+        addSubview(filterChrome)
517 654
         addSubview(scrollView)
518 655
         addSubview(ctaButton)
519 656
 
@@ -524,23 +661,19 @@ final class CVMakerPageView: NSView {
524 661
             headerStack.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -horizontalInset),
525 662
             headerStack.topAnchor.constraint(equalTo: topAnchor, constant: 8),
526 663
 
527
-            groupTabsRow.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
528
-            groupTabsRow.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -horizontalInset),
529
-            groupTabsRow.topAnchor.constraint(equalTo: headerStack.bottomAnchor, constant: 18),
530
-
531
-            familyChipsRow.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
532
-            familyChipsRow.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -horizontalInset),
533
-            familyChipsRow.topAnchor.constraint(equalTo: groupTabsRow.bottomAnchor, constant: 14),
664
+            filterChrome.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
665
+            filterChrome.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -horizontalInset),
666
+            filterChrome.topAnchor.constraint(equalTo: headerStack.bottomAnchor, constant: 16),
534 667
 
535 668
             scrollView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
536 669
             scrollView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset),
537
-            scrollView.topAnchor.constraint(equalTo: familyChipsRow.bottomAnchor, constant: 16),
538
-            scrollView.bottomAnchor.constraint(equalTo: ctaButton.topAnchor, constant: -16),
670
+            scrollView.topAnchor.constraint(equalTo: filterChrome.bottomAnchor, constant: 18),
671
+            scrollView.bottomAnchor.constraint(equalTo: ctaButton.topAnchor, constant: -18),
539 672
 
540 673
             ctaButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
541 674
             ctaButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset),
542 675
             ctaButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -24),
543
-            ctaButton.heightAnchor.constraint(equalToConstant: 50)
676
+            ctaButton.heightAnchor.constraint(equalToConstant: 52)
544 677
         ])
545 678
     }
546 679
 
@@ -624,12 +757,12 @@ final class CVMakerPageView: NSView {
624 757
             return
625 758
         }
626 759
 
627
-        let columns = Self.columns
760
+        let columns = resolvedGridColumnCount()
628 761
         var index = 0
629 762
         while index < templates.count {
630 763
             let row = NSStackView()
631 764
             row.orientation = .horizontal
632
-            row.spacing = 16
765
+            row.spacing = 22
633 766
             row.distribution = .fillEqually
634 767
             row.alignment = .top
635 768
             row.translatesAutoresizingMaskIntoConstraints = false
@@ -662,8 +795,29 @@ final class CVMakerPageView: NSView {
662 795
         applySelectionToCards()
663 796
     }
664 797
 
798
+    private func galleryLayoutWidth() -> CGFloat {
799
+        if bounds.width > 8 { return bounds.width }
800
+        if let s = superview, s.bounds.width > 8 { return max(s.bounds.width - 64, 400) }
801
+        return 900
802
+    }
803
+
665 804
     private func layoutGridCardsIfNeeded() {
666
-        // Stack will resize the rows; nothing else to do — kept as a hook for future enhancements.
805
+        let w = max(galleryLayoutWidth(), 400)
806
+        let cols: Int
807
+        if w < 780 { cols = 2 }
808
+        else if w < 1080 { cols = 3 }
809
+        else { cols = 4 }
810
+        guard cols != appliedGridColumnCount else { return }
811
+        appliedGridColumnCount = cols
812
+        reloadTemplateGrid()
813
+        updateSelectedChipStates()
814
+    }
815
+
816
+    private func resolvedGridColumnCount() -> Int {
817
+        let w = max(galleryLayoutWidth(), 400)
818
+        if w < 780 { return 2 }
819
+        if w < 1080 { return 3 }
820
+        return 4
667 821
     }
668 822
 
669 823
     private func applySelectionToCards() {
@@ -672,11 +826,12 @@ final class CVMakerPageView: NSView {
672 826
         }
673 827
     }
674 828
 
675
-    private func palette() -> CVTemplateCard.Palette {
676
-        CVTemplateCard.Palette(
829
+    private func palette() -> CVTemplateCardPalette {
830
+        CVTemplateCardPalette(
677 831
             border: Palette.cardBorder,
678 832
             borderHover: Palette.cardBorderHover,
679 833
             borderSelected: Palette.cardBorderSelected,
834
+            selectionGlow: Palette.selectionGlow,
680 835
             footerBackground: Palette.cardFooter,
681 836
             previewSurface: Palette.previewSurface,
682 837
             previewPaper: Palette.previewPaper,
@@ -753,7 +908,7 @@ final class CVMakerPageView: NSView {
753 908
         button.focusRingType = .none
754 909
         button.contentTintColor = Palette.ctaText
755 910
         button.wantsLayer = true
756
-        button.layer?.cornerRadius = 12
911
+        button.layer?.cornerRadius = 14
757 912
         button.layer?.backgroundColor = Palette.ctaBackground.cgColor
758 913
         button.pointerCursor = true
759 914
         let attrs: [NSAttributedString.Key: Any] = [
@@ -992,32 +1147,14 @@ private final class CVChipButton: NSView {
992 1147
 
993 1148
 // MARK: - Template card
994 1149
 
995
-/// Bordered card holding the mini CV preview, a hover "Preview" overlay, and a
996
-/// footer with the template name + family. Maintains its own hover/selected
997
-/// states so the parent can stay declarative.
1150
+/// Premium gallery card: live résumé thumbnail, glass-style preview overlay, soft
1151
+/// shadow, and an animated brand border when selected.
998 1152
 private final class CVTemplateCard: NSView {
999
-    struct Palette {
1000
-        let border: NSColor
1001
-        let borderHover: NSColor
1002
-        let borderSelected: NSColor
1003
-        let footerBackground: NSColor
1004
-        let previewSurface: NSColor
1005
-        let previewPaper: NSColor
1006
-        let previewSidebarTint: NSColor
1007
-        let previewInk: NSColor
1008
-        let previewMuted: NSColor
1009
-        let previewAccentRed: NSColor
1010
-        let previewAccentBlue: NSColor
1011
-        let primaryText: NSColor
1012
-        let secondaryText: NSColor
1013
-        let overlayTint: NSColor
1014
-    }
1015
-
1016 1153
     var onSelect: (() -> Void)?
1017 1154
     var onPreview: (() -> Void)?
1018
-    var isSelected: Bool = false { didSet { applyBorder() } }
1155
+    var isSelected: Bool = false { didSet { applyChrome() } }
1019 1156
     private let template: CVTemplate
1020
-    private let palette: Palette
1157
+    private let palette: CVTemplateCardPalette
1021 1158
     private let previewSurface = NSView()
1022 1159
     private let preview: CVTemplatePreviewView
1023 1160
     private let nameLabel = NSTextField(labelWithString: "")
@@ -1030,17 +1167,17 @@ private final class CVTemplateCard: NSView {
1030 1167
     private var isHovering: Bool = false
1031 1168
     private var didPushCursor: Bool = false
1032 1169
 
1033
-    init(template: CVTemplate, palette: Palette) {
1170
+    init(template: CVTemplate, palette: CVTemplateCardPalette) {
1034 1171
         self.template = template
1035 1172
         self.palette = palette
1036 1173
         self.preview = CVTemplatePreviewView(template: template, palette: palette)
1037 1174
         super.init(frame: .zero)
1038 1175
         wantsLayer = true
1039
-        layer?.cornerRadius = 14
1040
-        layer?.borderWidth = 1
1041
-        layer?.masksToBounds = true
1176
+        layer?.masksToBounds = false
1177
+        layer?.cornerRadius = 24
1178
+        layer?.backgroundColor = NSColor.white.cgColor
1042 1179
         translatesAutoresizingMaskIntoConstraints = false
1043
-        heightAnchor.constraint(greaterThanOrEqualToConstant: 280).isActive = true
1180
+        heightAnchor.constraint(greaterThanOrEqualToConstant: 292).isActive = true
1044 1181
 
1045 1182
         previewSurface.translatesAutoresizingMaskIntoConstraints = false
1046 1183
         previewSurface.wantsLayer = true
@@ -1050,14 +1187,14 @@ private final class CVTemplateCard: NSView {
1050 1187
         previewSurface.addSubview(preview)
1051 1188
 
1052 1189
         nameLabel.stringValue = template.name
1053
-        nameLabel.font = .systemFont(ofSize: 13, weight: .semibold)
1190
+        nameLabel.font = .systemFont(ofSize: 14, weight: .semibold)
1054 1191
         nameLabel.textColor = palette.primaryText
1055 1192
         nameLabel.isBordered = false
1056 1193
         nameLabel.drawsBackground = false
1057 1194
         nameLabel.isEditable = false
1058 1195
         nameLabel.isSelectable = false
1059 1196
 
1060
-        categoryLabel.stringValue = template.family.title
1197
+        categoryLabel.stringValue = "\(template.category) · \(template.layoutType.gallerySubtitle)"
1061 1198
         categoryLabel.font = .systemFont(ofSize: 11.5, weight: .regular)
1062 1199
         categoryLabel.textColor = palette.secondaryText
1063 1200
         categoryLabel.isBordered = false
@@ -1067,7 +1204,7 @@ private final class CVTemplateCard: NSView {
1067 1204
 
1068 1205
         let footerStack = NSStackView(views: [nameLabel, categoryLabel])
1069 1206
         footerStack.orientation = .vertical
1070
-        footerStack.spacing = 2
1207
+        footerStack.spacing = 3
1071 1208
         footerStack.alignment = .leading
1072 1209
         footerStack.translatesAutoresizingMaskIntoConstraints = false
1073 1210
 
@@ -1077,10 +1214,10 @@ private final class CVTemplateCard: NSView {
1077 1214
         footer.layer?.backgroundColor = palette.footerBackground.cgColor
1078 1215
         footer.addSubview(footerStack)
1079 1216
         NSLayoutConstraint.activate([
1080
-            footerStack.leadingAnchor.constraint(equalTo: footer.leadingAnchor, constant: 14),
1081
-            footerStack.trailingAnchor.constraint(lessThanOrEqualTo: footer.trailingAnchor, constant: -14),
1082
-            footerStack.topAnchor.constraint(equalTo: footer.topAnchor, constant: 12),
1083
-            footerStack.bottomAnchor.constraint(equalTo: footer.bottomAnchor, constant: -12)
1217
+            footerStack.leadingAnchor.constraint(equalTo: footer.leadingAnchor, constant: 16),
1218
+            footerStack.trailingAnchor.constraint(lessThanOrEqualTo: footer.trailingAnchor, constant: -16),
1219
+            footerStack.topAnchor.constraint(equalTo: footer.topAnchor, constant: 13),
1220
+            footerStack.bottomAnchor.constraint(equalTo: footer.bottomAnchor, constant: -13)
1084 1221
         ])
1085 1222
 
1086 1223
         addSubview(previewSurface)
@@ -1092,12 +1229,12 @@ private final class CVTemplateCard: NSView {
1092 1229
             previewSurface.topAnchor.constraint(equalTo: topAnchor),
1093 1230
             previewSurface.leadingAnchor.constraint(equalTo: leadingAnchor),
1094 1231
             previewSurface.trailingAnchor.constraint(equalTo: trailingAnchor),
1095
-            previewSurface.heightAnchor.constraint(greaterThanOrEqualToConstant: 230),
1232
+            previewSurface.heightAnchor.constraint(greaterThanOrEqualToConstant: 236),
1096 1233
 
1097
-            preview.topAnchor.constraint(equalTo: previewSurface.topAnchor, constant: 16),
1098
-            preview.leadingAnchor.constraint(equalTo: previewSurface.leadingAnchor, constant: 18),
1099
-            preview.trailingAnchor.constraint(equalTo: previewSurface.trailingAnchor, constant: -18),
1100
-            preview.bottomAnchor.constraint(equalTo: previewSurface.bottomAnchor, constant: -16),
1234
+            preview.topAnchor.constraint(equalTo: previewSurface.topAnchor, constant: 14),
1235
+            preview.leadingAnchor.constraint(equalTo: previewSurface.leadingAnchor, constant: 16),
1236
+            preview.trailingAnchor.constraint(equalTo: previewSurface.trailingAnchor, constant: -16),
1237
+            preview.bottomAnchor.constraint(equalTo: previewSurface.bottomAnchor, constant: -14),
1101 1238
 
1102 1239
             footer.topAnchor.constraint(equalTo: previewSurface.bottomAnchor),
1103 1240
             footer.leadingAnchor.constraint(equalTo: leadingAnchor),
@@ -1109,7 +1246,7 @@ private final class CVTemplateCard: NSView {
1109 1246
             overlay.trailingAnchor.constraint(equalTo: previewSurface.trailingAnchor),
1110 1247
             overlay.bottomAnchor.constraint(equalTo: previewSurface.bottomAnchor)
1111 1248
         ])
1112
-        applyBorder()
1249
+        applyChrome()
1113 1250
     }
1114 1251
 
1115 1252
     @available(*, unavailable)
@@ -1117,6 +1254,11 @@ private final class CVTemplateCard: NSView {
1117 1254
         fatalError("init(coder:) has not been implemented")
1118 1255
     }
1119 1256
 
1257
+    override func layout() {
1258
+        super.layout()
1259
+        layer?.shadowPath = CGPath(roundedRect: bounds, cornerWidth: 24, cornerHeight: 24, transform: nil)
1260
+    }
1261
+
1120 1262
     private func configureOverlay() {
1121 1263
         overlay.translatesAutoresizingMaskIntoConstraints = false
1122 1264
         overlay.wantsLayer = true
@@ -1126,15 +1268,17 @@ private final class CVTemplateCard: NSView {
1126 1268
 
1127 1269
         overlayBadge.translatesAutoresizingMaskIntoConstraints = false
1128 1270
         overlayBadge.wantsLayer = true
1129
-        overlayBadge.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.78).cgColor
1130
-        overlayBadge.layer?.cornerRadius = 16
1271
+        overlayBadge.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.22).cgColor
1272
+        overlayBadge.layer?.cornerRadius = 20
1273
+        overlayBadge.layer?.borderWidth = 1
1274
+        overlayBadge.layer?.borderColor = NSColor.white.withAlphaComponent(0.45).cgColor
1131 1275
 
1132 1276
         overlayBadgeIcon.translatesAutoresizingMaskIntoConstraints = false
1133 1277
         overlayBadgeIcon.image = NSImage(systemSymbolName: "eye", accessibilityDescription: nil)
1134
-        overlayBadgeIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 11, weight: .semibold)
1278
+        overlayBadgeIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 12, weight: .semibold)
1135 1279
         overlayBadgeIcon.contentTintColor = .white
1136 1280
 
1137
-        overlayBadgeLabel.font = .systemFont(ofSize: 12, weight: .semibold)
1281
+        overlayBadgeLabel.font = .systemFont(ofSize: 12.5, weight: .semibold)
1138 1282
         overlayBadgeLabel.textColor = .white
1139 1283
         overlayBadgeLabel.isBordered = false
1140 1284
         overlayBadgeLabel.drawsBackground = false
@@ -1143,17 +1287,17 @@ private final class CVTemplateCard: NSView {
1143 1287
 
1144 1288
         let badgeStack = NSStackView(views: [overlayBadgeIcon, overlayBadgeLabel])
1145 1289
         badgeStack.orientation = .horizontal
1146
-        badgeStack.spacing = 6
1290
+        badgeStack.spacing = 7
1147 1291
         badgeStack.alignment = .centerY
1148 1292
         badgeStack.translatesAutoresizingMaskIntoConstraints = false
1149 1293
 
1150 1294
         overlayBadge.addSubview(badgeStack)
1151 1295
         overlay.addSubview(overlayBadge)
1152 1296
         NSLayoutConstraint.activate([
1153
-            badgeStack.leadingAnchor.constraint(equalTo: overlayBadge.leadingAnchor, constant: 14),
1154
-            badgeStack.trailingAnchor.constraint(equalTo: overlayBadge.trailingAnchor, constant: -14),
1155
-            badgeStack.topAnchor.constraint(equalTo: overlayBadge.topAnchor, constant: 8),
1156
-            badgeStack.bottomAnchor.constraint(equalTo: overlayBadge.bottomAnchor, constant: -8),
1297
+            badgeStack.leadingAnchor.constraint(equalTo: overlayBadge.leadingAnchor, constant: 16),
1298
+            badgeStack.trailingAnchor.constraint(equalTo: overlayBadge.trailingAnchor, constant: -16),
1299
+            badgeStack.topAnchor.constraint(equalTo: overlayBadge.topAnchor, constant: 9),
1300
+            badgeStack.bottomAnchor.constraint(equalTo: overlayBadge.bottomAnchor, constant: -9),
1157 1301
             overlayBadge.centerXAnchor.constraint(equalTo: overlay.centerXAnchor),
1158 1302
             overlayBadge.centerYAnchor.constraint(equalTo: overlay.centerYAnchor)
1159 1303
         ])
@@ -1170,10 +1314,22 @@ private final class CVTemplateCard: NSView {
1170 1314
         if overlay.frame.contains(local) {
1171 1315
             onPreview?()
1172 1316
         } else {
1317
+            playTapPulse()
1173 1318
             onSelect?()
1174 1319
         }
1175 1320
     }
1176 1321
 
1322
+    private func playTapPulse() {
1323
+        guard let l = layer else { return }
1324
+        let a = CABasicAnimation(keyPath: "transform")
1325
+        a.fromValue = NSValue(caTransform3D: CATransform3DIdentity)
1326
+        a.toValue = NSValue(caTransform3D: CATransform3DMakeScale(0.985, 0.985, 1))
1327
+        a.duration = 0.1
1328
+        a.autoreverses = true
1329
+        a.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
1330
+        l.add(a, forKey: "tapPulse")
1331
+    }
1332
+
1177 1333
     override func updateTrackingAreas() {
1178 1334
         super.updateTrackingAreas()
1179 1335
         if let area = trackingArea { removeTrackingArea(area) }
@@ -1191,7 +1347,7 @@ private final class CVTemplateCard: NSView {
1191 1347
         super.mouseEntered(with: event)
1192 1348
         isHovering = true
1193 1349
         animateOverlay(visible: true)
1194
-        applyBorder()
1350
+        applyChrome()
1195 1351
         if !didPushCursor {
1196 1352
             NSCursor.pointingHand.push()
1197 1353
             didPushCursor = true
@@ -1202,7 +1358,7 @@ private final class CVTemplateCard: NSView {
1202 1358
         super.mouseExited(with: event)
1203 1359
         isHovering = false
1204 1360
         animateOverlay(visible: false)
1205
-        applyBorder()
1361
+        applyChrome()
1206 1362
         if didPushCursor {
1207 1363
             NSCursor.pop()
1208 1364
             didPushCursor = false
@@ -1221,346 +1377,39 @@ private final class CVTemplateCard: NSView {
1221 1377
     private func animateOverlay(visible: Bool) {
1222 1378
         let target: CGFloat = visible ? 1 : 0
1223 1379
         NSAnimationContext.runAnimationGroup { context in
1224
-            context.duration = 0.12
1380
+            context.duration = 0.18
1381
+            context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
1225 1382
             context.allowsImplicitAnimation = true
1226 1383
             overlay.animator().alphaValue = target
1227 1384
         }
1228 1385
     }
1229 1386
 
1230
-    private func applyBorder() {
1387
+    private func applyChrome() {
1231 1388
         if isSelected {
1232 1389
             layer?.borderColor = palette.borderSelected.cgColor
1233
-            layer?.borderWidth = 2
1390
+            layer?.borderWidth = 2.5
1391
+            layer?.shadowColor = palette.borderSelected.cgColor
1392
+            layer?.shadowOpacity = 0.42
1393
+            layer?.shadowRadius = 22
1394
+            layer?.shadowOffset = CGSize(width: 0, height: 10)
1234 1395
         } else if isHovering {
1235 1396
             layer?.borderColor = palette.borderHover.cgColor
1236
-            layer?.borderWidth = 1
1397
+            layer?.borderWidth = 1.5
1398
+            layer?.shadowColor = NSColor.black.cgColor
1399
+            layer?.shadowOpacity = 0.14
1400
+            layer?.shadowRadius = 18
1401
+            layer?.shadowOffset = CGSize(width: 0, height: 10)
1237 1402
         } else {
1238 1403
             layer?.borderColor = palette.border.cgColor
1239 1404
             layer?.borderWidth = 1
1405
+            layer?.shadowColor = NSColor.black.cgColor
1406
+            layer?.shadowOpacity = 0.08
1407
+            layer?.shadowRadius = 12
1408
+            layer?.shadowOffset = CGSize(width: 0, height: 6)
1240 1409
         }
1241 1410
     }
1242 1411
 }
1243 1412
 
1244
-// MARK: - Mini preview renderer
1245
-
1246
-/// Tiny stylized representation of a finished CV — accent strip, headline area,
1247
-/// faux paragraph + section lines. Adapts to the template's headline, accent,
1248
-/// sidebar, and section-label style so the grid feels varied at a glance.
1249
-private final class CVTemplatePreviewView: NSView {
1250
-    private let template: CVTemplate
1251
-    private let palette: CVTemplateCard.Palette
1252
-    private let paper = NSView()
1253
-
1254
-    init(template: CVTemplate, palette: CVTemplateCard.Palette) {
1255
-        self.template = template
1256
-        self.palette = palette
1257
-        super.init(frame: .zero)
1258
-        wantsLayer = true
1259
-        translatesAutoresizingMaskIntoConstraints = false
1260
-        configurePaper()
1261
-    }
1262
-
1263
-    @available(*, unavailable)
1264
-    required init?(coder: NSCoder) {
1265
-        fatalError("init(coder:) has not been implemented")
1266
-    }
1267
-
1268
-    private func configurePaper() {
1269
-        paper.translatesAutoresizingMaskIntoConstraints = false
1270
-        paper.wantsLayer = true
1271
-        paper.layer?.backgroundColor = palette.previewPaper.cgColor
1272
-        paper.layer?.cornerRadius = 4
1273
-        paper.layer?.borderColor = NSColor(srgbRed: 232 / 255, green: 235 / 255, blue: 241 / 255, alpha: 1).cgColor
1274
-        paper.layer?.borderWidth = 1
1275
-        paper.layer?.masksToBounds = true
1276
-        addSubview(paper)
1277
-
1278
-        NSLayoutConstraint.activate([
1279
-            paper.topAnchor.constraint(equalTo: topAnchor),
1280
-            paper.bottomAnchor.constraint(equalTo: bottomAnchor),
1281
-            paper.centerXAnchor.constraint(equalTo: centerXAnchor),
1282
-            paper.widthAnchor.constraint(equalTo: heightAnchor, multiplier: 0.78),
1283
-            paper.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor),
1284
-            paper.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor)
1285
-        ])
1286
-
1287
-        let header = makeHeader()
1288
-        let body = makeBody()
1289
-        let stack = NSStackView(views: [header, body])
1290
-        stack.orientation = .vertical
1291
-        stack.alignment = .leading
1292
-        stack.spacing = 6
1293
-        stack.distribution = .fill
1294
-        stack.translatesAutoresizingMaskIntoConstraints = false
1295
-        paper.addSubview(stack)
1296
-        NSLayoutConstraint.activate([
1297
-            stack.leadingAnchor.constraint(equalTo: paper.leadingAnchor, constant: 8),
1298
-            stack.trailingAnchor.constraint(equalTo: paper.trailingAnchor, constant: -8),
1299
-            stack.topAnchor.constraint(equalTo: paper.topAnchor, constant: 8),
1300
-            stack.bottomAnchor.constraint(lessThanOrEqualTo: paper.bottomAnchor, constant: -8),
1301
-            header.widthAnchor.constraint(equalTo: stack.widthAnchor),
1302
-            body.widthAnchor.constraint(equalTo: stack.widthAnchor)
1303
-        ])
1304
-    }
1305
-
1306
-    // MARK: Header
1307
-
1308
-    private func makeHeader() -> NSView {
1309
-        let container = NSView()
1310
-        container.translatesAutoresizingMaskIntoConstraints = false
1311
-
1312
-        let jitterA = templateVisualJitter(template.id)
1313
-        let jitterB = templateVisualJitter(template.id + "-b")
1314
-        let nameInk = palette.previewInk.blended(withFraction: CGFloat(jitterA * 0.14), of: palette.previewAccentBlue) ?? palette.previewInk
1315
-        let nameStrip = makeLine(color: nameInk, height: 5.2 + CGFloat(jitterB * 0.9), widthFraction: 0.52 + CGFloat(jitterA * 0.14))
1316
-        let roleStrip = makeLine(color: palette.previewMuted, height: 3, widthFraction: 0.36 + CGFloat(jitterB * 0.12))
1317
-        let contactStrip = makeLine(color: palette.previewMuted.withAlphaComponent(0.7), height: 2, widthFraction: 0.48 + CGFloat(jitterA * 0.1))
1318
-
1319
-        let textStack = NSStackView(views: [nameStrip, roleStrip, contactStrip])
1320
-        textStack.orientation = .vertical
1321
-        textStack.spacing = 4
1322
-        textStack.translatesAutoresizingMaskIntoConstraints = false
1323
-
1324
-        switch template.headline {
1325
-        case .centered:
1326
-            textStack.alignment = .centerX
1327
-        case .leftAligned, .leftWithInitials:
1328
-            textStack.alignment = .leading
1329
-        case .avatarStacked:
1330
-            textStack.alignment = .leading
1331
-        }
1332
-
1333
-        container.addSubview(textStack)
1334
-
1335
-        switch template.headline {
1336
-        case .leftWithInitials:
1337
-            let avatar = makeAvatar()
1338
-            container.addSubview(avatar)
1339
-            NSLayoutConstraint.activate([
1340
-                textStack.leadingAnchor.constraint(equalTo: container.leadingAnchor),
1341
-                textStack.trailingAnchor.constraint(lessThanOrEqualTo: avatar.leadingAnchor, constant: -6),
1342
-                textStack.topAnchor.constraint(equalTo: container.topAnchor),
1343
-                textStack.bottomAnchor.constraint(equalTo: container.bottomAnchor),
1344
-                avatar.trailingAnchor.constraint(equalTo: container.trailingAnchor),
1345
-                avatar.centerYAnchor.constraint(equalTo: container.centerYAnchor),
1346
-                avatar.widthAnchor.constraint(equalToConstant: 20),
1347
-                avatar.heightAnchor.constraint(equalToConstant: 20)
1348
-            ])
1349
-        case .avatarStacked:
1350
-            let avatar = makeAvatar()
1351
-            container.addSubview(avatar)
1352
-            NSLayoutConstraint.activate([
1353
-                avatar.topAnchor.constraint(equalTo: container.topAnchor),
1354
-                avatar.leadingAnchor.constraint(equalTo: container.leadingAnchor),
1355
-                avatar.widthAnchor.constraint(equalToConstant: 22),
1356
-                avatar.heightAnchor.constraint(equalToConstant: 22),
1357
-                textStack.topAnchor.constraint(equalTo: avatar.bottomAnchor, constant: 4),
1358
-                textStack.leadingAnchor.constraint(equalTo: container.leadingAnchor),
1359
-                textStack.trailingAnchor.constraint(equalTo: container.trailingAnchor),
1360
-                textStack.bottomAnchor.constraint(equalTo: container.bottomAnchor)
1361
-            ])
1362
-        case .centered:
1363
-            NSLayoutConstraint.activate([
1364
-                textStack.topAnchor.constraint(equalTo: container.topAnchor),
1365
-                textStack.bottomAnchor.constraint(equalTo: container.bottomAnchor),
1366
-                textStack.centerXAnchor.constraint(equalTo: container.centerXAnchor),
1367
-                textStack.leadingAnchor.constraint(greaterThanOrEqualTo: container.leadingAnchor),
1368
-                textStack.trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor)
1369
-            ])
1370
-        case .leftAligned:
1371
-            NSLayoutConstraint.activate([
1372
-                textStack.leadingAnchor.constraint(equalTo: container.leadingAnchor),
1373
-                textStack.trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor),
1374
-                textStack.topAnchor.constraint(equalTo: container.topAnchor),
1375
-                textStack.bottomAnchor.constraint(equalTo: container.bottomAnchor)
1376
-            ])
1377
-        }
1378
-
1379
-        // Accent decorations
1380
-        switch template.accent {
1381
-        case .none:
1382
-            break
1383
-        case .redUnderline, .redBar:
1384
-            let accent = NSView()
1385
-            accent.translatesAutoresizingMaskIntoConstraints = false
1386
-            accent.wantsLayer = true
1387
-            accent.layer?.backgroundColor = palette.previewAccentRed.cgColor
1388
-            container.addSubview(accent)
1389
-            NSLayoutConstraint.activate([
1390
-                accent.heightAnchor.constraint(equalToConstant: template.accent == .redBar ? 2.5 : 1.5),
1391
-                accent.topAnchor.constraint(equalTo: textStack.bottomAnchor, constant: 5),
1392
-                accent.leadingAnchor.constraint(equalTo: container.leadingAnchor),
1393
-                accent.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: template.accent == .redBar ? 0.32 : 0.9),
1394
-                accent.bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor)
1395
-            ])
1396
-        case .blueBar:
1397
-            let accent = NSView()
1398
-            accent.translatesAutoresizingMaskIntoConstraints = false
1399
-            accent.wantsLayer = true
1400
-            accent.layer?.backgroundColor = palette.previewAccentBlue.cgColor
1401
-            container.addSubview(accent)
1402
-            NSLayoutConstraint.activate([
1403
-                accent.heightAnchor.constraint(equalToConstant: 2.5),
1404
-                accent.topAnchor.constraint(equalTo: textStack.bottomAnchor, constant: 5),
1405
-                accent.leadingAnchor.constraint(equalTo: container.leadingAnchor),
1406
-                accent.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.32),
1407
-                accent.bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor)
1408
-            ])
1409
-        }
1410
-
1411
-        return container
1412
-    }
1413
-
1414
-    /// Stable 0…1 value per template so thumbnails are not visually identical when metadata is similar.
1415
-    private func templateVisualJitter(_ salt: String) -> Double {
1416
-        var hash: UInt64 = 1469598103934665603
1417
-        for b in salt.utf8 {
1418
-            hash ^= UInt64(b)
1419
-            hash &*= 1_099_511_628_211
1420
-        }
1421
-        return Double(hash % 10_007) / 10_006.0
1422
-    }
1423
-
1424
-    private func makeAvatar() -> NSView {
1425
-        let avatar = NSView()
1426
-        avatar.translatesAutoresizingMaskIntoConstraints = false
1427
-        avatar.wantsLayer = true
1428
-        avatar.layer?.backgroundColor = palette.previewSidebarTint.cgColor
1429
-        avatar.layer?.borderColor = palette.previewMuted.withAlphaComponent(0.4).cgColor
1430
-        avatar.layer?.borderWidth = 1
1431
-        avatar.layer?.cornerRadius = 11
1432
-        let initials = NSTextField(labelWithString: "SJ")
1433
-        initials.font = .systemFont(ofSize: 7, weight: .bold)
1434
-        initials.textColor = palette.previewInk
1435
-        initials.alignment = .center
1436
-        initials.translatesAutoresizingMaskIntoConstraints = false
1437
-        avatar.addSubview(initials)
1438
-        NSLayoutConstraint.activate([
1439
-            initials.centerXAnchor.constraint(equalTo: avatar.centerXAnchor),
1440
-            initials.centerYAnchor.constraint(equalTo: avatar.centerYAnchor)
1441
-        ])
1442
-        return avatar
1443
-    }
1444
-
1445
-    // MARK: Body
1446
-
1447
-    private func makeBody() -> NSView {
1448
-        switch template.layout {
1449
-        case .singleColumn:
1450
-            return makeColumn(width: nil, isSidebar: false)
1451
-        case .twoColumn(let side, let tinted):
1452
-            return makeTwoColumnLayout(sidebarSide: side, tinted: tinted)
1453
-        }
1454
-    }
1455
-
1456
-    private func makeTwoColumnLayout(sidebarSide: CVTemplate.SidebarSide, tinted: Bool) -> NSView {
1457
-        let container = NSView()
1458
-        container.translatesAutoresizingMaskIntoConstraints = false
1459
-
1460
-        let sidebar = makeColumn(width: nil, isSidebar: true)
1461
-        if tinted {
1462
-            sidebar.wantsLayer = true
1463
-            sidebar.layer?.backgroundColor = palette.previewSidebarTint.cgColor
1464
-            sidebar.layer?.cornerRadius = 3
1465
-        }
1466
-        let main = makeColumn(width: nil, isSidebar: false)
1467
-
1468
-        container.addSubview(sidebar)
1469
-        container.addSubview(main)
1470
-
1471
-        let leadingItem: NSView = (sidebarSide == .leading) ? sidebar : main
1472
-        let trailingItem: NSView = (sidebarSide == .leading) ? main : sidebar
1473
-
1474
-        NSLayoutConstraint.activate([
1475
-            leadingItem.leadingAnchor.constraint(equalTo: container.leadingAnchor),
1476
-            leadingItem.topAnchor.constraint(equalTo: container.topAnchor, constant: 2),
1477
-            leadingItem.bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor),
1478
-            trailingItem.trailingAnchor.constraint(equalTo: container.trailingAnchor),
1479
-            trailingItem.topAnchor.constraint(equalTo: container.topAnchor, constant: 2),
1480
-            trailingItem.bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor),
1481
-            trailingItem.leadingAnchor.constraint(equalTo: leadingItem.trailingAnchor, constant: 8),
1482
-            sidebar.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.32),
1483
-            main.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.6)
1484
-        ])
1485
-        return container
1486
-    }
1487
-
1488
-    private func makeColumn(width: CGFloat?, isSidebar: Bool) -> NSView {
1489
-        let stack = NSStackView()
1490
-        stack.orientation = .vertical
1491
-        stack.alignment = .leading
1492
-        stack.spacing = 6
1493
-        stack.translatesAutoresizingMaskIntoConstraints = false
1494
-
1495
-        let sectionNames: [String] = isSidebar
1496
-            ? ["CONTACT", "SKILLS", "LANGUAGES", "INTERESTS"]
1497
-            : ["PROFILE", "EXPERIENCE", "EDUCATION", "SKILLS"]
1498
-
1499
-        for (i, section) in sectionNames.enumerated() {
1500
-            let block = makeSectionBlock(title: section, lineCount: isSidebar ? 3 : (i == 1 ? 4 : 2), narrow: isSidebar)
1501
-            stack.addArrangedSubview(block)
1502
-            block.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
1503
-        }
1504
-        return stack
1505
-    }
1506
-
1507
-    private func makeSectionBlock(title: String, lineCount: Int, narrow: Bool) -> NSView {
1508
-        let label = NSTextField(labelWithString: formattedSectionLabel(title))
1509
-        label.font = .systemFont(ofSize: narrow ? 5 : 5.5, weight: .bold)
1510
-        label.textColor = palette.previewAccentBlue.blended(withFraction: 0.4, of: palette.previewInk) ?? palette.previewInk
1511
-        label.maximumNumberOfLines = 1
1512
-        label.isBordered = false
1513
-        label.drawsBackground = false
1514
-        label.isEditable = false
1515
-        label.isSelectable = false
1516
-        label.translatesAutoresizingMaskIntoConstraints = false
1517
-
1518
-        let lines = NSStackView()
1519
-        lines.orientation = .vertical
1520
-        lines.alignment = .leading
1521
-        lines.spacing = 2
1522
-        lines.translatesAutoresizingMaskIntoConstraints = false
1523
-        for index in 0..<lineCount {
1524
-            let widthFraction: CGFloat = max(0.4, 0.95 - CGFloat(index) * 0.13)
1525
-            let line = makeLine(color: palette.previewMuted.withAlphaComponent(0.65), height: 1.6, widthFraction: widthFraction)
1526
-            lines.addArrangedSubview(line)
1527
-            line.widthAnchor.constraint(equalTo: lines.widthAnchor, multiplier: widthFraction).isActive = true
1528
-        }
1529
-
1530
-        let block = NSStackView(views: [label, lines])
1531
-        block.orientation = .vertical
1532
-        block.alignment = .leading
1533
-        block.spacing = 2
1534
-        block.translatesAutoresizingMaskIntoConstraints = false
1535
-        let blockWidth = block.widthAnchor
1536
-        lines.widthAnchor.constraint(equalTo: blockWidth).isActive = true
1537
-        return block
1538
-    }
1539
-
1540
-    private func formattedSectionLabel(_ raw: String) -> String {
1541
-        switch template.sectionLabelStyle {
1542
-        case .uppercase: return raw
1543
-        case .slashed: return "// \(raw.capitalized)"
1544
-        case .bracketed: return "[ \(raw) ]"
1545
-        }
1546
-    }
1547
-
1548
-    private func makeLine(color: NSColor, height: CGFloat, widthFraction: CGFloat) -> NSView {
1549
-        let line = LineView()
1550
-        line.translatesAutoresizingMaskIntoConstraints = false
1551
-        line.wantsLayer = true
1552
-        line.layer?.backgroundColor = color.cgColor
1553
-        line.layer?.cornerRadius = max(height / 2, 1)
1554
-        line.heightAnchor.constraint(equalToConstant: height).isActive = true
1555
-        line.widthFraction = widthFraction
1556
-        return line
1557
-    }
1558
-}
1559
-
1560
-private final class LineView: NSView {
1561
-    var widthFraction: CGFloat = 1
1562
-}
1563
-
1564 1413
 // MARK: - Helpers
1565 1414
 
1566 1415
 /// Flipped origin so the grid stacks fill from the top.

Разлика између датотеке није приказан због своје велике величине
+ 1041 - 0
App for Indeed/Views/CVTemplateMiniPreview.swift