|
|
@@ -20,6 +20,12 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
20
|
20
|
static let chromeBackground = NSColor(srgbRed: 247 / 255, green: 247 / 255, blue: 247 / 255, alpha: 1)
|
|
21
|
21
|
static let sidebarBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
|
|
22
|
22
|
static let mainHostBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
|
|
|
23
|
+ /// Welcome hero (matches reference: `#0052CC` heading, `#334155` subline, `#EFF6FF` icon well).
|
|
|
24
|
+ static let welcomeHeroHeadingBlue = NSColor(srgbRed: 0, green: 82 / 255, blue: 204 / 255, alpha: 1)
|
|
|
25
|
+ static let welcomeHeroSubtitleText = NSColor(srgbRed: 51 / 255, green: 65 / 255, blue: 85 / 255, alpha: 1)
|
|
|
26
|
+ static let welcomeHeroIconWell = NSColor(srgbRed: 239 / 255, green: 246 / 255, blue: 255 / 255, alpha: 1)
|
|
|
27
|
+ /// Light decorative strokes / sparkles behind the welcome hero.
|
|
|
28
|
+ static let welcomeHeroWaveTint = NSColor(srgbRed: 186 / 255, green: 210 / 255, blue: 253 / 255, alpha: 1)
|
|
23
|
29
|
/// Subtitle on the welcome hero: dark neutral gray to match the reference layout.
|
|
24
|
30
|
static let welcomeSubtitleText = NSColor(srgbRed: 64 / 255, green: 64 / 255, blue: 64 / 255, alpha: 1)
|
|
25
|
31
|
static let selectionFill = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.12)
|
|
|
@@ -82,7 +88,11 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
82
|
88
|
private let findJobsCTAHost = NSView()
|
|
83
|
89
|
private let findJobsCTAChrome = HoverableView()
|
|
84
|
90
|
private var findJobsCTAGradientLayer: CAGradientLayer?
|
|
85
|
|
- private let welcomeSparkleIcon = NSImageView()
|
|
|
91
|
+ private let welcomeHeroHost = NSView()
|
|
|
92
|
+ private let welcomeHeroBackgroundView = WelcomeHeroBackgroundView()
|
|
|
93
|
+ private lazy var welcomeSparkleCluster: WelcomeSparkleClusterView = {
|
|
|
94
|
+ WelcomeSparkleClusterView(iconWell: Theme.welcomeHeroIconWell, tint: Theme.welcomeHeroHeadingBlue)
|
|
|
95
|
+ }()
|
|
86
|
96
|
private let featureCardsRow = NSStackView()
|
|
87
|
97
|
private let chatScrollView = NSScrollView()
|
|
88
|
98
|
private let chatDocumentView = JobListingsDocumentView()
|
|
|
@@ -125,8 +135,6 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
125
|
135
|
min(jobsPerSearchMaxCap, max(jobsPerSearchMin, requested))
|
|
126
|
136
|
}
|
|
127
|
137
|
|
|
128
|
|
- private static let welcomeSubtitleBreathKey = "welcomeSubtitleBreath"
|
|
129
|
|
-
|
|
130
|
138
|
override init(frame frameRect: NSRect) {
|
|
131
|
139
|
super.init(frame: frameRect)
|
|
132
|
140
|
setupLayout()
|
|
|
@@ -207,36 +215,54 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
207
|
215
|
mainOverlay.translatesAutoresizingMaskIntoConstraints = false
|
|
208
|
216
|
mainOverlay.setContentHuggingPriority(.defaultLow, for: .vertical)
|
|
209
|
217
|
|
|
210
|
|
- greetingLabel.font = .systemFont(ofSize: 32, weight: .bold)
|
|
211
|
|
- greetingLabel.textColor = Theme.brandBlue
|
|
|
218
|
+ greetingLabel.font = .systemFont(ofSize: 34, weight: .bold)
|
|
|
219
|
+ greetingLabel.textColor = Theme.welcomeHeroHeadingBlue
|
|
212
|
220
|
greetingLabel.alignment = .center
|
|
213
|
221
|
greetingLabel.maximumNumberOfLines = 1
|
|
214
|
222
|
|
|
215
|
223
|
subtitleLabel.font = .systemFont(ofSize: 15, weight: .regular)
|
|
216
|
|
- subtitleLabel.textColor = Theme.welcomeSubtitleText
|
|
|
224
|
+ subtitleLabel.textColor = Theme.welcomeHeroSubtitleText
|
|
217
|
225
|
subtitleLabel.alignment = .center
|
|
218
|
226
|
subtitleLabel.maximumNumberOfLines = 2
|
|
219
|
|
- subtitleLabel.wantsLayer = true
|
|
220
|
227
|
|
|
221
|
228
|
let topInset = NSView()
|
|
222
|
229
|
topInset.translatesAutoresizingMaskIntoConstraints = false
|
|
223
|
|
- topInset.heightAnchor.constraint(equalToConstant: 18).isActive = true
|
|
|
230
|
+ topInset.heightAnchor.constraint(equalToConstant: 24).isActive = true
|
|
224
|
231
|
|
|
225
|
232
|
configureSearchBar()
|
|
226
|
233
|
configureChatViews()
|
|
227
|
234
|
|
|
228
|
|
- welcomeSparkleIcon.translatesAutoresizingMaskIntoConstraints = false
|
|
229
|
|
- welcomeSparkleIcon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 16, weight: .semibold)
|
|
230
|
|
- welcomeSparkleIcon.image = NSImage(systemSymbolName: "sparkles", accessibilityDescription: nil)
|
|
231
|
|
- welcomeSparkleIcon.contentTintColor = Theme.brandBlue
|
|
|
235
|
+ welcomeHeroHost.translatesAutoresizingMaskIntoConstraints = false
|
|
|
236
|
+ welcomeHeroBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
237
|
+ welcomeHeroBackgroundView.waveTint = Theme.welcomeHeroWaveTint
|
|
232
|
238
|
|
|
233
|
|
- let titleBlock = NSStackView(views: [greetingLabel, subtitleLabel, welcomeSparkleIcon])
|
|
234
|
|
- titleBlock.orientation = .vertical
|
|
235
|
|
- titleBlock.spacing = 10
|
|
236
|
|
- titleBlock.alignment = .centerX
|
|
|
239
|
+ let welcomeHeroContent = NSStackView(views: [welcomeSparkleCluster, greetingLabel, subtitleLabel])
|
|
|
240
|
+ welcomeHeroContent.orientation = .vertical
|
|
|
241
|
+ welcomeHeroContent.spacing = 14
|
|
|
242
|
+ welcomeHeroContent.alignment = .centerX
|
|
|
243
|
+ welcomeHeroContent.translatesAutoresizingMaskIntoConstraints = false
|
|
|
244
|
+
|
|
|
245
|
+ welcomeHeroHost.addSubview(welcomeHeroBackgroundView)
|
|
|
246
|
+ welcomeHeroHost.addSubview(welcomeHeroContent)
|
|
|
247
|
+ NSLayoutConstraint.activate([
|
|
|
248
|
+ welcomeHeroBackgroundView.leadingAnchor.constraint(equalTo: welcomeHeroHost.leadingAnchor),
|
|
|
249
|
+ welcomeHeroBackgroundView.trailingAnchor.constraint(equalTo: welcomeHeroHost.trailingAnchor),
|
|
|
250
|
+ welcomeHeroBackgroundView.topAnchor.constraint(equalTo: welcomeHeroHost.topAnchor),
|
|
|
251
|
+ welcomeHeroBackgroundView.bottomAnchor.constraint(equalTo: welcomeHeroHost.bottomAnchor),
|
|
|
252
|
+
|
|
|
253
|
+ welcomeHeroContent.centerXAnchor.constraint(equalTo: welcomeHeroHost.centerXAnchor),
|
|
|
254
|
+ welcomeHeroContent.leadingAnchor.constraint(greaterThanOrEqualTo: welcomeHeroHost.leadingAnchor, constant: 16),
|
|
|
255
|
+ welcomeHeroContent.trailingAnchor.constraint(lessThanOrEqualTo: welcomeHeroHost.trailingAnchor, constant: -16),
|
|
|
256
|
+ welcomeHeroContent.topAnchor.constraint(equalTo: welcomeHeroHost.topAnchor, constant: 8),
|
|
|
257
|
+ welcomeHeroContent.bottomAnchor.constraint(equalTo: welcomeHeroHost.bottomAnchor, constant: -4)
|
|
|
258
|
+ ])
|
|
237
|
259
|
|
|
238
|
260
|
configureFeatureShortcutCards()
|
|
239
|
261
|
|
|
|
262
|
+ let heroCardsSpacer = NSView()
|
|
|
263
|
+ heroCardsSpacer.translatesAutoresizingMaskIntoConstraints = false
|
|
|
264
|
+ heroCardsSpacer.heightAnchor.constraint(equalToConstant: 36).isActive = true
|
|
|
265
|
+
|
|
240
|
266
|
let midSpacer = NSView()
|
|
241
|
267
|
midSpacer.translatesAutoresizingMaskIntoConstraints = false
|
|
242
|
268
|
midSpacer.heightAnchor.constraint(equalToConstant: 12).isActive = true
|
|
|
@@ -250,7 +276,8 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
250
|
276
|
chatBottomSpacer.heightAnchor.constraint(equalToConstant: 14).isActive = true
|
|
251
|
277
|
|
|
252
|
278
|
mainOverlay.addArrangedSubview(topInset)
|
|
253
|
|
- mainOverlay.addArrangedSubview(titleBlock)
|
|
|
279
|
+ mainOverlay.addArrangedSubview(welcomeHeroHost)
|
|
|
280
|
+ mainOverlay.addArrangedSubview(heroCardsSpacer)
|
|
254
|
281
|
mainOverlay.addArrangedSubview(featureCardsRow)
|
|
255
|
282
|
mainOverlay.addArrangedSubview(midSpacer)
|
|
256
|
283
|
mainOverlay.addArrangedSubview(chatTopSpacer)
|
|
|
@@ -289,10 +316,15 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
289
|
316
|
featureCardsRow.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
|
|
290
|
317
|
chatScrollView.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
|
|
291
|
318
|
|
|
|
319
|
+ welcomeHeroHost.leadingAnchor.constraint(equalTo: mainOverlay.leadingAnchor),
|
|
|
320
|
+ welcomeHeroHost.trailingAnchor.constraint(equalTo: mainOverlay.trailingAnchor),
|
|
|
321
|
+
|
|
292
|
322
|
greetingLabel.leadingAnchor.constraint(equalTo: mainOverlay.leadingAnchor, constant: 16),
|
|
293
|
323
|
greetingLabel.trailingAnchor.constraint(equalTo: mainOverlay.trailingAnchor, constant: -16),
|
|
294
|
324
|
subtitleLabel.leadingAnchor.constraint(equalTo: greetingLabel.leadingAnchor),
|
|
295
|
|
- subtitleLabel.trailingAnchor.constraint(equalTo: greetingLabel.trailingAnchor)
|
|
|
325
|
+ subtitleLabel.trailingAnchor.constraint(equalTo: greetingLabel.trailingAnchor),
|
|
|
326
|
+
|
|
|
327
|
+ welcomeHeroContent.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, constant: -32)
|
|
296
|
328
|
])
|
|
297
|
329
|
}
|
|
298
|
330
|
|
|
|
@@ -355,40 +387,10 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
355
|
387
|
])
|
|
356
|
388
|
}
|
|
357
|
389
|
|
|
358
|
|
- private func isWelcomeHeroVisible() -> Bool {
|
|
359
|
|
- !mainOverlay.isHidden
|
|
360
|
|
- }
|
|
361
|
|
-
|
|
362
|
390
|
private var prefersReducedMotion: Bool {
|
|
363
|
391
|
NSWorkspace.shared.accessibilityDisplayShouldReduceMotion
|
|
364
|
392
|
}
|
|
365
|
393
|
|
|
366
|
|
- private func syncWelcomeSubtitleBreathingAnimation() {
|
|
367
|
|
- guard !prefersReducedMotion else {
|
|
368
|
|
- stopWelcomeSubtitleBreathingAnimation()
|
|
369
|
|
- return
|
|
370
|
|
- }
|
|
371
|
|
- guard isWelcomeHeroVisible(), !subtitleLabel.stringValue.isEmpty else {
|
|
372
|
|
- stopWelcomeSubtitleBreathingAnimation()
|
|
373
|
|
- return
|
|
374
|
|
- }
|
|
375
|
|
- guard let layer = subtitleLabel.layer, layer.animation(forKey: Self.welcomeSubtitleBreathKey) == nil else { return }
|
|
376
|
|
-
|
|
377
|
|
- let pulse = CABasicAnimation(keyPath: "opacity")
|
|
378
|
|
- pulse.fromValue = 1.0
|
|
379
|
|
- pulse.toValue = 0.86
|
|
380
|
|
- pulse.duration = 2.4
|
|
381
|
|
- pulse.autoreverses = true
|
|
382
|
|
- pulse.repeatCount = .greatestFiniteMagnitude
|
|
383
|
|
- pulse.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
|
384
|
|
- layer.add(pulse, forKey: Self.welcomeSubtitleBreathKey)
|
|
385
|
|
- }
|
|
386
|
|
-
|
|
387
|
|
- private func stopWelcomeSubtitleBreathingAnimation() {
|
|
388
|
|
- subtitleLabel.layer?.removeAnimation(forKey: Self.welcomeSubtitleBreathKey)
|
|
389
|
|
- subtitleLabel.layer?.opacity = 1
|
|
390
|
|
- }
|
|
391
|
|
-
|
|
392
|
394
|
private func updateJobListingDescriptionWidths() {
|
|
393
|
395
|
updateDescriptionColumnWidths(in: savedJobsStack, containerWidth: savedJobsDocumentView.bounds.width)
|
|
394
|
396
|
walkChatJobStacks { stack in
|
|
|
@@ -1303,11 +1305,6 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
1303
|
1305
|
nonHomeTitleLabel.stringValue = currentSidebarItems[selectedSidebarIndex].title
|
|
1304
|
1306
|
}
|
|
1305
|
1307
|
}
|
|
1306
|
|
- if home {
|
|
1307
|
|
- syncWelcomeSubtitleBreathingAnimation()
|
|
1308
|
|
- } else {
|
|
1309
|
|
- stopWelcomeSubtitleBreathingAnimation()
|
|
1310
|
|
- }
|
|
1311
|
1308
|
}
|
|
1312
|
1309
|
|
|
1313
|
1310
|
/// Restores the main job-search experience: cleared query and a fresh chat history.
|
|
|
@@ -2467,6 +2464,135 @@ private struct OpenAIAPIErrorResponse: Codable {
|
|
2467
|
2464
|
}
|
|
2468
|
2465
|
}
|
|
2469
|
2466
|
|
|
|
2467
|
+/// Decorative waves and faint sparkles behind the welcome hero (reference layout).
|
|
|
2468
|
+private final class WelcomeHeroBackgroundView: NSView {
|
|
|
2469
|
+ /// Stroke color for side waves (pastel blue).
|
|
|
2470
|
+ var waveTint = NSColor(srgbRed: 186 / 255, green: 210 / 255, blue: 253 / 255, alpha: 1)
|
|
|
2471
|
+
|
|
|
2472
|
+ override var isFlipped: Bool { true }
|
|
|
2473
|
+
|
|
|
2474
|
+ override func draw(_ dirtyRect: NSRect) {
|
|
|
2475
|
+ NSColor.clear.setFill()
|
|
|
2476
|
+ bounds.fill()
|
|
|
2477
|
+
|
|
|
2478
|
+ guard bounds.width > 24, bounds.height > 24 else { return }
|
|
|
2479
|
+
|
|
|
2480
|
+ drawSideWaves(in: bounds, isLeft: true)
|
|
|
2481
|
+ drawSideWaves(in: bounds, isLeft: false)
|
|
|
2482
|
+ drawAmbientSparkles(in: bounds)
|
|
|
2483
|
+ }
|
|
|
2484
|
+
|
|
|
2485
|
+ private func drawSideWaves(in bounds: NSRect, isLeft: Bool) {
|
|
|
2486
|
+ for i in 0..<9 {
|
|
|
2487
|
+ let path = NSBezierPath()
|
|
|
2488
|
+ path.lineWidth = 1
|
|
|
2489
|
+ path.lineCapStyle = .round
|
|
|
2490
|
+ let phase = CGFloat(i) * 0.88
|
|
|
2491
|
+ let base = CGFloat(i + 1) * 11 + 4
|
|
|
2492
|
+ var first = true
|
|
|
2493
|
+ for y in stride(from: CGFloat(0), through: bounds.height, by: 2.8) {
|
|
|
2494
|
+ let wobble = sin(y * 0.048 + phase) * (4 + CGFloat(i % 5))
|
|
|
2495
|
+ let x = isLeft ? (base + wobble) : (bounds.width - base - wobble)
|
|
|
2496
|
+ let point = NSPoint(x: x, y: y)
|
|
|
2497
|
+ if first {
|
|
|
2498
|
+ path.move(to: point)
|
|
|
2499
|
+ first = false
|
|
|
2500
|
+ } else {
|
|
|
2501
|
+ path.line(to: point)
|
|
|
2502
|
+ }
|
|
|
2503
|
+ }
|
|
|
2504
|
+ let fade = 1 - CGFloat(i) / 10
|
|
|
2505
|
+ waveTint.withAlphaComponent((0.09 + CGFloat(i % 3) * 0.022) * fade).setStroke()
|
|
|
2506
|
+ path.stroke()
|
|
|
2507
|
+ }
|
|
|
2508
|
+ }
|
|
|
2509
|
+
|
|
|
2510
|
+ private func drawAmbientSparkles(in bounds: NSRect) {
|
|
|
2511
|
+ let accent = NSColor(srgbRed: 0, green: 82 / 255, blue: 204 / 255, alpha: 1)
|
|
|
2512
|
+ let specs: [(CGFloat, CGFloat, CGFloat, CGFloat)] = [
|
|
|
2513
|
+ (0.06, 0.14, 9, 0.28),
|
|
|
2514
|
+ (0.94, 0.12, 12, 0.34),
|
|
|
2515
|
+ (0.18, 0.42, 5, 0.18),
|
|
|
2516
|
+ (0.86, 0.44, 6, 0.2),
|
|
|
2517
|
+ (0.5, 0.06, 7, 0.15)
|
|
|
2518
|
+ ]
|
|
|
2519
|
+ for (nx, ny, size, a) in specs {
|
|
|
2520
|
+ let center = NSPoint(x: bounds.width * nx, y: bounds.height * ny)
|
|
|
2521
|
+ fillFourPointStar(center: center, radius: size, color: accent.withAlphaComponent(a))
|
|
|
2522
|
+ }
|
|
|
2523
|
+ }
|
|
|
2524
|
+
|
|
|
2525
|
+ private func fillFourPointStar(center: NSPoint, radius: CGFloat, color: NSColor) {
|
|
|
2526
|
+ let path = NSBezierPath()
|
|
|
2527
|
+ for i in 0..<4 {
|
|
|
2528
|
+ let angle = CGFloat(i) * .pi / 2 - .pi / 2
|
|
|
2529
|
+ let x = center.x + cos(angle) * radius
|
|
|
2530
|
+ let y = center.y + sin(angle) * radius
|
|
|
2531
|
+ let point = NSPoint(x: x, y: y)
|
|
|
2532
|
+ if i == 0 {
|
|
|
2533
|
+ path.move(to: point)
|
|
|
2534
|
+ } else {
|
|
|
2535
|
+ path.line(to: point)
|
|
|
2536
|
+ }
|
|
|
2537
|
+ }
|
|
|
2538
|
+ path.close()
|
|
|
2539
|
+ color.setFill()
|
|
|
2540
|
+ path.fill()
|
|
|
2541
|
+ }
|
|
|
2542
|
+}
|
|
|
2543
|
+
|
|
|
2544
|
+/// Circular pastel well with three sparkle symbols (reference: layered stars of different sizes).
|
|
|
2545
|
+private final class WelcomeSparkleClusterView: NSView {
|
|
|
2546
|
+ private let diameter: CGFloat = 72
|
|
|
2547
|
+
|
|
|
2548
|
+ override var intrinsicContentSize: NSSize {
|
|
|
2549
|
+ NSSize(width: diameter, height: diameter)
|
|
|
2550
|
+ }
|
|
|
2551
|
+
|
|
|
2552
|
+ init(iconWell: NSColor, tint: NSColor) {
|
|
|
2553
|
+ super.init(frame: .zero)
|
|
|
2554
|
+ translatesAutoresizingMaskIntoConstraints = false
|
|
|
2555
|
+ wantsLayer = true
|
|
|
2556
|
+ layer?.cornerRadius = diameter / 2
|
|
|
2557
|
+ if #available(macOS 11.0, *) {
|
|
|
2558
|
+ layer?.cornerCurve = .continuous
|
|
|
2559
|
+ }
|
|
|
2560
|
+ layer?.backgroundColor = iconWell.cgColor
|
|
|
2561
|
+
|
|
|
2562
|
+ let configs: [(CGFloat, NSFont.Weight, CGFloat, CGFloat)] = [
|
|
|
2563
|
+ (22, .medium, 0, 0),
|
|
|
2564
|
+ (14, .regular, -14, -11),
|
|
|
2565
|
+ (11, .regular, 15, 13)
|
|
|
2566
|
+ ]
|
|
|
2567
|
+
|
|
|
2568
|
+ let symbolName = Self.sparkleSymbolName()
|
|
|
2569
|
+ for (size, weight, ox, oy) in configs {
|
|
|
2570
|
+ let iv = NSImageView()
|
|
|
2571
|
+ iv.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2572
|
+ iv.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: size, weight: weight)
|
|
|
2573
|
+ iv.image = NSImage(systemSymbolName: symbolName, accessibilityDescription: nil)
|
|
|
2574
|
+ iv.contentTintColor = tint
|
|
|
2575
|
+ addSubview(iv)
|
|
|
2576
|
+ NSLayoutConstraint.activate([
|
|
|
2577
|
+ iv.centerXAnchor.constraint(equalTo: centerXAnchor, constant: ox),
|
|
|
2578
|
+ iv.centerYAnchor.constraint(equalTo: centerYAnchor, constant: oy)
|
|
|
2579
|
+ ])
|
|
|
2580
|
+ }
|
|
|
2581
|
+ }
|
|
|
2582
|
+
|
|
|
2583
|
+ private static func sparkleSymbolName() -> String {
|
|
|
2584
|
+ if NSImage(systemSymbolName: "sparkle", accessibilityDescription: nil) != nil {
|
|
|
2585
|
+ return "sparkle"
|
|
|
2586
|
+ }
|
|
|
2587
|
+ return "sparkles"
|
|
|
2588
|
+ }
|
|
|
2589
|
+
|
|
|
2590
|
+ @available(*, unavailable)
|
|
|
2591
|
+ required init?(coder: NSCoder) {
|
|
|
2592
|
+ fatalError("init(coder:) has not been implemented")
|
|
|
2593
|
+ }
|
|
|
2594
|
+}
|
|
|
2595
|
+
|
|
2470
|
2596
|
/// Home welcome row: three tappable shortcuts that seed the main search field (reference: white cards, pastel icon well, arrow at bottom trailing).
|
|
2471
|
2597
|
private final class FeatureShortcutCardView: NSView {
|
|
2472
|
2598
|
private static let cardCornerRadius: CGFloat = 14
|
|
|
@@ -2493,8 +2619,8 @@ private final class FeatureShortcutCardView: NSView {
|
|
2493
|
2619
|
layer?.shadowRadius = 12
|
|
2494
|
2620
|
layer?.shadowOpacity = 1
|
|
2495
|
2621
|
|
|
2496
|
|
- // `#0047AB` — primary title / icons / arrow.
|
|
2497
|
|
- let primaryBlue = NSColor(srgbRed: 0 / 255, green: 71 / 255, blue: 171 / 255, alpha: 1)
|
|
|
2622
|
+ // `#0052CC` — primary title / icons / arrow (matches welcome hero reference).
|
|
|
2623
|
+ let primaryBlue = NSColor(srgbRed: 0, green: 82 / 255, blue: 204 / 255, alpha: 1)
|
|
2498
|
2624
|
// `#EBF2FF` — circular icon well.
|
|
2499
|
2625
|
let iconWellColor = NSColor(srgbRed: 235 / 255, green: 242 / 255, blue: 255 / 255, alpha: 1)
|
|
2500
|
2626
|
// `#5D6D7E` — muted description.
|