Explorar o código

Refine dashboard home UI: status banner, feature cards, and theme colors

Align welcome hero, search bar, and chat status strip with the reference layout.
Add feature shortcut cards (Role, Company, etc.), status branding row, and
adjust subtitle and border colors for readability.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 hai 3 semanas
pai
achega
8a252e60bf
Modificáronse 1 ficheiros con 464 adicións e 80 borrados
  1. 464 80
      App for Indeed/Views/DashboardView.swift

+ 464 - 80
App for Indeed/Views/DashboardView.swift

@@ -20,8 +20,8 @@ final class DashboardView: NSView, NSTextFieldDelegate {
20
         static let chromeBackground = NSColor(srgbRed: 247 / 255, green: 247 / 255, blue: 247 / 255, alpha: 1)
20
         static let chromeBackground = NSColor(srgbRed: 247 / 255, green: 247 / 255, blue: 247 / 255, alpha: 1)
21
         static let sidebarBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
21
         static let sidebarBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
22
         static let mainHostBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
22
         static let mainHostBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
23
-        /// Subtitle on the welcome hero: readable blue-gray aligned with brand.
24
-        static let welcomeSubtitleText = NSColor(srgbRed: 52 / 255, green: 92 / 255, blue: 142 / 255, alpha: 1)
23
+        /// Subtitle on the welcome hero: dark neutral gray to match the reference layout.
24
+        static let welcomeSubtitleText = NSColor(srgbRed: 64 / 255, green: 64 / 255, blue: 64 / 255, alpha: 1)
25
         static let selectionFill = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.12)
25
         static let selectionFill = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.12)
26
         static let cardBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
26
         static let cardBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
27
         static let toggleBackground = NSColor(srgbRed: 232 / 255, green: 232 / 255, blue: 232 / 255, alpha: 1)
27
         static let toggleBackground = NSColor(srgbRed: 232 / 255, green: 232 / 255, blue: 232 / 255, alpha: 1)
@@ -29,10 +29,13 @@ final class DashboardView: NSView, NSTextFieldDelegate {
29
         static let secondaryText = NSColor(srgbRed: 118 / 255, green: 118 / 255, blue: 118 / 255, alpha: 1)
29
         static let secondaryText = NSColor(srgbRed: 118 / 255, green: 118 / 255, blue: 118 / 255, alpha: 1)
30
         static let tertiaryText = NSColor(srgbRed: 118 / 255, green: 118 / 255, blue: 118 / 255, alpha: 1)
30
         static let tertiaryText = NSColor(srgbRed: 118 / 255, green: 118 / 255, blue: 118 / 255, alpha: 1)
31
         static let border = NSColor(srgbRed: 212 / 255, green: 210 / 255, blue: 208 / 255, alpha: 1)
31
         static let border = NSColor(srgbRed: 212 / 255, green: 210 / 255, blue: 208 / 255, alpha: 1)
32
-        /// Job search bar outer stroke (charcoal).
33
-        static let searchBarBorder = NSColor(srgbRed: 58 / 255, green: 58 / 255, blue: 58 / 255, alpha: 1)
32
+        /// Home status strip and soft accents (light blue panel in the reference UI).
33
+        static let statusBannerBackground = NSColor(srgbRed: 232 / 255, green: 242 / 255, blue: 252 / 255, alpha: 1)
34
+        static let featureIconWell = NSColor(srgbRed: 220 / 255, green: 235 / 255, blue: 252 / 255, alpha: 1)
35
+        /// Job search bar outer stroke (soft blue-gray, pill field in the reference UI).
36
+        static let searchBarBorder = NSColor(srgbRed: 180 / 255, green: 200 / 255, blue: 228 / 255, alpha: 1)
34
         /// Search bar border on hover (brand-tinted, matches focus affordance elsewhere).
37
         /// Search bar border on hover (brand-tinted, matches focus affordance elsewhere).
35
-        static let searchBarBorderHover = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.45)
38
+        static let searchBarBorderHover = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.55)
36
         static let proCardFill = NSColor(srgbRed: 239 / 255, green: 244 / 255, blue: 252 / 255, alpha: 1)
39
         static let proCardFill = NSColor(srgbRed: 239 / 255, green: 244 / 255, blue: 252 / 255, alpha: 1)
37
         static let proCardBorder = NSColor(srgbRed: 212 / 255, green: 210 / 255, blue: 208 / 255, alpha: 1)
40
         static let proCardBorder = NSColor(srgbRed: 212 / 255, green: 210 / 255, blue: 208 / 255, alpha: 1)
38
         static let proAccent = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
41
         static let proAccent = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
@@ -81,11 +84,18 @@ final class DashboardView: NSView, NSTextFieldDelegate {
81
     private let findJobsCTAHost = NSView()
84
     private let findJobsCTAHost = NSView()
82
     private let findJobsCTAChrome = HoverableView()
85
     private let findJobsCTAChrome = HoverableView()
83
     private var findJobsCTAGradientLayer: CAGradientLayer?
86
     private var findJobsCTAGradientLayer: CAGradientLayer?
84
-    private let chatStatusStack = NSStackView()
87
+    private let statusBannerContainer = NSView()
88
+    private let statusBannerInner = NSView()
89
+    private let statusBannerRow = NSStackView()
85
     private let chatStatusSymbolContainer = NSView()
90
     private let chatStatusSymbolContainer = NSView()
86
     private let chatStatusIcon = NSImageView()
91
     private let chatStatusIcon = NSImageView()
87
     private let chatStatusLoadingIndicator = NSProgressIndicator()
92
     private let chatStatusLoadingIndicator = NSProgressIndicator()
88
-    private let chatStatusLabel = NSTextField(labelWithString: "Opening the vault...")
93
+    private let chatStatusLabel = NSTextField(wrappingLabelWithString: "Opening the vault...")
94
+    private let statusBrandTrailing = NSStackView()
95
+    private let statusBrandStarIcon = NSImageView()
96
+    private let statusBrandLabel = NSTextField(labelWithString: "AI Job Finder")
97
+    private let welcomeSparkleIcon = NSImageView()
98
+    private let featureCardsRow = NSStackView()
89
     private let chatScrollView = NSScrollView()
99
     private let chatScrollView = NSScrollView()
90
     private let chatDocumentView = JobListingsDocumentView()
100
     private let chatDocumentView = JobListingsDocumentView()
91
     private let chatStack = NSStackView()
101
     private let chatStack = NSStackView()
@@ -145,6 +155,7 @@ final class DashboardView: NSView, NSTextFieldDelegate {
145
         updateFindJobsCTAShadowPath()
155
         updateFindJobsCTAShadowPath()
146
         updateJobListingDescriptionWidths()
156
         updateJobListingDescriptionWidths()
147
         updateChatBubbleWidths()
157
         updateChatBubbleWidths()
158
+        updateStatusBannerLabelWidth()
148
     }
159
     }
149
 
160
 
150
     func render(_ data: DashboardData) {
161
     func render(_ data: DashboardData) {
@@ -226,14 +237,21 @@ final class DashboardView: NSView, NSTextFieldDelegate {
226
         configureSearchBar()
237
         configureSearchBar()
227
         configureChatViews()
238
         configureChatViews()
228
 
239
 
229
-        let titleBlock = NSStackView(views: [greetingLabel, subtitleLabel])
240
+        welcomeSparkleIcon.translatesAutoresizingMaskIntoConstraints = false
241
+        welcomeSparkleIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 16, weight: .semibold)
242
+        welcomeSparkleIcon.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: nil)
243
+        welcomeSparkleIcon.contentTintColor = Theme.brandBlue
244
+
245
+        let titleBlock = NSStackView(views: [greetingLabel, subtitleLabel, welcomeSparkleIcon])
230
         titleBlock.orientation = .vertical
246
         titleBlock.orientation = .vertical
231
         titleBlock.spacing = 10
247
         titleBlock.spacing = 10
232
         titleBlock.alignment = .centerX
248
         titleBlock.alignment = .centerX
233
 
249
 
250
+        configureFeatureShortcutCards()
251
+
234
         let midSpacer = NSView()
252
         let midSpacer = NSView()
235
         midSpacer.translatesAutoresizingMaskIntoConstraints = false
253
         midSpacer.translatesAutoresizingMaskIntoConstraints = false
236
-        midSpacer.heightAnchor.constraint(equalToConstant: 18).isActive = true
254
+        midSpacer.heightAnchor.constraint(equalToConstant: 12).isActive = true
237
 
255
 
238
         let chatTopSpacer = NSView()
256
         let chatTopSpacer = NSView()
239
         chatTopSpacer.translatesAutoresizingMaskIntoConstraints = false
257
         chatTopSpacer.translatesAutoresizingMaskIntoConstraints = false
@@ -245,8 +263,9 @@ final class DashboardView: NSView, NSTextFieldDelegate {
245
 
263
 
246
         mainOverlay.addArrangedSubview(topInset)
264
         mainOverlay.addArrangedSubview(topInset)
247
         mainOverlay.addArrangedSubview(titleBlock)
265
         mainOverlay.addArrangedSubview(titleBlock)
266
+        mainOverlay.addArrangedSubview(featureCardsRow)
248
         mainOverlay.addArrangedSubview(midSpacer)
267
         mainOverlay.addArrangedSubview(midSpacer)
249
-        mainOverlay.addArrangedSubview(chatStatusStack)
268
+        mainOverlay.addArrangedSubview(statusBannerContainer)
250
         mainOverlay.addArrangedSubview(chatTopSpacer)
269
         mainOverlay.addArrangedSubview(chatTopSpacer)
251
         mainOverlay.addArrangedSubview(chatScrollView)
270
         mainOverlay.addArrangedSubview(chatScrollView)
252
         mainOverlay.addArrangedSubview(chatBottomSpacer)
271
         mainOverlay.addArrangedSubview(chatBottomSpacer)
@@ -280,7 +299,8 @@ final class DashboardView: NSView, NSTextFieldDelegate {
280
             nonHomeHost.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor, constant: -24),
299
             nonHomeHost.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor, constant: -24),
281
 
300
 
282
             searchBarShadowHost.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
301
             searchBarShadowHost.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
283
-            chatStatusStack.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
302
+            statusBannerContainer.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
303
+            featureCardsRow.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
284
             chatScrollView.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
304
             chatScrollView.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
285
 
305
 
286
             greetingLabel.leadingAnchor.constraint(equalTo: mainOverlay.leadingAnchor, constant: 16),
306
             greetingLabel.leadingAnchor.constraint(equalTo: mainOverlay.leadingAnchor, constant: 16),
@@ -290,19 +310,60 @@ final class DashboardView: NSView, NSTextFieldDelegate {
290
         ])
310
         ])
291
     }
311
     }
292
 
312
 
313
+    private func configureFeatureShortcutCards() {
314
+        featureCardsRow.orientation = .horizontal
315
+        featureCardsRow.spacing = 12
316
+        featureCardsRow.distribution = .fillEqually
317
+        featureCardsRow.alignment = .top
318
+        featureCardsRow.translatesAutoresizingMaskIntoConstraints = false
319
+
320
+        let specs: [(symbol: String, title: String, subtitle: String, action: Selector)] = [
321
+            ("briefcase.fill", "Role", "Explore similar or better job roles", #selector(didTapFeatureRole)),
322
+            ("building.2.fill", "Company", "Find opportunities at other companies", #selector(didTapFeatureCompany)),
323
+            ("chevron.left.forwardslash.chevron.right", "Skill", "Match jobs that fit your skills", #selector(didTapFeatureSkill))
324
+        ]
325
+        for spec in specs {
326
+            let card = FeatureShortcutCardView(
327
+                symbolName: spec.symbol,
328
+                title: spec.title,
329
+                subtitle: spec.subtitle,
330
+                target: self,
331
+                action: spec.action
332
+            )
333
+            featureCardsRow.addArrangedSubview(card)
334
+        }
335
+    }
336
+
293
     private func configureChatViews() {
337
     private func configureChatViews() {
294
-        chatStatusStack.orientation = .vertical
295
-        chatStatusStack.spacing = 6
296
-        chatStatusStack.alignment = .centerX
297
-        chatStatusStack.translatesAutoresizingMaskIntoConstraints = false
338
+        statusBannerContainer.translatesAutoresizingMaskIntoConstraints = false
339
+        statusBannerContainer.setContentHuggingPriority(.defaultHigh, for: .vertical)
340
+
341
+        statusBannerInner.translatesAutoresizingMaskIntoConstraints = false
342
+        statusBannerInner.wantsLayer = true
343
+        statusBannerInner.layer?.backgroundColor = Theme.statusBannerBackground.cgColor
344
+        statusBannerInner.layer?.cornerRadius = 14
345
+        if #available(macOS 11.0, *) {
346
+            statusBannerInner.layer?.cornerCurve = .continuous
347
+        }
348
+        statusBannerInner.layer?.masksToBounds = true
349
+
350
+        statusBannerContainer.addSubview(statusBannerInner)
351
+
352
+        statusBannerRow.orientation = .horizontal
353
+        statusBannerRow.spacing = 12
354
+        statusBannerRow.alignment = .top
355
+        statusBannerRow.translatesAutoresizingMaskIntoConstraints = false
298
 
356
 
299
         chatStatusSymbolContainer.translatesAutoresizingMaskIntoConstraints = false
357
         chatStatusSymbolContainer.translatesAutoresizingMaskIntoConstraints = false
358
+        chatStatusSymbolContainer.wantsLayer = true
359
+        chatStatusSymbolContainer.layer?.backgroundColor = Theme.brandBlue.cgColor
360
+        chatStatusSymbolContainer.layer?.cornerRadius = 20
300
 
361
 
301
         chatStatusIcon.translatesAutoresizingMaskIntoConstraints = false
362
         chatStatusIcon.translatesAutoresizingMaskIntoConstraints = false
302
         chatStatusIcon.wantsLayer = true
363
         chatStatusIcon.wantsLayer = true
303
-        chatStatusIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 36, weight: .regular)
364
+        chatStatusIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 15, weight: .semibold)
304
         chatStatusIcon.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: "Assistant status")
365
         chatStatusIcon.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: "Assistant status")
305
-        chatStatusIcon.contentTintColor = Theme.brandBlue
366
+        chatStatusIcon.contentTintColor = .white
306
 
367
 
307
         chatStatusLoadingIndicator.translatesAutoresizingMaskIntoConstraints = false
368
         chatStatusLoadingIndicator.translatesAutoresizingMaskIntoConstraints = false
308
         chatStatusLoadingIndicator.style = .spinning
369
         chatStatusLoadingIndicator.style = .spinning
@@ -323,19 +384,66 @@ final class DashboardView: NSView, NSTextFieldDelegate {
323
             chatStatusIcon.centerYAnchor.constraint(equalTo: chatStatusSymbolContainer.centerYAnchor),
384
             chatStatusIcon.centerYAnchor.constraint(equalTo: chatStatusSymbolContainer.centerYAnchor),
324
             chatStatusLoadingIndicator.centerXAnchor.constraint(equalTo: chatStatusSymbolContainer.centerXAnchor),
385
             chatStatusLoadingIndicator.centerXAnchor.constraint(equalTo: chatStatusSymbolContainer.centerXAnchor),
325
             chatStatusLoadingIndicator.centerYAnchor.constraint(equalTo: chatStatusSymbolContainer.centerYAnchor),
386
             chatStatusLoadingIndicator.centerYAnchor.constraint(equalTo: chatStatusSymbolContainer.centerYAnchor),
326
-            chatStatusLoadingIndicator.widthAnchor.constraint(equalToConstant: 32),
327
-            chatStatusLoadingIndicator.heightAnchor.constraint(equalToConstant: 32)
387
+            chatStatusLoadingIndicator.widthAnchor.constraint(equalToConstant: 26),
388
+            chatStatusLoadingIndicator.heightAnchor.constraint(equalToConstant: 26)
328
         ])
389
         ])
329
 
390
 
330
-        chatStatusLabel.font = .systemFont(ofSize: 20, weight: .semibold)
391
+        chatStatusLabel.font = .systemFont(ofSize: 13, weight: .regular)
331
         chatStatusLabel.textColor = Theme.primaryText
392
         chatStatusLabel.textColor = Theme.primaryText
332
-        chatStatusLabel.alignment = .center
333
-        chatStatusLabel.maximumNumberOfLines = 1
393
+        chatStatusLabel.alignment = .left
394
+        chatStatusLabel.maximumNumberOfLines = 0
395
+        chatStatusLabel.lineBreakMode = .byWordWrapping
396
+        chatStatusLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
397
+        chatStatusLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
398
+        chatStatusLabel.translatesAutoresizingMaskIntoConstraints = false
399
+
400
+        statusBrandStarIcon.translatesAutoresizingMaskIntoConstraints = false
401
+        statusBrandStarIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 12, weight: .semibold)
402
+        statusBrandStarIcon.image = NSImage(systemSymbolName: "sparkle", accessibilityDescription: nil)
403
+        statusBrandStarIcon.contentTintColor = Theme.brandBlue
404
+
405
+        statusBrandLabel.font = .systemFont(ofSize: 12, weight: .semibold)
406
+        statusBrandLabel.textColor = Theme.brandBlue
407
+        statusBrandLabel.alignment = .right
408
+        statusBrandLabel.maximumNumberOfLines = 1
409
+
410
+        statusBrandTrailing.orientation = .horizontal
411
+        statusBrandTrailing.spacing = 5
412
+        statusBrandTrailing.alignment = .centerY
413
+        statusBrandTrailing.translatesAutoresizingMaskIntoConstraints = false
414
+        statusBrandTrailing.addArrangedSubview(statusBrandStarIcon)
415
+        statusBrandTrailing.addArrangedSubview(statusBrandLabel)
416
+        statusBrandTrailing.setContentHuggingPriority(.required, for: .horizontal)
417
+        statusBrandTrailing.setContentCompressionResistancePriority(.required, for: .horizontal)
418
+
419
+        statusBannerRow.addArrangedSubview(chatStatusSymbolContainer)
420
+        statusBannerRow.addArrangedSubview(chatStatusLabel)
421
+
422
+        statusBannerInner.addSubview(statusBannerRow)
423
+        statusBannerContainer.addSubview(statusBannerInner)
424
+        statusBannerContainer.addSubview(statusBrandTrailing)
425
+        statusBannerInner.setContentHuggingPriority(.defaultHigh, for: .horizontal)
426
+        statusBannerInner.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
334
 
427
 
335
-        chatStatusStack.addArrangedSubview(chatStatusSymbolContainer)
336
-        chatStatusStack.addArrangedSubview(chatStatusLabel)
428
+        NSLayoutConstraint.activate([
429
+            statusBannerInner.leadingAnchor.constraint(equalTo: statusBannerContainer.leadingAnchor),
430
+            statusBannerInner.topAnchor.constraint(equalTo: statusBannerContainer.topAnchor),
431
+            statusBannerInner.bottomAnchor.constraint(equalTo: statusBannerContainer.bottomAnchor),
432
+            statusBannerInner.trailingAnchor.constraint(lessThanOrEqualTo: statusBrandTrailing.leadingAnchor, constant: -16),
433
+
434
+            statusBrandTrailing.trailingAnchor.constraint(equalTo: statusBannerContainer.trailingAnchor),
435
+            statusBrandTrailing.centerYAnchor.constraint(equalTo: statusBannerContainer.centerYAnchor),
436
+
437
+            statusBannerRow.leadingAnchor.constraint(equalTo: statusBannerInner.leadingAnchor, constant: 14),
438
+            statusBannerRow.trailingAnchor.constraint(equalTo: statusBannerInner.trailingAnchor, constant: -16),
439
+            statusBannerRow.topAnchor.constraint(equalTo: statusBannerInner.topAnchor, constant: 10),
440
+            statusBannerRow.bottomAnchor.constraint(equalTo: statusBannerInner.bottomAnchor, constant: -10),
441
+
442
+            statusBannerContainer.heightAnchor.constraint(greaterThanOrEqualToConstant: 52)
443
+        ])
337
 
444
 
338
         syncChatStatusLoadingIndicator(forStatusText: chatStatusLabel.stringValue)
445
         syncChatStatusLoadingIndicator(forStatusText: chatStatusLabel.stringValue)
446
+        syncChatStatusBannerVisuals(forStatusText: chatStatusLabel.stringValue)
339
 
447
 
340
         chatDocumentView.translatesAutoresizingMaskIntoConstraints = false
448
         chatDocumentView.translatesAutoresizingMaskIntoConstraints = false
341
         chatStack.orientation = .vertical
449
         chatStack.orientation = .vertical
@@ -374,7 +482,43 @@ final class DashboardView: NSView, NSTextFieldDelegate {
374
     private func setChatStatusLabel(_ text: String) {
482
     private func setChatStatusLabel(_ text: String) {
375
         chatStatusLabel.stringValue = text
483
         chatStatusLabel.stringValue = text
376
         syncChatStatusLoadingIndicator(forStatusText: text)
484
         syncChatStatusLoadingIndicator(forStatusText: text)
485
+        syncChatStatusBannerVisuals(forStatusText: text)
377
         syncChatStatusSparkleAnimation()
486
         syncChatStatusSparkleAnimation()
487
+        updateStatusBannerLabelWidth()
488
+    }
489
+
490
+    private func updateStatusBannerLabelWidth() {
491
+        guard statusBannerContainer.bounds.width > 1 else { return }
492
+        let trailingWidth = max(96, statusBrandTrailing.fittingSize.width)
493
+        let chrome: CGFloat = 14 + 40 + 12 + 16 + 16 + trailingWidth
494
+        let target = max(80, statusBannerContainer.bounds.width - chrome)
495
+        if abs(chatStatusLabel.preferredMaxLayoutWidth - target) > 0.5 {
496
+            chatStatusLabel.preferredMaxLayoutWidth = target
497
+            chatStatusLabel.invalidateIntrinsicContentSize()
498
+        }
499
+    }
500
+
501
+    /// Leading status glyph: sparkles for prompts, magnifying glass for search-result summaries and errors.
502
+    private func syncChatStatusBannerVisuals(forStatusText text: String) {
503
+        if text == "Thinking..." { return }
504
+        let lower = text.lowercased()
505
+        let useMagnifyingGlass =
506
+            text.hasPrefix("Found ")
507
+            || text.hasPrefix("Here are")
508
+            || text.hasPrefix("No jobs")
509
+            || text.contains("couldn't find")
510
+            || lower.contains("try again")
511
+            || lower.contains("could not reach")
512
+            || lower.contains("search did not finish")
513
+        let config = NSImage.SymbolConfiguration(pointSize: 15, weight: .semibold)
514
+        chatStatusIcon.symbolConfiguration = config
515
+        if useMagnifyingGlass {
516
+            chatStatusIcon.image = NSImage(systemSymbolName: "magnifyingglass", accessibilityDescription: "Search status")
517
+        } else {
518
+            chatStatusIcon.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: "Assistant status")
519
+        }
520
+        chatStatusIcon.contentTintColor = .white
521
+        chatStatusSymbolContainer.layer?.backgroundColor = Theme.brandBlue.cgColor
378
     }
522
     }
379
 
523
 
380
     private func syncChatStatusLoadingIndicator(forStatusText text: String) {
524
     private func syncChatStatusLoadingIndicator(forStatusText text: String) {
@@ -383,9 +527,12 @@ final class DashboardView: NSView, NSTextFieldDelegate {
383
             chatStatusIcon.isHidden = true
527
             chatStatusIcon.isHidden = true
384
             chatStatusLoadingIndicator.isHidden = false
528
             chatStatusLoadingIndicator.isHidden = false
385
             chatStatusLoadingIndicator.startAnimation(nil)
529
             chatStatusLoadingIndicator.startAnimation(nil)
530
+            chatStatusSymbolContainer.layer?.backgroundColor = Theme.featureIconWell.cgColor
386
         } else {
531
         } else {
387
             chatStatusLoadingIndicator.stopAnimation(nil)
532
             chatStatusLoadingIndicator.stopAnimation(nil)
388
             chatStatusIcon.isHidden = false
533
             chatStatusIcon.isHidden = false
534
+            chatStatusLoadingIndicator.isHidden = true
535
+            chatStatusSymbolContainer.layer?.backgroundColor = Theme.brandBlue.cgColor
389
         }
536
         }
390
     }
537
     }
391
 
538
 
@@ -399,7 +546,7 @@ final class DashboardView: NSView, NSTextFieldDelegate {
399
 
546
 
400
     private func shouldAnimateChatStatusSparkles(for statusText: String) -> Bool {
547
     private func shouldAnimateChatStatusSparkles(for statusText: String) -> Bool {
401
         statusText == "Ask me to find jobs"
548
         statusText == "Ask me to find jobs"
402
-            || statusText == "Ask for another role, company, or skill match"
549
+            || statusText == "Opening the vault..."
403
     }
550
     }
404
 
551
 
405
     private func syncChatStatusSparkleAnimation() {
552
     private func syncChatStatusSparkleAnimation() {
@@ -542,30 +689,99 @@ final class DashboardView: NSView, NSTextFieldDelegate {
542
         persistSavedJobs()
689
         persistSavedJobs()
543
     }
690
     }
544
 
691
 
692
+    private func jobListingHostSubtitle(_ job: JobListing) -> String {
693
+        guard let raw = job.url, let url = URL(string: raw), let host = url.host?.lowercased() else {
694
+            return "Indeed"
695
+        }
696
+        if host.hasPrefix("www.") {
697
+            return String(host.dropFirst(4))
698
+        }
699
+        return host
700
+    }
701
+
702
+    private func jobListingCategorySymbol(for job: JobListing) -> String {
703
+        let blob = (job.title + " " + job.description).lowercased()
704
+        if blob.contains("machine learning") || blob.contains("deep learning") || blob.contains(" ml ") {
705
+            return "brain.head.profile"
706
+        }
707
+        if blob.contains("audio") || blob.contains(" sound ") || blob.contains("dsp") {
708
+            return "waveform"
709
+        }
710
+        if blob.contains("ios") || blob.contains("swift") || blob.contains("mobile") {
711
+            return "iphone"
712
+        }
713
+        if blob.contains("design") || blob.contains(" ux") || blob.contains("figma") {
714
+            return "paintpalette.fill"
715
+        }
716
+        if blob.contains("data ") || blob.contains("analytics") {
717
+            return "chart.bar.fill"
718
+        }
719
+        if blob.contains("ai") || blob.contains("llm") || blob.contains("nlp") {
720
+            return "cpu"
721
+        }
722
+        return "briefcase.fill"
723
+    }
724
+
545
     private func makeJobListingCard(_ job: JobListing, context: JobListingCardContext) -> NSView {
725
     private func makeJobListingCard(_ job: JobListing, context: JobListingCardContext) -> NSView {
546
         let card = NSView()
726
         let card = NSView()
547
         card.translatesAutoresizingMaskIntoConstraints = false
727
         card.translatesAutoresizingMaskIntoConstraints = false
548
         card.wantsLayer = true
728
         card.wantsLayer = true
549
         card.layer?.backgroundColor = Theme.cardBackground.cgColor
729
         card.layer?.backgroundColor = Theme.cardBackground.cgColor
550
-        card.layer?.cornerRadius = 12
730
+        card.layer?.cornerRadius = 14
551
         card.layer?.borderWidth = 1
731
         card.layer?.borderWidth = 1
552
         card.layer?.borderColor = Theme.border.cgColor
732
         card.layer?.borderColor = Theme.border.cgColor
553
         card.layer?.masksToBounds = true
733
         card.layer?.masksToBounds = true
554
 
734
 
735
+        let iconBox = NSView()
736
+        iconBox.translatesAutoresizingMaskIntoConstraints = false
737
+        iconBox.wantsLayer = true
738
+        iconBox.layer?.backgroundColor = Theme.brandBlue.cgColor
739
+        iconBox.layer?.cornerRadius = 12
740
+        if #available(macOS 11.0, *) {
741
+            iconBox.layer?.cornerCurve = .continuous
742
+        }
743
+
744
+        let categoryIcon = NSImageView()
745
+        categoryIcon.translatesAutoresizingMaskIntoConstraints = false
746
+        categoryIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 22, weight: .medium)
747
+        categoryIcon.image = NSImage(systemSymbolName: jobListingCategorySymbol(for: job), accessibilityDescription: nil)
748
+        categoryIcon.contentTintColor = .white
749
+        iconBox.addSubview(categoryIcon)
750
+
555
         let titleField = NSTextField(labelWithString: job.title)
751
         let titleField = NSTextField(labelWithString: job.title)
556
         titleField.font = .systemFont(ofSize: 16, weight: .semibold)
752
         titleField.font = .systemFont(ofSize: 16, weight: .semibold)
557
-        titleField.textColor = Theme.primaryText
753
+        titleField.textColor = Theme.brandBlue
558
         titleField.maximumNumberOfLines = 2
754
         titleField.maximumNumberOfLines = 2
559
         titleField.lineBreakMode = .byWordWrapping
755
         titleField.lineBreakMode = .byWordWrapping
560
         titleField.alignment = .left
756
         titleField.alignment = .left
757
+        titleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
561
         titleField.translatesAutoresizingMaskIntoConstraints = false
758
         titleField.translatesAutoresizingMaskIntoConstraints = false
562
 
759
 
760
+        let buildingIcon = NSImageView()
761
+        buildingIcon.translatesAutoresizingMaskIntoConstraints = false
762
+        buildingIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 12, weight: .medium)
763
+        buildingIcon.image = NSImage(systemSymbolName: "building.2.fill", accessibilityDescription: nil)
764
+        buildingIcon.contentTintColor = Theme.welcomeSubtitleText
765
+
766
+        let companyLabel = NSTextField(labelWithString: jobListingHostSubtitle(job))
767
+        companyLabel.font = .systemFont(ofSize: 12, weight: .medium)
768
+        companyLabel.textColor = Theme.welcomeSubtitleText
769
+        companyLabel.maximumNumberOfLines = 1
770
+        companyLabel.lineBreakMode = .byTruncatingTail
771
+        companyLabel.translatesAutoresizingMaskIntoConstraints = false
772
+
773
+        let companyRow = NSStackView(views: [buildingIcon, companyLabel])
774
+        companyRow.orientation = .horizontal
775
+        companyRow.spacing = 5
776
+        companyRow.alignment = .centerY
777
+        companyRow.translatesAutoresizingMaskIntoConstraints = false
778
+
563
         let descriptionField = NSTextField(wrappingLabelWithString: job.description)
779
         let descriptionField = NSTextField(wrappingLabelWithString: job.description)
564
         descriptionField.font = .systemFont(ofSize: 13, weight: .regular)
780
         descriptionField.font = .systemFont(ofSize: 13, weight: .regular)
565
         descriptionField.textColor = Theme.secondaryText
781
         descriptionField.textColor = Theme.secondaryText
566
-        descriptionField.maximumNumberOfLines = 0
567
-        descriptionField.alignment = .left
782
+        descriptionField.maximumNumberOfLines = 2
568
         descriptionField.lineBreakMode = .byWordWrapping
783
         descriptionField.lineBreakMode = .byWordWrapping
784
+        descriptionField.alignment = .left
569
         descriptionField.baseWritingDirection = .leftToRight
785
         descriptionField.baseWritingDirection = .leftToRight
570
         descriptionField.attributedStringValue = Self.jobListingDescriptionAttributedString(job.description)
786
         descriptionField.attributedStringValue = Self.jobListingDescriptionAttributedString(job.description)
571
         if let cell = descriptionField.cell as? NSTextFieldCell {
787
         if let cell = descriptionField.cell as? NSTextFieldCell {
@@ -584,7 +800,7 @@ final class DashboardView: NSView, NSTextFieldDelegate {
584
         applyButton.bezelStyle = .rounded
800
         applyButton.bezelStyle = .rounded
585
         applyButton.font = .systemFont(ofSize: 13, weight: .semibold)
801
         applyButton.font = .systemFont(ofSize: 13, weight: .semibold)
586
         applyButton.wantsLayer = true
802
         applyButton.wantsLayer = true
587
-        applyButton.layer?.cornerRadius = 6
803
+        applyButton.layer?.cornerRadius = 8
588
         applyButton.layer?.backgroundColor = Theme.brandBlue.cgColor
804
         applyButton.layer?.backgroundColor = Theme.brandBlue.cgColor
589
         applyButton.contentTintColor = Theme.proCTAText
805
         applyButton.contentTintColor = Theme.proCTAText
590
         applyButton.focusRingType = .none
806
         applyButton.focusRingType = .none
@@ -603,6 +819,9 @@ final class DashboardView: NSView, NSTextFieldDelegate {
603
         savedButton.isBordered = false
819
         savedButton.isBordered = false
604
         savedButton.bezelStyle = .rounded
820
         savedButton.bezelStyle = .rounded
605
         savedButton.font = .systemFont(ofSize: 13, weight: .semibold)
821
         savedButton.font = .systemFont(ofSize: 13, weight: .semibold)
822
+        savedButton.image = NSImage(systemSymbolName: savedOn ? "heart.fill" : "heart", accessibilityDescription: nil)
823
+        savedButton.imagePosition = .imageLeading
824
+        savedButton.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 11, weight: .semibold)
606
         savedButton.focusRingType = .none
825
         savedButton.focusRingType = .none
607
         savedButton.state = savedOn ? .on : .off
826
         savedButton.state = savedOn ? .on : .off
608
         savedButton.pointerCursor = true
827
         savedButton.pointerCursor = true
@@ -620,7 +839,7 @@ final class DashboardView: NSView, NSTextFieldDelegate {
620
         dismissButton.image = NSImage(systemSymbolName: "xmark", accessibilityDescription: "Dismiss")
839
         dismissButton.image = NSImage(systemSymbolName: "xmark", accessibilityDescription: "Dismiss")
621
         dismissButton.imagePosition = .imageOnly
840
         dismissButton.imagePosition = .imageOnly
622
         dismissButton.imageScaling = .scaleProportionallyDown
841
         dismissButton.imageScaling = .scaleProportionallyDown
623
-        dismissButton.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 11, weight: .semibold)
842
+        dismissButton.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 12, weight: .semibold)
624
         dismissButton.isBordered = false
843
         dismissButton.isBordered = false
625
         dismissButton.bezelStyle = .rounded
844
         dismissButton.bezelStyle = .rounded
626
         dismissButton.contentTintColor = Theme.secondaryText
845
         dismissButton.contentTintColor = Theme.secondaryText
@@ -629,7 +848,7 @@ final class DashboardView: NSView, NSTextFieldDelegate {
629
         dismissButton.toolTip = context == .savedJobsPage ? "Remove from saved" : "Dismiss"
848
         dismissButton.toolTip = context == .savedJobsPage ? "Remove from saved" : "Dismiss"
630
         dismissButton.focusRingType = .none
849
         dismissButton.focusRingType = .none
631
         dismissButton.wantsLayer = true
850
         dismissButton.wantsLayer = true
632
-        dismissButton.layer?.cornerRadius = 14
851
+        dismissButton.layer?.cornerRadius = 8
633
         dismissButton.layer?.backgroundColor = NSColor.clear.cgColor
852
         dismissButton.layer?.backgroundColor = NSColor.clear.cgColor
634
         dismissButton.pointerCursor = true
853
         dismissButton.pointerCursor = true
635
         dismissButton.hoverHandler = { [weak dismissButton] hovering in
854
         dismissButton.hoverHandler = { [weak dismissButton] hovering in
@@ -641,50 +860,64 @@ final class DashboardView: NSView, NSTextFieldDelegate {
641
         let buttonRow = NSStackView(views: [applyButton, savedButton, dismissButton])
860
         let buttonRow = NSStackView(views: [applyButton, savedButton, dismissButton])
642
         buttonRow.orientation = .horizontal
861
         buttonRow.orientation = .horizontal
643
         buttonRow.spacing = 8
862
         buttonRow.spacing = 8
644
-        buttonRow.alignment = .centerY
863
+        buttonRow.alignment = .top
645
         buttonRow.translatesAutoresizingMaskIntoConstraints = false
864
         buttonRow.translatesAutoresizingMaskIntoConstraints = false
646
         buttonRow.setContentHuggingPriority(.required, for: .horizontal)
865
         buttonRow.setContentHuggingPriority(.required, for: .horizontal)
647
         buttonRow.setContentCompressionResistancePriority(.required, for: .horizontal)
866
         buttonRow.setContentCompressionResistancePriority(.required, for: .horizontal)
867
+        buttonRow.setContentHuggingPriority(.required, for: .vertical)
868
+        buttonRow.setContentCompressionResistancePriority(.required, for: .vertical)
869
+        applyButton.setContentCompressionResistancePriority(.required, for: .horizontal)
870
+        savedButton.setContentCompressionResistancePriority(.required, for: .horizontal)
871
+        dismissButton.setContentCompressionResistancePriority(.required, for: .horizontal)
872
+
873
+        let middleColumn = NSStackView(views: [titleField, companyRow, descriptionField])
874
+        middleColumn.orientation = .vertical
875
+        middleColumn.spacing = 5
876
+        middleColumn.alignment = .leading
877
+        middleColumn.translatesAutoresizingMaskIntoConstraints = false
878
+        middleColumn.setContentHuggingPriority(.defaultLow, for: .horizontal)
879
+        middleColumn.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
880
+
881
+        let contentRow = NSStackView(views: [iconBox, middleColumn])
882
+        contentRow.orientation = .horizontal
883
+        contentRow.spacing = 14
884
+        contentRow.alignment = .top
885
+        contentRow.distribution = .fill
886
+        contentRow.translatesAutoresizingMaskIntoConstraints = false
887
+
888
+        card.addSubview(contentRow)
889
+        card.addSubview(buttonRow)
890
+        let actionCornerInset: CGFloat = 8
891
+        let contentToActionsGap: CGFloat = 12
892
+        let bodyTrailingInset: CGFloat = 16
893
+        NSLayoutConstraint.activate([
894
+            contentRow.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
895
+            contentRow.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -bodyTrailingInset),
896
+            contentRow.topAnchor.constraint(equalTo: card.topAnchor, constant: 14),
897
+            contentRow.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -14),
648
 
898
 
649
-        // Title hugs the leading edge; a low–hugging-priority spacer absorbs remaining width so buttons stay trailing.
650
-        titleField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
651
-        titleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
899
+            buttonRow.topAnchor.constraint(equalTo: card.topAnchor, constant: actionCornerInset),
900
+            buttonRow.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -actionCornerInset),
652
 
901
 
653
-        let titleRowSpacer = NSView()
654
-        titleRowSpacer.translatesAutoresizingMaskIntoConstraints = false
655
-        titleRowSpacer.setContentHuggingPriority(NSLayoutConstraint.Priority(1), for: .horizontal)
656
-        titleRowSpacer.setContentCompressionResistancePriority(NSLayoutConstraint.Priority(1), for: .horizontal)
657
-
658
-        let titleAndActionsRow = NSStackView(views: [titleField, titleRowSpacer, buttonRow])
659
-        titleAndActionsRow.orientation = .horizontal
660
-        titleAndActionsRow.spacing = 0
661
-        titleAndActionsRow.setCustomSpacing(14, after: titleRowSpacer)
662
-        titleAndActionsRow.alignment = .centerY
663
-        titleAndActionsRow.distribution = .fill
664
-        titleAndActionsRow.userInterfaceLayoutDirection = .leftToRight
665
-        titleAndActionsRow.translatesAutoresizingMaskIntoConstraints = false
666
-
667
-        let contentColumn = NSStackView(views: [titleAndActionsRow, descriptionField])
668
-        contentColumn.orientation = .vertical
669
-        contentColumn.spacing = 6
670
-        contentColumn.alignment = .width
671
-        contentColumn.translatesAutoresizingMaskIntoConstraints = false
672
-
673
-        card.addSubview(contentColumn)
674
-        NSLayoutConstraint.activate([
675
-            contentColumn.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
676
-            contentColumn.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -16),
677
-            contentColumn.topAnchor.constraint(equalTo: card.topAnchor, constant: 14),
678
-            contentColumn.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -14),
679
-
680
-            applyButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 72),
681
-            applyButton.heightAnchor.constraint(equalToConstant: 28),
682
-            savedButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 72),
683
-            savedButton.heightAnchor.constraint(equalToConstant: 28),
684
-            dismissButton.widthAnchor.constraint(equalToConstant: 28),
685
-            dismissButton.heightAnchor.constraint(equalToConstant: 28),
686
-
687
-            descriptionField.widthAnchor.constraint(equalTo: contentColumn.widthAnchor)
902
+            middleColumn.trailingAnchor.constraint(lessThanOrEqualTo: buttonRow.leadingAnchor, constant: -contentToActionsGap),
903
+
904
+            iconBox.widthAnchor.constraint(equalToConstant: 58),
905
+            iconBox.heightAnchor.constraint(equalToConstant: 58),
906
+
907
+            categoryIcon.centerXAnchor.constraint(equalTo: iconBox.centerXAnchor),
908
+            categoryIcon.centerYAnchor.constraint(equalTo: iconBox.centerYAnchor),
909
+
910
+            buildingIcon.widthAnchor.constraint(equalToConstant: 14),
911
+            buildingIcon.heightAnchor.constraint(equalToConstant: 14),
912
+
913
+            applyButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 76),
914
+            applyButton.heightAnchor.constraint(equalToConstant: 32),
915
+            savedButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 84),
916
+            savedButton.heightAnchor.constraint(equalToConstant: 32),
917
+            dismissButton.widthAnchor.constraint(equalToConstant: 32),
918
+            dismissButton.heightAnchor.constraint(equalToConstant: 32),
919
+
920
+            descriptionField.widthAnchor.constraint(equalTo: middleColumn.widthAnchor)
688
         ])
921
         ])
689
 
922
 
690
         return card
923
         return card
@@ -692,7 +925,7 @@ final class DashboardView: NSView, NSTextFieldDelegate {
692
 
925
 
693
     private func styleJobSavedButton(_ button: NSButton) {
926
     private func styleJobSavedButton(_ button: NSButton) {
694
         button.wantsLayer = true
927
         button.wantsLayer = true
695
-        button.layer?.cornerRadius = 6
928
+        button.layer?.cornerRadius = 8
696
         let on = button.state == .on
929
         let on = button.state == .on
697
         let hovering = (button as? HoverableButton)?.isHovering ?? false
930
         let hovering = (button as? HoverableButton)?.isHovering ?? false
698
         if on {
931
         if on {
@@ -701,10 +934,10 @@ final class DashboardView: NSView, NSTextFieldDelegate {
701
             button.layer?.borderColor = Theme.brandBlue.cgColor
934
             button.layer?.borderColor = Theme.brandBlue.cgColor
702
             button.contentTintColor = Theme.brandBlue
935
             button.contentTintColor = Theme.brandBlue
703
         } else {
936
         } else {
704
-            button.layer?.backgroundColor = (hovering ? Theme.neutralHoverFill : Theme.cardBackground).cgColor
937
+            button.layer?.backgroundColor = (hovering ? Theme.proCardFill : Theme.cardBackground).cgColor
705
             button.layer?.borderWidth = 1
938
             button.layer?.borderWidth = 1
706
-            button.layer?.borderColor = Theme.border.cgColor
707
-            button.contentTintColor = Theme.primaryText
939
+            button.layer?.borderColor = Theme.brandBlue.cgColor
940
+            button.contentTintColor = Theme.brandBlue
708
         }
941
         }
709
     }
942
     }
710
 
943
 
@@ -726,6 +959,7 @@ final class DashboardView: NSView, NSTextFieldDelegate {
726
         applySavedState(willSave, for: job)
959
         applySavedState(willSave, for: job)
727
         sender.state = willSave ? .on : .off
960
         sender.state = willSave ? .on : .off
728
         sender.title = willSave ? "Saved" : "Save"
961
         sender.title = willSave ? "Saved" : "Save"
962
+        sender.image = NSImage(systemSymbolName: willSave ? "heart.fill" : "heart", accessibilityDescription: nil)
729
         styleJobSavedButton(sender)
963
         styleJobSavedButton(sender)
730
         if isSavedJobsSidebarIndex(selectedSidebarIndex) {
964
         if isSavedJobsSidebarIndex(selectedSidebarIndex) {
731
             reloadSavedJobsListings()
965
             reloadSavedJobsListings()
@@ -819,9 +1053,9 @@ final class DashboardView: NSView, NSTextFieldDelegate {
819
         }
1053
         }
820
 
1054
 
821
         jobSearchIcon.translatesAutoresizingMaskIntoConstraints = false
1055
         jobSearchIcon.translatesAutoresizingMaskIntoConstraints = false
822
-        jobSearchIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 15, weight: .medium)
823
-        jobSearchIcon.image = NSImage(systemSymbolName: "magnifyingglass", accessibilityDescription: "Job search")
824
-        jobSearchIcon.contentTintColor = Theme.primaryText
1056
+        jobSearchIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 16, weight: .semibold)
1057
+        jobSearchIcon.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: "Ask AI")
1058
+        jobSearchIcon.contentTintColor = Theme.brandBlue
825
 
1059
 
826
         configureField(jobKeywordsField, placeholder: "Ask for roles, skills, salary, or job descriptions...")
1060
         configureField(jobKeywordsField, placeholder: "Ask for roles, skills, salary, or job descriptions...")
827
 
1061
 
@@ -865,14 +1099,18 @@ final class DashboardView: NSView, NSTextFieldDelegate {
865
 
1099
 
866
         findJobsButton.translatesAutoresizingMaskIntoConstraints = false
1100
         findJobsButton.translatesAutoresizingMaskIntoConstraints = false
867
         findJobsButton.title = ""
1101
         findJobsButton.title = ""
1102
+        findJobsButton.image = NSImage(systemSymbolName: "paperplane.fill", accessibilityDescription: nil)
1103
+        findJobsButton.imagePosition = .imageLeading
1104
+        findJobsButton.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 11, weight: .semibold)
868
         findJobsButton.attributedTitle = NSAttributedString(
1105
         findJobsButton.attributedTitle = NSAttributedString(
869
-            string: "Send",
1106
+            string: " Send",
870
             attributes: [
1107
             attributes: [
871
                 .font: NSFont.systemFont(ofSize: 14, weight: .semibold),
1108
                 .font: NSFont.systemFont(ofSize: 14, weight: .semibold),
872
                 .foregroundColor: Theme.proCTAText,
1109
                 .foregroundColor: Theme.proCTAText,
873
                 .kern: 0.35
1110
                 .kern: 0.35
874
             ]
1111
             ]
875
         )
1112
         )
1113
+        findJobsButton.contentTintColor = Theme.proCTAText
876
         findJobsButton.isBordered = false
1114
         findJobsButton.isBordered = false
877
         findJobsButton.bezelStyle = .rounded
1115
         findJobsButton.bezelStyle = .rounded
878
         findJobsButton.wantsLayer = true
1116
         findJobsButton.wantsLayer = true
@@ -1324,6 +1562,26 @@ final class DashboardView: NSView, NSTextFieldDelegate {
1324
         window?.makeFirstResponder(nil)
1562
         window?.makeFirstResponder(nil)
1325
     }
1563
     }
1326
 
1564
 
1565
+    @objc private func didTapFeatureRole() {
1566
+        focusSearchField(seed: "Find roles similar to: ")
1567
+    }
1568
+
1569
+    @objc private func didTapFeatureCompany() {
1570
+        focusSearchField(seed: "Find jobs at company: ")
1571
+    }
1572
+
1573
+    @objc private func didTapFeatureSkill() {
1574
+        focusSearchField(seed: "Find jobs that require skill: ")
1575
+    }
1576
+
1577
+    private func focusSearchField(seed: String) {
1578
+        jobKeywordsField.stringValue = seed
1579
+        window?.makeFirstResponder(jobKeywordsField)
1580
+        if let editor = jobKeywordsField.window?.fieldEditor(true, for: jobKeywordsField) as? NSTextView {
1581
+            editor.moveToEndOfDocument(nil)
1582
+        }
1583
+    }
1584
+
1327
     @objc private func didTapLoadMoreJobs() {
1585
     @objc private func didTapLoadMoreJobs() {
1328
         let prompt = "Show more jobs"
1586
         let prompt = "Show more jobs"
1329
         guard !isAwaitingResponse, isContinuationPrompt(prompt) else { return }
1587
         guard !isAwaitingResponse, isContinuationPrompt(prompt) else { return }
@@ -1364,7 +1622,7 @@ final class DashboardView: NSView, NSTextFieldDelegate {
1364
                     )
1622
                     )
1365
                     self.chatMessages.append(ChatMessage(role: "assistant", content: reply))
1623
                     self.chatMessages.append(ChatMessage(role: "assistant", content: reply))
1366
                     self.appendChatBubble(text: reply, isUser: false, jobs: freshJobs)
1624
                     self.appendChatBubble(text: reply, isUser: false, jobs: freshJobs)
1367
-                    self.setChatStatusLabel("Ask for another role, company, or skill match")
1625
+                    self.setChatStatusLabel(reply)
1368
                 case .failure(let error):
1626
                 case .failure(let error):
1369
                     self.appendChatBubble(text: error.localizedDescription, isUser: false)
1627
                     self.appendChatBubble(text: error.localizedDescription, isUser: false)
1370
                     if error is URLError {
1628
                     if error is URLError {
@@ -2289,6 +2547,132 @@ private struct OpenAIAPIErrorResponse: Codable {
2289
     }
2547
     }
2290
 }
2548
 }
2291
 
2549
 
2550
+/// Home welcome row: three tappable shortcuts that seed the main search field (matches the reference dashboard tiles).
2551
+private final class FeatureShortcutCardView: NSView {
2552
+    private weak var actionTarget: AnyObject?
2553
+    private var actionSelector: Selector
2554
+
2555
+    init(symbolName: String, title: String, subtitle: String, target: AnyObject?, action: Selector) {
2556
+        self.actionTarget = target
2557
+        self.actionSelector = action
2558
+        super.init(frame: .zero)
2559
+        translatesAutoresizingMaskIntoConstraints = false
2560
+        wantsLayer = true
2561
+        layer?.cornerRadius = 14
2562
+        if #available(macOS 11.0, *) {
2563
+            layer?.cornerCurve = .continuous
2564
+        }
2565
+        layer?.backgroundColor = NSColor.white.cgColor
2566
+        layer?.masksToBounds = false
2567
+        layer?.shadowColor = NSColor.black.withAlphaComponent(0.12).cgColor
2568
+        layer?.shadowOffset = CGSize(width: 0, height: 2)
2569
+        layer?.shadowRadius = 10
2570
+        layer?.shadowOpacity = 1
2571
+
2572
+        let brandBlue = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
2573
+        let iconWellColor = NSColor(srgbRed: 220 / 255, green: 235 / 255, blue: 252 / 255, alpha: 1)
2574
+        let secondary = NSColor(srgbRed: 118 / 255, green: 118 / 255, blue: 118 / 255, alpha: 1)
2575
+
2576
+        let iconHost = NSView()
2577
+        iconHost.translatesAutoresizingMaskIntoConstraints = false
2578
+        iconHost.wantsLayer = true
2579
+        iconHost.layer?.backgroundColor = iconWellColor.cgColor
2580
+        iconHost.layer?.cornerRadius = 22
2581
+
2582
+        let icon = NSImageView()
2583
+        icon.translatesAutoresizingMaskIntoConstraints = false
2584
+        icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 16, weight: .semibold)
2585
+        icon.image = NSImage(systemSymbolName: symbolName, accessibilityDescription: nil)
2586
+        icon.contentTintColor = brandBlue
2587
+        iconHost.addSubview(icon)
2588
+
2589
+        let titleField = NSTextField(wrappingLabelWithString: title)
2590
+        titleField.font = .systemFont(ofSize: 15, weight: .bold)
2591
+        titleField.textColor = brandBlue
2592
+        titleField.maximumNumberOfLines = 1
2593
+        titleField.isEditable = false
2594
+        titleField.isBordered = false
2595
+        titleField.drawsBackground = false
2596
+        titleField.alignment = .left
2597
+
2598
+        let subtitleField = NSTextField(wrappingLabelWithString: subtitle)
2599
+        subtitleField.font = .systemFont(ofSize: 11, weight: .regular)
2600
+        subtitleField.textColor = secondary
2601
+        subtitleField.maximumNumberOfLines = 2
2602
+        subtitleField.isEditable = false
2603
+        subtitleField.isBordered = false
2604
+        subtitleField.drawsBackground = false
2605
+        subtitleField.alignment = .left
2606
+        subtitleField.preferredMaxLayoutWidth = 160
2607
+
2608
+        let textColumn = NSStackView(views: [titleField, subtitleField])
2609
+        textColumn.orientation = .vertical
2610
+        textColumn.spacing = 4
2611
+        textColumn.alignment = .leading
2612
+        textColumn.translatesAutoresizingMaskIntoConstraints = false
2613
+        textColumn.setContentHuggingPriority(.defaultLow, for: .horizontal)
2614
+        textColumn.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
2615
+
2616
+        let chevron = NSImageView()
2617
+        chevron.translatesAutoresizingMaskIntoConstraints = false
2618
+        chevron.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 13, weight: .semibold)
2619
+        chevron.image = NSImage(systemSymbolName: "arrow.right", accessibilityDescription: nil)
2620
+        chevron.contentTintColor = brandBlue
2621
+        chevron.setContentHuggingPriority(.required, for: .horizontal)
2622
+        chevron.setContentCompressionResistancePriority(.required, for: .horizontal)
2623
+
2624
+        iconHost.setContentHuggingPriority(.required, for: .horizontal)
2625
+        iconHost.setContentCompressionResistancePriority(.required, for: .horizontal)
2626
+
2627
+        let row = NSStackView(views: [iconHost, textColumn, chevron])
2628
+        row.orientation = .horizontal
2629
+        row.spacing = 12
2630
+        row.alignment = .centerY
2631
+        row.distribution = .fill
2632
+        row.translatesAutoresizingMaskIntoConstraints = false
2633
+        addSubview(row)
2634
+
2635
+        NSLayoutConstraint.activate([
2636
+            row.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 14),
2637
+            row.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -14),
2638
+            row.topAnchor.constraint(equalTo: topAnchor, constant: 16),
2639
+            row.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16),
2640
+
2641
+            iconHost.widthAnchor.constraint(equalToConstant: 44),
2642
+            iconHost.heightAnchor.constraint(equalToConstant: 44),
2643
+            icon.centerXAnchor.constraint(equalTo: iconHost.centerXAnchor),
2644
+            icon.centerYAnchor.constraint(equalTo: iconHost.centerYAnchor)
2645
+        ])
2646
+
2647
+        setAccessibilityElement(true)
2648
+        setAccessibilityRole(.button)
2649
+        setAccessibilityLabel("\(title). \(subtitle)")
2650
+    }
2651
+
2652
+    @available(*, unavailable)
2653
+    required init?(coder: NSCoder) {
2654
+        fatalError("init(coder:) has not been implemented")
2655
+    }
2656
+
2657
+    override func layout() {
2658
+        super.layout()
2659
+        guard let layer = layer, bounds.width > 0, bounds.height > 0 else { return }
2660
+        let r = bounds
2661
+        layer.shadowPath = CGPath(roundedRect: r, cornerWidth: 14, cornerHeight: 14, transform: nil)
2662
+    }
2663
+
2664
+    override func mouseDown(with event: NSEvent) {
2665
+        if let target = actionTarget {
2666
+            _ = target.perform(actionSelector, with: nil)
2667
+        }
2668
+    }
2669
+
2670
+    override func resetCursorRects() {
2671
+        super.resetCursorRects()
2672
+        addCursorRect(bounds, cursor: .pointingHand)
2673
+    }
2674
+}
2675
+
2292
 /// `NSButton` that carries a `JobListing` for card actions (`representedObject` is unavailable on `NSButton` in this target).
2676
 /// `NSButton` that carries a `JobListing` for card actions (`representedObject` is unavailable on `NSButton` in this target).
2293
 private final class JobPayloadButton: HoverableButton {
2677
 private final class JobPayloadButton: HoverableButton {
2294
     var jobPayload: JobListing?
2678
     var jobPayload: JobListing?