| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030 |
- //
- // 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 cardShellBackground: 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
- }
- // MARK: - Demo résumé content
- fileprivate enum CVPreviewDemoContent {
- static let fullName = "Sarah Johnson"
- /// Shown in the header / contact band (broad role).
- static let title = "Senior Product Manager"
- /// Scoped title under Experience so it is not a verbatim repeat of the header line.
- static let experienceRole = "Group PM, Consumer Growth & Activation"
- 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 educationYears = "2014 – 2016"
- 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 bullet3 = "Stood up quarterly business reviews with finance and GTM, aligning spend to north-star metrics."
- /// Sidebar “highlights” blurb kept distinct from the experience bullets.
- static let careerHighlights = "Presented roadmap shifts to the leadership team and translated trade-offs into clear investment asks."
- /// Single tools line reused wherever the résumé lists a stack (avoids scattered near-duplicate strings).
- static let toolsLine = "Figma · SQL · Amplitude · Jira · BigQuery"
- 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()
- /// Same variant index as `CVProfileDocumentView` (shared `CVTemplate.galleryLayoutVariant`).
- private var layoutVariant: Int { template.galleryLayoutVariant }
- 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 {
- CVResumeAppearance.paperBackground(variant: layoutVariant, base: palette.previewPaper)
- }
- // MARK: - Family: Professional (ATS-friendly)
- private func buildProfessionalResume() -> NSView {
- let swapExpFirst = (layoutVariant % 3) == 1
- let sidebarMult: CGFloat = (layoutVariant % 5 == 2) ? 0.38 : 0.34
- switch template.layout {
- case .singleColumn:
- let v = NSStackView()
- v.orientation = .vertical
- v.spacing = 4 + CGFloat(layoutVariant % 3)
- v.alignment = .leading
- v.addArrangedSubview(proHeaderBlock())
- if (layoutVariant % 6) == 4 {
- v.addArrangedSubview(proInlineSkillsRow())
- }
- v.addArrangedSubview(hairline())
- let main = proMainColumn(compact: false, experienceFirst: swapExpFirst)
- // Single-column professional résumés use the same left rail in the gallery
- // and in `CVProfileDocumentView` so the filled CV matches the thumbnail.
- v.addArrangedSubview(horizontalWithLeadingRail(theme: template.themeColor, content: 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(layoutVariant % 3)
- row.alignment = .top
- let sidebar = proSidebarColumn(tinted: tinted, variant: layoutVariant)
- 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(CVPreviewDemoContent.toolsLine, 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(layoutVariant % 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.experienceRole, 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))
- stack.addArrangedSubview(self.bulletRow(CVPreviewDemoContent.bullet3, 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) · \(CVPreviewDemoContent.educationYears)", 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 layoutVariant % 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(layoutVariant % 3)
- row.alignment = .top
- if side == .leading {
- row.addArrangedSubview(sideCol)
- row.addArrangedSubview(main)
- } else {
- row.addArrangedSubview(main)
- row.addArrangedSubview(sideCol)
- }
- let mult: CGFloat = (layoutVariant % 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 = layoutVariant % 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(layoutVariant % 3) * 2)
- ])
- let body = modernPrimaryBody(theme: theme)
- let wrap = NSStackView(views: [header, body])
- wrap.orientation = .vertical
- wrap.spacing = 6 + CGFloat(layoutVariant % 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(layoutVariant % 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(CVPreviewDemoContent.careerHighlights, 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.experienceRole) — \(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 through launch and post-ship learning.", 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) (\(CVPreviewDemoContent.educationYears))", 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 = (layoutVariant % 3 == 0) ? .ultraLight : .light
- let nameSize: CGFloat = template.headline == .centered ? 11 + CGFloat(layoutVariant % 2) : 9.5 + CGFloat(layoutVariant % 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(layoutVariant % 4)
- head.alignment = template.headline == .centered ? .centerX : .leading
- if template.headline == .avatarStacked {
- head.addArrangedSubview(initialsAvatar(diameter: 24 + CGFloat(layoutVariant % 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 layoutVariant % 5 == 1 {
- head.addArrangedSubview(hairlineSoft())
- }
- let swapEdu = (layoutVariant % 4) == 2
- let body: NSView
- switch template.layout {
- case .singleColumn:
- body = minimalBody(spacing: 6 + CGFloat(layoutVariant % 3), educationBeforeExperience: swapEdu)
- case .twoColumn(let side, _):
- let a = minimalBody(spacing: 5, educationBeforeExperience: swapEdu)
- let b = minimalAside(numbered: layoutVariant % 3 == 1)
- let row = NSStackView()
- row.orientation = .horizontal
- row.spacing = 8 + CGFloat(layoutVariant % 3)
- row.alignment = .top
- if side == .leading {
- row.addArrangedSubview(b)
- row.addArrangedSubview(a)
- } else {
- row.addArrangedSubview(a)
- row.addArrangedSubview(b)
- }
- let mult: CGFloat = (layoutVariant % 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(layoutVariant % 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) · \(CVPreviewDemoContent.educationYears)", 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.experienceRole) · \(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))
- stack.addArrangedSubview(self.makeLabel(CVPreviewDemoContent.bullet2, 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 = (layoutVariant % 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: layoutVariant % 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: layoutVariant % 5 == 2)
- case .twoColumn(let side, let tinted):
- let main = executiveBody(serif: serif, ink: ink, muted: muted, theme: theme, compact: true, tightLeading: layoutVariant % 5 == 2)
- let sideC = executiveSidebar(serif: serif, tinted: tinted, showMetrics: layoutVariant % 4 == 1)
- let row = NSStackView()
- row.orientation = .horizontal
- row.spacing = 6 + CGFloat(layoutVariant % 3)
- row.alignment = .top
- if side == .leading {
- row.addArrangedSubview(sideC)
- row.addArrangedSubview(main)
- } else {
- row.addArrangedSubview(main)
- row.addArrangedSubview(sideC)
- }
- let mult: CGFloat = (layoutVariant % 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(layoutVariant % 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 = (layoutVariant % 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.experienceRole), \(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(makeLabel(CVPreviewDemoContent.bullet2, font: serif, color: muted, alignment: .left, maxLines: 2))
- stack.addArrangedSubview(sectionHeading("EDUCATION"))
- stack.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.university), \(CVPreviewDemoContent.degree) · \(CVPreviewDemoContent.educationYears)", 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 = (layoutVariant % 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 = layoutVariant % 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(CVPreviewDemoContent.toolsLine, 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 = (layoutVariant % 3 == 0) ? "• " : "▸ "
- let sidebar = NSStackView()
- sidebar.orientation = .vertical
- sidebar.spacing = 4 + CGFloat(layoutVariant % 3)
- sidebar.alignment = .leading
- sidebar.wantsLayer = true
- sidebar.layer?.backgroundColor = deep.cgColor
- sidebar.layer?.cornerRadius = layoutVariant % 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(layoutVariant % 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(layoutVariant % 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.experienceRole)", font: .systemFont(ofSize: 6.6, weight: .heavy), color: palette.previewInk, alignment: .left, maxLines: 2))
- let bMark = (layoutVariant % 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))
- main.addArrangedSubview(sectionHeading("EDUCATION"))
- main.addArrangedSubview(makeLabel("\(CVPreviewDemoContent.university) · \(CVPreviewDemoContent.degree) · \(CVPreviewDemoContent.educationYears)", font: .systemFont(ofSize: 6.1), color: palette.previewInk, alignment: .left, maxLines: 2))
- let sidebarMult = 0.32 + CGFloat(layoutVariant % 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 = layoutVariant % 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(layoutVariant % 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(layoutVariant % 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 layoutVariant % 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 {
- CVResumeAppearance.accentColor(for: template)
- }
- 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
- }
- }
|