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

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 2 месяцев назад
Родитель
С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 {
     let badge: String?
 }
 
-struct StatCard {
-    let title: String
-    let value: String
-    let trend: String
-}
-
-struct JobRecommendation {
-    let title: String
-    let company: String
-    let location: String
-    let matchRate: String
-    let postedAgo: String
-}
-
-struct InsightItem {
-    let title: String
-    let description: String
-    let systemImage: String
-}
-
 struct DashboardData {
     let greetingName: String
+    let subtitle: String
     let sidebarItems: [SidebarItem]
-    let stats: [StatCard]
-    let recommendations: [JobRecommendation]
-    let insights: [InsightItem]
+    let profileInsightsTitle: String
+    let profileInsightsBody: String
+    let profileInsightsLinkTitle: String
 }
 
 protocol DashboardDataProviding {
@@ -47,31 +28,17 @@ final class MockDashboardDataProvider: DashboardDataProviding {
     func loadDashboardData() -> DashboardData {
         DashboardData(
             greetingName: "Olivia",
+            subtitle: "Find your perfect job with the power of AI.",
             sidebarItems: [
                 SidebarItem(title: "Overview", systemImage: "house.fill", badge: nil),
-                SidebarItem(title: "AI Job Search", systemImage: "wand.and.stars", badge: "New"),
-                SidebarItem(title: "My Jobs", systemImage: "bookmark", badge: nil),
                 SidebarItem(title: "Saved Jobs", systemImage: "heart", badge: nil),
-                SidebarItem(title: "Applications", systemImage: "doc.text", badge: nil),
+                SidebarItem(title: "Interviews", systemImage: "list.bullet", badge: nil),
                 SidebarItem(title: "Profile", systemImage: "person", badge: nil),
                 SidebarItem(title: "Settings", systemImage: "gearshape", badge: nil)
             ],
-            stats: [
-                StatCard(title: "Jobs Found", value: "128", trend: "+24 this week"),
-                StatCard(title: "Saved Jobs", value: "32", trend: "+6 this week"),
-                StatCard(title: "Applications", value: "14", trend: "+3 this week"),
-                StatCard(title: "Interviews", value: "5", trend: "Upcoming")
-            ],
-            recommendations: [
-                JobRecommendation(title: "Senior Product Designer", company: "Dropbox", location: "San Francisco, CA", matchRate: "95% Match", postedAgo: "2h ago"),
-                JobRecommendation(title: "UX Researcher", company: "Airbnb", location: "Remote", matchRate: "90% Match", postedAgo: "4h ago"),
-                JobRecommendation(title: "Product Manager", company: "Stripe", location: "New York, NY", matchRate: "88% Match", postedAgo: "6h ago")
-            ],
-            insights: [
-                InsightItem(title: "High Demand", description: "Product designer roles are in high demand right now.", systemImage: "chart.line.uptrend.xyaxis"),
-                InsightItem(title: "Improve Your Profile", description: "Add more skills to increase your match rate.", systemImage: "person.crop.circle.badge.checkmark"),
-                InsightItem(title: "New Opportunities", description: "24 new jobs match your profile this week.", systemImage: "briefcase.fill")
-            ]
+            profileInsightsTitle: "AI Profile Insights",
+            profileInsightsBody: "Your personal dashboard, powered by AI. Check your saved jobs and upcoming interviews.",
+            profileInsightsLinkTitle: "View Full Insights"
         )
     }
 }

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

@@ -4,40 +4,40 @@
 //
 
 import Cocoa
+import QuartzCore
 
 final class DashboardView: NSView {
     private enum Theme {
-        static let pageBackground = NSColor(calibratedRed: 0.02, green: 0.02, blue: 0.03, alpha: 1)
-        static let chromeBackground = NSColor(calibratedRed: 0.05, green: 0.05, blue: 0.06, alpha: 1)
-        static let sidebarBackground = NSColor(calibratedRed: 0.07, green: 0.07, blue: 0.09, alpha: 1)
-        static let heroBackground = NSColor(calibratedRed: 0.09, green: 0.09, blue: 0.12, alpha: 1)
-        static let sectionBackground = NSColor(calibratedRed: 0.08, green: 0.08, blue: 0.1, alpha: 1)
-        static let statCardBackground = NSColor(calibratedRed: 0.1, green: 0.1, blue: 0.12, alpha: 1)
-        static let insightCardBackground = NSColor(calibratedRed: 0.09, green: 0.09, blue: 0.11, alpha: 1)
-        static let accent = NSColor(calibratedRed: 0.3, green: 0.48, blue: 1.0, alpha: 1)
-        static let accentMuted = NSColor(calibratedRed: 0.3, green: 0.48, blue: 1.0, alpha: 0.3)
-        static let iconBackground = NSColor(calibratedRed: 0.3, green: 0.48, blue: 1.0, alpha: 0.24)
-        static let primaryText = NSColor(calibratedRed: 0.95, green: 0.95, blue: 0.97, alpha: 1)
-        static let secondaryText = NSColor(calibratedRed: 0.73, green: 0.74, blue: 0.79, alpha: 1)
-        static let tertiaryText = NSColor(calibratedRed: 0.56, green: 0.57, blue: 0.62, alpha: 1)
-        static let bubbleText = NSColor(calibratedRed: 0.86, green: 0.89, blue: 0.98, alpha: 1)
-        static let bubbleBackground = NSColor(calibratedRed: 0.16, green: 0.2, blue: 0.33, alpha: 0.45)
+        static let pageBackground = NSColor(calibratedWhite: 0.02, alpha: 1)
+        static let chromeBackground = NSColor(calibratedWhite: 0.04, alpha: 1)
+        static let sidebarBackground = NSColor(calibratedWhite: 0.07, alpha: 1)
+        static let selectionFill = NSColor(calibratedWhite: 1, alpha: 0.1)
+        static let cardBackground = NSColor(calibratedWhite: 0.11, alpha: 1)
+        static let toggleBackground = NSColor(calibratedWhite: 0.16, alpha: 1)
+        static let primaryText = NSColor(calibratedWhite: 0.96, alpha: 1)
+        static let secondaryText = NSColor(calibratedWhite: 0.62, alpha: 1)
+        static let tertiaryText = NSColor(calibratedWhite: 0.48, alpha: 1)
+        static let linkText = NSColor(calibratedWhite: 0.82, alpha: 1)
     }
 
     private let contentStack = NSStackView()
     private let documentContainer = NSView()
     private let chromeContainer = NSView()
     private let sidebar = NSStackView()
-    private let mainColumn = NSStackView()
+    private let mainHost = NSView()
+    private let mainGradient = NeutralGradientBackgroundView()
+    private let mainOverlay = NSStackView()
     private let greetingLabel = NSTextField(labelWithString: "")
-    private let subtitleLabel = NSTextField(labelWithString: "Find your perfect job with the power of AI.")
-    private let heroCard = NSView()
-    private let statGrid = NSGridView(views: [[]])
-    private let recommendationsStack = NSStackView()
-    private let insightsStack = NSStackView()
+    private let subtitleLabel = NSTextField(labelWithString: "")
+    private let insightsCard = NSView()
+    private let insightsTitleLabel = NSTextField(labelWithString: "")
+    private let insightsBodyLabel = NSTextField(labelWithString: "")
+    private let insightsLinkButton = NSButton(title: "", target: nil, action: nil)
+    private let togglesLabel = NSTextField(labelWithString: "Dashboard Toggles:")
+    private let savedToggleButton = NSButton(title: "Saved", target: nil, action: nil)
+    private let interviewsToggleButton = NSButton(title: "Interviews", target: nil, action: nil)
+    private let sparkleView = NSImageView()
     private let scrollView = NSScrollView()
-    private var recommendationsWidthConstraint: NSLayoutConstraint?
-    private var insightsWidthConstraint: NSLayoutConstraint?
 
     override init(frame frameRect: NSRect) {
         super.init(frame: frameRect)
@@ -56,10 +56,11 @@ final class DashboardView: NSView {
 
     func render(_ data: DashboardData) {
         greetingLabel.stringValue = "Welcome back, \(data.greetingName)! 👋"
+        subtitleLabel.stringValue = data.subtitle
+        insightsTitleLabel.stringValue = data.profileInsightsTitle
+        insightsBodyLabel.stringValue = data.profileInsightsBody
+        insightsLinkButton.title = data.profileInsightsLinkTitle
         configureSidebar(data.sidebarItems)
-        configureStats(data.stats)
-        configureRecommendations(data.recommendations)
-        configureInsights(data.insights)
         updateDocumentLayout()
     }
 
@@ -91,6 +92,7 @@ final class DashboardView: NSView {
 
         sidebar.orientation = .vertical
         sidebar.spacing = 10
+        sidebar.distribution = .fill
         sidebar.alignment = .leading
         sidebar.wantsLayer = true
         sidebar.layer?.backgroundColor = Theme.sidebarBackground.cgColor
@@ -98,50 +100,66 @@ final class DashboardView: NSView {
         sidebar.edgeInsets = NSEdgeInsets(top: 18, left: 14, bottom: 18, right: 14)
         sidebar.translatesAutoresizingMaskIntoConstraints = false
 
-        mainColumn.orientation = .vertical
-        mainColumn.spacing = 14
-        mainColumn.alignment = .leading
-        mainColumn.translatesAutoresizingMaskIntoConstraints = false
+        mainHost.translatesAutoresizingMaskIntoConstraints = false
 
-        greetingLabel.font = .systemFont(ofSize: 30, weight: .bold)
+        mainGradient.translatesAutoresizingMaskIntoConstraints = false
+        mainGradient.wantsLayer = true
+        mainGradient.layer?.cornerRadius = 16
+        mainGradient.layer?.masksToBounds = true
+
+        mainHost.addSubview(mainGradient)
+        mainHost.addSubview(mainOverlay)
+
+        mainOverlay.orientation = .vertical
+        mainOverlay.spacing = 0
+        mainOverlay.alignment = .centerX
+        mainOverlay.translatesAutoresizingMaskIntoConstraints = false
+
+        greetingLabel.font = .systemFont(ofSize: 32, weight: .bold)
         greetingLabel.textColor = Theme.primaryText
-        subtitleLabel.font = .systemFont(ofSize: 14, weight: .regular)
-        subtitleLabel.textColor = Theme.secondaryText
+        greetingLabel.alignment = .center
+        greetingLabel.maximumNumberOfLines = 1
 
-        heroCard.wantsLayer = true
-        heroCard.layer?.backgroundColor = Theme.heroBackground.cgColor
-        heroCard.layer?.cornerRadius = 18
-        heroCard.translatesAutoresizingMaskIntoConstraints = false
-        let hero = buildHeroContent()
-        heroCard.addSubview(hero)
-
-        statGrid.translatesAutoresizingMaskIntoConstraints = false
-        statGrid.rowSpacing = 10
-        statGrid.columnSpacing = 10
-
-        let lowerSection = NSStackView()
-        lowerSection.orientation = .horizontal
-        lowerSection.spacing = 12
-        lowerSection.alignment = .top
-        lowerSection.distribution = .fill
-        lowerSection.translatesAutoresizingMaskIntoConstraints = false
-
-        let recommendationsBox = sectionBox(title: "Recommended for You", content: recommendationsStack)
-        let insightsBox = sectionBox(title: "AI Insights", content: insightsStack)
-        recommendationsBox.translatesAutoresizingMaskIntoConstraints = false
-        insightsBox.translatesAutoresizingMaskIntoConstraints = false
-
-        lowerSection.addArrangedSubview(recommendationsBox)
-        lowerSection.addArrangedSubview(insightsBox)
-
-        mainColumn.addArrangedSubview(greetingLabel)
-        mainColumn.addArrangedSubview(subtitleLabel)
-        mainColumn.addArrangedSubview(heroCard)
-        mainColumn.addArrangedSubview(statGrid)
-        mainColumn.addArrangedSubview(lowerSection)
+        subtitleLabel.font = .systemFont(ofSize: 15, weight: .regular)
+        subtitleLabel.textColor = Theme.secondaryText
+        subtitleLabel.alignment = .center
+        subtitleLabel.maximumNumberOfLines = 2
+
+        let topSpacer = NSView()
+        topSpacer.translatesAutoresizingMaskIntoConstraints = false
+        topSpacer.setContentHuggingPriority(.defaultLow, for: .vertical)
+        topSpacer.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
+
+        let headerStack = NSStackView(views: [greetingLabel, subtitleLabel])
+        headerStack.orientation = .vertical
+        headerStack.spacing = 10
+        headerStack.alignment = .centerX
+
+        configureInsightsCard()
+
+        let midSpacer = NSView()
+        midSpacer.translatesAutoresizingMaskIntoConstraints = false
+        midSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: 28).isActive = true
+
+        let bottomSpacer = NSView()
+        bottomSpacer.translatesAutoresizingMaskIntoConstraints = false
+        bottomSpacer.setContentHuggingPriority(.defaultLow, for: .vertical)
+        bottomSpacer.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
+
+        mainOverlay.addArrangedSubview(topSpacer)
+        mainOverlay.addArrangedSubview(headerStack)
+        mainOverlay.addArrangedSubview(midSpacer)
+        mainOverlay.addArrangedSubview(insightsCard)
+        mainOverlay.addArrangedSubview(bottomSpacer)
+
+        sparkleView.translatesAutoresizingMaskIntoConstraints = false
+        sparkleView.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 22, weight: .regular)
+        sparkleView.image = NSImage(systemSymbolName: "sparkle", accessibilityDescription: "Accent")
+        sparkleView.contentTintColor = NSColor(calibratedWhite: 0.9, alpha: 0.55)
+        mainHost.addSubview(sparkleView)
 
         contentStack.addArrangedSubview(sidebar)
-        contentStack.addArrangedSubview(mainColumn)
+        contentStack.addArrangedSubview(mainHost)
 
         NSLayoutConstraint.activate([
             scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
@@ -161,65 +179,98 @@ final class DashboardView: NSView {
             contentStack.bottomAnchor.constraint(equalTo: chromeContainer.bottomAnchor),
 
             sidebar.widthAnchor.constraint(equalToConstant: 225),
-            mainColumn.widthAnchor.constraint(greaterThanOrEqualToConstant: 720),
-            heroCard.widthAnchor.constraint(equalTo: mainColumn.widthAnchor),
-            heroCard.heightAnchor.constraint(equalToConstant: 140),
-
-            hero.leadingAnchor.constraint(equalTo: heroCard.leadingAnchor, constant: 22),
-            hero.trailingAnchor.constraint(equalTo: heroCard.trailingAnchor, constant: -22),
-            hero.topAnchor.constraint(equalTo: heroCard.topAnchor, constant: 18),
-            hero.bottomAnchor.constraint(equalTo: heroCard.bottomAnchor, constant: -18),
+            mainHost.widthAnchor.constraint(greaterThanOrEqualToConstant: 720),
+            mainHost.heightAnchor.constraint(greaterThanOrEqualToConstant: 620),
+
+            mainGradient.leadingAnchor.constraint(equalTo: mainHost.leadingAnchor),
+            mainGradient.trailingAnchor.constraint(equalTo: mainHost.trailingAnchor),
+            mainGradient.topAnchor.constraint(equalTo: mainHost.topAnchor),
+            mainGradient.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor),
+
+            mainOverlay.leadingAnchor.constraint(equalTo: mainHost.leadingAnchor),
+            mainOverlay.trailingAnchor.constraint(equalTo: mainHost.trailingAnchor),
+            mainOverlay.topAnchor.constraint(equalTo: mainHost.topAnchor),
+            mainOverlay.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor),
+
+            greetingLabel.leadingAnchor.constraint(equalTo: mainOverlay.leadingAnchor, constant: 24),
+            greetingLabel.trailingAnchor.constraint(equalTo: mainOverlay.trailingAnchor, constant: -24),
+            subtitleLabel.leadingAnchor.constraint(equalTo: greetingLabel.leadingAnchor),
+            subtitleLabel.trailingAnchor.constraint(equalTo: greetingLabel.trailingAnchor),
+
+            sparkleView.trailingAnchor.constraint(equalTo: mainHost.trailingAnchor, constant: -28),
+            sparkleView.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor, constant: -28)
+        ])
+    }
 
-            lowerSection.widthAnchor.constraint(equalTo: mainColumn.widthAnchor)
+    private func configureInsightsCard() {
+        insightsCard.wantsLayer = true
+        insightsCard.layer?.backgroundColor = Theme.cardBackground.cgColor
+        insightsCard.layer?.cornerRadius = 18
+        insightsCard.translatesAutoresizingMaskIntoConstraints = false
+
+        insightsTitleLabel.font = .systemFont(ofSize: 20, weight: .semibold)
+        insightsTitleLabel.textColor = Theme.primaryText
+        insightsTitleLabel.alignment = .center
+        insightsTitleLabel.maximumNumberOfLines = 1
+
+        insightsBodyLabel.font = .systemFont(ofSize: 13, weight: .regular)
+        insightsBodyLabel.textColor = Theme.secondaryText
+        insightsBodyLabel.alignment = .center
+        insightsBodyLabel.maximumNumberOfLines = 4
+        insightsBodyLabel.lineBreakMode = .byWordWrapping
+        insightsBodyLabel.preferredMaxLayoutWidth = 400
+
+        insightsLinkButton.bezelStyle = .inline
+        insightsLinkButton.isBordered = false
+        insightsLinkButton.font = .systemFont(ofSize: 13, weight: .medium)
+        insightsLinkButton.contentTintColor = Theme.linkText
+        insightsLinkButton.setButtonType(.momentaryPushIn)
+
+        togglesLabel.font = .systemFont(ofSize: 12, weight: .medium)
+        togglesLabel.textColor = Theme.tertiaryText
+        togglesLabel.alignment = .center
+        togglesLabel.maximumNumberOfLines = 1
+
+        styleToggle(savedToggleButton)
+        styleToggle(interviewsToggleButton)
+
+        let toggleRow = NSStackView(views: [savedToggleButton, interviewsToggleButton])
+        toggleRow.orientation = .horizontal
+        toggleRow.spacing = 10
+        toggleRow.alignment = .centerY
+
+        let inner = NSStackView(views: [
+            insightsTitleLabel,
+            insightsBodyLabel,
+            insightsLinkButton,
+            togglesLabel,
+            toggleRow
         ])
+        inner.orientation = .vertical
+        inner.spacing = 14
+        inner.alignment = .centerX
+        inner.translatesAutoresizingMaskIntoConstraints = false
 
-        recommendationsWidthConstraint = recommendationsBox.widthAnchor.constraint(equalTo: mainColumn.widthAnchor, multiplier: 0.68)
-        insightsWidthConstraint = insightsBox.widthAnchor.constraint(equalTo: mainColumn.widthAnchor, multiplier: 0.32, constant: -8)
-        recommendationsWidthConstraint?.isActive = true
-        insightsWidthConstraint?.isActive = true
+        insightsCard.addSubview(inner)
+        NSLayoutConstraint.activate([
+            inner.leadingAnchor.constraint(equalTo: insightsCard.leadingAnchor, constant: 32),
+            inner.trailingAnchor.constraint(equalTo: insightsCard.trailingAnchor, constant: -32),
+            inner.topAnchor.constraint(equalTo: insightsCard.topAnchor, constant: 28),
+            inner.bottomAnchor.constraint(equalTo: insightsCard.bottomAnchor, constant: -28),
+            insightsCard.widthAnchor.constraint(equalToConstant: 440)
+        ])
     }
 
-    private func buildHeroContent() -> NSView {
-        let container = NSStackView()
-        container.orientation = .horizontal
-        container.translatesAutoresizingMaskIntoConstraints = false
-        container.distribution = .fillEqually
-
-        let left = NSStackView()
-        left.orientation = .vertical
-        left.spacing = 8
-        left.alignment = .leading
-
-        let title = NSTextField(labelWithString: "AI Job Search Assistant")
-        title.font = .systemFont(ofSize: 24, weight: .semibold)
-        title.textColor = Theme.primaryText
-
-        let body = NSTextField(labelWithString: "Let AI find the best jobs for you on Indeed based on your preferences.")
-        body.font = .systemFont(ofSize: 12, weight: .regular)
-        body.textColor = Theme.secondaryText
-
-        let action = NSButton(title: "Find Jobs with AI", target: nil, action: nil)
-        action.bezelStyle = .rounded
-        action.wantsLayer = true
-        action.layer?.backgroundColor = Theme.accent.cgColor
-        action.layer?.cornerRadius = 8
-        action.contentTintColor = .white
-        action.font = .systemFont(ofSize: 13, weight: .semibold)
-
-        left.addArrangedSubview(title)
-        left.addArrangedSubview(body)
-        left.addArrangedSubview(action)
-
-        let right = NSStackView()
-        right.orientation = .vertical
-        right.alignment = .trailing
-        right.addArrangedSubview(tagBubble("Quick"))
-        right.addArrangedSubview(tagBubble("Smart"))
-        right.addArrangedSubview(tagBubble("Personalized"))
-
-        container.addArrangedSubview(left)
-        container.addArrangedSubview(right)
-        return container
+    private func styleToggle(_ button: NSButton) {
+        button.bezelStyle = .rounded
+        button.font = .systemFont(ofSize: 12, weight: .medium)
+        button.contentTintColor = Theme.secondaryText
+        button.wantsLayer = true
+        button.layer?.backgroundColor = Theme.toggleBackground.cgColor
+        button.layer?.cornerRadius = 8
+        button.translatesAutoresizingMaskIntoConstraints = false
+        button.widthAnchor.constraint(equalToConstant: 108).isActive = true
+        button.heightAnchor.constraint(equalToConstant: 30).isActive = true
     }
 
     private func configureSidebar(_ items: [SidebarItem]) {
@@ -231,12 +282,13 @@ final class DashboardView: NSView {
         let brand = NSTextField(labelWithString: "Indeed AI\nJob Finder")
         brand.font = .systemFont(ofSize: 18, weight: .bold)
         brand.textColor = Theme.primaryText
+        brand.alignment = .left
         sidebar.addArrangedSubview(brand)
 
-        let spacer = NSView()
-        spacer.translatesAutoresizingMaskIntoConstraints = false
-        spacer.heightAnchor.constraint(equalToConstant: 8).isActive = true
-        sidebar.addArrangedSubview(spacer)
+        let titleToMenuSpacer = NSView()
+        titleToMenuSpacer.translatesAutoresizingMaskIntoConstraints = false
+        titleToMenuSpacer.heightAnchor.constraint(equalToConstant: 24).isActive = true
+        sidebar.addArrangedSubview(titleToMenuSpacer)
 
         items.enumerated().forEach { index, item in
             let row = NSStackView()
@@ -246,18 +298,19 @@ final class DashboardView: NSView {
             row.wantsLayer = true
             row.layer?.cornerRadius = 8
             row.edgeInsets = NSEdgeInsets(top: 8, left: 10, bottom: 8, right: 10)
-            if index == 0 {
-                row.layer?.backgroundColor = Theme.accentMuted.cgColor
+            let isSelected = index == 0
+            if isSelected {
+                row.layer?.backgroundColor = Theme.selectionFill.cgColor
             }
 
             let icon = NSImageView()
             icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 13, weight: .medium)
             icon.image = NSImage(systemSymbolName: item.systemImage, accessibilityDescription: item.title)
-            icon.contentTintColor = Theme.primaryText
+            icon.contentTintColor = isSelected ? Theme.primaryText : Theme.secondaryText
 
             let text = NSTextField(labelWithString: item.title)
             text.font = .systemFont(ofSize: 14, weight: .medium)
-            text.textColor = Theme.primaryText
+            text.textColor = isSelected ? Theme.primaryText : Theme.secondaryText
 
             row.addArrangedSubview(icon)
             row.addArrangedSubview(text)
@@ -265,9 +318,9 @@ final class DashboardView: NSView {
             if let badge = item.badge {
                 let badgeField = NSTextField(labelWithString: badge)
                 badgeField.font = .systemFont(ofSize: 11, weight: .semibold)
-                badgeField.textColor = .white
+                badgeField.textColor = Theme.primaryText
                 badgeField.wantsLayer = true
-                badgeField.layer?.backgroundColor = Theme.accent.cgColor
+                badgeField.layer?.backgroundColor = Theme.toggleBackground.cgColor
                 badgeField.layer?.cornerRadius = 8
                 badgeField.alignment = .center
                 badgeField.maximumNumberOfLines = 1
@@ -279,173 +332,51 @@ final class DashboardView: NSView {
             }
             sidebar.addArrangedSubview(row)
         }
-    }
 
-    private func configureStats(_ stats: [StatCard]) {
-        statGrid.removeRow(at: 0)
-        let cards = stats.map { stat -> NSView in
-            let card = NSStackView()
-            card.orientation = .vertical
-            card.spacing = 6
-            card.edgeInsets = NSEdgeInsets(top: 14, left: 14, bottom: 14, right: 14)
-            card.wantsLayer = true
-            card.layer?.backgroundColor = Theme.statCardBackground.cgColor
-            card.layer?.cornerRadius = 14
-
-            let value = NSTextField(labelWithString: stat.value)
-            value.font = .systemFont(ofSize: 30, weight: .bold)
-            value.textColor = Theme.primaryText
-
-            let title = NSTextField(labelWithString: stat.title)
-            title.font = .systemFont(ofSize: 13, weight: .medium)
-            title.textColor = Theme.secondaryText
-
-            let trend = NSTextField(labelWithString: stat.trend)
-            trend.font = .systemFont(ofSize: 12, weight: .semibold)
-            trend.textColor = NSColor.systemGreen
-
-            card.addArrangedSubview(value)
-            card.addArrangedSubview(title)
-            card.addArrangedSubview(trend)
-            card.translatesAutoresizingMaskIntoConstraints = false
-            card.widthAnchor.constraint(equalToConstant: 185).isActive = true
-            card.heightAnchor.constraint(equalToConstant: 120).isActive = true
-            return card
-        }
-        statGrid.addRow(with: cards)
+        let sidebarBottomSpacer = NSView()
+        sidebarBottomSpacer.translatesAutoresizingMaskIntoConstraints = false
+        sidebarBottomSpacer.setContentHuggingPriority(.defaultLow, for: .vertical)
+        sidebarBottomSpacer.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
+        sidebar.addArrangedSubview(sidebarBottomSpacer)
     }
 
-    private func configureRecommendations(_ recommendations: [JobRecommendation]) {
-        recommendationsStack.orientation = .vertical
-        recommendationsStack.spacing = 10
-        recommendationsStack.alignment = .leading
-        recommendationsStack.arrangedSubviews.forEach {
-            recommendationsStack.removeArrangedSubview($0)
-            $0.removeFromSuperview()
-        }
-
-        recommendations.forEach { recommendation in
-            let row = NSStackView()
-            row.orientation = .horizontal
-            row.spacing = 12
-            row.alignment = .centerY
-
-            let icon = NSView()
-            icon.wantsLayer = true
-            icon.layer?.cornerRadius = 10
-            icon.layer?.backgroundColor = Theme.iconBackground.cgColor
-            icon.translatesAutoresizingMaskIntoConstraints = false
-            icon.widthAnchor.constraint(equalToConstant: 38).isActive = true
-            icon.heightAnchor.constraint(equalToConstant: 38).isActive = true
-
-            let textColumn = NSStackView()
-            textColumn.orientation = .vertical
-            textColumn.spacing = 2
-            textColumn.alignment = .leading
-
-            let title = NSTextField(labelWithString: recommendation.title)
-            title.font = .systemFont(ofSize: 15, weight: .semibold)
-            title.textColor = Theme.primaryText
-
-            let subtitle = NSTextField(labelWithString: "\(recommendation.company) • \(recommendation.location)")
-            subtitle.font = .systemFont(ofSize: 12, weight: .regular)
-            subtitle.textColor = Theme.secondaryText
-
-            textColumn.addArrangedSubview(title)
-            textColumn.addArrangedSubview(subtitle)
-
-            let meta = NSStackView()
-            meta.orientation = .vertical
-            meta.alignment = .trailing
-
-            let match = NSTextField(labelWithString: recommendation.matchRate)
-            match.font = .systemFont(ofSize: 12, weight: .semibold)
-            match.textColor = NSColor.systemGreen
-
-            let posted = NSTextField(labelWithString: recommendation.postedAgo)
-            posted.font = .systemFont(ofSize: 11, weight: .regular)
-            posted.textColor = Theme.tertiaryText
-
-            meta.addArrangedSubview(match)
-            meta.addArrangedSubview(posted)
-
-            row.addArrangedSubview(icon)
-            row.addArrangedSubview(textColumn)
-            row.addArrangedSubview(NSView())
-            row.addArrangedSubview(meta)
-
-            recommendationsStack.addArrangedSubview(row)
-        }
+    private func updateDocumentLayout() {
+        documentContainer.layoutSubtreeIfNeeded()
+        let fittingHeight = max(chromeContainer.fittingSize.height + 36, bounds.height)
+        documentContainer.frame = NSRect(x: 0, y: 0, width: bounds.width, height: fittingHeight)
     }
+}
 
-    private func configureInsights(_ insights: [InsightItem]) {
-        insightsStack.orientation = .vertical
-        insightsStack.spacing = 12
-        insightsStack.alignment = .leading
-        insightsStack.arrangedSubviews.forEach {
-            insightsStack.removeArrangedSubview($0)
-            $0.removeFromSuperview()
-        }
+// MARK: - Neutral main-area gradient (black / grey only)
 
-        insights.forEach { insight in
-            let card = NSStackView()
-            card.orientation = .vertical
-            card.spacing = 4
-            card.edgeInsets = NSEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
-            card.wantsLayer = true
-            card.layer?.backgroundColor = Theme.insightCardBackground.cgColor
-            card.layer?.cornerRadius = 10
-
-            let title = NSTextField(labelWithString: insight.title)
-            title.font = .systemFont(ofSize: 14, weight: .semibold)
-            title.textColor = Theme.primaryText
-
-            let body = NSTextField(labelWithString: insight.description)
-            body.font = .systemFont(ofSize: 12, weight: .regular)
-            body.textColor = Theme.secondaryText
-            body.maximumNumberOfLines = 2
-            body.lineBreakMode = .byWordWrapping
-
-            card.addArrangedSubview(title)
-            card.addArrangedSubview(body)
-            insightsStack.addArrangedSubview(card)
-        }
+private final class NeutralGradientBackgroundView: NSView {
+    private let gradientLayer = CAGradientLayer()
+
+    override init(frame frameRect: NSRect) {
+        super.init(frame: frameRect)
+        commonInit()
     }
 
-    private func sectionBox(title: String, content: NSStackView) -> NSView {
-        let box = NSStackView()
-        box.orientation = .vertical
-        box.spacing = 12
-        box.alignment = .leading
-        box.edgeInsets = NSEdgeInsets(top: 14, left: 14, bottom: 14, right: 14)
-        box.wantsLayer = true
-        box.layer?.backgroundColor = Theme.sectionBackground.cgColor
-        box.layer?.cornerRadius = 16
-
-        let titleLabel = NSTextField(labelWithString: title)
-        titleLabel.font = .systemFont(ofSize: 18, weight: .semibold)
-        titleLabel.textColor = Theme.primaryText
-        box.addArrangedSubview(titleLabel)
-        box.addArrangedSubview(content)
-        return box
+    required init?(coder: NSCoder) {
+        super.init(coder: coder)
+        commonInit()
     }
 
-    private func tagBubble(_ text: String) -> NSView {
-        let label = NSTextField(labelWithString: text)
-        label.font = .systemFont(ofSize: 11, weight: .medium)
-        label.textColor = Theme.bubbleText
-        label.wantsLayer = true
-        label.layer?.backgroundColor = Theme.bubbleBackground.cgColor
-        label.layer?.cornerRadius = 7
-        label.alignment = .center
-        label.translatesAutoresizingMaskIntoConstraints = false
-        label.widthAnchor.constraint(equalToConstant: 90).isActive = true
-        return label
+    private func commonInit() {
+        wantsLayer = true
+        gradientLayer.colors = [
+            NSColor(calibratedWhite: 0.12, alpha: 1).cgColor,
+            NSColor(calibratedWhite: 0.05, alpha: 1).cgColor,
+            NSColor.black.cgColor
+        ]
+        gradientLayer.locations = [0, 0.42, 1] as [NSNumber]
+        gradientLayer.startPoint = CGPoint(x: 0.5, y: 1)
+        gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)
+        layer?.addSublayer(gradientLayer)
     }
 
-    private func updateDocumentLayout() {
-        documentContainer.layoutSubtreeIfNeeded()
-        let fittingHeight = max(chromeContainer.fittingSize.height + 36, bounds.height)
-        documentContainer.frame = NSRect(x: 0, y: 0, width: bounds.width, height: fittingHeight)
+    override func layout() {
+        super.layout()
+        gradientLayer.frame = bounds
     }
 }