Explorar el Código

Implement dynamic light, dark, and system appearance across the app.

Add shared dashboard palettes, live UI refresh on theme changes, and dark-mode styling for the dashboard shell and loading screen so the Settings theme picker fully applies.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 hace 2 semanas
padre
commit
d94b1c2fee

+ 9 - 7
App for Indeed/Services/AppAppearanceManager.swift

@@ -48,15 +48,21 @@ final class AppAppearanceManager {
48 48
             queue: .main
49 49
         ) { [weak self] _ in
50 50
             guard let self, self.mode == .system else { return }
51
-            self.updateWindowChrome()
51
+            self.apply()
52
+            NotificationCenter.default.post(name: Self.didChangeNotification, object: self)
52 53
         }
53 54
     }
54 55
 
56
+    /// Whether the app is currently rendering with a dark appearance (respects System mode).
57
+    var isDark: Bool {
58
+        NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
59
+    }
60
+
55 61
     var mode: Mode {
56 62
         get {
57 63
             guard let raw = UserDefaults.standard.string(forKey: UserDefaultsKey.appearanceMode),
58 64
                   let stored = Mode(rawValue: raw) else {
59
-                return .light
65
+                return .system
60 66
             }
61 67
             return stored
62 68
         }
@@ -70,11 +76,7 @@ final class AppAppearanceManager {
70 76
 
71 77
     /// Window backing color aligned with dashboard chrome for the active appearance.
72 78
     var windowChromeColor: NSColor {
73
-        let isDark = NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
74
-        if isDark {
75
-            return NSColor(srgbRed: 28 / 255, green: 28 / 255, blue: 30 / 255, alpha: 1)
76
-        }
77
-        return NSColor(srgbRed: 247 / 255, green: 247 / 255, blue: 247 / 255, alpha: 1)
79
+        AppDashboardTheme.chromeBackground
78 80
     }
79 81
 
80 82
     func apply() {

+ 236 - 0
App for Indeed/Services/AppDashboardTheme.swift

@@ -0,0 +1,236 @@
1
+//
2
+//  AppDashboardTheme.swift
3
+//  App for Indeed
4
+//
5
+
6
+import AppKit
7
+
8
+/// Indeed-inspired dashboard colors that follow the active light / dark appearance.
9
+@MainActor
10
+enum AppDashboardTheme {
11
+    static var isDark: Bool { AppAppearanceManager.shared.isDark }
12
+
13
+    static var brandBlue: NSColor {
14
+        NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
15
+    }
16
+
17
+    static var brandBlueHover: NSColor {
18
+        isDark
19
+            ? NSColor(srgbRed: 54 / 255, green: 110 / 255, blue: 198 / 255, alpha: 1)
20
+            : NSColor(srgbRed: 28 / 255, green: 70 / 255, blue: 140 / 255, alpha: 1)
21
+    }
22
+
23
+    static var pageBackground: NSColor {
24
+        isDark
25
+            ? NSColor(srgbRed: 28 / 255, green: 28 / 255, blue: 30 / 255, alpha: 1)
26
+            : NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
27
+    }
28
+
29
+    static var chromeBackground: NSColor {
30
+        isDark
31
+            ? NSColor(srgbRed: 28 / 255, green: 28 / 255, blue: 30 / 255, alpha: 1)
32
+            : NSColor(srgbRed: 247 / 255, green: 247 / 255, blue: 247 / 255, alpha: 1)
33
+    }
34
+
35
+    static var sidebarBackground: NSColor {
36
+        isDark
37
+            ? NSColor(srgbRed: 36 / 255, green: 36 / 255, blue: 38 / 255, alpha: 1)
38
+            : NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
39
+    }
40
+
41
+    static var mainHostBackground: NSColor {
42
+        pageBackground
43
+    }
44
+
45
+    static var welcomeHeroHeadingBlue: NSColor {
46
+        isDark
47
+            ? NSColor(srgbRed: 96 / 255, green: 165 / 255, blue: 250 / 255, alpha: 1)
48
+            : NSColor(srgbRed: 0, green: 82 / 255, blue: 204 / 255, alpha: 1)
49
+    }
50
+
51
+    static var welcomeHeroSubtitleText: NSColor {
52
+        isDark
53
+            ? NSColor(srgbRed: 174 / 255, green: 184 / 255, blue: 198 / 255, alpha: 1)
54
+            : NSColor(srgbRed: 51 / 255, green: 65 / 255, blue: 85 / 255, alpha: 1)
55
+    }
56
+
57
+    static var welcomeHeroIconWell: NSColor {
58
+        isDark
59
+            ? NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.22)
60
+            : NSColor(srgbRed: 239 / 255, green: 246 / 255, blue: 255 / 255, alpha: 1)
61
+    }
62
+
63
+    static var welcomeHeroWaveTint: NSColor {
64
+        isDark
65
+            ? NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.35)
66
+            : NSColor(srgbRed: 186 / 255, green: 210 / 255, blue: 253 / 255, alpha: 1)
67
+    }
68
+
69
+    static var welcomeSubtitleText: NSColor {
70
+        isDark
71
+            ? NSColor(srgbRed: 174 / 255, green: 174 / 255, blue: 178 / 255, alpha: 1)
72
+            : NSColor(srgbRed: 64 / 255, green: 64 / 255, blue: 64 / 255, alpha: 1)
73
+    }
74
+
75
+    static var selectionFill: NSColor {
76
+        NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: isDark ? 0.28 : 0.12)
77
+    }
78
+
79
+    static var selectionFillHover: NSColor {
80
+        NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: isDark ? 0.38 : 0.2)
81
+    }
82
+
83
+    static var cardBackground: NSColor {
84
+        isDark
85
+            ? NSColor(srgbRed: 44 / 255, green: 44 / 255, blue: 46 / 255, alpha: 1)
86
+            : NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
87
+    }
88
+
89
+    static var toggleBackground: NSColor {
90
+        isDark
91
+            ? NSColor(srgbRed: 58 / 255, green: 58 / 255, blue: 60 / 255, alpha: 1)
92
+            : NSColor(srgbRed: 232 / 255, green: 232 / 255, blue: 232 / 255, alpha: 1)
93
+    }
94
+
95
+    static var primaryText: NSColor {
96
+        isDark
97
+            ? NSColor(srgbRed: 245 / 255, green: 245 / 255, blue: 247 / 255, alpha: 1)
98
+            : NSColor(srgbRed: 45 / 255, green: 45 / 255, blue: 45 / 255, alpha: 1)
99
+    }
100
+
101
+    static var secondaryText: NSColor {
102
+        isDark
103
+            ? NSColor(srgbRed: 152 / 255, green: 152 / 255, blue: 157 / 255, alpha: 1)
104
+            : NSColor(srgbRed: 118 / 255, green: 118 / 255, blue: 118 / 255, alpha: 1)
105
+    }
106
+
107
+    static var tertiaryText: NSColor { secondaryText }
108
+
109
+    static var border: NSColor {
110
+        isDark
111
+            ? NSColor(srgbRed: 58 / 255, green: 58 / 255, blue: 60 / 255, alpha: 1)
112
+            : NSColor(srgbRed: 212 / 255, green: 210 / 255, blue: 208 / 255, alpha: 1)
113
+    }
114
+
115
+    static var searchBarBorder: NSColor {
116
+        isDark
117
+            ? NSColor(srgbRed: 72 / 255, green: 96 / 255, blue: 140 / 255, alpha: 0.55)
118
+            : NSColor(srgbRed: 180 / 255, green: 200 / 255, blue: 228 / 255, alpha: 1)
119
+    }
120
+
121
+    static var searchBarBorderHover: NSColor {
122
+        isDark
123
+            ? NSColor(srgbRed: 96 / 255, green: 140 / 255, blue: 210 / 255, alpha: 0.75)
124
+            : NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.55)
125
+    }
126
+
127
+    static var proCardFill: NSColor {
128
+        isDark
129
+            ? NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.14)
130
+            : NSColor(srgbRed: 239 / 255, green: 244 / 255, blue: 252 / 255, alpha: 1)
131
+    }
132
+
133
+    static var proCardBorder: NSColor { border }
134
+
135
+    static var proAccent: NSColor { brandBlue }
136
+
137
+    static var proCTABackground: NSColor { brandBlue }
138
+
139
+    static var proCTAText: NSColor {
140
+        NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
141
+    }
142
+
143
+    static var neutralHoverFill: NSColor {
144
+        isDark
145
+            ? NSColor(srgbRed: 58 / 255, green: 58 / 255, blue: 60 / 255, alpha: 1)
146
+            : NSColor(srgbRed: 240 / 255, green: 240 / 255, blue: 240 / 255, alpha: 1)
147
+    }
148
+
149
+    static var sidebarRowHoverFill: NSColor {
150
+        isDark
151
+            ? NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 0.06)
152
+            : NSColor(srgbRed: 0, green: 0, blue: 0, alpha: 0.04)
153
+    }
154
+
155
+    static var settingsPageBackground: NSColor { mainHostBackground }
156
+
157
+    static var settingsGroupBackground: NSColor { cardBackground }
158
+
159
+    static var settingsIconBackground: NSColor {
160
+        isDark
161
+            ? NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.2)
162
+            : NSColor(srgbRed: 239 / 255, green: 244 / 255, blue: 252 / 255, alpha: 1)
163
+    }
164
+
165
+    static var settingsDivider: NSColor {
166
+        isDark
167
+            ? NSColor(srgbRed: 58 / 255, green: 58 / 255, blue: 60 / 255, alpha: 1)
168
+            : NSColor(srgbRed: 228 / 255, green: 228 / 255, blue: 228 / 255, alpha: 1)
169
+    }
170
+
171
+    static var featureCardBackground: NSColor { cardBackground }
172
+
173
+    static var featureCardBorder: NSColor {
174
+        isDark
175
+            ? NSColor(srgbRed: 58 / 255, green: 58 / 255, blue: 60 / 255, alpha: 1)
176
+            : NSColor(srgbRed: 237 / 255, green: 242 / 255, blue: 247 / 255, alpha: 1)
177
+    }
178
+
179
+    static var featureIconWell: NSColor {
180
+        isDark
181
+            ? NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.22)
182
+            : NSColor(srgbRed: 235 / 255, green: 242 / 255, blue: 255 / 255, alpha: 1)
183
+    }
184
+
185
+    static var featurePrimaryBlue: NSColor { welcomeHeroHeadingBlue }
186
+
187
+    static var featureSecondaryText: NSColor {
188
+        isDark
189
+            ? NSColor(srgbRed: 174 / 255, green: 184 / 255, blue: 198 / 255, alpha: 1)
190
+            : NSColor(srgbRed: 93 / 255, green: 109 / 255, blue: 126 / 255, alpha: 1)
191
+    }
192
+
193
+    static var loadingPageBackgroundTop: NSColor {
194
+        isDark
195
+            ? NSColor(srgbRed: 36 / 255, green: 36 / 255, blue: 38 / 255, alpha: 1)
196
+            : NSColor(srgbRed: 252 / 255, green: 253 / 255, blue: 255 / 255, alpha: 1)
197
+    }
198
+
199
+    static var loadingPageBackgroundBottom: NSColor {
200
+        isDark
201
+            ? NSColor(srgbRed: 28 / 255, green: 28 / 255, blue: 30 / 255, alpha: 1)
202
+            : NSColor(srgbRed: 241 / 255, green: 245 / 255, blue: 252 / 255, alpha: 1)
203
+    }
204
+
205
+    static var loadingIconWell: NSColor { featureIconWell }
206
+
207
+    static var loadingHeadingBlue: NSColor { welcomeHeroHeadingBlue }
208
+
209
+    static var loadingSubtitleText: NSColor { welcomeHeroSubtitleText }
210
+
211
+    static var loadingStatusText: NSColor { secondaryText }
212
+
213
+    static var loadingWaveTint: NSColor { welcomeHeroWaveTint }
214
+
215
+    static var loadingBadgeFill: NSColor {
216
+        isDark
217
+            ? NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.28)
218
+            : NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.1)
219
+    }
220
+
221
+    static var loadingBadgeText: NSColor { brandBlue }
222
+
223
+    static var loadingTrackFill: NSColor {
224
+        isDark
225
+            ? NSColor(srgbRed: 58 / 255, green: 58 / 255, blue: 60 / 255, alpha: 1)
226
+            : NSColor(srgbRed: 228 / 255, green: 233 / 255, blue: 242 / 255, alpha: 1)
227
+    }
228
+
229
+    static var loadingTrackBorder: NSColor { border }
230
+
231
+    static var loadingIconWellBorder: NSColor {
232
+        isDark
233
+            ? NSColor(srgbRed: 58 / 255, green: 58 / 255, blue: 60 / 255, alpha: 1)
234
+            : NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
235
+    }
236
+}

+ 226 - 62
App for Indeed/Views/DashboardView.swift

@@ -20,47 +20,40 @@ private enum PremiumSheetLayout {
20 20
 }
21 21
 
22 22
 final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDelegate, NSSharingServiceDelegate {
23
-    /// Indeed.com-inspired neutrals and brand blue (white surfaces, `#2557a7` accent, `#2d2d2d` / `#767676` text, `#d4d2d0` borders).
23
+    /// Indeed.com-inspired neutrals and brand blue; values follow light / dark / system appearance.
24 24
     private enum Theme {
25
-        static let brandBlue = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
26
-        static let pageBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
27
-        static let chromeBackground = NSColor(srgbRed: 247 / 255, green: 247 / 255, blue: 247 / 255, alpha: 1)
28
-        static let sidebarBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
29
-        static let mainHostBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
30
-        /// Welcome hero (matches reference: `#0052CC` heading, `#334155` subline, `#EFF6FF` icon well).
31
-        static let welcomeHeroHeadingBlue = NSColor(srgbRed: 0, green: 82 / 255, blue: 204 / 255, alpha: 1)
32
-        static let welcomeHeroSubtitleText = NSColor(srgbRed: 51 / 255, green: 65 / 255, blue: 85 / 255, alpha: 1)
33
-        static let welcomeHeroIconWell = NSColor(srgbRed: 239 / 255, green: 246 / 255, blue: 255 / 255, alpha: 1)
34
-        /// Light decorative strokes / sparkles behind the welcome hero.
35
-        static let welcomeHeroWaveTint = NSColor(srgbRed: 186 / 255, green: 210 / 255, blue: 253 / 255, alpha: 1)
36
-        /// Subtitle on the welcome hero: dark neutral gray to match the reference layout.
37
-        static let welcomeSubtitleText = NSColor(srgbRed: 64 / 255, green: 64 / 255, blue: 64 / 255, alpha: 1)
38
-        static let selectionFill = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.12)
39
-        static let cardBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
40
-        static let toggleBackground = NSColor(srgbRed: 232 / 255, green: 232 / 255, blue: 232 / 255, alpha: 1)
41
-        static let primaryText = NSColor(srgbRed: 45 / 255, green: 45 / 255, blue: 45 / 255, alpha: 1)
42
-        static let secondaryText = NSColor(srgbRed: 118 / 255, green: 118 / 255, blue: 118 / 255, alpha: 1)
43
-        static let tertiaryText = NSColor(srgbRed: 118 / 255, green: 118 / 255, blue: 118 / 255, alpha: 1)
44
-        static let border = NSColor(srgbRed: 212 / 255, green: 210 / 255, blue: 208 / 255, alpha: 1)
45
-        static let featureIconWell = NSColor(srgbRed: 220 / 255, green: 235 / 255, blue: 252 / 255, alpha: 1)
46
-        /// Job search bar outer stroke (soft blue-gray, pill field in the reference UI).
47
-        static let searchBarBorder = NSColor(srgbRed: 180 / 255, green: 200 / 255, blue: 228 / 255, alpha: 1)
48
-        /// Search bar border on hover (brand-tinted, matches focus affordance elsewhere).
49
-        static let searchBarBorderHover = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.55)
50
-        static let proCardFill = NSColor(srgbRed: 239 / 255, green: 244 / 255, blue: 252 / 255, alpha: 1)
51
-        static let proCardBorder = NSColor(srgbRed: 212 / 255, green: 210 / 255, blue: 208 / 255, alpha: 1)
52
-        static let proAccent = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
53
-        static let proCTABackground = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
54
-        static let proCTAText = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
55
-        /// Hover states: darker brand blue, stronger tints, and subtle neutral fills used across CTAs, toggles, and the sidebar.
56
-        static let brandBlueHover = NSColor(srgbRed: 28 / 255, green: 70 / 255, blue: 140 / 255, alpha: 1)
57
-        static let selectionFillHover = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.2)
58
-        static let neutralHoverFill = NSColor(srgbRed: 240 / 255, green: 240 / 255, blue: 240 / 255, alpha: 1)
59
-        static let sidebarRowHoverFill = NSColor(srgbRed: 0, green: 0, blue: 0, alpha: 0.04)
60
-        static let settingsPageBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
61
-        static let settingsGroupBackground = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)
62
-        static let settingsIconBackground = NSColor(srgbRed: 239 / 255, green: 244 / 255, blue: 252 / 255, alpha: 1)
63
-        static let settingsDivider = NSColor(srgbRed: 228 / 255, green: 228 / 255, blue: 228 / 255, alpha: 1)
25
+        static var brandBlue: NSColor { AppDashboardTheme.brandBlue }
26
+        static var pageBackground: NSColor { AppDashboardTheme.pageBackground }
27
+        static var chromeBackground: NSColor { AppDashboardTheme.chromeBackground }
28
+        static var sidebarBackground: NSColor { AppDashboardTheme.sidebarBackground }
29
+        static var mainHostBackground: NSColor { AppDashboardTheme.mainHostBackground }
30
+        static var welcomeHeroHeadingBlue: NSColor { AppDashboardTheme.welcomeHeroHeadingBlue }
31
+        static var welcomeHeroSubtitleText: NSColor { AppDashboardTheme.welcomeHeroSubtitleText }
32
+        static var welcomeHeroIconWell: NSColor { AppDashboardTheme.welcomeHeroIconWell }
33
+        static var welcomeHeroWaveTint: NSColor { AppDashboardTheme.welcomeHeroWaveTint }
34
+        static var welcomeSubtitleText: NSColor { AppDashboardTheme.welcomeSubtitleText }
35
+        static var selectionFill: NSColor { AppDashboardTheme.selectionFill }
36
+        static var cardBackground: NSColor { AppDashboardTheme.cardBackground }
37
+        static var toggleBackground: NSColor { AppDashboardTheme.toggleBackground }
38
+        static var primaryText: NSColor { AppDashboardTheme.primaryText }
39
+        static var secondaryText: NSColor { AppDashboardTheme.secondaryText }
40
+        static var tertiaryText: NSColor { AppDashboardTheme.tertiaryText }
41
+        static var border: NSColor { AppDashboardTheme.border }
42
+        static var searchBarBorder: NSColor { AppDashboardTheme.searchBarBorder }
43
+        static var searchBarBorderHover: NSColor { AppDashboardTheme.searchBarBorderHover }
44
+        static var proCardFill: NSColor { AppDashboardTheme.proCardFill }
45
+        static var proCardBorder: NSColor { AppDashboardTheme.proCardBorder }
46
+        static var proAccent: NSColor { AppDashboardTheme.proAccent }
47
+        static var proCTABackground: NSColor { AppDashboardTheme.proCTABackground }
48
+        static var proCTAText: NSColor { AppDashboardTheme.proCTAText }
49
+        static var brandBlueHover: NSColor { AppDashboardTheme.brandBlueHover }
50
+        static var selectionFillHover: NSColor { AppDashboardTheme.selectionFillHover }
51
+        static var neutralHoverFill: NSColor { AppDashboardTheme.neutralHoverFill }
52
+        static var sidebarRowHoverFill: NSColor { AppDashboardTheme.sidebarRowHoverFill }
53
+        static var settingsPageBackground: NSColor { AppDashboardTheme.settingsPageBackground }
54
+        static var settingsGroupBackground: NSColor { AppDashboardTheme.settingsGroupBackground }
55
+        static var settingsIconBackground: NSColor { AppDashboardTheme.settingsIconBackground }
56
+        static var settingsDivider: NSColor { AppDashboardTheme.settingsDivider }
64 57
     }
65 58
 
66 59
     /// Multiline `NSTextField` often ignores `alignment` for wrapped runs; explicit paragraph alignment matches the title.
@@ -163,6 +156,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
163 156
     private weak var sidebarUpgradeDescription: NSTextField?
164 157
     private weak var sidebarUpgradeButton: HoverableButton?
165 158
     private var subscriptionObserver: NSObjectProtocol?
159
+    private var appearanceObserver: NSObjectProtocol?
166 160
     /// Retains the system share picker until the user picks a destination or dismisses the menu.
167 161
     private var appSharePicker: NSSharingServicePicker?
168 162
 
@@ -178,17 +172,37 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
178 172
     override init(frame frameRect: NSRect) {
179 173
         super.init(frame: frameRect)
180 174
         setupLayout()
175
+        registerAppearanceObservers()
181 176
     }
182 177
 
183 178
     required init?(coder: NSCoder) {
184 179
         super.init(coder: coder)
185 180
         setupLayout()
181
+        registerAppearanceObservers()
186 182
     }
187 183
 
188 184
     deinit {
189 185
         if let subscriptionObserver {
190 186
             NotificationCenter.default.removeObserver(subscriptionObserver)
191 187
         }
188
+        if let appearanceObserver {
189
+            NotificationCenter.default.removeObserver(appearanceObserver)
190
+        }
191
+    }
192
+
193
+    override func viewDidChangeEffectiveAppearance() {
194
+        super.viewDidChangeEffectiveAppearance()
195
+        applyCurrentAppearance()
196
+    }
197
+
198
+    private func registerAppearanceObservers() {
199
+        appearanceObserver = NotificationCenter.default.addObserver(
200
+            forName: AppAppearanceManager.didChangeNotification,
201
+            object: nil,
202
+            queue: .main
203
+        ) { [weak self] _ in
204
+            self?.applyCurrentAppearance()
205
+        }
192 206
     }
193 207
 
194 208
     override func viewDidMoveToWindow() {
@@ -220,6 +234,95 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
220 234
         savedJobOrder = Self.normalizedSavedJobs(SavedJobsStore.load())
221 235
         resetChatState()
222 236
         updateMainContentVisibility()
237
+        applyCurrentAppearance()
238
+    }
239
+
240
+    private func applyCurrentAppearance() {
241
+        window?.backgroundColor = AppAppearanceManager.shared.windowChromeColor
242
+
243
+        layer?.backgroundColor = Theme.chromeBackground.cgColor
244
+        chromeContainer.layer?.backgroundColor = Theme.chromeBackground.cgColor
245
+        sidebar.layer?.backgroundColor = Theme.sidebarBackground.cgColor
246
+        mainHost.layer?.backgroundColor = Theme.mainHostBackground.cgColor
247
+        indeedJobBrowserHost.layer?.backgroundColor = Theme.mainHostBackground.cgColor
248
+        nonHomeHost.layer?.backgroundColor = Theme.mainHostBackground.cgColor
249
+        cvMakerPageContainer.layer?.backgroundColor = Theme.mainHostBackground.cgColor
250
+        profilePageContainer.layer?.backgroundColor = Theme.mainHostBackground.cgColor
251
+        settingsPageContainer.layer?.backgroundColor = Theme.settingsPageBackground.cgColor
252
+
253
+        greetingLabel.textColor = Theme.welcomeHeroHeadingBlue
254
+        subtitleLabel.textColor = Theme.welcomeHeroSubtitleText
255
+        welcomeHeroBackgroundView.waveTint = Theme.welcomeHeroWaveTint
256
+        welcomeLogoWell.layer?.backgroundColor = Theme.welcomeHeroIconWell.cgColor
257
+
258
+        nonHomeTitleLabel.textColor = Theme.primaryText
259
+        nonHomeSubtitleLabel.textColor = Theme.secondaryText
260
+        savedJobsPageTitleLabel.textColor = Theme.primaryText
261
+        savedJobsPageSubtitleLabel.textColor = Theme.secondaryText
262
+
263
+        let searchHovering = searchCard.isHovering
264
+        searchCard.layer?.backgroundColor = (searchHovering ? Theme.neutralHoverFill : Theme.cardBackground).cgColor
265
+        searchCard.layer?.borderColor = (searchHovering ? Theme.searchBarBorderHover : Theme.searchBarBorder).cgColor
266
+        jobKeywordsField.textColor = Theme.primaryText
267
+        jobKeywordsField.placeholderAttributedString = NSAttributedString(
268
+            string: "Ask for roles, skills, salary, or job descriptions...",
269
+            attributes: [
270
+                .foregroundColor: Theme.secondaryText,
271
+                .font: NSFont.systemFont(ofSize: 14, weight: .regular)
272
+            ]
273
+        )
274
+        jobSearchIcon.contentTintColor = Theme.brandBlue
275
+        let ctaHovering = findJobsButton.isHovering
276
+        findJobsCTAPill.layer?.backgroundColor = (ctaHovering ? Theme.brandBlueHover : Theme.brandBlue).cgColor
277
+        sendIconView.contentTintColor = Theme.proCTAText
278
+        sendLabel.textColor = Theme.proCTAText
279
+
280
+        appearanceModeSegment?.selectedSegment = AppAppearanceManager.shared.mode.segmentIndex
281
+        refreshSettingsPageAppearance(in: settingsPageContainer)
282
+        rebuildFeatureShortcutCards()
283
+        configureSidebar()
284
+        reloadSavedJobsListings()
285
+        rebuildChatUI()
286
+        applyProSubscriptionToSidebar()
287
+        needsLayout = true
288
+    }
289
+
290
+    private func refreshSettingsPageAppearance(in root: NSView) {
291
+        for case let field as NSTextField in root.subviewsRecursive() where !field.isEditable {
292
+            switch field.font?.pointSize {
293
+            case 12, 14:
294
+                field.textColor = Theme.secondaryText
295
+            default:
296
+                break
297
+            }
298
+        }
299
+        for case let stack as NSStackView in root.subviewsRecursive() where stack.orientation == .vertical && stack.layer?.cornerRadius == 14 {
300
+            stack.layer?.backgroundColor = Theme.settingsGroupBackground.cgColor
301
+            stack.layer?.borderColor = Theme.border.cgColor
302
+            for case let divider as NSView in stack.arrangedSubviews where divider.bounds.height <= 1.5 && divider.wantsLayer {
303
+                divider.layer?.backgroundColor = Theme.settingsDivider.cgColor
304
+            }
305
+        }
306
+        for case let tile as NSView in root.subviewsRecursive() where tile.layer?.cornerRadius == 9 && tile.bounds.width == 38 {
307
+            tile.layer?.backgroundColor = Theme.settingsIconBackground.cgColor
308
+            for case let icon as NSImageView in tile.subviews {
309
+                icon.contentTintColor = Theme.brandBlue
310
+            }
311
+        }
312
+    }
313
+
314
+    private func rebuildFeatureShortcutCards() {
315
+        let selectedIndex = featureCardsRow.arrangedSubviews.firstIndex {
316
+            ($0 as? FeatureShortcutCardView)?.isSelected == true
317
+        }
318
+        featureCardsRow.arrangedSubviews.forEach {
319
+            featureCardsRow.removeArrangedSubview($0)
320
+            $0.removeFromSuperview()
321
+        }
322
+        configureFeatureShortcutCards()
323
+        if let selectedIndex, let shortcut = FeatureShortcut(rawValue: selectedIndex) {
324
+            selectFeatureShortcut(shortcut)
325
+        }
223 326
     }
224 327
 
225 328
     private func setupLayout() {
@@ -2039,7 +2142,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
2039 2142
                         newJobsCount: freshJobs.count,
2040 2143
                         isContinuation: isContinuation
2041 2144
                     )
2042
-                    self.chatMessages.append(ChatMessage(role: "assistant", content: reply))
2145
+                    self.chatMessages.append(ChatMessage(role: "assistant", content: reply, attachedJobs: freshJobs.isEmpty ? nil : freshJobs))
2043 2146
                     self.appendChatBubble(text: reply, isUser: false, jobs: freshJobs)
2044 2147
                 case .failure(let error):
2045 2148
                     self.appendChatBubble(text: error.localizedDescription, isUser: false)
@@ -2175,13 +2278,33 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
2175 2278
         trailingLoadMoreJobsButton = nil
2176 2279
         chatMessages.removeAll()
2177 2280
         lastSearchResults.removeAll()
2281
+        clearChatStack()
2282
+        let welcome = "Tell me what role you want and I will return job descriptions, key skills, and a quick fit summary."
2283
+        chatMessages.append(ChatMessage(role: "assistant", content: welcome))
2284
+        appendChatBubble(text: welcome, isUser: false)
2285
+    }
2286
+
2287
+    private func clearChatStack() {
2178 2288
         chatStack.arrangedSubviews.forEach {
2179 2289
             chatStack.removeArrangedSubview($0)
2180 2290
             $0.removeFromSuperview()
2181 2291
         }
2182
-        let welcome = "Tell me what role you want and I will return job descriptions, key skills, and a quick fit summary."
2183
-        chatMessages.append(ChatMessage(role: "assistant", content: welcome))
2184
-        appendChatBubble(text: welcome, isUser: false)
2292
+    }
2293
+
2294
+    private func rebuildChatUI() {
2295
+        guard !chatMessages.isEmpty else { return }
2296
+        removeInlineChatThinkingRow()
2297
+        trailingLoadMoreJobsRow = nil
2298
+        trailingLoadMoreJobsButton = nil
2299
+        clearChatStack()
2300
+        for message in chatMessages {
2301
+            let isUser = message.role == "user"
2302
+            appendChatBubble(text: message.content, isUser: isUser, jobs: message.attachedJobs)
2303
+        }
2304
+        for host in chatStack.arrangedSubviews {
2305
+            host.alphaValue = 1
2306
+        }
2307
+        updateChatBubbleWidths()
2185 2308
     }
2186 2309
 
2187 2310
     private func appendChatBubble(text: String, isUser: Bool, jobs: [JobListing]? = nil) {
@@ -2741,6 +2864,31 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
2741 2864
 private struct ChatMessage: Codable {
2742 2865
     let role: String
2743 2866
     let content: String
2867
+    /// Job cards shown under this assistant message (not sent to the API).
2868
+    var attachedJobs: [JobListing]?
2869
+
2870
+    enum CodingKeys: String, CodingKey {
2871
+        case role
2872
+        case content
2873
+    }
2874
+
2875
+    init(role: String, content: String, attachedJobs: [JobListing]? = nil) {
2876
+        self.role = role
2877
+        self.content = content
2878
+        self.attachedJobs = attachedJobs
2879
+    }
2880
+}
2881
+
2882
+private extension NSView {
2883
+    func subviewsRecursive() -> [NSView] {
2884
+        var result: [NSView] = []
2885
+        var stack: [NSView] = [self]
2886
+        while let view = stack.popLast() {
2887
+            result.append(view)
2888
+            stack.append(contentsOf: view.subviews)
2889
+        }
2890
+        return result
2891
+    }
2744 2892
 }
2745 2893
 
2746 2894
 private final class OpenAIJobSearchService {
@@ -3303,9 +3451,12 @@ private final class WelcomeHeroBackgroundView: NSView {
3303 3451
 /// Home welcome row: three tappable shortcuts that seed the main search field (reference: white cards, pastel icon well, arrow at bottom trailing).
3304 3452
 private final class FeatureShortcutCardView: NSView {
3305 3453
     private static let cardCornerRadius: CGFloat = 14
3306
-    private static let primaryBlue = NSColor(srgbRed: 0, green: 82 / 255, blue: 204 / 255, alpha: 1)
3307
-    private static let defaultBorderColor = NSColor(srgbRed: 237 / 255, green: 242 / 255, blue: 247 / 255, alpha: 1)
3308 3454
     var isSelected = false { didSet { updateSelectionAppearance() } }
3455
+    private weak var titleField: NSTextField?
3456
+    private weak var subtitleField: NSTextField?
3457
+    private weak var iconHost: NSView?
3458
+    private weak var iconView: NSImageView?
3459
+    private weak var chevronView: NSImageView?
3309 3460
     private weak var actionTarget: AnyObject?
3310 3461
     private var actionSelector: Selector
3311 3462
 
@@ -3319,37 +3470,31 @@ private final class FeatureShortcutCardView: NSView {
3319 3470
         if #available(macOS 11.0, *) {
3320 3471
             layer?.cornerCurve = .continuous
3321 3472
         }
3322
-        layer?.backgroundColor = NSColor.white.cgColor
3473
+        layer?.backgroundColor = AppDashboardTheme.featureCardBackground.cgColor
3323 3474
         layer?.masksToBounds = false
3324
-        layer?.shadowColor = NSColor.black.withAlphaComponent(0.06).cgColor
3475
+        layer?.shadowColor = NSColor.black.withAlphaComponent(AppDashboardTheme.isDark ? 0.35 : 0.06).cgColor
3325 3476
         layer?.shadowOffset = CGSize(width: 0, height: 2)
3326 3477
         layer?.shadowRadius = 12
3327 3478
         layer?.shadowOpacity = 1
3328 3479
         updateSelectionAppearance()
3329 3480
 
3330
-        let primaryBlue = Self.primaryBlue
3331
-        // `#EBF2FF` — circular icon well.
3332
-        let iconWellColor = NSColor(srgbRed: 235 / 255, green: 242 / 255, blue: 255 / 255, alpha: 1)
3333
-        // `#5D6D7E` — muted description.
3334
-        let secondary = NSColor(srgbRed: 93 / 255, green: 109 / 255, blue: 126 / 255, alpha: 1)
3335
-
3336 3481
         let iconSize: CGFloat = 40
3337 3482
         let iconHost = NSView()
3483
+        self.iconHost = iconHost
3338 3484
         iconHost.translatesAutoresizingMaskIntoConstraints = false
3339 3485
         iconHost.wantsLayer = true
3340
-        iconHost.layer?.backgroundColor = iconWellColor.cgColor
3341 3486
         iconHost.layer?.cornerRadius = iconSize / 2
3342 3487
 
3343 3488
         let icon = NSImageView()
3489
+        self.iconView = icon
3344 3490
         icon.translatesAutoresizingMaskIntoConstraints = false
3345 3491
         icon.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 16, weight: .regular)
3346 3492
         icon.image = NSImage(systemSymbolName: symbolName, accessibilityDescription: nil)
3347
-        icon.contentTintColor = primaryBlue
3348 3493
         iconHost.addSubview(icon)
3349 3494
 
3350 3495
         let titleField = NSTextField(wrappingLabelWithString: title)
3496
+        self.titleField = titleField
3351 3497
         titleField.font = .systemFont(ofSize: 15, weight: .bold)
3352
-        titleField.textColor = primaryBlue
3353 3498
         titleField.maximumNumberOfLines = 1
3354 3499
         titleField.isEditable = false
3355 3500
         titleField.isBordered = false
@@ -3357,8 +3502,8 @@ private final class FeatureShortcutCardView: NSView {
3357 3502
         titleField.alignment = .left
3358 3503
 
3359 3504
         let subtitleField = NSTextField(wrappingLabelWithString: subtitle)
3505
+        self.subtitleField = subtitleField
3360 3506
         subtitleField.font = .systemFont(ofSize: 12, weight: .regular)
3361
-        subtitleField.textColor = secondary
3362 3507
         subtitleField.maximumNumberOfLines = 2
3363 3508
         subtitleField.isEditable = false
3364 3509
         subtitleField.isBordered = false
@@ -3369,10 +3514,10 @@ private final class FeatureShortcutCardView: NSView {
3369 3514
         subtitleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
3370 3515
 
3371 3516
         let chevron = NSImageView()
3517
+        self.chevronView = chevron
3372 3518
         chevron.translatesAutoresizingMaskIntoConstraints = false
3373 3519
         chevron.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 12, weight: .semibold)
3374 3520
         chevron.image = NSImage(systemSymbolName: "arrow.right", accessibilityDescription: nil)
3375
-        chevron.contentTintColor = primaryBlue
3376 3521
         chevron.setContentHuggingPriority(.required, for: .horizontal)
3377 3522
         chevron.setContentCompressionResistancePriority(.required, for: .horizontal)
3378 3523
 
@@ -3418,17 +3563,36 @@ private final class FeatureShortcutCardView: NSView {
3418 3563
         setAccessibilityElement(true)
3419 3564
         setAccessibilityRole(.button)
3420 3565
         setAccessibilityLabel("\(title). \(subtitle)")
3566
+        applyCardAppearance()
3567
+    }
3568
+
3569
+    func applyCardAppearance() {
3570
+        layer?.backgroundColor = AppDashboardTheme.featureCardBackground.cgColor
3571
+        layer?.shadowColor = NSColor.black.withAlphaComponent(AppDashboardTheme.isDark ? 0.35 : 0.06).cgColor
3572
+        iconHost?.layer?.backgroundColor = AppDashboardTheme.featureIconWell.cgColor
3573
+        let accent = AppDashboardTheme.featurePrimaryBlue
3574
+        iconView?.contentTintColor = accent
3575
+        titleField?.textColor = accent
3576
+        subtitleField?.textColor = AppDashboardTheme.featureSecondaryText
3577
+        chevronView?.contentTintColor = accent
3578
+        updateSelectionAppearance()
3579
+    }
3580
+
3581
+    override func viewDidChangeEffectiveAppearance() {
3582
+        super.viewDidChangeEffectiveAppearance()
3583
+        applyCardAppearance()
3421 3584
     }
3422 3585
 
3423 3586
     private func updateSelectionAppearance() {
3424 3587
         guard let layer else { return }
3588
+        let accent = AppDashboardTheme.featurePrimaryBlue
3425 3589
         if isSelected {
3426 3590
             layer.borderWidth = 2
3427
-            layer.borderColor = Self.primaryBlue.cgColor
3428
-            layer.shadowOpacity = 0.1
3591
+            layer.borderColor = accent.cgColor
3592
+            layer.shadowOpacity = AppDashboardTheme.isDark ? 0.2 : 0.1
3429 3593
         } else {
3430 3594
             layer.borderWidth = 1
3431
-            layer.borderColor = Self.defaultBorderColor.cgColor
3595
+            layer.borderColor = AppDashboardTheme.featureCardBorder.cgColor
3432 3596
             layer.shadowOpacity = 1
3433 3597
         }
3434 3598
         setAccessibilitySelected(isSelected)

+ 65 - 17
App for Indeed/Views/LoadingView.swift

@@ -8,19 +8,22 @@ import QuartzCore
8 8
 
9 9
 final class LoadingView: NSView {
10 10
     private enum Theme {
11
-        static let brandBlue = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
12
-        static let brandBlueLight = NSColor(srgbRed: 54 / 255, green: 110 / 255, blue: 198 / 255, alpha: 1)
13
-        static let pageBackgroundTop = NSColor(srgbRed: 252 / 255, green: 253 / 255, blue: 255 / 255, alpha: 1)
14
-        static let pageBackgroundBottom = NSColor(srgbRed: 241 / 255, green: 245 / 255, blue: 252 / 255, alpha: 1)
15
-        static let iconWell = NSColor(srgbRed: 239 / 255, green: 246 / 255, blue: 255 / 255, alpha: 1)
16
-        static let headingBlue = NSColor(srgbRed: 0, green: 82 / 255, blue: 204 / 255, alpha: 1)
17
-        static let subtitleText = NSColor(srgbRed: 51 / 255, green: 65 / 255, blue: 85 / 255, alpha: 1)
18
-        static let statusText = NSColor(srgbRed: 118 / 255, green: 118 / 255, blue: 118 / 255, alpha: 1)
19
-        static let waveTint = NSColor(srgbRed: 186 / 255, green: 210 / 255, blue: 253 / 255, alpha: 1)
20
-        static let badgeFill = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 0.1)
21
-        static let badgeText = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
11
+        static var brandBlue: NSColor { AppDashboardTheme.brandBlue }
12
+        static var brandBlueLight: NSColor { AppDashboardTheme.brandBlueHover }
13
+        static var pageBackgroundTop: NSColor { AppDashboardTheme.loadingPageBackgroundTop }
14
+        static var pageBackgroundBottom: NSColor { AppDashboardTheme.loadingPageBackgroundBottom }
15
+        static var iconWell: NSColor { AppDashboardTheme.loadingIconWell }
16
+        static var headingBlue: NSColor { AppDashboardTheme.loadingHeadingBlue }
17
+        static var subtitleText: NSColor { AppDashboardTheme.loadingSubtitleText }
18
+        static var statusText: NSColor { AppDashboardTheme.loadingStatusText }
19
+        static var waveTint: NSColor { AppDashboardTheme.loadingWaveTint }
20
+        static var badgeFill: NSColor { AppDashboardTheme.loadingBadgeFill }
21
+        static var badgeText: NSColor { AppDashboardTheme.loadingBadgeText }
22
+        static var iconWellBorder: NSColor { AppDashboardTheme.loadingIconWellBorder }
22 23
     }
23 24
 
25
+    private var appearanceObserver: NSObjectProtocol?
26
+
24 27
     private let backgroundGradientHost = NSView()
25 28
     private let heroBackground = LoadingSplashBackgroundView()
26 29
     private let iconWell = NSView()
@@ -39,6 +42,19 @@ final class LoadingView: NSView {
39 42
     override init(frame frameRect: NSRect) {
40 43
         super.init(frame: frameRect)
41 44
         setUp()
45
+        appearanceObserver = NotificationCenter.default.addObserver(
46
+            forName: AppAppearanceManager.didChangeNotification,
47
+            object: nil,
48
+            queue: .main
49
+        ) { [weak self] _ in
50
+            self?.applyCurrentAppearance()
51
+        }
52
+    }
53
+
54
+    deinit {
55
+        if let appearanceObserver {
56
+            NotificationCenter.default.removeObserver(appearanceObserver)
57
+        }
42 58
     }
43 59
 
44 60
     @available(*, unavailable)
@@ -46,6 +62,11 @@ final class LoadingView: NSView {
46 62
         fatalError("init(coder:) has not been implemented")
47 63
     }
48 64
 
65
+    override func viewDidChangeEffectiveAppearance() {
66
+        super.viewDidChangeEffectiveAppearance()
67
+        applyCurrentAppearance()
68
+    }
69
+
49 70
     override func layout() {
50 71
         super.layout()
51 72
         backgroundGradientHost.layer?.sublayers?
@@ -98,7 +119,7 @@ final class LoadingView: NSView {
98 119
             iconWell.layer?.cornerCurve = .continuous
99 120
         }
100 121
         iconWell.layer?.masksToBounds = true
101
-        iconWell.layer?.borderColor = NSColor.white.cgColor
122
+        iconWell.layer?.borderColor = Theme.iconWellBorder.cgColor
102 123
         iconWell.layer?.borderWidth = 3
103 124
         iconWell.layer?.shadowColor = NSColor.black.cgColor
104 125
         iconWell.layer?.shadowOpacity = 0.08
@@ -219,6 +240,9 @@ final class LoadingView: NSView {
219 240
     }
220 241
 
221 242
     private func installPageGradient() {
243
+        backgroundGradientHost.layer?.sublayers?
244
+            .filter { $0.name == "pageGradient" }
245
+            .forEach { $0.removeFromSuperlayer() }
222 246
         let gradient = CAGradientLayer()
223 247
         gradient.name = "pageGradient"
224 248
         gradient.colors = [
@@ -231,17 +255,31 @@ final class LoadingView: NSView {
231 255
         backgroundGradientHost.layer?.addSublayer(gradient)
232 256
     }
233 257
 
258
+    private func applyCurrentAppearance() {
259
+        window?.backgroundColor = AppAppearanceManager.shared.windowChromeColor
260
+        heroBackground.waveTint = Theme.waveTint
261
+        iconWell.layer?.backgroundColor = Theme.iconWell.cgColor
262
+        iconWell.layer?.borderColor = Theme.iconWellBorder.cgColor
263
+        aiBadgeHost.layer?.backgroundColor = Theme.badgeFill.cgColor
264
+        aiBadgeLabel.textColor = Theme.badgeText
265
+        titleLabel.textColor = Theme.headingBlue
266
+        subtitleLabel.textColor = Theme.subtitleText
267
+        statusLabel.textColor = Theme.statusText
268
+        installPageGradient()
269
+        progressBar.applyCurrentAppearance()
270
+    }
271
+
234 272
 }
235 273
 
236 274
 // MARK: - Loading progress bar
237 275
 
238 276
 private final class LoadingProgressBarView: NSView {
239 277
     private enum Theme {
240
-        static let brandBlue = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
241
-        static let brandBlueLight = NSColor(srgbRed: 54 / 255, green: 110 / 255, blue: 198 / 255, alpha: 1)
242
-        static let trackFill = NSColor(srgbRed: 228 / 255, green: 233 / 255, blue: 242 / 255, alpha: 1)
243
-        static let trackBorder = NSColor(srgbRed: 212 / 255, green: 218 / 255, blue: 230 / 255, alpha: 1)
244
-        static let percentText = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
278
+        static var brandBlue: NSColor { AppDashboardTheme.brandBlue }
279
+        static var brandBlueLight: NSColor { AppDashboardTheme.brandBlueHover }
280
+        static var trackFill: NSColor { AppDashboardTheme.loadingTrackFill }
281
+        static var trackBorder: NSColor { AppDashboardTheme.loadingTrackBorder }
282
+        static var percentText: NSColor { AppDashboardTheme.brandBlue }
245 283
     }
246 284
 
247 285
     private let track = NSView()
@@ -319,6 +357,16 @@ private final class LoadingProgressBarView: NSView {
319 357
         shimmerLayer?.removeAnimation(forKey: "shimmer")
320 358
     }
321 359
 
360
+    func applyCurrentAppearance() {
361
+        track.layer?.backgroundColor = Theme.trackFill.cgColor
362
+        track.layer?.borderColor = Theme.trackBorder.cgColor
363
+        percentLabel.textColor = Theme.percentText
364
+        fillGradientLayer?.colors = [
365
+            Theme.brandBlueLight.cgColor,
366
+            Theme.brandBlue.cgColor
367
+        ]
368
+    }
369
+
322 370
     private func setUp() {
323 371
         translatesAutoresizingMaskIntoConstraints = false
324 372