Просмотр исходного кода

Redesign dashboard with centered welcome layout and AI insights card.

Replace stats grid, recommendations list, and insights stack with a centered greeting, gradient main area, and a single AI Profile Insights card with Saved/Interviews toggles. Update sidebar items, switch to a monochrome theme, and add a sparkle accent.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 недель назад: 3
Родитель
Сommit
da224daf1a
2 измененных файлов с 225 добавлено и 327 удалено
  1. 9 42
      App for Indeed/Models/DashboardModels.swift
  2. 216 285
      App for Indeed/Views/DashboardView.swift

+ 9 - 42
App for Indeed/Models/DashboardModels.swift

@@ -11,32 +11,13 @@ struct SidebarItem {
11 11
     let badge: String?
12 12
 }
13 13
 
14
-struct StatCard {
15
-    let title: String
16
-    let value: String
17
-    let trend: String
18
-}
19
-
20
-struct JobRecommendation {
21
-    let title: String
22
-    let company: String
23
-    let location: String
24
-    let matchRate: String
25
-    let postedAgo: String
26
-}
27
-
28
-struct InsightItem {
29
-    let title: String
30
-    let description: String
31
-    let systemImage: String
32
-}
33
-
34 14
 struct DashboardData {
35 15
     let greetingName: String
16
+    let subtitle: String
36 17
     let sidebarItems: [SidebarItem]
37
-    let stats: [StatCard]
38
-    let recommendations: [JobRecommendation]
39
-    let insights: [InsightItem]
18
+    let profileInsightsTitle: String
19
+    let profileInsightsBody: String
20
+    let profileInsightsLinkTitle: String
40 21
 }
41 22
 
42 23
 protocol DashboardDataProviding {
@@ -47,31 +28,17 @@ final class MockDashboardDataProvider: DashboardDataProviding {
47 28
     func loadDashboardData() -> DashboardData {
48 29
         DashboardData(
49 30
             greetingName: "Olivia",
31
+            subtitle: "Find your perfect job with the power of AI.",
50 32
             sidebarItems: [
51 33
                 SidebarItem(title: "Overview", systemImage: "house.fill", badge: nil),
52
-                SidebarItem(title: "AI Job Search", systemImage: "wand.and.stars", badge: "New"),
53
-                SidebarItem(title: "My Jobs", systemImage: "bookmark", badge: nil),
54 34
                 SidebarItem(title: "Saved Jobs", systemImage: "heart", badge: nil),
55
-                SidebarItem(title: "Applications", systemImage: "doc.text", badge: nil),
35
+                SidebarItem(title: "Interviews", systemImage: "list.bullet", badge: nil),
56 36
                 SidebarItem(title: "Profile", systemImage: "person", badge: nil),
57 37
                 SidebarItem(title: "Settings", systemImage: "gearshape", badge: nil)
58 38
             ],
59
-            stats: [
60
-                StatCard(title: "Jobs Found", value: "128", trend: "+24 this week"),
61
-                StatCard(title: "Saved Jobs", value: "32", trend: "+6 this week"),
62
-                StatCard(title: "Applications", value: "14", trend: "+3 this week"),
63
-                StatCard(title: "Interviews", value: "5", trend: "Upcoming")
64
-            ],
65
-            recommendations: [
66
-                JobRecommendation(title: "Senior Product Designer", company: "Dropbox", location: "San Francisco, CA", matchRate: "95% Match", postedAgo: "2h ago"),
67
-                JobRecommendation(title: "UX Researcher", company: "Airbnb", location: "Remote", matchRate: "90% Match", postedAgo: "4h ago"),
68
-                JobRecommendation(title: "Product Manager", company: "Stripe", location: "New York, NY", matchRate: "88% Match", postedAgo: "6h ago")
69
-            ],
70
-            insights: [
71
-                InsightItem(title: "High Demand", description: "Product designer roles are in high demand right now.", systemImage: "chart.line.uptrend.xyaxis"),
72
-                InsightItem(title: "Improve Your Profile", description: "Add more skills to increase your match rate.", systemImage: "person.crop.circle.badge.checkmark"),
73
-                InsightItem(title: "New Opportunities", description: "24 new jobs match your profile this week.", systemImage: "briefcase.fill")
74
-            ]
39
+            profileInsightsTitle: "AI Profile Insights",
40
+            profileInsightsBody: "Your personal dashboard, powered by AI. Check your saved jobs and upcoming interviews.",
41
+            profileInsightsLinkTitle: "View Full Insights"
75 42
         )
76 43
     }
77 44
 }

+ 216 - 285
App for Indeed/Views/DashboardView.swift

@@ -4,40 +4,40 @@
4 4
 //
5 5
 
6 6
 import Cocoa
7
+import QuartzCore
7 8
 
8 9
 final class DashboardView: NSView {
9 10
     private enum Theme {
10
-        static let pageBackground = NSColor(calibratedRed: 0.02, green: 0.02, blue: 0.03, alpha: 1)
11
-        static let chromeBackground = NSColor(calibratedRed: 0.05, green: 0.05, blue: 0.06, alpha: 1)
12
-        static let sidebarBackground = NSColor(calibratedRed: 0.07, green: 0.07, blue: 0.09, alpha: 1)
13
-        static let heroBackground = NSColor(calibratedRed: 0.09, green: 0.09, blue: 0.12, alpha: 1)
14
-        static let sectionBackground = NSColor(calibratedRed: 0.08, green: 0.08, blue: 0.1, alpha: 1)
15
-        static let statCardBackground = NSColor(calibratedRed: 0.1, green: 0.1, blue: 0.12, alpha: 1)
16
-        static let insightCardBackground = NSColor(calibratedRed: 0.09, green: 0.09, blue: 0.11, alpha: 1)
17
-        static let accent = NSColor(calibratedRed: 0.3, green: 0.48, blue: 1.0, alpha: 1)
18
-        static let accentMuted = NSColor(calibratedRed: 0.3, green: 0.48, blue: 1.0, alpha: 0.3)
19
-        static let iconBackground = NSColor(calibratedRed: 0.3, green: 0.48, blue: 1.0, alpha: 0.24)
20
-        static let primaryText = NSColor(calibratedRed: 0.95, green: 0.95, blue: 0.97, alpha: 1)
21
-        static let secondaryText = NSColor(calibratedRed: 0.73, green: 0.74, blue: 0.79, alpha: 1)
22
-        static let tertiaryText = NSColor(calibratedRed: 0.56, green: 0.57, blue: 0.62, alpha: 1)
23
-        static let bubbleText = NSColor(calibratedRed: 0.86, green: 0.89, blue: 0.98, alpha: 1)
24
-        static let bubbleBackground = NSColor(calibratedRed: 0.16, green: 0.2, blue: 0.33, alpha: 0.45)
11
+        static let pageBackground = NSColor(calibratedWhite: 0.02, alpha: 1)
12
+        static let chromeBackground = NSColor(calibratedWhite: 0.04, alpha: 1)
13
+        static let sidebarBackground = NSColor(calibratedWhite: 0.07, alpha: 1)
14
+        static let selectionFill = NSColor(calibratedWhite: 1, alpha: 0.1)
15
+        static let cardBackground = NSColor(calibratedWhite: 0.11, alpha: 1)
16
+        static let toggleBackground = NSColor(calibratedWhite: 0.16, alpha: 1)
17
+        static let primaryText = NSColor(calibratedWhite: 0.96, alpha: 1)
18
+        static let secondaryText = NSColor(calibratedWhite: 0.62, alpha: 1)
19
+        static let tertiaryText = NSColor(calibratedWhite: 0.48, alpha: 1)
20
+        static let linkText = NSColor(calibratedWhite: 0.82, alpha: 1)
25 21
     }
26 22
 
27 23
     private let contentStack = NSStackView()
28 24
     private let documentContainer = NSView()
29 25
     private let chromeContainer = NSView()
30 26
     private let sidebar = NSStackView()
31
-    private let mainColumn = NSStackView()
27
+    private let mainHost = NSView()
28
+    private let mainGradient = NeutralGradientBackgroundView()
29
+    private let mainOverlay = NSStackView()
32 30
     private let greetingLabel = NSTextField(labelWithString: "")
33
-    private let subtitleLabel = NSTextField(labelWithString: "Find your perfect job with the power of AI.")
34
-    private let heroCard = NSView()
35
-    private let statGrid = NSGridView(views: [[]])
36
-    private let recommendationsStack = NSStackView()
37
-    private let insightsStack = NSStackView()
31
+    private let subtitleLabel = NSTextField(labelWithString: "")
32
+    private let insightsCard = NSView()
33
+    private let insightsTitleLabel = NSTextField(labelWithString: "")
34
+    private let insightsBodyLabel = NSTextField(labelWithString: "")
35
+    private let insightsLinkButton = NSButton(title: "", target: nil, action: nil)
36
+    private let togglesLabel = NSTextField(labelWithString: "Dashboard Toggles:")
37
+    private let savedToggleButton = NSButton(title: "Saved", target: nil, action: nil)
38
+    private let interviewsToggleButton = NSButton(title: "Interviews", target: nil, action: nil)
39
+    private let sparkleView = NSImageView()
38 40
     private let scrollView = NSScrollView()
39
-    private var recommendationsWidthConstraint: NSLayoutConstraint?
40
-    private var insightsWidthConstraint: NSLayoutConstraint?
41 41
 
42 42
     override init(frame frameRect: NSRect) {
43 43
         super.init(frame: frameRect)
@@ -56,10 +56,11 @@ final class DashboardView: NSView {
56 56
 
57 57
     func render(_ data: DashboardData) {
58 58
         greetingLabel.stringValue = "Welcome back, \(data.greetingName)! 👋"
59
+        subtitleLabel.stringValue = data.subtitle
60
+        insightsTitleLabel.stringValue = data.profileInsightsTitle
61
+        insightsBodyLabel.stringValue = data.profileInsightsBody
62
+        insightsLinkButton.title = data.profileInsightsLinkTitle
59 63
         configureSidebar(data.sidebarItems)
60
-        configureStats(data.stats)
61
-        configureRecommendations(data.recommendations)
62
-        configureInsights(data.insights)
63 64
         updateDocumentLayout()
64 65
     }
65 66
 
@@ -91,6 +92,7 @@ final class DashboardView: NSView {
91 92
 
92 93
         sidebar.orientation = .vertical
93 94
         sidebar.spacing = 10
95
+        sidebar.distribution = .fill
94 96
         sidebar.alignment = .leading
95 97
         sidebar.wantsLayer = true
96 98
         sidebar.layer?.backgroundColor = Theme.sidebarBackground.cgColor
@@ -98,50 +100,66 @@ final class DashboardView: NSView {
98 100
         sidebar.edgeInsets = NSEdgeInsets(top: 18, left: 14, bottom: 18, right: 14)
99 101
         sidebar.translatesAutoresizingMaskIntoConstraints = false
100 102
 
101
-        mainColumn.orientation = .vertical
102
-        mainColumn.spacing = 14
103
-        mainColumn.alignment = .leading
104
-        mainColumn.translatesAutoresizingMaskIntoConstraints = false
103
+        mainHost.translatesAutoresizingMaskIntoConstraints = false
105 104
 
106
-        greetingLabel.font = .systemFont(ofSize: 30, weight: .bold)
105
+        mainGradient.translatesAutoresizingMaskIntoConstraints = false
106
+        mainGradient.wantsLayer = true
107
+        mainGradient.layer?.cornerRadius = 16
108
+        mainGradient.layer?.masksToBounds = true
109
+
110
+        mainHost.addSubview(mainGradient)
111
+        mainHost.addSubview(mainOverlay)
112
+
113
+        mainOverlay.orientation = .vertical
114
+        mainOverlay.spacing = 0
115
+        mainOverlay.alignment = .centerX
116
+        mainOverlay.translatesAutoresizingMaskIntoConstraints = false
117
+
118
+        greetingLabel.font = .systemFont(ofSize: 32, weight: .bold)
107 119
         greetingLabel.textColor = Theme.primaryText
108
-        subtitleLabel.font = .systemFont(ofSize: 14, weight: .regular)
109
-        subtitleLabel.textColor = Theme.secondaryText
120
+        greetingLabel.alignment = .center
121
+        greetingLabel.maximumNumberOfLines = 1
110 122
 
111
-        heroCard.wantsLayer = true
112
-        heroCard.layer?.backgroundColor = Theme.heroBackground.cgColor
113
-        heroCard.layer?.cornerRadius = 18
114
-        heroCard.translatesAutoresizingMaskIntoConstraints = false
115
-        let hero = buildHeroContent()
116
-        heroCard.addSubview(hero)
117
-
118
-        statGrid.translatesAutoresizingMaskIntoConstraints = false
119
-        statGrid.rowSpacing = 10
120
-        statGrid.columnSpacing = 10
121
-
122
-        let lowerSection = NSStackView()
123
-        lowerSection.orientation = .horizontal
124
-        lowerSection.spacing = 12
125
-        lowerSection.alignment = .top
126
-        lowerSection.distribution = .fill
127
-        lowerSection.translatesAutoresizingMaskIntoConstraints = false
128
-
129
-        let recommendationsBox = sectionBox(title: "Recommended for You", content: recommendationsStack)
130
-        let insightsBox = sectionBox(title: "AI Insights", content: insightsStack)
131
-        recommendationsBox.translatesAutoresizingMaskIntoConstraints = false
132
-        insightsBox.translatesAutoresizingMaskIntoConstraints = false
133
-
134
-        lowerSection.addArrangedSubview(recommendationsBox)
135
-        lowerSection.addArrangedSubview(insightsBox)
136
-
137
-        mainColumn.addArrangedSubview(greetingLabel)
138
-        mainColumn.addArrangedSubview(subtitleLabel)
139
-        mainColumn.addArrangedSubview(heroCard)
140
-        mainColumn.addArrangedSubview(statGrid)
141
-        mainColumn.addArrangedSubview(lowerSection)
123
+        subtitleLabel.font = .systemFont(ofSize: 15, weight: .regular)
124
+        subtitleLabel.textColor = Theme.secondaryText
125
+        subtitleLabel.alignment = .center
126
+        subtitleLabel.maximumNumberOfLines = 2
127
+
128
+        let topSpacer = NSView()
129
+        topSpacer.translatesAutoresizingMaskIntoConstraints = false
130
+        topSpacer.setContentHuggingPriority(.defaultLow, for: .vertical)
131
+        topSpacer.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
132
+
133
+        let headerStack = NSStackView(views: [greetingLabel, subtitleLabel])
134
+        headerStack.orientation = .vertical
135
+        headerStack.spacing = 10
136
+        headerStack.alignment = .centerX
137
+
138
+        configureInsightsCard()
139
+
140
+        let midSpacer = NSView()
141
+        midSpacer.translatesAutoresizingMaskIntoConstraints = false
142
+        midSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: 28).isActive = true
143
+
144
+        let bottomSpacer = NSView()
145
+        bottomSpacer.translatesAutoresizingMaskIntoConstraints = false
146
+        bottomSpacer.setContentHuggingPriority(.defaultLow, for: .vertical)
147
+        bottomSpacer.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
148
+
149
+        mainOverlay.addArrangedSubview(topSpacer)
150
+        mainOverlay.addArrangedSubview(headerStack)
151
+        mainOverlay.addArrangedSubview(midSpacer)
152
+        mainOverlay.addArrangedSubview(insightsCard)
153
+        mainOverlay.addArrangedSubview(bottomSpacer)
154
+
155
+        sparkleView.translatesAutoresizingMaskIntoConstraints = false
156
+        sparkleView.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 22, weight: .regular)
157
+        sparkleView.image = NSImage(systemSymbolName: "sparkle", accessibilityDescription: "Accent")
158
+        sparkleView.contentTintColor = NSColor(calibratedWhite: 0.9, alpha: 0.55)
159
+        mainHost.addSubview(sparkleView)
142 160
 
143 161
         contentStack.addArrangedSubview(sidebar)
144
-        contentStack.addArrangedSubview(mainColumn)
162
+        contentStack.addArrangedSubview(mainHost)
145 163
 
146 164
         NSLayoutConstraint.activate([
147 165
             scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
@@ -161,65 +179,98 @@ final class DashboardView: NSView {
161 179
             contentStack.bottomAnchor.constraint(equalTo: chromeContainer.bottomAnchor),
162 180
 
163 181
             sidebar.widthAnchor.constraint(equalToConstant: 225),
164
-            mainColumn.widthAnchor.constraint(greaterThanOrEqualToConstant: 720),
165
-            heroCard.widthAnchor.constraint(equalTo: mainColumn.widthAnchor),
166
-            heroCard.heightAnchor.constraint(equalToConstant: 140),
167
-
168
-            hero.leadingAnchor.constraint(equalTo: heroCard.leadingAnchor, constant: 22),
169
-            hero.trailingAnchor.constraint(equalTo: heroCard.trailingAnchor, constant: -22),
170
-            hero.topAnchor.constraint(equalTo: heroCard.topAnchor, constant: 18),
171
-            hero.bottomAnchor.constraint(equalTo: heroCard.bottomAnchor, constant: -18),
182
+            mainHost.widthAnchor.constraint(greaterThanOrEqualToConstant: 720),
183
+            mainHost.heightAnchor.constraint(greaterThanOrEqualToConstant: 620),
184
+
185
+            mainGradient.leadingAnchor.constraint(equalTo: mainHost.leadingAnchor),
186
+            mainGradient.trailingAnchor.constraint(equalTo: mainHost.trailingAnchor),
187
+            mainGradient.topAnchor.constraint(equalTo: mainHost.topAnchor),
188
+            mainGradient.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor),
189
+
190
+            mainOverlay.leadingAnchor.constraint(equalTo: mainHost.leadingAnchor),
191
+            mainOverlay.trailingAnchor.constraint(equalTo: mainHost.trailingAnchor),
192
+            mainOverlay.topAnchor.constraint(equalTo: mainHost.topAnchor),
193
+            mainOverlay.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor),
194
+
195
+            greetingLabel.leadingAnchor.constraint(equalTo: mainOverlay.leadingAnchor, constant: 24),
196
+            greetingLabel.trailingAnchor.constraint(equalTo: mainOverlay.trailingAnchor, constant: -24),
197
+            subtitleLabel.leadingAnchor.constraint(equalTo: greetingLabel.leadingAnchor),
198
+            subtitleLabel.trailingAnchor.constraint(equalTo: greetingLabel.trailingAnchor),
199
+
200
+            sparkleView.trailingAnchor.constraint(equalTo: mainHost.trailingAnchor, constant: -28),
201
+            sparkleView.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor, constant: -28)
202
+        ])
203
+    }
172 204
 
173
-            lowerSection.widthAnchor.constraint(equalTo: mainColumn.widthAnchor)
205
+    private func configureInsightsCard() {
206
+        insightsCard.wantsLayer = true
207
+        insightsCard.layer?.backgroundColor = Theme.cardBackground.cgColor
208
+        insightsCard.layer?.cornerRadius = 18
209
+        insightsCard.translatesAutoresizingMaskIntoConstraints = false
210
+
211
+        insightsTitleLabel.font = .systemFont(ofSize: 20, weight: .semibold)
212
+        insightsTitleLabel.textColor = Theme.primaryText
213
+        insightsTitleLabel.alignment = .center
214
+        insightsTitleLabel.maximumNumberOfLines = 1
215
+
216
+        insightsBodyLabel.font = .systemFont(ofSize: 13, weight: .regular)
217
+        insightsBodyLabel.textColor = Theme.secondaryText
218
+        insightsBodyLabel.alignment = .center
219
+        insightsBodyLabel.maximumNumberOfLines = 4
220
+        insightsBodyLabel.lineBreakMode = .byWordWrapping
221
+        insightsBodyLabel.preferredMaxLayoutWidth = 400
222
+
223
+        insightsLinkButton.bezelStyle = .inline
224
+        insightsLinkButton.isBordered = false
225
+        insightsLinkButton.font = .systemFont(ofSize: 13, weight: .medium)
226
+        insightsLinkButton.contentTintColor = Theme.linkText
227
+        insightsLinkButton.setButtonType(.momentaryPushIn)
228
+
229
+        togglesLabel.font = .systemFont(ofSize: 12, weight: .medium)
230
+        togglesLabel.textColor = Theme.tertiaryText
231
+        togglesLabel.alignment = .center
232
+        togglesLabel.maximumNumberOfLines = 1
233
+
234
+        styleToggle(savedToggleButton)
235
+        styleToggle(interviewsToggleButton)
236
+
237
+        let toggleRow = NSStackView(views: [savedToggleButton, interviewsToggleButton])
238
+        toggleRow.orientation = .horizontal
239
+        toggleRow.spacing = 10
240
+        toggleRow.alignment = .centerY
241
+
242
+        let inner = NSStackView(views: [
243
+            insightsTitleLabel,
244
+            insightsBodyLabel,
245
+            insightsLinkButton,
246
+            togglesLabel,
247
+            toggleRow
174 248
         ])
249
+        inner.orientation = .vertical
250
+        inner.spacing = 14
251
+        inner.alignment = .centerX
252
+        inner.translatesAutoresizingMaskIntoConstraints = false
175 253
 
176
-        recommendationsWidthConstraint = recommendationsBox.widthAnchor.constraint(equalTo: mainColumn.widthAnchor, multiplier: 0.68)
177
-        insightsWidthConstraint = insightsBox.widthAnchor.constraint(equalTo: mainColumn.widthAnchor, multiplier: 0.32, constant: -8)
178
-        recommendationsWidthConstraint?.isActive = true
179
-        insightsWidthConstraint?.isActive = true
254
+        insightsCard.addSubview(inner)
255
+        NSLayoutConstraint.activate([
256
+            inner.leadingAnchor.constraint(equalTo: insightsCard.leadingAnchor, constant: 32),
257
+            inner.trailingAnchor.constraint(equalTo: insightsCard.trailingAnchor, constant: -32),
258
+            inner.topAnchor.constraint(equalTo: insightsCard.topAnchor, constant: 28),
259
+            inner.bottomAnchor.constraint(equalTo: insightsCard.bottomAnchor, constant: -28),
260
+            insightsCard.widthAnchor.constraint(equalToConstant: 440)
261
+        ])
180 262
     }
181 263
 
182
-    private func buildHeroContent() -> NSView {
183
-        let container = NSStackView()
184
-        container.orientation = .horizontal
185
-        container.translatesAutoresizingMaskIntoConstraints = false
186
-        container.distribution = .fillEqually
187
-
188
-        let left = NSStackView()
189
-        left.orientation = .vertical
190
-        left.spacing = 8
191
-        left.alignment = .leading
192
-
193
-        let title = NSTextField(labelWithString: "AI Job Search Assistant")
194
-        title.font = .systemFont(ofSize: 24, weight: .semibold)
195
-        title.textColor = Theme.primaryText
196
-
197
-        let body = NSTextField(labelWithString: "Let AI find the best jobs for you on Indeed based on your preferences.")
198
-        body.font = .systemFont(ofSize: 12, weight: .regular)
199
-        body.textColor = Theme.secondaryText
200
-
201
-        let action = NSButton(title: "Find Jobs with AI", target: nil, action: nil)
202
-        action.bezelStyle = .rounded
203
-        action.wantsLayer = true
204
-        action.layer?.backgroundColor = Theme.accent.cgColor
205
-        action.layer?.cornerRadius = 8
206
-        action.contentTintColor = .white
207
-        action.font = .systemFont(ofSize: 13, weight: .semibold)
208
-
209
-        left.addArrangedSubview(title)
210
-        left.addArrangedSubview(body)
211
-        left.addArrangedSubview(action)
212
-
213
-        let right = NSStackView()
214
-        right.orientation = .vertical
215
-        right.alignment = .trailing
216
-        right.addArrangedSubview(tagBubble("Quick"))
217
-        right.addArrangedSubview(tagBubble("Smart"))
218
-        right.addArrangedSubview(tagBubble("Personalized"))
219
-
220
-        container.addArrangedSubview(left)
221
-        container.addArrangedSubview(right)
222
-        return container
264
+    private func styleToggle(_ button: NSButton) {
265
+        button.bezelStyle = .rounded
266
+        button.font = .systemFont(ofSize: 12, weight: .medium)
267
+        button.contentTintColor = Theme.secondaryText
268
+        button.wantsLayer = true
269
+        button.layer?.backgroundColor = Theme.toggleBackground.cgColor
270
+        button.layer?.cornerRadius = 8
271
+        button.translatesAutoresizingMaskIntoConstraints = false
272
+        button.widthAnchor.constraint(equalToConstant: 108).isActive = true
273
+        button.heightAnchor.constraint(equalToConstant: 30).isActive = true
223 274
     }
224 275
 
225 276
     private func configureSidebar(_ items: [SidebarItem]) {
@@ -231,12 +282,13 @@ final class DashboardView: NSView {
231 282
         let brand = NSTextField(labelWithString: "Indeed AI\nJob Finder")
232 283
         brand.font = .systemFont(ofSize: 18, weight: .bold)
233 284
         brand.textColor = Theme.primaryText
285
+        brand.alignment = .left
234 286
         sidebar.addArrangedSubview(brand)
235 287
 
236
-        let spacer = NSView()
237
-        spacer.translatesAutoresizingMaskIntoConstraints = false
238
-        spacer.heightAnchor.constraint(equalToConstant: 8).isActive = true
239
-        sidebar.addArrangedSubview(spacer)
288
+        let titleToMenuSpacer = NSView()
289
+        titleToMenuSpacer.translatesAutoresizingMaskIntoConstraints = false
290
+        titleToMenuSpacer.heightAnchor.constraint(equalToConstant: 24).isActive = true
291
+        sidebar.addArrangedSubview(titleToMenuSpacer)
240 292
 
241 293
         items.enumerated().forEach { index, item in
242 294
             let row = NSStackView()
@@ -246,18 +298,19 @@ final class DashboardView: NSView {
246 298
             row.wantsLayer = true
247 299
             row.layer?.cornerRadius = 8
248 300
             row.edgeInsets = NSEdgeInsets(top: 8, left: 10, bottom: 8, right: 10)
249
-            if index == 0 {
250
-                row.layer?.backgroundColor = Theme.accentMuted.cgColor
301
+            let isSelected = index == 0
302
+            if isSelected {
303
+                row.layer?.backgroundColor = Theme.selectionFill.cgColor
251 304
             }
252 305
 
253 306
             let icon = NSImageView()
254 307
             icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 13, weight: .medium)
255 308
             icon.image = NSImage(systemSymbolName: item.systemImage, accessibilityDescription: item.title)
256
-            icon.contentTintColor = Theme.primaryText
309
+            icon.contentTintColor = isSelected ? Theme.primaryText : Theme.secondaryText
257 310
 
258 311
             let text = NSTextField(labelWithString: item.title)
259 312
             text.font = .systemFont(ofSize: 14, weight: .medium)
260
-            text.textColor = Theme.primaryText
313
+            text.textColor = isSelected ? Theme.primaryText : Theme.secondaryText
261 314
 
262 315
             row.addArrangedSubview(icon)
263 316
             row.addArrangedSubview(text)
@@ -265,9 +318,9 @@ final class DashboardView: NSView {
265 318
             if let badge = item.badge {
266 319
                 let badgeField = NSTextField(labelWithString: badge)
267 320
                 badgeField.font = .systemFont(ofSize: 11, weight: .semibold)
268
-                badgeField.textColor = .white
321
+                badgeField.textColor = Theme.primaryText
269 322
                 badgeField.wantsLayer = true
270
-                badgeField.layer?.backgroundColor = Theme.accent.cgColor
323
+                badgeField.layer?.backgroundColor = Theme.toggleBackground.cgColor
271 324
                 badgeField.layer?.cornerRadius = 8
272 325
                 badgeField.alignment = .center
273 326
                 badgeField.maximumNumberOfLines = 1
@@ -279,173 +332,51 @@ final class DashboardView: NSView {
279 332
             }
280 333
             sidebar.addArrangedSubview(row)
281 334
         }
282
-    }
283 335
 
284
-    private func configureStats(_ stats: [StatCard]) {
285
-        statGrid.removeRow(at: 0)
286
-        let cards = stats.map { stat -> NSView in
287
-            let card = NSStackView()
288
-            card.orientation = .vertical
289
-            card.spacing = 6
290
-            card.edgeInsets = NSEdgeInsets(top: 14, left: 14, bottom: 14, right: 14)
291
-            card.wantsLayer = true
292
-            card.layer?.backgroundColor = Theme.statCardBackground.cgColor
293
-            card.layer?.cornerRadius = 14
294
-
295
-            let value = NSTextField(labelWithString: stat.value)
296
-            value.font = .systemFont(ofSize: 30, weight: .bold)
297
-            value.textColor = Theme.primaryText
298
-
299
-            let title = NSTextField(labelWithString: stat.title)
300
-            title.font = .systemFont(ofSize: 13, weight: .medium)
301
-            title.textColor = Theme.secondaryText
302
-
303
-            let trend = NSTextField(labelWithString: stat.trend)
304
-            trend.font = .systemFont(ofSize: 12, weight: .semibold)
305
-            trend.textColor = NSColor.systemGreen
306
-
307
-            card.addArrangedSubview(value)
308
-            card.addArrangedSubview(title)
309
-            card.addArrangedSubview(trend)
310
-            card.translatesAutoresizingMaskIntoConstraints = false
311
-            card.widthAnchor.constraint(equalToConstant: 185).isActive = true
312
-            card.heightAnchor.constraint(equalToConstant: 120).isActive = true
313
-            return card
314
-        }
315
-        statGrid.addRow(with: cards)
336
+        let sidebarBottomSpacer = NSView()
337
+        sidebarBottomSpacer.translatesAutoresizingMaskIntoConstraints = false
338
+        sidebarBottomSpacer.setContentHuggingPriority(.defaultLow, for: .vertical)
339
+        sidebarBottomSpacer.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
340
+        sidebar.addArrangedSubview(sidebarBottomSpacer)
316 341
     }
317 342
 
318
-    private func configureRecommendations(_ recommendations: [JobRecommendation]) {
319
-        recommendationsStack.orientation = .vertical
320
-        recommendationsStack.spacing = 10
321
-        recommendationsStack.alignment = .leading
322
-        recommendationsStack.arrangedSubviews.forEach {
323
-            recommendationsStack.removeArrangedSubview($0)
324
-            $0.removeFromSuperview()
325
-        }
326
-
327
-        recommendations.forEach { recommendation in
328
-            let row = NSStackView()
329
-            row.orientation = .horizontal
330
-            row.spacing = 12
331
-            row.alignment = .centerY
332
-
333
-            let icon = NSView()
334
-            icon.wantsLayer = true
335
-            icon.layer?.cornerRadius = 10
336
-            icon.layer?.backgroundColor = Theme.iconBackground.cgColor
337
-            icon.translatesAutoresizingMaskIntoConstraints = false
338
-            icon.widthAnchor.constraint(equalToConstant: 38).isActive = true
339
-            icon.heightAnchor.constraint(equalToConstant: 38).isActive = true
340
-
341
-            let textColumn = NSStackView()
342
-            textColumn.orientation = .vertical
343
-            textColumn.spacing = 2
344
-            textColumn.alignment = .leading
345
-
346
-            let title = NSTextField(labelWithString: recommendation.title)
347
-            title.font = .systemFont(ofSize: 15, weight: .semibold)
348
-            title.textColor = Theme.primaryText
349
-
350
-            let subtitle = NSTextField(labelWithString: "\(recommendation.company) • \(recommendation.location)")
351
-            subtitle.font = .systemFont(ofSize: 12, weight: .regular)
352
-            subtitle.textColor = Theme.secondaryText
353
-
354
-            textColumn.addArrangedSubview(title)
355
-            textColumn.addArrangedSubview(subtitle)
356
-
357
-            let meta = NSStackView()
358
-            meta.orientation = .vertical
359
-            meta.alignment = .trailing
360
-
361
-            let match = NSTextField(labelWithString: recommendation.matchRate)
362
-            match.font = .systemFont(ofSize: 12, weight: .semibold)
363
-            match.textColor = NSColor.systemGreen
364
-
365
-            let posted = NSTextField(labelWithString: recommendation.postedAgo)
366
-            posted.font = .systemFont(ofSize: 11, weight: .regular)
367
-            posted.textColor = Theme.tertiaryText
368
-
369
-            meta.addArrangedSubview(match)
370
-            meta.addArrangedSubview(posted)
371
-
372
-            row.addArrangedSubview(icon)
373
-            row.addArrangedSubview(textColumn)
374
-            row.addArrangedSubview(NSView())
375
-            row.addArrangedSubview(meta)
376
-
377
-            recommendationsStack.addArrangedSubview(row)
378
-        }
343
+    private func updateDocumentLayout() {
344
+        documentContainer.layoutSubtreeIfNeeded()
345
+        let fittingHeight = max(chromeContainer.fittingSize.height + 36, bounds.height)
346
+        documentContainer.frame = NSRect(x: 0, y: 0, width: bounds.width, height: fittingHeight)
379 347
     }
348
+}
380 349
 
381
-    private func configureInsights(_ insights: [InsightItem]) {
382
-        insightsStack.orientation = .vertical
383
-        insightsStack.spacing = 12
384
-        insightsStack.alignment = .leading
385
-        insightsStack.arrangedSubviews.forEach {
386
-            insightsStack.removeArrangedSubview($0)
387
-            $0.removeFromSuperview()
388
-        }
350
+// MARK: - Neutral main-area gradient (black / grey only)
389 351
 
390
-        insights.forEach { insight in
391
-            let card = NSStackView()
392
-            card.orientation = .vertical
393
-            card.spacing = 4
394
-            card.edgeInsets = NSEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
395
-            card.wantsLayer = true
396
-            card.layer?.backgroundColor = Theme.insightCardBackground.cgColor
397
-            card.layer?.cornerRadius = 10
398
-
399
-            let title = NSTextField(labelWithString: insight.title)
400
-            title.font = .systemFont(ofSize: 14, weight: .semibold)
401
-            title.textColor = Theme.primaryText
402
-
403
-            let body = NSTextField(labelWithString: insight.description)
404
-            body.font = .systemFont(ofSize: 12, weight: .regular)
405
-            body.textColor = Theme.secondaryText
406
-            body.maximumNumberOfLines = 2
407
-            body.lineBreakMode = .byWordWrapping
408
-
409
-            card.addArrangedSubview(title)
410
-            card.addArrangedSubview(body)
411
-            insightsStack.addArrangedSubview(card)
412
-        }
352
+private final class NeutralGradientBackgroundView: NSView {
353
+    private let gradientLayer = CAGradientLayer()
354
+
355
+    override init(frame frameRect: NSRect) {
356
+        super.init(frame: frameRect)
357
+        commonInit()
413 358
     }
414 359
 
415
-    private func sectionBox(title: String, content: NSStackView) -> NSView {
416
-        let box = NSStackView()
417
-        box.orientation = .vertical
418
-        box.spacing = 12
419
-        box.alignment = .leading
420
-        box.edgeInsets = NSEdgeInsets(top: 14, left: 14, bottom: 14, right: 14)
421
-        box.wantsLayer = true
422
-        box.layer?.backgroundColor = Theme.sectionBackground.cgColor
423
-        box.layer?.cornerRadius = 16
424
-
425
-        let titleLabel = NSTextField(labelWithString: title)
426
-        titleLabel.font = .systemFont(ofSize: 18, weight: .semibold)
427
-        titleLabel.textColor = Theme.primaryText
428
-        box.addArrangedSubview(titleLabel)
429
-        box.addArrangedSubview(content)
430
-        return box
360
+    required init?(coder: NSCoder) {
361
+        super.init(coder: coder)
362
+        commonInit()
431 363
     }
432 364
 
433
-    private func tagBubble(_ text: String) -> NSView {
434
-        let label = NSTextField(labelWithString: text)
435
-        label.font = .systemFont(ofSize: 11, weight: .medium)
436
-        label.textColor = Theme.bubbleText
437
-        label.wantsLayer = true
438
-        label.layer?.backgroundColor = Theme.bubbleBackground.cgColor
439
-        label.layer?.cornerRadius = 7
440
-        label.alignment = .center
441
-        label.translatesAutoresizingMaskIntoConstraints = false
442
-        label.widthAnchor.constraint(equalToConstant: 90).isActive = true
443
-        return label
365
+    private func commonInit() {
366
+        wantsLayer = true
367
+        gradientLayer.colors = [
368
+            NSColor(calibratedWhite: 0.12, alpha: 1).cgColor,
369
+            NSColor(calibratedWhite: 0.05, alpha: 1).cgColor,
370
+            NSColor.black.cgColor
371
+        ]
372
+        gradientLayer.locations = [0, 0.42, 1] as [NSNumber]
373
+        gradientLayer.startPoint = CGPoint(x: 0.5, y: 1)
374
+        gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)
375
+        layer?.addSublayer(gradientLayer)
444 376
     }
445 377
 
446
-    private func updateDocumentLayout() {
447
-        documentContainer.layoutSubtreeIfNeeded()
448
-        let fittingHeight = max(chromeContainer.fittingSize.height + 36, bounds.height)
449
-        documentContainer.frame = NSRect(x: 0, y: 0, width: bounds.width, height: fittingHeight)
378
+    override func layout() {
379
+        super.layout()
380
+        gradientLayer.frame = bounds
450 381
     }
451 382
 }