Răsfoiți Sursa

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 2 luni în urmă
părinte
comite
b9d9a1505b

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

@@ -8,6 +8,7 @@
 //
 
 import Cocoa
+import QuartzCore
 
 // MARK: - Data model
 
@@ -37,6 +38,21 @@ enum CVDesignFamily: String, CaseIterable, Hashable {
     }
 }
 
+/// High-level layout bucket for catalog metadata and filtering.
+enum CVTemplateLayoutType: String, Hashable {
+    case atsSingleColumn
+    case twoColumnSidebarLeading
+    case twoColumnSidebarTrailing
+
+    var gallerySubtitle: String {
+        switch self {
+        case .atsSingleColumn: return "ATS layout"
+        case .twoColumnSidebarLeading: return "Sidebar left"
+        case .twoColumnSidebarTrailing: return "Sidebar right"
+        }
+    }
+}
+
 /// Visual recipe used by the mini preview renderer so every template can vary
 /// the headline style, accent line, and sidebar layout without bespoke views.
 struct CVTemplate: Hashable {
@@ -77,6 +93,92 @@ struct CVTemplate: Hashable {
     let accent: Accent
     let layout: Layout
     let sectionLabelStyle: SectionLabelStyle
+    /// sRGB accent used for headers, tags, and sidebar tints in the mini preview.
+    let themeRed: CGFloat
+    let themeGreen: CGFloat
+    let themeBlue: CGFloat
+
+    /// Shown on cards; mirrors the design family in this build.
+    var category: String { family.title }
+
+    var layoutType: CVTemplateLayoutType {
+        switch layout {
+        case .singleColumn: return .atsSingleColumn
+        case .twoColumn(sidebar: .leading, _): return .twoColumnSidebarLeading
+        case .twoColumn(sidebar: .trailing, _): return .twoColumnSidebarTrailing
+        }
+    }
+
+    var themeColor: NSColor {
+        NSColor(srgbRed: themeRed, green: themeGreen, blue: themeBlue, alpha: 1)
+    }
+
+    /// Optional bundle image name; `nil` means render a live vector/text preview.
+    var previewImageAssetName: String? { nil }
+
+    init(
+        id: String,
+        name: String,
+        family: CVDesignFamily,
+        headline: Headline,
+        accent: Accent,
+        layout: Layout,
+        sectionLabelStyle: SectionLabelStyle,
+        themeRed: CGFloat? = nil,
+        themeGreen: CGFloat? = nil,
+        themeBlue: CGFloat? = nil
+    ) {
+        self.id = id
+        self.name = name
+        self.family = family
+        self.headline = headline
+        self.accent = accent
+        self.layout = layout
+        self.sectionLabelStyle = sectionLabelStyle
+        if let tr = themeRed, let tg = themeGreen, let tb = themeBlue {
+            self.themeRed = tr
+            self.themeGreen = tg
+            self.themeBlue = tb
+        } else {
+            let rgb = Self.resolvedThemeRGB(family: family, id: id)
+            self.themeRed = rgb.0
+            self.themeGreen = rgb.1
+            self.themeBlue = rgb.2
+        }
+    }
+
+    private static func resolvedThemeRGB(family: CVDesignFamily, id: String) -> (CGFloat, CGFloat, CGFloat) {
+        var hash: UInt64 = 1469598103934665603
+        for b in id.utf8 {
+            hash ^= UInt64(b)
+            hash &*= 1_099_511_628_211
+        }
+        let t = Double(hash % 1000) / 1000.0
+        switch family {
+        case .professional:
+            let r = 0.12 + t * 0.06
+            let g = 0.32 + t * 0.08
+            let b = 0.58 + t * 0.12
+            return (r, g, b)
+        case .modern:
+            let r = 0.0 + t * 0.08
+            let g = 0.45 + t * 0.12
+            let bl = 0.85 + t * 0.1
+            return (min(r, 1), min(g, 1), min(bl, 1))
+        case .minimal:
+            return (0.45 + t * 0.05, 0.48 + t * 0.04, 0.55 + t * 0.06)
+        case .executive:
+            let r = 0.08 + t * 0.06
+            let g = 0.12 + t * 0.05
+            let b = 0.22 + t * 0.08
+            return (r, g, b)
+        case .creative:
+            let r = 0.25 + t * 0.2
+            let g = 0.35 + t * 0.15
+            let b = 0.72 + t * 0.15
+            return (min(r, 1), min(g, 1), min(b, 1))
+        }
+    }
 }
 
 // MARK: - Catalog
@@ -403,12 +505,17 @@ final class CVMakerPageView: NSView {
         static let ctaHover = NSColor(srgbRed: 28 / 255, green: 70 / 255, blue: 140 / 255, alpha: 1)
         static let ctaText = NSColor.white
         static let overlayTint = NSColor.black.withAlphaComponent(0.45)
+        static let selectionGlow = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.55)
+        static let gradientTop = NSColor(srgbRed: 250 / 255, green: 252 / 255, blue: 1, alpha: 1)
+        static let gradientBottom = NSColor(srgbRed: 236 / 255, green: 244 / 255, blue: 1, alpha: 1)
     }
 
-    private static let columns: Int = 4
+    private let pageGradientLayer = CAGradientLayer()
+    private let filterChrome = NSVisualEffectView()
+    private let filterStack = NSStackView()
 
     private let titleLabel = NSTextField(labelWithString: "Templates")
-    private let subtitleLabel = NSTextField(labelWithString: "Browse and select a template for your CV")
+    private let subtitleLabel = NSTextField(labelWithString: "Polished layouts with live previews — pick a style that fits your story.")
     private let groupTabsRow = NSStackView()
     private let familyChipsRow = NSStackView()
     private let scrollView = NSScrollView()
@@ -425,6 +532,8 @@ final class CVMakerPageView: NSView {
     private var familyChipButtons: [CVDesignFamily?: CVChipButton] = [:]
     private var templateCardsByID: [String: CVTemplateCard] = [:]
 
+    private var appliedGridColumnCount: Int = 0
+
     override init(frame frameRect: NSRect) {
         super.init(frame: frameRect)
         configureLayout()
@@ -441,7 +550,7 @@ final class CVMakerPageView: NSView {
 
     override func layout() {
         super.layout()
-        // Re-measure the grid in case the container width changed (window resize).
+        pageGradientLayer.frame = bounds
         layoutGridCardsIfNeeded()
     }
 
@@ -449,7 +558,36 @@ final class CVMakerPageView: NSView {
 
     private func configureLayout() {
         wantsLayer = true
-        layer?.backgroundColor = Palette.pageBackground.cgColor
+        layer?.backgroundColor = NSColor.clear.cgColor
+
+        pageGradientLayer.colors = [Palette.gradientBottom.cgColor, Palette.gradientTop.cgColor]
+        pageGradientLayer.locations = [0, 1] as [NSNumber]
+        pageGradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
+        pageGradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
+        layer?.insertSublayer(pageGradientLayer, at: 0)
+
+        filterChrome.translatesAutoresizingMaskIntoConstraints = false
+        filterChrome.material = .sidebar
+        filterChrome.blendingMode = .withinWindow
+        filterChrome.state = .active
+        filterChrome.wantsLayer = true
+        filterChrome.layer?.cornerRadius = 18
+        filterChrome.layer?.borderWidth = 1
+        filterChrome.layer?.borderColor = NSColor.white.withAlphaComponent(0.65).cgColor
+
+        filterStack.orientation = .vertical
+        filterStack.spacing = 12
+        filterStack.alignment = .leading
+        filterStack.translatesAutoresizingMaskIntoConstraints = false
+        filterStack.addArrangedSubview(groupTabsRow)
+        filterStack.addArrangedSubview(familyChipsRow)
+        filterChrome.addSubview(filterStack)
+        NSLayoutConstraint.activate([
+            filterStack.leadingAnchor.constraint(equalTo: filterChrome.leadingAnchor, constant: 14),
+            filterStack.trailingAnchor.constraint(equalTo: filterChrome.trailingAnchor, constant: -14),
+            filterStack.topAnchor.constraint(equalTo: filterChrome.topAnchor, constant: 12),
+            filterStack.bottomAnchor.constraint(equalTo: filterChrome.bottomAnchor, constant: -12)
+        ])
 
         titleLabel.font = .systemFont(ofSize: 22, weight: .bold)
         titleLabel.textColor = Palette.primaryText
@@ -478,7 +616,7 @@ final class CVMakerPageView: NSView {
         familyChipsRow.translatesAutoresizingMaskIntoConstraints = false
 
         gridStack.orientation = .vertical
-        gridStack.spacing = 16
+        gridStack.spacing = 26
         gridStack.alignment = .leading
         gridStack.distribution = .fill
         gridStack.translatesAutoresizingMaskIntoConstraints = false
@@ -512,8 +650,7 @@ final class CVMakerPageView: NSView {
         ctaButton.action = #selector(didTapUseTemplate)
 
         addSubview(headerStack)
-        addSubview(groupTabsRow)
-        addSubview(familyChipsRow)
+        addSubview(filterChrome)
         addSubview(scrollView)
         addSubview(ctaButton)
 
@@ -524,23 +661,19 @@ final class CVMakerPageView: NSView {
             headerStack.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -horizontalInset),
             headerStack.topAnchor.constraint(equalTo: topAnchor, constant: 8),
 
-            groupTabsRow.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
-            groupTabsRow.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -horizontalInset),
-            groupTabsRow.topAnchor.constraint(equalTo: headerStack.bottomAnchor, constant: 18),
-
-            familyChipsRow.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
-            familyChipsRow.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -horizontalInset),
-            familyChipsRow.topAnchor.constraint(equalTo: groupTabsRow.bottomAnchor, constant: 14),
+            filterChrome.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
+            filterChrome.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -horizontalInset),
+            filterChrome.topAnchor.constraint(equalTo: headerStack.bottomAnchor, constant: 16),
 
             scrollView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
             scrollView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset),
-            scrollView.topAnchor.constraint(equalTo: familyChipsRow.bottomAnchor, constant: 16),
-            scrollView.bottomAnchor.constraint(equalTo: ctaButton.topAnchor, constant: -16),
+            scrollView.topAnchor.constraint(equalTo: filterChrome.bottomAnchor, constant: 18),
+            scrollView.bottomAnchor.constraint(equalTo: ctaButton.topAnchor, constant: -18),
 
             ctaButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
             ctaButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset),
             ctaButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -24),
-            ctaButton.heightAnchor.constraint(equalToConstant: 50)
+            ctaButton.heightAnchor.constraint(equalToConstant: 52)
         ])
     }
 
@@ -624,12 +757,12 @@ final class CVMakerPageView: NSView {
             return
         }
 
-        let columns = Self.columns
+        let columns = resolvedGridColumnCount()
         var index = 0
         while index < templates.count {
             let row = NSStackView()
             row.orientation = .horizontal
-            row.spacing = 16
+            row.spacing = 22
             row.distribution = .fillEqually
             row.alignment = .top
             row.translatesAutoresizingMaskIntoConstraints = false
@@ -662,8 +795,29 @@ final class CVMakerPageView: NSView {
         applySelectionToCards()
     }
 
+    private func galleryLayoutWidth() -> CGFloat {
+        if bounds.width > 8 { return bounds.width }
+        if let s = superview, s.bounds.width > 8 { return max(s.bounds.width - 64, 400) }
+        return 900
+    }
+
     private func layoutGridCardsIfNeeded() {
-        // Stack will resize the rows; nothing else to do — kept as a hook for future enhancements.
+        let w = max(galleryLayoutWidth(), 400)
+        let cols: Int
+        if w < 780 { cols = 2 }
+        else if w < 1080 { cols = 3 }
+        else { cols = 4 }
+        guard cols != appliedGridColumnCount else { return }
+        appliedGridColumnCount = cols
+        reloadTemplateGrid()
+        updateSelectedChipStates()
+    }
+
+    private func resolvedGridColumnCount() -> Int {
+        let w = max(galleryLayoutWidth(), 400)
+        if w < 780 { return 2 }
+        if w < 1080 { return 3 }
+        return 4
     }
 
     private func applySelectionToCards() {
@@ -672,11 +826,12 @@ final class CVMakerPageView: NSView {
         }
     }
 
-    private func palette() -> CVTemplateCard.Palette {
-        CVTemplateCard.Palette(
+    private func palette() -> CVTemplateCardPalette {
+        CVTemplateCardPalette(
             border: Palette.cardBorder,
             borderHover: Palette.cardBorderHover,
             borderSelected: Palette.cardBorderSelected,
+            selectionGlow: Palette.selectionGlow,
             footerBackground: Palette.cardFooter,
             previewSurface: Palette.previewSurface,
             previewPaper: Palette.previewPaper,
@@ -753,7 +908,7 @@ final class CVMakerPageView: NSView {
         button.focusRingType = .none
         button.contentTintColor = Palette.ctaText
         button.wantsLayer = true
-        button.layer?.cornerRadius = 12
+        button.layer?.cornerRadius = 14
         button.layer?.backgroundColor = Palette.ctaBackground.cgColor
         button.pointerCursor = true
         let attrs: [NSAttributedString.Key: Any] = [
@@ -992,32 +1147,14 @@ private final class CVChipButton: NSView {
 
 // MARK: - Template card
 
-/// Bordered card holding the mini CV preview, a hover "Preview" overlay, and a
-/// footer with the template name + family. Maintains its own hover/selected
-/// states so the parent can stay declarative.
+/// Premium gallery card: live résumé thumbnail, glass-style preview overlay, soft
+/// shadow, and an animated brand border when selected.
 private final class CVTemplateCard: NSView {
-    struct Palette {
-        let border: NSColor
-        let borderHover: NSColor
-        let borderSelected: NSColor
-        let footerBackground: NSColor
-        let previewSurface: NSColor
-        let previewPaper: NSColor
-        let previewSidebarTint: NSColor
-        let previewInk: NSColor
-        let previewMuted: NSColor
-        let previewAccentRed: NSColor
-        let previewAccentBlue: NSColor
-        let primaryText: NSColor
-        let secondaryText: NSColor
-        let overlayTint: NSColor
-    }
-
     var onSelect: (() -> Void)?
     var onPreview: (() -> Void)?
-    var isSelected: Bool = false { didSet { applyBorder() } }
+    var isSelected: Bool = false { didSet { applyChrome() } }
     private let template: CVTemplate
-    private let palette: Palette
+    private let palette: CVTemplateCardPalette
     private let previewSurface = NSView()
     private let preview: CVTemplatePreviewView
     private let nameLabel = NSTextField(labelWithString: "")
@@ -1030,17 +1167,17 @@ private final class CVTemplateCard: NSView {
     private var isHovering: Bool = false
     private var didPushCursor: Bool = false
 
-    init(template: CVTemplate, palette: Palette) {
+    init(template: CVTemplate, palette: CVTemplateCardPalette) {
         self.template = template
         self.palette = palette
         self.preview = CVTemplatePreviewView(template: template, palette: palette)
         super.init(frame: .zero)
         wantsLayer = true
-        layer?.cornerRadius = 14
-        layer?.borderWidth = 1
-        layer?.masksToBounds = true
+        layer?.masksToBounds = false
+        layer?.cornerRadius = 24
+        layer?.backgroundColor = NSColor.white.cgColor
         translatesAutoresizingMaskIntoConstraints = false
-        heightAnchor.constraint(greaterThanOrEqualToConstant: 280).isActive = true
+        heightAnchor.constraint(greaterThanOrEqualToConstant: 292).isActive = true
 
         previewSurface.translatesAutoresizingMaskIntoConstraints = false
         previewSurface.wantsLayer = true
@@ -1050,14 +1187,14 @@ private final class CVTemplateCard: NSView {
         previewSurface.addSubview(preview)
 
         nameLabel.stringValue = template.name
-        nameLabel.font = .systemFont(ofSize: 13, weight: .semibold)
+        nameLabel.font = .systemFont(ofSize: 14, weight: .semibold)
         nameLabel.textColor = palette.primaryText
         nameLabel.isBordered = false
         nameLabel.drawsBackground = false
         nameLabel.isEditable = false
         nameLabel.isSelectable = false
 
-        categoryLabel.stringValue = template.family.title
+        categoryLabel.stringValue = "\(template.category) · \(template.layoutType.gallerySubtitle)"
         categoryLabel.font = .systemFont(ofSize: 11.5, weight: .regular)
         categoryLabel.textColor = palette.secondaryText
         categoryLabel.isBordered = false
@@ -1067,7 +1204,7 @@ private final class CVTemplateCard: NSView {
 
         let footerStack = NSStackView(views: [nameLabel, categoryLabel])
         footerStack.orientation = .vertical
-        footerStack.spacing = 2
+        footerStack.spacing = 3
         footerStack.alignment = .leading
         footerStack.translatesAutoresizingMaskIntoConstraints = false
 
@@ -1077,10 +1214,10 @@ private final class CVTemplateCard: NSView {
         footer.layer?.backgroundColor = palette.footerBackground.cgColor
         footer.addSubview(footerStack)
         NSLayoutConstraint.activate([
-            footerStack.leadingAnchor.constraint(equalTo: footer.leadingAnchor, constant: 14),
-            footerStack.trailingAnchor.constraint(lessThanOrEqualTo: footer.trailingAnchor, constant: -14),
-            footerStack.topAnchor.constraint(equalTo: footer.topAnchor, constant: 12),
-            footerStack.bottomAnchor.constraint(equalTo: footer.bottomAnchor, constant: -12)
+            footerStack.leadingAnchor.constraint(equalTo: footer.leadingAnchor, constant: 16),
+            footerStack.trailingAnchor.constraint(lessThanOrEqualTo: footer.trailingAnchor, constant: -16),
+            footerStack.topAnchor.constraint(equalTo: footer.topAnchor, constant: 13),
+            footerStack.bottomAnchor.constraint(equalTo: footer.bottomAnchor, constant: -13)
         ])
 
         addSubview(previewSurface)
@@ -1092,12 +1229,12 @@ private final class CVTemplateCard: NSView {
             previewSurface.topAnchor.constraint(equalTo: topAnchor),
             previewSurface.leadingAnchor.constraint(equalTo: leadingAnchor),
             previewSurface.trailingAnchor.constraint(equalTo: trailingAnchor),
-            previewSurface.heightAnchor.constraint(greaterThanOrEqualToConstant: 230),
+            previewSurface.heightAnchor.constraint(greaterThanOrEqualToConstant: 236),
 
-            preview.topAnchor.constraint(equalTo: previewSurface.topAnchor, constant: 16),
-            preview.leadingAnchor.constraint(equalTo: previewSurface.leadingAnchor, constant: 18),
-            preview.trailingAnchor.constraint(equalTo: previewSurface.trailingAnchor, constant: -18),
-            preview.bottomAnchor.constraint(equalTo: previewSurface.bottomAnchor, constant: -16),
+            preview.topAnchor.constraint(equalTo: previewSurface.topAnchor, constant: 14),
+            preview.leadingAnchor.constraint(equalTo: previewSurface.leadingAnchor, constant: 16),
+            preview.trailingAnchor.constraint(equalTo: previewSurface.trailingAnchor, constant: -16),
+            preview.bottomAnchor.constraint(equalTo: previewSurface.bottomAnchor, constant: -14),
 
             footer.topAnchor.constraint(equalTo: previewSurface.bottomAnchor),
             footer.leadingAnchor.constraint(equalTo: leadingAnchor),
@@ -1109,7 +1246,7 @@ private final class CVTemplateCard: NSView {
             overlay.trailingAnchor.constraint(equalTo: previewSurface.trailingAnchor),
             overlay.bottomAnchor.constraint(equalTo: previewSurface.bottomAnchor)
         ])
-        applyBorder()
+        applyChrome()
     }
 
     @available(*, unavailable)
@@ -1117,6 +1254,11 @@ private final class CVTemplateCard: NSView {
         fatalError("init(coder:) has not been implemented")
     }
 
+    override func layout() {
+        super.layout()
+        layer?.shadowPath = CGPath(roundedRect: bounds, cornerWidth: 24, cornerHeight: 24, transform: nil)
+    }
+
     private func configureOverlay() {
         overlay.translatesAutoresizingMaskIntoConstraints = false
         overlay.wantsLayer = true
@@ -1126,15 +1268,17 @@ private final class CVTemplateCard: NSView {
 
         overlayBadge.translatesAutoresizingMaskIntoConstraints = false
         overlayBadge.wantsLayer = true
-        overlayBadge.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.78).cgColor
-        overlayBadge.layer?.cornerRadius = 16
+        overlayBadge.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.22).cgColor
+        overlayBadge.layer?.cornerRadius = 20
+        overlayBadge.layer?.borderWidth = 1
+        overlayBadge.layer?.borderColor = NSColor.white.withAlphaComponent(0.45).cgColor
 
         overlayBadgeIcon.translatesAutoresizingMaskIntoConstraints = false
         overlayBadgeIcon.image = NSImage(systemSymbolName: "eye", accessibilityDescription: nil)
-        overlayBadgeIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 11, weight: .semibold)
+        overlayBadgeIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 12, weight: .semibold)
         overlayBadgeIcon.contentTintColor = .white
 
-        overlayBadgeLabel.font = .systemFont(ofSize: 12, weight: .semibold)
+        overlayBadgeLabel.font = .systemFont(ofSize: 12.5, weight: .semibold)
         overlayBadgeLabel.textColor = .white
         overlayBadgeLabel.isBordered = false
         overlayBadgeLabel.drawsBackground = false
@@ -1143,17 +1287,17 @@ private final class CVTemplateCard: NSView {
 
         let badgeStack = NSStackView(views: [overlayBadgeIcon, overlayBadgeLabel])
         badgeStack.orientation = .horizontal
-        badgeStack.spacing = 6
+        badgeStack.spacing = 7
         badgeStack.alignment = .centerY
         badgeStack.translatesAutoresizingMaskIntoConstraints = false
 
         overlayBadge.addSubview(badgeStack)
         overlay.addSubview(overlayBadge)
         NSLayoutConstraint.activate([
-            badgeStack.leadingAnchor.constraint(equalTo: overlayBadge.leadingAnchor, constant: 14),
-            badgeStack.trailingAnchor.constraint(equalTo: overlayBadge.trailingAnchor, constant: -14),
-            badgeStack.topAnchor.constraint(equalTo: overlayBadge.topAnchor, constant: 8),
-            badgeStack.bottomAnchor.constraint(equalTo: overlayBadge.bottomAnchor, constant: -8),
+            badgeStack.leadingAnchor.constraint(equalTo: overlayBadge.leadingAnchor, constant: 16),
+            badgeStack.trailingAnchor.constraint(equalTo: overlayBadge.trailingAnchor, constant: -16),
+            badgeStack.topAnchor.constraint(equalTo: overlayBadge.topAnchor, constant: 9),
+            badgeStack.bottomAnchor.constraint(equalTo: overlayBadge.bottomAnchor, constant: -9),
             overlayBadge.centerXAnchor.constraint(equalTo: overlay.centerXAnchor),
             overlayBadge.centerYAnchor.constraint(equalTo: overlay.centerYAnchor)
         ])
@@ -1170,10 +1314,22 @@ private final class CVTemplateCard: NSView {
         if overlay.frame.contains(local) {
             onPreview?()
         } else {
+            playTapPulse()
             onSelect?()
         }
     }
 
+    private func playTapPulse() {
+        guard let l = layer else { return }
+        let a = CABasicAnimation(keyPath: "transform")
+        a.fromValue = NSValue(caTransform3D: CATransform3DIdentity)
+        a.toValue = NSValue(caTransform3D: CATransform3DMakeScale(0.985, 0.985, 1))
+        a.duration = 0.1
+        a.autoreverses = true
+        a.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
+        l.add(a, forKey: "tapPulse")
+    }
+
     override func updateTrackingAreas() {
         super.updateTrackingAreas()
         if let area = trackingArea { removeTrackingArea(area) }
@@ -1191,7 +1347,7 @@ private final class CVTemplateCard: NSView {
         super.mouseEntered(with: event)
         isHovering = true
         animateOverlay(visible: true)
-        applyBorder()
+        applyChrome()
         if !didPushCursor {
             NSCursor.pointingHand.push()
             didPushCursor = true
@@ -1202,7 +1358,7 @@ private final class CVTemplateCard: NSView {
         super.mouseExited(with: event)
         isHovering = false
         animateOverlay(visible: false)
-        applyBorder()
+        applyChrome()
         if didPushCursor {
             NSCursor.pop()
             didPushCursor = false
@@ -1221,346 +1377,39 @@ private final class CVTemplateCard: NSView {
     private func animateOverlay(visible: Bool) {
         let target: CGFloat = visible ? 1 : 0
         NSAnimationContext.runAnimationGroup { context in
-            context.duration = 0.12
+            context.duration = 0.18
+            context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
             context.allowsImplicitAnimation = true
             overlay.animator().alphaValue = target
         }
     }
 
-    private func applyBorder() {
+    private func applyChrome() {
         if isSelected {
             layer?.borderColor = palette.borderSelected.cgColor
-            layer?.borderWidth = 2
+            layer?.borderWidth = 2.5
+            layer?.shadowColor = palette.borderSelected.cgColor
+            layer?.shadowOpacity = 0.42
+            layer?.shadowRadius = 22
+            layer?.shadowOffset = CGSize(width: 0, height: 10)
         } else if isHovering {
             layer?.borderColor = palette.borderHover.cgColor
-            layer?.borderWidth = 1
+            layer?.borderWidth = 1.5
+            layer?.shadowColor = NSColor.black.cgColor
+            layer?.shadowOpacity = 0.14
+            layer?.shadowRadius = 18
+            layer?.shadowOffset = CGSize(width: 0, height: 10)
         } else {
             layer?.borderColor = palette.border.cgColor
             layer?.borderWidth = 1
+            layer?.shadowColor = NSColor.black.cgColor
+            layer?.shadowOpacity = 0.08
+            layer?.shadowRadius = 12
+            layer?.shadowOffset = CGSize(width: 0, height: 6)
         }
     }
 }
 
-// MARK: - Mini preview renderer
-
-/// Tiny stylized representation of a finished CV — accent strip, headline area,
-/// faux paragraph + section lines. Adapts to the template's headline, accent,
-/// sidebar, and section-label style so the grid feels varied at a glance.
-private final class CVTemplatePreviewView: NSView {
-    private let template: CVTemplate
-    private let palette: CVTemplateCard.Palette
-    private let paper = NSView()
-
-    init(template: CVTemplate, palette: CVTemplateCard.Palette) {
-        self.template = template
-        self.palette = palette
-        super.init(frame: .zero)
-        wantsLayer = true
-        translatesAutoresizingMaskIntoConstraints = false
-        configurePaper()
-    }
-
-    @available(*, unavailable)
-    required init?(coder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    private func configurePaper() {
-        paper.translatesAutoresizingMaskIntoConstraints = false
-        paper.wantsLayer = true
-        paper.layer?.backgroundColor = palette.previewPaper.cgColor
-        paper.layer?.cornerRadius = 4
-        paper.layer?.borderColor = NSColor(srgbRed: 232 / 255, green: 235 / 255, blue: 241 / 255, alpha: 1).cgColor
-        paper.layer?.borderWidth = 1
-        paper.layer?.masksToBounds = true
-        addSubview(paper)
-
-        NSLayoutConstraint.activate([
-            paper.topAnchor.constraint(equalTo: topAnchor),
-            paper.bottomAnchor.constraint(equalTo: bottomAnchor),
-            paper.centerXAnchor.constraint(equalTo: centerXAnchor),
-            paper.widthAnchor.constraint(equalTo: heightAnchor, multiplier: 0.78),
-            paper.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor),
-            paper.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor)
-        ])
-
-        let header = makeHeader()
-        let body = makeBody()
-        let stack = NSStackView(views: [header, body])
-        stack.orientation = .vertical
-        stack.alignment = .leading
-        stack.spacing = 6
-        stack.distribution = .fill
-        stack.translatesAutoresizingMaskIntoConstraints = false
-        paper.addSubview(stack)
-        NSLayoutConstraint.activate([
-            stack.leadingAnchor.constraint(equalTo: paper.leadingAnchor, constant: 8),
-            stack.trailingAnchor.constraint(equalTo: paper.trailingAnchor, constant: -8),
-            stack.topAnchor.constraint(equalTo: paper.topAnchor, constant: 8),
-            stack.bottomAnchor.constraint(lessThanOrEqualTo: paper.bottomAnchor, constant: -8),
-            header.widthAnchor.constraint(equalTo: stack.widthAnchor),
-            body.widthAnchor.constraint(equalTo: stack.widthAnchor)
-        ])
-    }
-
-    // MARK: Header
-
-    private func makeHeader() -> NSView {
-        let container = NSView()
-        container.translatesAutoresizingMaskIntoConstraints = false
-
-        let jitterA = templateVisualJitter(template.id)
-        let jitterB = templateVisualJitter(template.id + "-b")
-        let nameInk = palette.previewInk.blended(withFraction: CGFloat(jitterA * 0.14), of: palette.previewAccentBlue) ?? palette.previewInk
-        let nameStrip = makeLine(color: nameInk, height: 5.2 + CGFloat(jitterB * 0.9), widthFraction: 0.52 + CGFloat(jitterA * 0.14))
-        let roleStrip = makeLine(color: palette.previewMuted, height: 3, widthFraction: 0.36 + CGFloat(jitterB * 0.12))
-        let contactStrip = makeLine(color: palette.previewMuted.withAlphaComponent(0.7), height: 2, widthFraction: 0.48 + CGFloat(jitterA * 0.1))
-
-        let textStack = NSStackView(views: [nameStrip, roleStrip, contactStrip])
-        textStack.orientation = .vertical
-        textStack.spacing = 4
-        textStack.translatesAutoresizingMaskIntoConstraints = false
-
-        switch template.headline {
-        case .centered:
-            textStack.alignment = .centerX
-        case .leftAligned, .leftWithInitials:
-            textStack.alignment = .leading
-        case .avatarStacked:
-            textStack.alignment = .leading
-        }
-
-        container.addSubview(textStack)
-
-        switch template.headline {
-        case .leftWithInitials:
-            let avatar = makeAvatar()
-            container.addSubview(avatar)
-            NSLayoutConstraint.activate([
-                textStack.leadingAnchor.constraint(equalTo: container.leadingAnchor),
-                textStack.trailingAnchor.constraint(lessThanOrEqualTo: avatar.leadingAnchor, constant: -6),
-                textStack.topAnchor.constraint(equalTo: container.topAnchor),
-                textStack.bottomAnchor.constraint(equalTo: container.bottomAnchor),
-                avatar.trailingAnchor.constraint(equalTo: container.trailingAnchor),
-                avatar.centerYAnchor.constraint(equalTo: container.centerYAnchor),
-                avatar.widthAnchor.constraint(equalToConstant: 20),
-                avatar.heightAnchor.constraint(equalToConstant: 20)
-            ])
-        case .avatarStacked:
-            let avatar = makeAvatar()
-            container.addSubview(avatar)
-            NSLayoutConstraint.activate([
-                avatar.topAnchor.constraint(equalTo: container.topAnchor),
-                avatar.leadingAnchor.constraint(equalTo: container.leadingAnchor),
-                avatar.widthAnchor.constraint(equalToConstant: 22),
-                avatar.heightAnchor.constraint(equalToConstant: 22),
-                textStack.topAnchor.constraint(equalTo: avatar.bottomAnchor, constant: 4),
-                textStack.leadingAnchor.constraint(equalTo: container.leadingAnchor),
-                textStack.trailingAnchor.constraint(equalTo: container.trailingAnchor),
-                textStack.bottomAnchor.constraint(equalTo: container.bottomAnchor)
-            ])
-        case .centered:
-            NSLayoutConstraint.activate([
-                textStack.topAnchor.constraint(equalTo: container.topAnchor),
-                textStack.bottomAnchor.constraint(equalTo: container.bottomAnchor),
-                textStack.centerXAnchor.constraint(equalTo: container.centerXAnchor),
-                textStack.leadingAnchor.constraint(greaterThanOrEqualTo: container.leadingAnchor),
-                textStack.trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor)
-            ])
-        case .leftAligned:
-            NSLayoutConstraint.activate([
-                textStack.leadingAnchor.constraint(equalTo: container.leadingAnchor),
-                textStack.trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor),
-                textStack.topAnchor.constraint(equalTo: container.topAnchor),
-                textStack.bottomAnchor.constraint(equalTo: container.bottomAnchor)
-            ])
-        }
-
-        // Accent decorations
-        switch template.accent {
-        case .none:
-            break
-        case .redUnderline, .redBar:
-            let accent = NSView()
-            accent.translatesAutoresizingMaskIntoConstraints = false
-            accent.wantsLayer = true
-            accent.layer?.backgroundColor = palette.previewAccentRed.cgColor
-            container.addSubview(accent)
-            NSLayoutConstraint.activate([
-                accent.heightAnchor.constraint(equalToConstant: template.accent == .redBar ? 2.5 : 1.5),
-                accent.topAnchor.constraint(equalTo: textStack.bottomAnchor, constant: 5),
-                accent.leadingAnchor.constraint(equalTo: container.leadingAnchor),
-                accent.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: template.accent == .redBar ? 0.32 : 0.9),
-                accent.bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor)
-            ])
-        case .blueBar:
-            let accent = NSView()
-            accent.translatesAutoresizingMaskIntoConstraints = false
-            accent.wantsLayer = true
-            accent.layer?.backgroundColor = palette.previewAccentBlue.cgColor
-            container.addSubview(accent)
-            NSLayoutConstraint.activate([
-                accent.heightAnchor.constraint(equalToConstant: 2.5),
-                accent.topAnchor.constraint(equalTo: textStack.bottomAnchor, constant: 5),
-                accent.leadingAnchor.constraint(equalTo: container.leadingAnchor),
-                accent.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.32),
-                accent.bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor)
-            ])
-        }
-
-        return container
-    }
-
-    /// Stable 0…1 value per template so thumbnails are not visually identical when metadata is similar.
-    private func templateVisualJitter(_ salt: String) -> Double {
-        var hash: UInt64 = 1469598103934665603
-        for b in salt.utf8 {
-            hash ^= UInt64(b)
-            hash &*= 1_099_511_628_211
-        }
-        return Double(hash % 10_007) / 10_006.0
-    }
-
-    private func makeAvatar() -> NSView {
-        let avatar = NSView()
-        avatar.translatesAutoresizingMaskIntoConstraints = false
-        avatar.wantsLayer = true
-        avatar.layer?.backgroundColor = palette.previewSidebarTint.cgColor
-        avatar.layer?.borderColor = palette.previewMuted.withAlphaComponent(0.4).cgColor
-        avatar.layer?.borderWidth = 1
-        avatar.layer?.cornerRadius = 11
-        let initials = NSTextField(labelWithString: "SJ")
-        initials.font = .systemFont(ofSize: 7, weight: .bold)
-        initials.textColor = palette.previewInk
-        initials.alignment = .center
-        initials.translatesAutoresizingMaskIntoConstraints = false
-        avatar.addSubview(initials)
-        NSLayoutConstraint.activate([
-            initials.centerXAnchor.constraint(equalTo: avatar.centerXAnchor),
-            initials.centerYAnchor.constraint(equalTo: avatar.centerYAnchor)
-        ])
-        return avatar
-    }
-
-    // MARK: Body
-
-    private func makeBody() -> NSView {
-        switch template.layout {
-        case .singleColumn:
-            return makeColumn(width: nil, isSidebar: false)
-        case .twoColumn(let side, let tinted):
-            return makeTwoColumnLayout(sidebarSide: side, tinted: tinted)
-        }
-    }
-
-    private func makeTwoColumnLayout(sidebarSide: CVTemplate.SidebarSide, tinted: Bool) -> NSView {
-        let container = NSView()
-        container.translatesAutoresizingMaskIntoConstraints = false
-
-        let sidebar = makeColumn(width: nil, isSidebar: true)
-        if tinted {
-            sidebar.wantsLayer = true
-            sidebar.layer?.backgroundColor = palette.previewSidebarTint.cgColor
-            sidebar.layer?.cornerRadius = 3
-        }
-        let main = makeColumn(width: nil, isSidebar: false)
-
-        container.addSubview(sidebar)
-        container.addSubview(main)
-
-        let leadingItem: NSView = (sidebarSide == .leading) ? sidebar : main
-        let trailingItem: NSView = (sidebarSide == .leading) ? main : sidebar
-
-        NSLayoutConstraint.activate([
-            leadingItem.leadingAnchor.constraint(equalTo: container.leadingAnchor),
-            leadingItem.topAnchor.constraint(equalTo: container.topAnchor, constant: 2),
-            leadingItem.bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor),
-            trailingItem.trailingAnchor.constraint(equalTo: container.trailingAnchor),
-            trailingItem.topAnchor.constraint(equalTo: container.topAnchor, constant: 2),
-            trailingItem.bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor),
-            trailingItem.leadingAnchor.constraint(equalTo: leadingItem.trailingAnchor, constant: 8),
-            sidebar.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.32),
-            main.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.6)
-        ])
-        return container
-    }
-
-    private func makeColumn(width: CGFloat?, isSidebar: Bool) -> NSView {
-        let stack = NSStackView()
-        stack.orientation = .vertical
-        stack.alignment = .leading
-        stack.spacing = 6
-        stack.translatesAutoresizingMaskIntoConstraints = false
-
-        let sectionNames: [String] = isSidebar
-            ? ["CONTACT", "SKILLS", "LANGUAGES", "INTERESTS"]
-            : ["PROFILE", "EXPERIENCE", "EDUCATION", "SKILLS"]
-
-        for (i, section) in sectionNames.enumerated() {
-            let block = makeSectionBlock(title: section, lineCount: isSidebar ? 3 : (i == 1 ? 4 : 2), narrow: isSidebar)
-            stack.addArrangedSubview(block)
-            block.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
-        }
-        return stack
-    }
-
-    private func makeSectionBlock(title: String, lineCount: Int, narrow: Bool) -> NSView {
-        let label = NSTextField(labelWithString: formattedSectionLabel(title))
-        label.font = .systemFont(ofSize: narrow ? 5 : 5.5, weight: .bold)
-        label.textColor = palette.previewAccentBlue.blended(withFraction: 0.4, of: palette.previewInk) ?? palette.previewInk
-        label.maximumNumberOfLines = 1
-        label.isBordered = false
-        label.drawsBackground = false
-        label.isEditable = false
-        label.isSelectable = false
-        label.translatesAutoresizingMaskIntoConstraints = false
-
-        let lines = NSStackView()
-        lines.orientation = .vertical
-        lines.alignment = .leading
-        lines.spacing = 2
-        lines.translatesAutoresizingMaskIntoConstraints = false
-        for index in 0..<lineCount {
-            let widthFraction: CGFloat = max(0.4, 0.95 - CGFloat(index) * 0.13)
-            let line = makeLine(color: palette.previewMuted.withAlphaComponent(0.65), height: 1.6, widthFraction: widthFraction)
-            lines.addArrangedSubview(line)
-            line.widthAnchor.constraint(equalTo: lines.widthAnchor, multiplier: widthFraction).isActive = true
-        }
-
-        let block = NSStackView(views: [label, lines])
-        block.orientation = .vertical
-        block.alignment = .leading
-        block.spacing = 2
-        block.translatesAutoresizingMaskIntoConstraints = false
-        let blockWidth = block.widthAnchor
-        lines.widthAnchor.constraint(equalTo: blockWidth).isActive = true
-        return block
-    }
-
-    private func formattedSectionLabel(_ raw: String) -> String {
-        switch template.sectionLabelStyle {
-        case .uppercase: return raw
-        case .slashed: return "// \(raw.capitalized)"
-        case .bracketed: return "[ \(raw) ]"
-        }
-    }
-
-    private func makeLine(color: NSColor, height: CGFloat, widthFraction: CGFloat) -> NSView {
-        let line = LineView()
-        line.translatesAutoresizingMaskIntoConstraints = false
-        line.wantsLayer = true
-        line.layer?.backgroundColor = color.cgColor
-        line.layer?.cornerRadius = max(height / 2, 1)
-        line.heightAnchor.constraint(equalToConstant: height).isActive = true
-        line.widthFraction = widthFraction
-        return line
-    }
-}
-
-private final class LineView: NSView {
-    var widthFraction: CGFloat = 1
-}
-
 // MARK: - Helpers
 
 /// Flipped origin so the grid stacks fill from the top.

+ 1041 - 0
App for Indeed/Views/CVTemplateMiniPreview.swift

@@ -0,0 +1,1041 @@
+//
+//  CVTemplateMiniPreview.swift
+//  App for Indeed
+//
+//  Realistic mini résumé thumbnails for the template gallery — typography,
+//  spacing, and layout vary by design family so cards read as finished previews.
+//
+
+import Cocoa
+
+// MARK: - Palette (shared with gallery card)
+
+struct CVTemplateCardPalette {
+    let border: NSColor
+    let borderHover: NSColor
+    let borderSelected: NSColor
+    let selectionGlow: NSColor
+    let footerBackground: NSColor
+    let previewSurface: NSColor
+    let previewPaper: NSColor
+    let previewSidebarTint: NSColor
+    let previewInk: NSColor
+    let previewMuted: NSColor
+    let previewAccentRed: NSColor
+    let previewAccentBlue: NSColor
+    let primaryText: NSColor
+    let secondaryText: NSColor
+    let overlayTint: NSColor
+}
+
+// MARK: - Demo résumé content
+
+fileprivate enum CVPreviewDemoContent {
+    static let fullName = "Sarah Johnson"
+    static let title = "Senior Product Manager"
+    static let company = "Google"
+    static let companyLine = "Google · Mountain View, CA · 2019 – Present"
+    static let university = "Stanford University"
+    static let degree = "M.S. Management Science & Engineering"
+    static let email = "sarah.johnson@email.com"
+    static let phone = "(415) 555-0198"
+    static let location = "Mountain View, CA"
+    static let summary = "Product leader shipping roadmap, discovery, and analytics for high-scale consumer experiences."
+    static let bullet1 = "Defined multi-year platform strategy with exec stakeholders and quarterly OKRs."
+    static let bullet2 = "Partnered with engineering and design to launch experiments improving activation by 12%."
+    static let skillsList = ["Product Strategy", "SQL", "Figma", "A/B Testing", "Roadmapping"]
+}
+
+// MARK: - Mini preview
+
+final class CVTemplatePreviewView: NSView {
+    private let template: CVTemplate
+    private let palette: CVTemplateCardPalette
+    private let paper = NSView()
+
+    /// Stable 0…11 from template identity so each row looks different (not just by `family`).
+    private var idVariant: Int {
+        var h: UInt64 = 1469598103934665603
+        let layoutDesc: String
+        switch template.layout {
+        case .singleColumn: layoutDesc = "1col"
+        case .twoColumn(let s, let t): layoutDesc = "2col_\(s)_\(t)"
+        }
+        let blob = "\(template.id)|\(template.family.rawValue)|\(template.headline)|\(template.accent)|\(layoutDesc)|\(template.sectionLabelStyle)"
+        for b in blob.utf8 {
+            h ^= UInt64(b)
+            h &*= 1_099_511_628_211
+        }
+        return Int(h % 12)
+    }
+
+    init(template: CVTemplate, palette: CVTemplateCardPalette) {
+        self.template = template
+        self.palette = palette
+        super.init(frame: .zero)
+        wantsLayer = true
+        translatesAutoresizingMaskIntoConstraints = false
+        configurePaper()
+    }
+
+    @available(*, unavailable)
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    private func configurePaper() {
+        paper.translatesAutoresizingMaskIntoConstraints = false
+        paper.wantsLayer = true
+        paper.layer?.backgroundColor = paperBackgroundColor().cgColor
+        paper.layer?.cornerRadius = 6
+        paper.layer?.borderColor = NSColor(srgbRed: 228 / 255, green: 232 / 255, blue: 240 / 255, alpha: 1).cgColor
+        paper.layer?.borderWidth = 1
+        paper.layer?.masksToBounds = true
+        addSubview(paper)
+
+        NSLayoutConstraint.activate([
+            paper.topAnchor.constraint(equalTo: topAnchor),
+            paper.bottomAnchor.constraint(equalTo: bottomAnchor),
+            paper.centerXAnchor.constraint(equalTo: centerXAnchor),
+            paper.widthAnchor.constraint(equalTo: heightAnchor, multiplier: 0.82),
+            paper.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor),
+            paper.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor)
+        ])
+
+        let root = buildResumeRoot()
+        root.translatesAutoresizingMaskIntoConstraints = false
+        paper.addSubview(root)
+        NSLayoutConstraint.activate([
+            root.leadingAnchor.constraint(equalTo: paper.leadingAnchor, constant: 7),
+            root.trailingAnchor.constraint(equalTo: paper.trailingAnchor, constant: -7),
+            root.topAnchor.constraint(equalTo: paper.topAnchor, constant: 7),
+            root.bottomAnchor.constraint(lessThanOrEqualTo: paper.bottomAnchor, constant: -7)
+        ])
+    }
+
+    private func buildResumeRoot() -> NSView {
+        switch template.family {
+        case .professional: return buildProfessionalResume()
+        case .modern: return buildModernResume()
+        case .minimal: return buildMinimalResume()
+        case .executive: return buildExecutiveResume()
+        case .creative: return buildCreativeResume()
+        }
+    }
+
+    private func paperBackgroundColor() -> NSColor {
+        switch idVariant % 5 {
+        case 0: return palette.previewPaper
+        case 1: return NSColor(srgbRed: 0.995, green: 0.992, blue: 0.985, alpha: 1)
+        case 2: return NSColor(srgbRed: 0.96, green: 0.99, blue: 1, alpha: 1)
+        case 3: return NSColor(srgbRed: 0.99, green: 0.99, blue: 0.99, alpha: 1)
+        default: return NSColor(srgbRed: 0.99, green: 0.98, blue: 0.995, alpha: 1)
+        }
+    }
+
+    // MARK: - Family: Professional (ATS-friendly)
+
+    private func buildProfessionalResume() -> NSView {
+        let swapExpFirst = (idVariant % 3) == 1
+        let showRail = (idVariant % 4) == 0
+        let sidebarMult: CGFloat = (idVariant % 5 == 2) ? 0.38 : 0.34
+
+        switch template.layout {
+        case .singleColumn:
+            let v = NSStackView()
+            v.orientation = .vertical
+            v.spacing = 4 + CGFloat(idVariant % 3)
+            v.alignment = .leading
+            v.addArrangedSubview(proHeaderBlock())
+            if (idVariant % 6) == 4 {
+                v.addArrangedSubview(proInlineSkillsRow())
+            }
+            v.addArrangedSubview(hairline())
+            let main = proMainColumn(compact: false, experienceFirst: swapExpFirst)
+            if showRail {
+                v.addArrangedSubview(horizontalWithLeadingRail(theme: template.themeColor, content: main))
+            } else {
+                v.addArrangedSubview(main)
+            }
+            return v
+        case .twoColumn(let side, let tinted):
+            let bar = proHeaderBlock()
+            let rule = hairline()
+            let row = NSStackView()
+            row.orientation = .horizontal
+            row.spacing = 5 + CGFloat(idVariant % 3)
+            row.alignment = .top
+            let sidebar = proSidebarColumn(tinted: tinted, variant: idVariant)
+            let main = proMainColumn(compact: true, experienceFirst: swapExpFirst)
+            if side == .leading {
+                row.addArrangedSubview(sidebar)
+                row.addArrangedSubview(main)
+            } else {
+                row.addArrangedSubview(main)
+                row.addArrangedSubview(sidebar)
+            }
+            sidebar.widthAnchor.constraint(equalTo: row.widthAnchor, multiplier: sidebarMult).isActive = true
+
+            let wrap = NSStackView(views: [bar, rule, row])
+            wrap.orientation = .vertical
+            wrap.spacing = 5
+            wrap.alignment = .leading
+            return wrap
+        }
+    }
+
+    private func horizontalWithLeadingRail(theme: NSColor, content: NSView) -> NSView {
+        let rail = NSView()
+        rail.translatesAutoresizingMaskIntoConstraints = false
+        rail.wantsLayer = true
+        rail.layer?.backgroundColor = theme.cgColor
+        rail.layer?.cornerRadius = 1
+        rail.widthAnchor.constraint(equalToConstant: 3).isActive = true
+        let row = NSStackView(views: [rail, content])
+        row.orientation = .horizontal
+        row.spacing = 7
+        row.alignment = .top
+        return row
+    }
+
+    private func proInlineSkillsRow() -> NSView {
+        let joined = CVPreviewDemoContent.skillsList.prefix(4).joined(separator: "  ·  ")
+        return makeLabel(joined, font: .systemFont(ofSize: 5.8, weight: .medium), color: template.themeColor, alignment: .left, maxLines: 2)
+    }
+
+    private func proHeaderBlock() -> NSView {
+        let theme = template.themeColor
+        let ink = palette.previewInk
+        let muted = palette.previewMuted
+        let name = makeLabel(CVPreviewDemoContent.fullName, font: .systemFont(ofSize: 9.5, weight: .semibold), color: ink, alignment: .left, maxLines: 1)
+        let role = makeLabel(CVPreviewDemoContent.title, font: .systemFont(ofSize: 7.4, weight: .regular), color: muted, alignment: .left, maxLines: 1)
+        let contact = makeLabel("\(CVPreviewDemoContent.email) · \(CVPreviewDemoContent.phone)", font: .systemFont(ofSize: 6.2, weight: .regular), color: muted.withAlphaComponent(0.88), alignment: .left, maxLines: 1)
+
+        let textCol = NSStackView(views: [name, role, contact])
+        textCol.orientation = .vertical
+        textCol.spacing = 2
+        textCol.alignment = .leading
+
+        let row = NSStackView()
+        row.orientation = .horizontal
+        row.spacing = 6
+        row.alignment = .centerY
+
+        switch template.headline {
+        case .centered:
+            textCol.alignment = .centerX
+            name.alignment = .center
+            role.alignment = .center
+            contact.alignment = .center
+            let accent = headlineAccent(theme: theme, width: 0.42)
+            let stack = NSStackView(views: [textCol, accent])
+            stack.orientation = .vertical
+            stack.spacing = 4
+            stack.alignment = .centerX
+            return stack
+        case .leftAligned:
+            row.addArrangedSubview(textCol)
+            let col = NSStackView(views: [row, headlineAccent(theme: theme, width: 0.38)])
+            col.orientation = .vertical
+            col.spacing = 4
+            col.alignment = .leading
+            return col
+        case .leftWithInitials:
+            let avatar = initialsAvatar(diameter: 22, ink: ink)
+            row.addArrangedSubview(textCol)
+            row.addArrangedSubview(NSView()) // spacer
+            row.addArrangedSubview(avatar)
+            let col = NSStackView(views: [row, headlineAccent(theme: theme, width: 0.36)])
+            col.orientation = .vertical
+            col.spacing = 4
+            col.alignment = .leading
+            return col
+        case .avatarStacked:
+            let avatar = initialsAvatar(diameter: 24, ink: ink)
+            let stack = NSStackView()
+            stack.orientation = .vertical
+            stack.spacing = 3
+            stack.alignment = .centerX
+            stack.addArrangedSubview(avatar)
+            textCol.alignment = .centerX
+            name.alignment = .center
+            role.alignment = .center
+            contact.alignment = .center
+            stack.addArrangedSubview(textCol)
+            stack.addArrangedSubview(headlineAccent(theme: theme, width: 0.4))
+            return stack
+        }
+    }
+
+    private func proSidebarColumn(tinted: Bool, variant: Int) -> NSView {
+        let box = NSStackView()
+        box.orientation = .vertical
+        box.spacing = 4 + CGFloat(variant % 3)
+        box.alignment = .leading
+        if tinted {
+            box.wantsLayer = true
+            let tint = (variant % 4 == 1)
+                ? template.themeColor.withAlphaComponent(0.08)
+                : palette.previewSidebarTint
+            box.layer?.backgroundColor = tint.cgColor
+            box.layer?.cornerRadius = variant % 3 == 0 ? 5 : 4
+        }
+        let pad: CGFloat = tinted ? 5 : 0
+        let inner = NSStackView()
+        inner.edgeInsets = NSEdgeInsets(top: pad, left: pad, bottom: pad, right: pad)
+        inner.orientation = .vertical
+        inner.spacing = 5
+        inner.alignment = .leading
+        inner.addArrangedSubview(sectionHeading("CONTACT"))
+        inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.email, font: .systemFont(ofSize: 6.2), color: palette.previewInk, alignment: .left, maxLines: 2))
+        inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.phone, font: .systemFont(ofSize: 6.2), color: palette.previewMuted, alignment: .left, maxLines: 1))
+        inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.location, font: .systemFont(ofSize: 6.2), color: palette.previewMuted, alignment: .left, maxLines: 1))
+        inner.addArrangedSubview(sectionHeading("SKILLS"))
+        if variant % 5 == 2 {
+            inner.addArrangedSubview(tagRow(theme: template.themeColor))
+        } else {
+            for s in CVPreviewDemoContent.skillsList.prefix(4) {
+                inner.addArrangedSubview(skillLineBullet(s))
+            }
+        }
+        if variant % 7 == 3 {
+            inner.addArrangedSubview(sectionHeading("TOOLS"))
+            inner.addArrangedSubview(makeLabel("Amplitude · Jira · BigQuery", font: .systemFont(ofSize: 5.9), color: palette.previewMuted, alignment: .left, maxLines: 1))
+        }
+        box.addArrangedSubview(inner)
+        return box
+    }
+
+    private func proMainColumn(compact: Bool, experienceFirst: Bool) -> NSView {
+        let stack = NSStackView()
+        stack.orientation = .vertical
+        stack.spacing = compact ? 4 : 5 + CGFloat(idVariant % 2)
+        stack.alignment = .leading
+        let sp: CGFloat = compact ? 6.2 : 6.5
+
+        let profileBlock: () -> Void = {
+            stack.addArrangedSubview(self.sectionHeading("PROFILE"))
+            stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.summary, font: .systemFont(ofSize: sp), color: self.palette.previewInk, alignment: .left, maxLines: 3))
+        }
+        let experienceBlock: () -> Void = {
+            stack.addArrangedSubview(self.sectionHeading("EXPERIENCE"))
+            stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.title, font: .systemFont(ofSize: 6.8, weight: .semibold), color: self.palette.previewInk, alignment: .left, maxLines: 1))
+            stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.companyLine, font: .systemFont(ofSize: 6.2, weight: .medium), color: self.template.themeColor, alignment: .left, maxLines: 1))
+            stack.addArrangedSubview(self.bulletRow(CVPreviewDemoContent.bullet1, size: sp))
+            stack.addArrangedSubview(self.bulletRow(CVPreviewDemoContent.bullet2, size: sp))
+        }
+
+        if experienceFirst {
+            experienceBlock()
+            profileBlock()
+        } else {
+            profileBlock()
+            experienceBlock()
+        }
+        stack.addArrangedSubview(sectionHeading("EDUCATION"))
+        stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.university, font: .systemFont(ofSize: 6.6, weight: .semibold), color: palette.previewInk, alignment: .left, maxLines: 1))
+        stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.degree, font: .systemFont(ofSize: 6.2), color: palette.previewMuted, alignment: .left, maxLines: 2))
+        return stack
+    }
+
+    // MARK: - Family: Modern (three distinct silhouettes per id)
+
+    private func buildModernResume() -> NSView {
+        switch idVariant % 3 {
+        case 0: return buildModernClassicBandLayout()
+        case 1: return buildModernRailDocLayout()
+        default: return buildModernSplitHeaderLayout()
+        }
+    }
+
+    private func modernPrimaryBody(theme: NSColor) -> NSView {
+        switch template.layout {
+        case .singleColumn:
+            return modernBodySingleColumn(theme: theme)
+        case .twoColumn(let side, let tinted):
+            let main = modernBodySingleColumn(theme: theme)
+            let sideCol = modernSidebar(theme: theme, tinted: tinted)
+            let row = NSStackView()
+            row.orientation = .horizontal
+            row.spacing = 5 + CGFloat(idVariant % 3)
+            row.alignment = .top
+            if side == .leading {
+                row.addArrangedSubview(sideCol)
+                row.addArrangedSubview(main)
+            } else {
+                row.addArrangedSubview(main)
+                row.addArrangedSubview(sideCol)
+            }
+            let mult: CGFloat = (idVariant % 4 == 2) ? 0.36 : 0.32
+            sideCol.widthAnchor.constraint(equalTo: row.widthAnchor, multiplier: mult).isActive = true
+            return row
+        }
+    }
+
+    private func buildModernClassicBandLayout() -> NSView {
+        let theme = template.themeColor
+        let header = NSView()
+        header.translatesAutoresizingMaskIntoConstraints = false
+        header.wantsLayer = true
+        header.layer?.backgroundColor = theme.cgColor
+        header.layer?.cornerRadius = idVariant % 2 == 0 ? 5 : 3
+
+        let white = NSColor.white
+        let name = makeLabel(CVPreviewDemoContent.fullName, font: .systemFont(ofSize: 9, weight: .bold), color: white, alignment: .left, maxLines: 1)
+        let role = makeLabel(CVPreviewDemoContent.title, font: .systemFont(ofSize: 7.2, weight: .medium), color: white.withAlphaComponent(0.92), alignment: .left, maxLines: 1)
+        let hstack = NSStackView(views: [name, role])
+        hstack.orientation = .vertical
+        hstack.spacing = 2
+        hstack.alignment = .leading
+        hstack.translatesAutoresizingMaskIntoConstraints = false
+
+        let iconRow = NSStackView()
+        iconRow.orientation = .horizontal
+        iconRow.spacing = 5
+        iconRow.translatesAutoresizingMaskIntoConstraints = false
+        for sym in ["mappin.and.ellipse", "phone.fill", "envelope.fill"] {
+            guard let img = NSImage(systemSymbolName: sym, accessibilityDescription: nil) else { continue }
+            let iv = NSImageView(image: img)
+            iv.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 6, weight: .semibold)
+            iv.contentTintColor = white.withAlphaComponent(0.85)
+            iconRow.addArrangedSubview(iv)
+        }
+
+        let topRow = NSStackView()
+        topRow.orientation = .horizontal
+        topRow.spacing = 8
+        topRow.alignment = .centerY
+        topRow.translatesAutoresizingMaskIntoConstraints = false
+        topRow.addArrangedSubview(hstack)
+        topRow.addArrangedSubview(NSView())
+        topRow.addArrangedSubview(iconRow)
+
+        header.addSubview(topRow)
+        NSLayoutConstraint.activate([
+            topRow.leadingAnchor.constraint(equalTo: header.leadingAnchor, constant: 7),
+            topRow.trailingAnchor.constraint(equalTo: header.trailingAnchor, constant: -7),
+            topRow.topAnchor.constraint(equalTo: header.topAnchor, constant: 6),
+            topRow.bottomAnchor.constraint(equalTo: header.bottomAnchor, constant: -6),
+            header.heightAnchor.constraint(greaterThanOrEqualToConstant: 32 + CGFloat(idVariant % 3) * 2)
+        ])
+
+        let body = modernPrimaryBody(theme: theme)
+        let wrap = NSStackView(views: [header, body])
+        wrap.orientation = .vertical
+        wrap.spacing = 6 + CGFloat(idVariant % 2)
+        wrap.alignment = .leading
+        return wrap
+    }
+
+    private func buildModernRailDocLayout() -> NSView {
+        let theme = template.themeColor
+        let ink = palette.previewInk
+        let muted = palette.previewMuted
+        let rail = NSView()
+        rail.translatesAutoresizingMaskIntoConstraints = false
+        rail.wantsLayer = true
+        rail.layer?.backgroundColor = theme.cgColor
+        rail.layer?.cornerRadius = 2
+        rail.widthAnchor.constraint(equalToConstant: 3 + CGFloat(idVariant % 2)).isActive = true
+
+        let inner = NSStackView()
+        inner.orientation = .vertical
+        inner.spacing = 5
+        inner.alignment = .leading
+        inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.fullName, font: .systemFont(ofSize: 9.5, weight: .bold), color: ink, alignment: .left, maxLines: 1))
+        inner.addArrangedSubview(makeLabel(CVPreviewDemoContent.title, font: .systemFont(ofSize: 7.2, weight: .semibold), color: theme, alignment: .left, maxLines: 1))
+        inner.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.email) · \(CVPreviewDemoContent.phone)", font: .systemFont(ofSize: 6), color: muted, alignment: .left, maxLines: 1))
+        inner.addArrangedSubview(hairline())
+        inner.addArrangedSubview(tagRow(theme: theme))
+        inner.addArrangedSubview(modernPrimaryBody(theme: theme))
+
+        let row = NSStackView(views: [rail, inner])
+        row.orientation = .horizontal
+        row.spacing = 7
+        row.alignment = .top
+        return row
+    }
+
+    private func buildModernSplitHeaderLayout() -> NSView {
+        let theme = template.themeColor
+        let ink = palette.previewInk
+        let muted = palette.previewMuted
+        let left = NSStackView()
+        left.orientation = .vertical
+        left.spacing = 3
+        left.alignment = .leading
+        left.addArrangedSubview(makeLabel(CVPreviewDemoContent.fullName, font: .systemFont(ofSize: 9.2, weight: .bold), color: ink, alignment: .left, maxLines: 2))
+        left.addArrangedSubview(makeLabel(CVPreviewDemoContent.title, font: .systemFont(ofSize: 7, weight: .medium), color: muted, alignment: .left, maxLines: 2))
+        left.addArrangedSubview(makeLabel(CVPreviewDemoContent.location, font: .systemFont(ofSize: 6), color: muted.withAlphaComponent(0.85), alignment: .left, maxLines: 1))
+
+        let right = NSStackView()
+        right.orientation = .vertical
+        right.spacing = 4
+        right.alignment = .leading
+        right.wantsLayer = true
+        right.layer?.backgroundColor = theme.cgColor
+        right.layer?.cornerRadius = 5
+        right.edgeInsets = NSEdgeInsets(top: 6, left: 7, bottom: 6, right: 7)
+        let onW = NSColor.white
+        right.addArrangedSubview(makeLabel(CVPreviewDemoContent.email, font: .systemFont(ofSize: 5.9, weight: .medium), color: onW.withAlphaComponent(0.95), alignment: .left, maxLines: 2))
+        right.addArrangedSubview(makeLabel(CVPreviewDemoContent.phone, font: .systemFont(ofSize: 5.9, weight: .medium), color: onW.withAlphaComponent(0.9), alignment: .left, maxLines: 1))
+        right.addArrangedSubview(makeLabel("Open to relocation", font: .systemFont(ofSize: 5.6, weight: .regular), color: onW.withAlphaComponent(0.75), alignment: .left, maxLines: 1))
+
+        let top = NSStackView(views: [left, right])
+        top.orientation = .horizontal
+        top.spacing = 6
+        top.alignment = .top
+        left.widthAnchor.constraint(equalTo: top.widthAnchor, multiplier: 0.54).isActive = true
+
+        let col = NSStackView(views: [top, hairline(), modernPrimaryBody(theme: theme)])
+        col.orientation = .vertical
+        col.spacing = 7
+        col.alignment = .leading
+        return col
+    }
+
+    private func modernSidebar(theme: NSColor, tinted: Bool) -> NSView {
+        let box = NSStackView()
+        box.orientation = .vertical
+        box.spacing = 5
+        box.alignment = .leading
+        if tinted {
+            box.wantsLayer = true
+            box.layer?.backgroundColor = theme.withAlphaComponent(0.1).cgColor
+            box.layer?.cornerRadius = 4
+        }
+        box.edgeInsets = NSEdgeInsets(top: 4, left: 4, bottom: 4, right: 4)
+        box.addArrangedSubview(modernSectionRow(symbol: "person.crop.circle", title: "About", theme: theme))
+        box.addArrangedSubview(makeLabel(CVPreviewDemoContent.summary, font: .systemFont(ofSize: 6), color: palette.previewInk, alignment: .left, maxLines: 4))
+        box.addArrangedSubview(modernSectionRow(symbol: "star.fill", title: "Highlights", theme: theme))
+        box.addArrangedSubview(makeLabel("Launched growth experiments, SQL deep dives, design QA.", font: .systemFont(ofSize: 6), color: palette.previewMuted, alignment: .left, maxLines: 3))
+        return box
+    }
+
+    private func modernSectionRow(symbol: String, title: String, theme: NSColor) -> NSView {
+        guard let img = NSImage(systemSymbolName: symbol, accessibilityDescription: nil) else {
+            return makeLabel(title, font: .systemFont(ofSize: 6.5, weight: .bold), color: palette.previewInk, alignment: .left, maxLines: 1)
+        }
+        let iv = NSImageView(image: img)
+        iv.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 6, weight: .semibold)
+        iv.contentTintColor = theme
+        let t = makeLabel(title, font: .systemFont(ofSize: 6.5, weight: .bold), color: palette.previewInk, alignment: .left, maxLines: 1)
+        let r = NSStackView(views: [iv, t])
+        r.orientation = .horizontal
+        r.spacing = 4
+        r.alignment = .centerY
+        return r
+    }
+
+    private func modernBodySingleColumn(theme: NSColor) -> NSView {
+        let stack = NSStackView()
+        stack.orientation = .vertical
+        stack.spacing = 5
+        stack.alignment = .leading
+        stack.addArrangedSubview(modernSectionRow(symbol: "briefcase.fill", title: "Experience", theme: theme))
+        stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.title) — \(CVPreviewDemoContent.company)", font: .systemFont(ofSize: 6.6, weight: .semibold), color: palette.previewInk, alignment: .left, maxLines: 2))
+        stack.addArrangedSubview(makeLabel("2019 – Present · Led cross-functional pods from discovery to launch.", font: .systemFont(ofSize: 6.1), color: palette.previewMuted, alignment: .left, maxLines: 2))
+        stack.addArrangedSubview(tagRow(theme: theme))
+        stack.addArrangedSubview(modernSectionRow(symbol: "graduationcap.fill", title: "Education", theme: theme))
+        stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.university), \(CVPreviewDemoContent.degree)", font: .systemFont(ofSize: 6.2), color: palette.previewInk, alignment: .left, maxLines: 2))
+        return stack
+    }
+
+    private func tagRow(theme: NSColor) -> NSView {
+        let row = NSStackView()
+        row.orientation = .horizontal
+        row.spacing = 3
+        row.alignment = .centerY
+        for s in CVPreviewDemoContent.skillsList.prefix(4) {
+            let tag = NSView()
+            tag.wantsLayer = true
+            tag.layer?.backgroundColor = theme.withAlphaComponent(0.14).cgColor
+            tag.layer?.cornerRadius = 3
+            tag.translatesAutoresizingMaskIntoConstraints = false
+            let lab = makeLabel(s, font: .systemFont(ofSize: 5.5, weight: .semibold), color: theme.blended(withFraction: 0.35, of: palette.previewInk) ?? palette.previewInk, alignment: .center, maxLines: 1)
+            lab.translatesAutoresizingMaskIntoConstraints = false
+            tag.addSubview(lab)
+            NSLayoutConstraint.activate([
+                lab.leadingAnchor.constraint(equalTo: tag.leadingAnchor, constant: 4),
+                lab.trailingAnchor.constraint(equalTo: tag.trailingAnchor, constant: -4),
+                lab.topAnchor.constraint(equalTo: tag.topAnchor, constant: 2),
+                lab.bottomAnchor.constraint(equalTo: tag.bottomAnchor, constant: -2)
+            ])
+            row.addArrangedSubview(tag)
+        }
+        return row
+    }
+
+    // MARK: - Family: Minimal
+
+    private func buildMinimalResume() -> NSView {
+        let ink = palette.previewInk
+        let muted = palette.previewMuted
+        let nameWeight: NSFont.Weight = (idVariant % 3 == 0) ? .ultraLight : .light
+        let nameSize: CGFloat = template.headline == .centered ? 11 + CGFloat(idVariant % 2) : 9.5 + CGFloat(idVariant % 3)
+        let name = makeLabel(CVPreviewDemoContent.fullName, font: .systemFont(ofSize: nameSize, weight: nameWeight), color: ink, alignment: .left, maxLines: 1)
+        let role = makeLabel(CVPreviewDemoContent.title.uppercased(), font: .systemFont(ofSize: 6.5, weight: .medium), color: muted, alignment: .left, maxLines: 1)
+        let contact = makeLabel("\(CVPreviewDemoContent.email)   \(CVPreviewDemoContent.phone)", font: .systemFont(ofSize: 5.8, weight: .regular), color: muted.withAlphaComponent(0.75), alignment: .left, maxLines: 1)
+
+        let head = NSStackView()
+        head.orientation = .vertical
+        head.spacing = template.headline == .avatarStacked ? 8 : 4 + CGFloat(idVariant % 4)
+        head.alignment = template.headline == .centered ? .centerX : .leading
+        if template.headline == .avatarStacked {
+            head.addArrangedSubview(initialsAvatar(diameter: 24 + CGFloat(idVariant % 2) * 2, ink: ink))
+        }
+        if template.headline == .centered {
+            name.alignment = .center
+            role.alignment = .center
+            contact.alignment = .center
+        }
+        head.addArrangedSubview(name)
+        head.addArrangedSubview(role)
+        head.addArrangedSubview(contact)
+        head.addArrangedSubview(hairlineSoft())
+        if idVariant % 5 == 1 {
+            head.addArrangedSubview(hairlineSoft())
+        }
+
+        let swapEdu = (idVariant % 4) == 2
+        let body: NSView
+        switch template.layout {
+        case .singleColumn:
+            body = minimalBody(spacing: 6 + CGFloat(idVariant % 3), educationBeforeExperience: swapEdu)
+        case .twoColumn(let side, _):
+            let a = minimalBody(spacing: 5, educationBeforeExperience: swapEdu)
+            let b = minimalAside(numbered: idVariant % 3 == 1)
+            let row = NSStackView()
+            row.orientation = .horizontal
+            row.spacing = 8 + CGFloat(idVariant % 3)
+            row.alignment = .top
+            if side == .leading {
+                row.addArrangedSubview(b)
+                row.addArrangedSubview(a)
+            } else {
+                row.addArrangedSubview(a)
+                row.addArrangedSubview(b)
+            }
+            let mult: CGFloat = (idVariant % 5 == 0) ? 0.34 : 0.3
+            b.widthAnchor.constraint(equalTo: row.widthAnchor, multiplier: mult).isActive = true
+            body = row
+        }
+
+        let wrap = NSStackView(views: [head, body])
+        wrap.orientation = .vertical
+        wrap.spacing = 7 + CGFloat(idVariant % 2)
+        wrap.alignment = .leading
+        return wrap
+    }
+
+    private func minimalBody(spacing: CGFloat, educationBeforeExperience: Bool) -> NSView {
+        let stack = NSStackView()
+        stack.orientation = .vertical
+        stack.spacing = spacing
+        stack.alignment = .leading
+
+        let edu: () -> Void = {
+            stack.addArrangedSubview(self.sectionHeading("EDUCATION"))
+            stack.addArrangedSubview(self.makeLabel("\(CVPreviewDemoContent.university) — \(CVPreviewDemoContent.degree)", font: .systemFont(ofSize: 6.1, weight: .light), color: self.palette.previewInk, alignment: .left, maxLines: 2))
+        }
+        let prof: () -> Void = {
+            stack.addArrangedSubview(self.sectionHeading("PROFILE"))
+            stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.summary, font: .systemFont(ofSize: 6.3, weight: .light), color: self.palette.previewInk, alignment: .left, maxLines: 3))
+        }
+        let exp: () -> Void = {
+            stack.addArrangedSubview(self.sectionHeading("EXPERIENCE"))
+            stack.addArrangedSubview(self.makeLabel("\(CVPreviewDemoContent.title), \(CVPreviewDemoContent.company)", font: .systemFont(ofSize: 6.5, weight: .regular), color: self.palette.previewInk, alignment: .left, maxLines: 2))
+            stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.bullet1, font: .systemFont(ofSize: 6, weight: .light), color: self.palette.previewMuted, alignment: .left, maxLines: 2))
+        }
+
+        if educationBeforeExperience {
+            edu()
+            prof()
+            exp()
+        } else {
+            prof()
+            exp()
+            edu()
+        }
+        return stack
+    }
+
+    private func minimalAside(numbered: Bool) -> NSView {
+        let stack = NSStackView()
+        stack.orientation = .vertical
+        stack.spacing = 6
+        stack.alignment = .leading
+        stack.addArrangedSubview(sectionHeading("SKILLS"))
+        for (i, s) in CVPreviewDemoContent.skillsList.enumerated() {
+            let prefix = numbered ? "\(i + 1). " : "·  "
+            stack.addArrangedSubview(makeLabel("\(prefix)\(s)", font: .systemFont(ofSize: 6, weight: .light), color: palette.previewMuted, alignment: .left, maxLines: 1))
+        }
+        return stack
+    }
+
+    // MARK: - Family: Executive (serif)
+
+    private func buildExecutiveResume() -> NSView {
+        let serif = NSFont(name: "Georgia", size: 6.4) ?? .systemFont(ofSize: 6.4)
+        let serifBase = NSFont(name: "Georgia", size: 7.8) ?? .systemFont(ofSize: 7.8)
+        let serifBold = NSFontManager.shared.convert(serifBase, toHaveTrait: .boldFontMask)
+        let ink = palette.previewInk
+        let muted = palette.previewMuted
+        let theme = template.themeColor
+        let centeredHead = (idVariant % 2) == 0
+
+        let name = makeLabel(CVPreviewDemoContent.fullName, font: serifBold, color: ink, alignment: centeredHead ? .center : .left, maxLines: 1)
+        let role = makeLabel(CVPreviewDemoContent.title, font: serif, color: muted, alignment: centeredHead ? .center : .left, maxLines: 1)
+        let contact = makeLabel("\(CVPreviewDemoContent.email) · \(CVPreviewDemoContent.phone) · \(CVPreviewDemoContent.location)", font: NSFont(name: "Georgia", size: 5.8) ?? serif, color: muted.withAlphaComponent(0.9), alignment: centeredHead ? .center : .left, maxLines: 2)
+
+        let rule = executiveRule(theme: theme, wide: idVariant % 3 == 0)
+        let head = NSStackView(views: [name, role, contact, rule])
+        head.orientation = .vertical
+        head.spacing = 4
+        head.alignment = centeredHead ? .centerX : .leading
+
+        let body: NSView
+        switch template.layout {
+        case .singleColumn:
+            body = executiveBody(serif: serif, ink: ink, muted: muted, theme: theme, compact: false, tightLeading: idVariant % 5 == 2)
+        case .twoColumn(let side, let tinted):
+            let main = executiveBody(serif: serif, ink: ink, muted: muted, theme: theme, compact: true, tightLeading: idVariant % 5 == 2)
+            let sideC = executiveSidebar(serif: serif, tinted: tinted, showMetrics: idVariant % 4 == 1)
+            let row = NSStackView()
+            row.orientation = .horizontal
+            row.spacing = 6 + CGFloat(idVariant % 3)
+            row.alignment = .top
+            if side == .leading {
+                row.addArrangedSubview(sideC)
+                row.addArrangedSubview(main)
+            } else {
+                row.addArrangedSubview(main)
+                row.addArrangedSubview(sideC)
+            }
+            let mult: CGFloat = (idVariant % 5 == 3) ? 0.38 : 0.33
+            sideC.widthAnchor.constraint(equalTo: row.widthAnchor, multiplier: mult).isActive = true
+            body = row
+        }
+
+        let wrap = NSStackView(views: [head, body])
+        wrap.orientation = .vertical
+        wrap.spacing = 6 + CGFloat(idVariant % 2)
+        wrap.alignment = .leading
+        return wrap
+    }
+
+    private func executiveRule(theme: NSColor, wide: Bool) -> NSView {
+        let v = NSView()
+        v.translatesAutoresizingMaskIntoConstraints = false
+        v.wantsLayer = true
+        v.layer?.backgroundColor = theme.withAlphaComponent(0.45).cgColor
+        v.heightAnchor.constraint(equalToConstant: wide ? 2 : 1).isActive = true
+        v.widthAnchor.constraint(equalToConstant: wide ? 160 : 110).isActive = true
+        return v
+    }
+
+    private func executiveBody(serif: NSFont, ink: NSColor, muted: NSColor, theme: NSColor, compact: Bool, tightLeading: Bool) -> NSView {
+        let stack = NSStackView()
+        stack.orientation = .vertical
+        stack.spacing = (compact ? 4 : 5) - (tightLeading ? 1 : 0)
+        stack.alignment = .leading
+        let sumTitle = (idVariant % 6 == 3) ? "SUMMARY" : "PROFESSIONAL SUMMARY"
+        stack.addArrangedSubview(sectionHeading(sumTitle))
+        stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.summary, font: serif, color: ink, alignment: .left, maxLines: 3))
+        stack.addArrangedSubview(sectionHeading("SELECTED EXPERIENCE"))
+        let jobFont = NSFontManager.shared.convert(serif, toHaveTrait: .boldFontMask)
+        stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.title), \(CVPreviewDemoContent.company)", font: jobFont, color: ink, alignment: .left, maxLines: 2))
+        stack.addArrangedSubview(makeLabel("2019 – Present", font: serif, color: theme, alignment: .left, maxLines: 1))
+        stack.addArrangedSubview(makeLabel(CVPreviewDemoContent.bullet1, font: serif, color: muted, alignment: .left, maxLines: 2))
+        stack.addArrangedSubview(sectionHeading("EDUCATION"))
+        stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.university), \(CVPreviewDemoContent.degree)", font: serif, color: ink, alignment: .left, maxLines: 2))
+        return stack
+    }
+
+    private func executiveSidebar(serif: NSFont, tinted: Bool, showMetrics: Bool) -> NSView {
+        let stack = NSStackView()
+        stack.orientation = .vertical
+        stack.spacing = 4
+        stack.alignment = .leading
+        if tinted {
+            stack.wantsLayer = true
+            let fill = (idVariant % 3 == 0)
+                ? NSColor(srgbRed: 0.97, green: 0.97, blue: 0.98, alpha: 1)
+                : template.themeColor.withAlphaComponent(0.07)
+            stack.layer?.backgroundColor = fill.cgColor
+            stack.layer?.cornerRadius = idVariant % 4 == 2 ? 5 : 3
+            stack.edgeInsets = NSEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
+        }
+        stack.addArrangedSubview(sectionHeading("CORE COMPETENCIES"))
+        for s in CVPreviewDemoContent.skillsList {
+            stack.addArrangedSubview(makeLabel("· \(s)", font: serif, color: palette.previewInk, alignment: .left, maxLines: 1))
+        }
+        stack.addArrangedSubview(sectionHeading("TOOLS"))
+        stack.addArrangedSubview(makeLabel("Figma, SQL, Amplitude, Jira", font: serif, color: palette.previewMuted, alignment: .left, maxLines: 2))
+        if showMetrics {
+            stack.addArrangedSubview(sectionHeading("IMPACT"))
+            stack.addArrangedSubview(makeLabel("+12% activation · $4.2M ARR influenced", font: serif, color: palette.previewInk, alignment: .left, maxLines: 2))
+        }
+        return stack
+    }
+
+    // MARK: - Family: Creative (sidebar + bold accent)
+
+    private func buildCreativeResume() -> NSView {
+        let theme = template.themeColor
+        let deep = creativeDeepBackground(theme: theme)
+        let onSidebar = NSColor.white.withAlphaComponent(0.95)
+        let skillPrefix = (idVariant % 3 == 0) ? "•  " : "▸  "
+
+        let sidebar = NSStackView()
+        sidebar.orientation = .vertical
+        sidebar.spacing = 4 + CGFloat(idVariant % 3)
+        sidebar.alignment = .leading
+        sidebar.wantsLayer = true
+        sidebar.layer?.backgroundColor = deep.cgColor
+        sidebar.layer?.cornerRadius = idVariant % 2 == 0 ? 6 : 4
+        sidebar.edgeInsets = NSEdgeInsets(top: 6, left: 6, bottom: 6, right: 6)
+
+        let sbTitle = makeLabel(CVPreviewDemoContent.fullName, font: .systemFont(ofSize: 7.5 + CGFloat(idVariant % 2), weight: .bold), color: onSidebar, alignment: .left, maxLines: 2)
+        sidebar.addArrangedSubview(sbTitle)
+        sidebar.addArrangedSubview(makeLabel(CVPreviewDemoContent.title, font: .systemFont(ofSize: 6.5, weight: .medium), color: onSidebar.withAlphaComponent(0.85), alignment: .left, maxLines: 2))
+        sidebar.addArrangedSubview(makeLabel(CVPreviewDemoContent.email, font: .systemFont(ofSize: 5.8), color: onSidebar.withAlphaComponent(0.8), alignment: .left, maxLines: 1))
+        sidebar.addArrangedSubview(makeLabel(CVPreviewDemoContent.phone, font: .systemFont(ofSize: 5.8), color: onSidebar.withAlphaComponent(0.8), alignment: .left, maxLines: 1))
+        sidebar.addArrangedSubview(creativeSidebarHeading("STRENGTHS", onSidebar: onSidebar, accent: theme))
+        for s in CVPreviewDemoContent.skillsList.prefix(5) {
+            sidebar.addArrangedSubview(makeLabel("\(skillPrefix)\(s)", font: .systemFont(ofSize: 6, weight: .semibold), color: onSidebar.withAlphaComponent(0.92), alignment: .left, maxLines: 1))
+        }
+
+        let main = NSStackView()
+        main.orientation = .vertical
+        main.spacing = 4 + CGFloat(idVariant % 3)
+        main.alignment = .leading
+        main.addArrangedSubview(creativeMainHeader(theme: theme))
+        main.addArrangedSubview(sectionHeading("PROFILE"))
+        main.addArrangedSubview(makeLabel(CVPreviewDemoContent.summary, font: .systemFont(ofSize: 6.2), color: palette.previewInk, alignment: .left, maxLines: 3))
+        main.addArrangedSubview(sectionHeading("IMPACT"))
+        main.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.company) — \(CVPreviewDemoContent.title)", font: .systemFont(ofSize: 6.6, weight: .heavy), color: palette.previewInk, alignment: .left, maxLines: 2))
+        let bMark = (idVariant % 2 == 0) ? "—  " : "▸  "
+        main.addArrangedSubview(makeLabel("\(bMark)\(CVPreviewDemoContent.bullet1)", font: .systemFont(ofSize: 6), color: palette.previewMuted, alignment: .left, maxLines: 2))
+        main.addArrangedSubview(makeLabel("\(bMark)\(CVPreviewDemoContent.bullet2)", font: .systemFont(ofSize: 6), color: palette.previewMuted, alignment: .left, maxLines: 2))
+
+        let sidebarMult = 0.32 + CGFloat(idVariant % 3) * 0.02
+
+        switch template.layout {
+        case .singleColumn:
+            let banner = NSView()
+            banner.translatesAutoresizingMaskIntoConstraints = false
+            banner.wantsLayer = true
+            banner.layer?.backgroundColor = theme.cgColor
+            banner.layer?.cornerRadius = idVariant % 4 == 1 ? 6 : 3
+            let inner = makeLabel("  \(CVPreviewDemoContent.fullName)  ·  \(CVPreviewDemoContent.title)", font: .systemFont(ofSize: 6.5, weight: .bold), color: .white, alignment: .left, maxLines: 1)
+            inner.translatesAutoresizingMaskIntoConstraints = false
+            banner.addSubview(inner)
+            NSLayoutConstraint.activate([
+                inner.leadingAnchor.constraint(equalTo: banner.leadingAnchor, constant: 5),
+                inner.trailingAnchor.constraint(lessThanOrEqualTo: banner.trailingAnchor, constant: -5),
+                inner.topAnchor.constraint(equalTo: banner.topAnchor, constant: 4 + CGFloat(idVariant % 2)),
+                inner.bottomAnchor.constraint(equalTo: banner.bottomAnchor, constant: -4)
+            ])
+            let col = NSStackView(views: [banner, main])
+            col.orientation = .vertical
+            col.spacing = 6
+            col.alignment = .leading
+            return col
+        case .twoColumn(let side, _):
+            let row = NSStackView()
+            row.orientation = .horizontal
+            row.spacing = 5 + CGFloat(idVariant % 3)
+            row.alignment = .top
+            if side == .leading {
+                row.addArrangedSubview(sidebar)
+                row.addArrangedSubview(main)
+            } else {
+                row.addArrangedSubview(main)
+                row.addArrangedSubview(sidebar)
+            }
+            sidebar.widthAnchor.constraint(equalTo: row.widthAnchor, multiplier: sidebarMult).isActive = true
+            return row
+        }
+    }
+
+    private func creativeDeepBackground(theme: NSColor) -> NSColor {
+        let navy = NSColor(srgbRed: 0.08, green: 0.1, blue: 0.18, alpha: 1)
+        let plum = NSColor(srgbRed: 0.14, green: 0.07, blue: 0.24, alpha: 1)
+        switch idVariant % 4 {
+        case 0: return theme.blended(withFraction: 0.52, of: navy) ?? theme
+        case 1: return theme.blended(withFraction: 0.7, of: NSColor.black) ?? theme
+        case 2: return palette.previewInk.blended(withFraction: 0.38, of: theme) ?? theme
+        default: return theme.blended(withFraction: 0.4, of: plum) ?? theme
+        }
+    }
+
+    private func creativeSidebarHeading(_ raw: String, onSidebar: NSColor, accent: NSColor) -> NSView {
+        let t = makeLabel(raw, font: .systemFont(ofSize: 5.5, weight: .heavy), color: onSidebar, alignment: .left, maxLines: 1)
+        let bar = NSView()
+        bar.translatesAutoresizingMaskIntoConstraints = false
+        bar.wantsLayer = true
+        bar.layer?.backgroundColor = accent.cgColor
+        bar.heightAnchor.constraint(equalToConstant: 2).isActive = true
+        let c = NSStackView(views: [t, bar])
+        c.orientation = .vertical
+        c.spacing = 2
+        c.alignment = .leading
+        bar.leadingAnchor.constraint(equalTo: t.leadingAnchor).isActive = true
+        bar.trailingAnchor.constraint(lessThanOrEqualTo: c.trailingAnchor).isActive = true
+        bar.widthAnchor.constraint(equalToConstant: 56).isActive = true
+        return c
+    }
+
+    private func creativeMainHeader(theme: NSColor) -> NSView {
+        let v = NSView()
+        v.translatesAutoresizingMaskIntoConstraints = false
+        v.wantsLayer = true
+        v.layer?.borderColor = theme.cgColor
+        v.layer?.borderWidth = 0
+        let stripe = NSView()
+        stripe.translatesAutoresizingMaskIntoConstraints = false
+        stripe.wantsLayer = true
+        stripe.layer?.backgroundColor = theme.cgColor
+        v.addSubview(stripe)
+        let row = NSStackView()
+        row.orientation = .horizontal
+        row.spacing = 5
+        row.translatesAutoresizingMaskIntoConstraints = false
+        let lab = makeLabel("PORTFOLIO SNAPSHOT", font: .systemFont(ofSize: 6.5, weight: .black), color: palette.previewInk, alignment: .left, maxLines: 1)
+        row.addArrangedSubview(stripe)
+        row.addArrangedSubview(lab)
+        v.addSubview(row)
+        NSLayoutConstraint.activate([
+            stripe.widthAnchor.constraint(equalToConstant: 3),
+            stripe.heightAnchor.constraint(equalToConstant: 12),
+            row.leadingAnchor.constraint(equalTo: v.leadingAnchor),
+            row.topAnchor.constraint(equalTo: v.topAnchor),
+            row.bottomAnchor.constraint(equalTo: v.bottomAnchor)
+        ])
+        return v
+    }
+
+    // MARK: - Shared pieces
+
+    private func sectionHeading(_ raw: String) -> NSTextField {
+        let s = formattedSectionLabel(raw)
+        let accent = accentDecorationColor()
+        return makeLabel(s, font: .systemFont(ofSize: 5.8, weight: .bold), color: accent, alignment: .left, maxLines: 1)
+    }
+
+    private func formattedSectionLabel(_ raw: String) -> String {
+        switch template.sectionLabelStyle {
+        case .uppercase: return raw
+        case .slashed: return "// \(raw.capitalized)"
+        case .bracketed: return "[ \(raw) ]"
+        }
+    }
+
+    private func accentDecorationColor() -> NSColor {
+        switch template.accent {
+        case .redUnderline, .redBar: return palette.previewAccentRed
+        case .blueBar: return palette.previewAccentBlue
+        case .none: return template.themeColor.blended(withFraction: 0.5, of: palette.previewInk) ?? palette.previewInk
+        }
+    }
+
+    private func headlineAccent(theme: NSColor, width: CGFloat) -> NSView {
+        let v = NSView()
+        v.translatesAutoresizingMaskIntoConstraints = false
+        v.wantsLayer = true
+        switch template.accent {
+        case .none:
+            v.layer?.backgroundColor = theme.withAlphaComponent(0.35).cgColor
+        case .redUnderline:
+            v.layer?.backgroundColor = palette.previewAccentRed.cgColor
+        case .redBar:
+            v.layer?.backgroundColor = palette.previewAccentRed.cgColor
+        case .blueBar:
+            v.layer?.backgroundColor = palette.previewAccentBlue.cgColor
+        }
+        v.heightAnchor.constraint(equalToConstant: template.accent == .redUnderline ? 1.2 : 2.2).isActive = true
+        v.widthAnchor.constraint(equalToConstant: 72 * width).isActive = true
+        return v
+    }
+
+    private func hairline() -> NSView {
+        let v = NSView()
+        v.translatesAutoresizingMaskIntoConstraints = false
+        v.wantsLayer = true
+        v.layer?.backgroundColor = palette.previewMuted.withAlphaComponent(0.35).cgColor
+        v.heightAnchor.constraint(equalToConstant: 1).isActive = true
+        return v
+    }
+
+    private func hairlineSoft() -> NSView {
+        let v = NSView()
+        v.translatesAutoresizingMaskIntoConstraints = false
+        v.wantsLayer = true
+        v.layer?.backgroundColor = palette.previewMuted.withAlphaComponent(0.22).cgColor
+        v.heightAnchor.constraint(equalToConstant: 1).isActive = true
+        return v
+    }
+
+    private func initialsAvatar(diameter: CGFloat, ink: NSColor) -> NSView {
+        let avatar = NSView()
+        avatar.translatesAutoresizingMaskIntoConstraints = false
+        avatar.wantsLayer = true
+        avatar.layer?.backgroundColor = template.themeColor.withAlphaComponent(0.12).cgColor
+        avatar.layer?.borderColor = template.themeColor.withAlphaComponent(0.35).cgColor
+        avatar.layer?.borderWidth = 1
+        avatar.layer?.cornerRadius = diameter / 2
+        avatar.widthAnchor.constraint(equalToConstant: diameter).isActive = true
+        avatar.heightAnchor.constraint(equalToConstant: diameter).isActive = true
+        let initials = makeLabel("SJ", font: .systemFont(ofSize: diameter * 0.32, weight: .bold), color: ink, alignment: .center, maxLines: 1)
+        initials.translatesAutoresizingMaskIntoConstraints = false
+        avatar.addSubview(initials)
+        NSLayoutConstraint.activate([
+            initials.centerXAnchor.constraint(equalTo: avatar.centerXAnchor),
+            initials.centerYAnchor.constraint(equalTo: avatar.centerYAnchor)
+        ])
+        return avatar
+    }
+
+    private func skillLineBullet(_ text: String) -> NSView {
+        makeLabel("·  \(text)", font: .systemFont(ofSize: 6.1, weight: .regular), color: palette.previewInk, alignment: .left, maxLines: 1)
+    }
+
+    private func bulletRow(_ text: String, size: CGFloat) -> NSView {
+        let row = NSStackView()
+        row.orientation = .horizontal
+        row.spacing = 3
+        row.alignment = .top
+        let mark = makeLabel("•", font: .systemFont(ofSize: size, weight: .bold), color: template.themeColor, alignment: .left, maxLines: 1)
+        mark.setContentHuggingPriority(.required, for: .horizontal)
+        let body = makeLabel(text, font: .systemFont(ofSize: size), color: palette.previewInk, alignment: .left, maxLines: 3)
+        row.addArrangedSubview(mark)
+        row.addArrangedSubview(body)
+        return row
+    }
+
+    private func makeLabel(
+        _ text: String,
+        font: NSFont,
+        color: NSColor,
+        alignment: NSTextAlignment,
+        maxLines: Int
+    ) -> NSTextField {
+        let tf: NSTextField
+        if maxLines == 1 {
+            tf = NSTextField(labelWithString: text)
+        } else {
+            tf = NSTextField(wrappingLabelWithString: text)
+            tf.maximumNumberOfLines = maxLines
+        }
+        tf.font = font
+        tf.textColor = color
+        tf.alignment = alignment
+        tf.isEditable = false
+        tf.isSelectable = false
+        tf.drawsBackground = false
+        tf.isBordered = false
+        tf.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
+        return tf
+    }
+}