Преглед изворни кода

Unify the home shell so sidebar, title bar, and content read as one surface.

Remove floating top-bar/sidebar card cues and use subtle internal dividers so the layout matches Zoom's continuous panel structure.

Made-with: Cursor
huzaifahayat12 пре 6 дана
родитељ
комит
2f751d4e5f
1 измењених фајлова са 240 додато и 97 уклоњено
  1. 240 97
      zoom_app/ViewController.swift

+ 240 - 97
zoom_app/ViewController.swift

@@ -20,6 +20,14 @@ class ViewController: NSViewController {
20
     private let sidebarActiveBackground = NSColor(calibratedRed: 22 / 255, green: 23 / 255, blue: 26 / 255, alpha: 1)
20
     private let sidebarActiveBackground = NSColor(calibratedRed: 22 / 255, green: 23 / 255, blue: 26 / 255, alpha: 1)
21
     private let cardBackground = NSColor(calibratedRed: 20 / 255, green: 21 / 255, blue: 24 / 255, alpha: 1)
21
     private let cardBackground = NSColor(calibratedRed: 20 / 255, green: 21 / 255, blue: 24 / 255, alpha: 1)
22
     private let secondaryCardBackground = NSColor(calibratedRed: 26 / 255, green: 28 / 255, blue: 33 / 255, alpha: 1)
22
     private let secondaryCardBackground = NSColor(calibratedRed: 26 / 255, green: 28 / 255, blue: 33 / 255, alpha: 1)
23
+    private let appShellBackground = NSColor(calibratedRed: 13 / 255, green: 14 / 255, blue: 17 / 255, alpha: 1)
24
+    private let contentShellBackground = NSColor(calibratedRed: 14 / 255, green: 15 / 255, blue: 19 / 255, alpha: 1)
25
+    private let topStripBackground = NSColor(calibratedRed: 28 / 255, green: 30 / 255, blue: 36 / 255, alpha: 1)
26
+    private let searchPillBackground = NSColor.white.withAlphaComponent(0.06)
27
+    private let meetingCardBackground = NSColor(calibratedRed: 38 / 255, green: 43 / 255, blue: 58 / 255, alpha: 1)
28
+    private let titleBarControlBackground = NSColor.white.withAlphaComponent(0.04)
29
+    private let titleBarLightControlBackground = NSColor.white.withAlphaComponent(0.02)
30
+    private let appShellCornerRadius: CGFloat = 20
23
     private let accentBlue = NSColor(calibratedRed: 56 / 255, green: 132 / 255, blue: 255 / 255, alpha: 1)
31
     private let accentBlue = NSColor(calibratedRed: 56 / 255, green: 132 / 255, blue: 255 / 255, alpha: 1)
24
     private let accentOrange = NSColor(calibratedRed: 241 / 255, green: 116 / 255, blue: 49 / 255, alpha: 1)
32
     private let accentOrange = NSColor(calibratedRed: 241 / 255, green: 116 / 255, blue: 49 / 255, alpha: 1)
25
     private let primaryText = NSColor(calibratedWhite: 0.98, alpha: 1)
33
     private let primaryText = NSColor(calibratedWhite: 0.98, alpha: 1)
@@ -48,6 +56,11 @@ class ViewController: NSViewController {
48
     private var lastScrollEdgeRefreshAt = Date.distantPast
56
     private var lastScrollEdgeRefreshAt = Date.distantPast
49
     private let meetingsRefreshInterval: TimeInterval = 8
57
     private let meetingsRefreshInterval: TimeInterval = 8
50
     private let scrollRefreshCooldown: TimeInterval = 3
58
     private let scrollRefreshCooldown: TimeInterval = 3
59
+    
60
+    private enum SidebarStyle {
61
+        case login
62
+        case home
63
+    }
51
 
64
 
52
     override func viewDidLoad() {
65
     override func viewDidLoad() {
53
         super.viewDidLoad()
66
         super.viewDidLoad()
@@ -209,6 +222,10 @@ class ViewController: NSViewController {
209
         persistLoggedInState(false)
222
         persistLoggedInState(false)
210
         showLoginView()
223
         showLoginView()
211
     }
224
     }
225
+    
226
+    @objc private func topBarPlaceholderTapped() {
227
+        // Reserved for future titlebar control actions.
228
+    }
212
 
229
 
213
     private func startMeetingsAutoRefresh() {
230
     private func startMeetingsAutoRefresh() {
214
         meetingsRefreshTimer?.invalidate()
231
         meetingsRefreshTimer?.invalidate()
@@ -520,7 +537,7 @@ class ViewController: NSViewController {
520
 
537
 
521
     private func makeLoginView() -> NSView {
538
     private func makeLoginView() -> NSView {
522
         let root = NSView()
539
         let root = NSView()
523
-        let sidebar = makeSidebar(items: ["Home", "Chat", "Phone", "Docs", "Whiteboards", "Clips", "More"], selected: "Home")
540
+        let sidebar = makeSidebar(items: ["Home", "Chat", "Phone", "Docs", "Whiteboards", "Clips", "More"], selected: "Home", style: .login)
524
         let content = NSView()
541
         let content = NSView()
525
 
542
 
526
         root.addSubview(sidebar)
543
         root.addSubview(sidebar)
@@ -625,53 +642,91 @@ class ViewController: NSViewController {
625
 
642
 
626
     private func makeHomeView(profile: GoogleUserProfile?) -> NSView {
643
     private func makeHomeView(profile: GoogleUserProfile?) -> NSView {
627
         let root = NSView()
644
         let root = NSView()
628
-        let sidebar = makeSidebar(items: ["Home", "Meetings", "Chat", "Scheduler", "Hub", "More"], selected: "Home")
645
+        let shell = NSView()
646
+        shell.wantsLayer = true
647
+        shell.layer?.backgroundColor = appShellBackground.cgColor
648
+        shell.layer?.cornerRadius = appShellCornerRadius
649
+        shell.layer?.borderWidth = 1
650
+        shell.layer?.borderColor = NSColor.white.withAlphaComponent(0.06).cgColor
651
+        let sidebar = makeSidebar(items: ["Home", "Meetings", "Chat", "Scheduler", "Hub", "More"], selected: "Home", style: .home)
629
         let content = NSView()
652
         let content = NSView()
653
+        content.wantsLayer = true
654
+        content.layer?.backgroundColor = NSColor.clear.cgColor
630
 
655
 
631
-        root.addSubview(sidebar)
632
-        root.addSubview(content)
656
+        root.addSubview(shell)
657
+        shell.addSubview(sidebar)
658
+        shell.addSubview(content)
659
+        shell.translatesAutoresizingMaskIntoConstraints = false
633
         sidebar.translatesAutoresizingMaskIntoConstraints = false
660
         sidebar.translatesAutoresizingMaskIntoConstraints = false
634
         content.translatesAutoresizingMaskIntoConstraints = false
661
         content.translatesAutoresizingMaskIntoConstraints = false
635
 
662
 
636
         NSLayoutConstraint.activate([
663
         NSLayoutConstraint.activate([
637
-            sidebar.leadingAnchor.constraint(equalTo: root.leadingAnchor),
638
-            sidebar.topAnchor.constraint(equalTo: root.topAnchor),
639
-            sidebar.bottomAnchor.constraint(equalTo: root.bottomAnchor),
664
+            shell.leadingAnchor.constraint(equalTo: root.leadingAnchor, constant: 10),
665
+            shell.trailingAnchor.constraint(equalTo: root.trailingAnchor, constant: -10),
666
+            shell.topAnchor.constraint(equalTo: root.topAnchor, constant: 10),
667
+            shell.bottomAnchor.constraint(equalTo: root.bottomAnchor, constant: -10),
668
+            sidebar.leadingAnchor.constraint(equalTo: shell.leadingAnchor),
669
+            sidebar.topAnchor.constraint(equalTo: shell.topAnchor),
670
+            sidebar.bottomAnchor.constraint(equalTo: shell.bottomAnchor),
640
             sidebar.widthAnchor.constraint(equalToConstant: sidebarWidth),
671
             sidebar.widthAnchor.constraint(equalToConstant: sidebarWidth),
641
             content.leadingAnchor.constraint(equalTo: sidebar.trailingAnchor),
672
             content.leadingAnchor.constraint(equalTo: sidebar.trailingAnchor),
642
-            content.trailingAnchor.constraint(equalTo: root.trailingAnchor),
643
-            content.topAnchor.constraint(equalTo: root.topAnchor),
644
-            content.bottomAnchor.constraint(equalTo: root.bottomAnchor)
673
+            content.trailingAnchor.constraint(equalTo: shell.trailingAnchor),
674
+            content.topAnchor.constraint(equalTo: shell.topAnchor),
675
+            content.bottomAnchor.constraint(equalTo: shell.bottomAnchor)
645
         ])
676
         ])
646
 
677
 
647
         let topBar = NSView()
678
         let topBar = NSView()
648
         topBar.wantsLayer = true
679
         topBar.wantsLayer = true
649
-        topBar.layer?.backgroundColor = cardBackground.cgColor
650
-        topBar.layer?.cornerRadius = 12
651
-        topBar.layer?.borderWidth = 1
652
-        topBar.layer?.borderColor = NSColor.white.withAlphaComponent(0.06).cgColor
680
+        topBar.layer?.backgroundColor = topStripBackground.withAlphaComponent(0.88).cgColor
681
+        
682
+        let topBarDivider = NSView()
683
+        topBarDivider.wantsLayer = true
684
+        topBarDivider.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.06).cgColor
653
 
685
 
654
         let searchPill = NSView()
686
         let searchPill = NSView()
655
         searchPill.wantsLayer = true
687
         searchPill.wantsLayer = true
656
-        searchPill.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.05).cgColor
657
-        searchPill.layer?.cornerRadius = 9
658
-        let search = makeLabel("Search meetings, people (\u{2318}E)", size: 13, color: mutedText, weight: .regular, centered: false)
659
-
660
-        let profileChip = NSView()
688
+        searchPill.layer?.backgroundColor = searchPillBackground.cgColor
689
+        searchPill.layer?.cornerRadius = 10
690
+        let search = makeLabel("Search (\u{2318}E)", size: 13, color: mutedText, weight: .regular, centered: true)
691
+        
692
+        let brandStack = NSStackView()
693
+        brandStack.orientation = .vertical
694
+        brandStack.spacing = 0
695
+        brandStack.alignment = .leading
696
+        let brandTop = makeLabel("zoom", size: 14, color: primaryText, weight: .semibold, centered: false)
697
+        let brandBottom = makeLabel("Workplace", size: 27, color: primaryText, weight: .bold, centered: false)
698
+        brandBottom.font = .systemFont(ofSize: 14, weight: .bold)
699
+        [brandTop, brandBottom].forEach { brandStack.addArrangedSubview($0) }
700
+        
701
+        let leftTopBarCluster = NSStackView()
702
+        leftTopBarCluster.orientation = .horizontal
703
+        leftTopBarCluster.spacing = 10
704
+        leftTopBarCluster.alignment = .centerY
705
+        let backButton = makeTopBarGlyphButton(symbol: "chevron.left", action: #selector(topBarPlaceholderTapped))
706
+        let forwardButton = makeTopBarGlyphButton(symbol: "chevron.right", action: #selector(topBarPlaceholderTapped))
707
+        let historyButton = makeTopBarGlyphButton(symbol: "clock.arrow.circlepath", action: #selector(topBarPlaceholderTapped))
708
+        [backButton, forwardButton, historyButton].forEach { leftTopBarCluster.addArrangedSubview($0) }
709
+        
710
+        let rightTopBarCluster = NSStackView()
711
+        rightTopBarCluster.orientation = .horizontal
712
+        rightTopBarCluster.spacing = 8
713
+        rightTopBarCluster.alignment = .centerY
714
+        let plusButton = makeTopBarGlyphButton(symbol: "plus", action: #selector(topBarPlaceholderTapped))
715
+        let notificationButton = makeTopBarGlyphButton(symbol: "bell", action: #selector(topBarPlaceholderTapped))
716
+        let appsButton = makeTopBarIconButton(symbol: "square.grid.2x2", action: #selector(topBarPlaceholderTapped))
717
+
718
+        let profileChip = NSButton(title: String((profile?.name ?? "H").prefix(1)).uppercased(), target: self, action: #selector(logoutTapped))
719
+        profileChip.isBordered = false
661
         profileChip.wantsLayer = true
720
         profileChip.wantsLayer = true
662
-        profileChip.layer?.backgroundColor = accentBlue.withAlphaComponent(0.22).cgColor
663
-        profileChip.layer?.cornerRadius = 9
664
-        let name = makeLabel(profile?.name ?? "User", size: 13, color: primaryText, weight: .semibold, centered: false)
665
-        let logoutButton = NSButton(title: "Logout", target: self, action: #selector(logoutTapped))
666
-        logoutButton.isBordered = false
667
-        logoutButton.font = .systemFont(ofSize: 13, weight: .semibold)
668
-        logoutButton.contentTintColor = primaryText
669
-        logoutButton.wantsLayer = true
670
-        logoutButton.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.07).cgColor
671
-        logoutButton.layer?.cornerRadius = 8
721
+        profileChip.layer?.backgroundColor = accentBlue.withAlphaComponent(0.75).cgColor
722
+        profileChip.layer?.cornerRadius = 10
723
+        profileChip.contentTintColor = primaryText
724
+        profileChip.font = .systemFont(ofSize: 14, weight: .bold)
725
+        profileChip.toolTip = "Profile (click to logout)"
726
+        [plusButton, notificationButton, appsButton, profileChip].forEach { rightTopBarCluster.addArrangedSubview($0) }
672
 
727
 
673
         let welcome = makeLabel("Home", size: 15, color: secondaryText, weight: .medium, centered: false)
728
         let welcome = makeLabel("Home", size: 15, color: secondaryText, weight: .medium, centered: false)
674
-        let timeTitle = makeLabel("--:--", size: 50, color: primaryText, weight: .bold, centered: true)
729
+        let timeTitle = makeLabel("--:--", size: 56, color: primaryText, weight: .bold, centered: true)
675
         let dateTitle = makeLabel("-", size: 16, color: secondaryText, weight: .regular, centered: true)
730
         let dateTitle = makeLabel("-", size: 16, color: secondaryText, weight: .regular, centered: true)
676
 
731
 
677
         let actions = NSStackView(views: [
732
         let actions = NSStackView(views: [
@@ -688,14 +743,19 @@ class ViewController: NSViewController {
688
 
743
 
689
         let panel = NSView()
744
         let panel = NSView()
690
         panel.wantsLayer = true
745
         panel.wantsLayer = true
691
-        panel.layer?.backgroundColor = secondaryCardBackground.cgColor
746
+        panel.layer?.backgroundColor = secondaryCardBackground.withAlphaComponent(0.94).cgColor
692
         panel.layer?.cornerRadius = 16
747
         panel.layer?.cornerRadius = 16
693
         panel.layer?.borderWidth = 1
748
         panel.layer?.borderWidth = 1
694
         panel.layer?.borderColor = NSColor.white.withAlphaComponent(0.07).cgColor
749
         panel.layer?.borderColor = NSColor.white.withAlphaComponent(0.07).cgColor
750
+        
751
+        let panelHeaderStrip = NSView()
752
+        panelHeaderStrip.wantsLayer = true
753
+        panelHeaderStrip.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.03).cgColor
754
+        panelHeaderStrip.layer?.cornerRadius = 12
695
 
755
 
696
         let todaysDateFormatter = DateFormatter()
756
         let todaysDateFormatter = DateFormatter()
697
         todaysDateFormatter.dateFormat = "EEEE, MMM d"
757
         todaysDateFormatter.dateFormat = "EEEE, MMM d"
698
-        let panelHeader = makeLabel(todaysDateFormatter.string(from: Date()), size: 22, color: primaryText, weight: .semibold, centered: false)
758
+        let panelHeader = makeLabel(todaysDateFormatter.string(from: Date()), size: 21, color: primaryText, weight: .semibold, centered: false)
699
         let meetingsStatus = makeLabel("Upcoming meetings", size: 12, color: secondaryText, weight: .medium, centered: false)
759
         let meetingsStatus = makeLabel("Upcoming meetings", size: 12, color: secondaryText, weight: .medium, centered: false)
700
         let noMeeting = makeLabel("No meetings scheduled for today.", size: 18, color: secondaryText, weight: .regular, centered: true)
760
         let noMeeting = makeLabel("No meetings scheduled for today.", size: 18, color: secondaryText, weight: .regular, centered: true)
701
         let meetingsScrollView = NSScrollView()
761
         let meetingsScrollView = NSScrollView()
@@ -714,7 +774,7 @@ class ViewController: NSViewController {
714
         openRecordings.font = .systemFont(ofSize: 14, weight: .semibold)
774
         openRecordings.font = .systemFont(ofSize: 14, weight: .semibold)
715
         openRecordings.contentTintColor = primaryText
775
         openRecordings.contentTintColor = primaryText
716
         openRecordings.wantsLayer = true
776
         openRecordings.wantsLayer = true
717
-        openRecordings.layer?.backgroundColor = NSColor(calibratedRed: 31 / 255, green: 33 / 255, blue: 39 / 255, alpha: 1).cgColor
777
+        openRecordings.layer?.backgroundColor = NSColor(calibratedRed: 36 / 255, green: 39 / 255, blue: 46 / 255, alpha: 1).cgColor
718
         openRecordings.layer?.cornerRadius = 11
778
         openRecordings.layer?.cornerRadius = 11
719
         openRecordings.layer?.borderWidth = 1
779
         openRecordings.layer?.borderWidth = 1
720
         openRecordings.layer?.borderColor = NSColor.white.withAlphaComponent(0.07).cgColor
780
         openRecordings.layer?.borderColor = NSColor.white.withAlphaComponent(0.07).cgColor
@@ -723,7 +783,7 @@ class ViewController: NSViewController {
723
         contentColumn.translatesAutoresizingMaskIntoConstraints = false
783
         contentColumn.translatesAutoresizingMaskIntoConstraints = false
724
         content.addSubview(contentColumn)
784
         content.addSubview(contentColumn)
725
 
785
 
726
-        [topBar, searchPill, search, profileChip, name, logoutButton, welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, noMeeting, meetingsScrollView, openRecordings].forEach {
786
+        [topBar, topBarDivider, brandStack, leftTopBarCluster, rightTopBarCluster, searchPill, search, welcome, timeTitle, dateTitle, actions, panel, panelHeaderStrip, panelHeader, meetingsStatus, noMeeting, meetingsScrollView, openRecordings].forEach {
727
             $0.translatesAutoresizingMaskIntoConstraints = false
787
             $0.translatesAutoresizingMaskIntoConstraints = false
728
             contentColumn.addSubview($0)
788
             contentColumn.addSubview($0)
729
         }
789
         }
@@ -735,64 +795,72 @@ class ViewController: NSViewController {
735
         NSLayoutConstraint.activate([
795
         NSLayoutConstraint.activate([
736
             contentColumn.topAnchor.constraint(equalTo: content.topAnchor),
796
             contentColumn.topAnchor.constraint(equalTo: content.topAnchor),
737
             contentColumn.bottomAnchor.constraint(equalTo: content.bottomAnchor),
797
             contentColumn.bottomAnchor.constraint(equalTo: content.bottomAnchor),
738
-            contentColumn.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 20),
739
-            contentColumn.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20),
740
-
741
-            topBar.topAnchor.constraint(equalTo: contentColumn.topAnchor, constant: 12),
742
-            topBar.leadingAnchor.constraint(equalTo: contentColumn.leadingAnchor, constant: 12),
743
-            topBar.trailingAnchor.constraint(equalTo: contentColumn.trailingAnchor, constant: -12),
744
-            topBar.heightAnchor.constraint(equalToConstant: 50),
745
-
746
-            searchPill.leadingAnchor.constraint(equalTo: topBar.leadingAnchor, constant: 12),
798
+            contentColumn.leadingAnchor.constraint(equalTo: content.leadingAnchor),
799
+            contentColumn.trailingAnchor.constraint(equalTo: content.trailingAnchor),
800
+
801
+            topBar.topAnchor.constraint(equalTo: contentColumn.topAnchor),
802
+            topBar.leadingAnchor.constraint(equalTo: contentColumn.leadingAnchor),
803
+            topBar.trailingAnchor.constraint(equalTo: contentColumn.trailingAnchor),
804
+            topBar.heightAnchor.constraint(equalToConstant: 48),
805
+            topBarDivider.topAnchor.constraint(equalTo: topBar.bottomAnchor),
806
+            topBarDivider.leadingAnchor.constraint(equalTo: contentColumn.leadingAnchor),
807
+            topBarDivider.trailingAnchor.constraint(equalTo: contentColumn.trailingAnchor),
808
+            topBarDivider.heightAnchor.constraint(equalToConstant: 1),
809
+
810
+            brandStack.leadingAnchor.constraint(equalTo: topBar.leadingAnchor, constant: 12),
811
+            brandStack.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
812
+
813
+            leftTopBarCluster.leadingAnchor.constraint(equalTo: brandStack.trailingAnchor, constant: 18),
814
+            leftTopBarCluster.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
815
+            rightTopBarCluster.trailingAnchor.constraint(equalTo: topBar.trailingAnchor, constant: -12),
816
+            rightTopBarCluster.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
817
+
818
+            searchPill.centerXAnchor.constraint(equalTo: topBar.centerXAnchor),
747
             searchPill.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
819
             searchPill.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
748
             searchPill.heightAnchor.constraint(equalToConstant: 32),
820
             searchPill.heightAnchor.constraint(equalToConstant: 32),
749
             searchPill.widthAnchor.constraint(equalToConstant: 320),
821
             searchPill.widthAnchor.constraint(equalToConstant: 320),
822
+            searchPill.leadingAnchor.constraint(greaterThanOrEqualTo: leftTopBarCluster.trailingAnchor, constant: 12),
823
+            searchPill.trailingAnchor.constraint(lessThanOrEqualTo: rightTopBarCluster.leadingAnchor, constant: -12),
750
             search.leadingAnchor.constraint(equalTo: searchPill.leadingAnchor, constant: 12),
824
             search.leadingAnchor.constraint(equalTo: searchPill.leadingAnchor, constant: 12),
751
             search.trailingAnchor.constraint(equalTo: searchPill.trailingAnchor, constant: -12),
825
             search.trailingAnchor.constraint(equalTo: searchPill.trailingAnchor, constant: -12),
752
             search.centerYAnchor.constraint(equalTo: searchPill.centerYAnchor),
826
             search.centerYAnchor.constraint(equalTo: searchPill.centerYAnchor),
827
+            profileChip.widthAnchor.constraint(equalToConstant: 34),
828
+            profileChip.heightAnchor.constraint(equalToConstant: 34),
753
 
829
 
754
-            profileChip.trailingAnchor.constraint(equalTo: topBar.trailingAnchor, constant: -10),
755
-            profileChip.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
756
-            profileChip.heightAnchor.constraint(equalToConstant: 32),
757
-            profileChip.widthAnchor.constraint(greaterThanOrEqualToConstant: 92),
758
-            name.leadingAnchor.constraint(equalTo: profileChip.leadingAnchor, constant: 12),
759
-            name.trailingAnchor.constraint(equalTo: profileChip.trailingAnchor, constant: -12),
760
-            name.centerYAnchor.constraint(equalTo: profileChip.centerYAnchor),
761
-
762
-            logoutButton.trailingAnchor.constraint(equalTo: profileChip.leadingAnchor, constant: -10),
763
-            logoutButton.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
764
-            logoutButton.widthAnchor.constraint(equalToConstant: 76),
765
-            logoutButton.heightAnchor.constraint(equalToConstant: 32),
766
-
767
-            welcome.topAnchor.constraint(equalTo: topBar.bottomAnchor, constant: 18),
830
+            welcome.topAnchor.constraint(equalTo: topBar.bottomAnchor, constant: 22),
768
             welcome.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
831
             welcome.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
769
 
832
 
770
-            timeTitle.topAnchor.constraint(equalTo: welcome.bottomAnchor, constant: 14),
833
+            timeTitle.topAnchor.constraint(equalTo: welcome.bottomAnchor, constant: 12),
771
             timeTitle.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
834
             timeTitle.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
772
             dateTitle.topAnchor.constraint(equalTo: timeTitle.bottomAnchor, constant: 6),
835
             dateTitle.topAnchor.constraint(equalTo: timeTitle.bottomAnchor, constant: 6),
773
             dateTitle.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
836
             dateTitle.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
774
 
837
 
775
-            actions.topAnchor.constraint(equalTo: dateTitle.bottomAnchor, constant: 30),
838
+            actions.topAnchor.constraint(equalTo: dateTitle.bottomAnchor, constant: 28),
776
             actions.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
839
             actions.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
777
             actions.leadingAnchor.constraint(greaterThanOrEqualTo: contentColumn.leadingAnchor, constant: 18),
840
             actions.leadingAnchor.constraint(greaterThanOrEqualTo: contentColumn.leadingAnchor, constant: 18),
778
             actions.trailingAnchor.constraint(lessThanOrEqualTo: contentColumn.trailingAnchor, constant: -18),
841
             actions.trailingAnchor.constraint(lessThanOrEqualTo: contentColumn.trailingAnchor, constant: -18),
779
-            actions.heightAnchor.constraint(equalToConstant: 96),
842
+            actions.heightAnchor.constraint(equalToConstant: 100),
780
 
843
 
781
-            panel.topAnchor.constraint(equalTo: actions.bottomAnchor, constant: 24),
782
-            panel.leadingAnchor.constraint(equalTo: contentColumn.leadingAnchor, constant: 28),
783
-            panel.trailingAnchor.constraint(equalTo: contentColumn.trailingAnchor, constant: -28),
784
-            panel.heightAnchor.constraint(equalToConstant: 300),
844
+            panel.topAnchor.constraint(equalTo: actions.bottomAnchor, constant: 22),
845
+            panel.leadingAnchor.constraint(equalTo: contentColumn.leadingAnchor, constant: 26),
846
+            panel.trailingAnchor.constraint(equalTo: contentColumn.trailingAnchor, constant: -26),
847
+            panel.heightAnchor.constraint(equalToConstant: 316),
785
             panel.bottomAnchor.constraint(lessThanOrEqualTo: contentColumn.bottomAnchor, constant: -20),
848
             panel.bottomAnchor.constraint(lessThanOrEqualTo: contentColumn.bottomAnchor, constant: -20),
786
-
787
-            panelHeader.topAnchor.constraint(equalTo: panel.topAnchor, constant: 16),
788
-            panelHeader.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 18),
789
-            meetingsStatus.centerYAnchor.constraint(equalTo: panelHeader.centerYAnchor),
790
-            meetingsStatus.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -18),
849
+            
850
+            panelHeaderStrip.topAnchor.constraint(equalTo: panel.topAnchor, constant: 10),
851
+            panelHeaderStrip.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 10),
852
+            panelHeaderStrip.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -10),
853
+            panelHeaderStrip.heightAnchor.constraint(equalToConstant: 44),
854
+
855
+            panelHeader.centerYAnchor.constraint(equalTo: panelHeaderStrip.centerYAnchor),
856
+            panelHeader.leadingAnchor.constraint(equalTo: panelHeaderStrip.leadingAnchor, constant: 14),
857
+            meetingsStatus.centerYAnchor.constraint(equalTo: panelHeaderStrip.centerYAnchor),
858
+            meetingsStatus.trailingAnchor.constraint(equalTo: panelHeaderStrip.trailingAnchor, constant: -14),
791
             noMeeting.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 18),
859
             noMeeting.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 18),
792
             noMeeting.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -18),
860
             noMeeting.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -18),
793
             noMeeting.centerYAnchor.constraint(equalTo: panel.centerYAnchor),
861
             noMeeting.centerYAnchor.constraint(equalTo: panel.centerYAnchor),
794
 
862
 
795
-            meetingsScrollView.topAnchor.constraint(equalTo: panel.topAnchor, constant: 58),
863
+            meetingsScrollView.topAnchor.constraint(equalTo: panelHeaderStrip.bottomAnchor, constant: 10),
796
             meetingsScrollView.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 14),
864
             meetingsScrollView.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 14),
797
             meetingsScrollView.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -14),
865
             meetingsScrollView.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -14),
798
             meetingsScrollView.bottomAnchor.constraint(equalTo: openRecordings.topAnchor, constant: -14),
866
             meetingsScrollView.bottomAnchor.constraint(equalTo: openRecordings.topAnchor, constant: -14),
@@ -806,7 +874,7 @@ class ViewController: NSViewController {
806
             openRecordings.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 14),
874
             openRecordings.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 14),
807
             openRecordings.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -14),
875
             openRecordings.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -14),
808
             openRecordings.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -12),
876
             openRecordings.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -12),
809
-            openRecordings.heightAnchor.constraint(equalToConstant: 38)
877
+            openRecordings.heightAnchor.constraint(equalToConstant: 40)
810
         ])
878
         ])
811
 
879
 
812
         timeLabel = timeTitle
880
         timeLabel = timeTitle
@@ -839,14 +907,27 @@ class ViewController: NSViewController {
839
 
907
 
840
     // MARK: - Shared UI
908
     // MARK: - Shared UI
841
 
909
 
842
-    private func makeSidebar(items: [String], selected: String) -> NSView {
910
+    private func makeSidebar(items: [String], selected: String, style: SidebarStyle = .login) -> NSView {
843
         let sidebar = NSView()
911
         let sidebar = NSView()
844
         sidebar.wantsLayer = true
912
         sidebar.wantsLayer = true
845
-        sidebar.layer?.backgroundColor = sidebarBackground.cgColor
913
+        sidebar.layer?.backgroundColor = (style == .home ? appShellBackground.withAlphaComponent(0.98) : sidebarBackground).cgColor
914
+        if style == .home {
915
+            let divider = NSView()
916
+            divider.wantsLayer = true
917
+            divider.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.06).cgColor
918
+            divider.translatesAutoresizingMaskIntoConstraints = false
919
+            sidebar.addSubview(divider)
920
+            NSLayoutConstraint.activate([
921
+                divider.topAnchor.constraint(equalTo: sidebar.topAnchor),
922
+                divider.bottomAnchor.constraint(equalTo: sidebar.bottomAnchor),
923
+                divider.trailingAnchor.constraint(equalTo: sidebar.trailingAnchor),
924
+                divider.widthAnchor.constraint(equalToConstant: 1)
925
+            ])
926
+        }
846
 
927
 
847
         let stack = NSStackView()
928
         let stack = NSStackView()
848
         stack.orientation = .vertical
929
         stack.orientation = .vertical
849
-        stack.spacing = 16
930
+        stack.spacing = style == .home ? 12 : 16
850
         stack.alignment = .centerX
931
         stack.alignment = .centerX
851
         stack.translatesAutoresizingMaskIntoConstraints = false
932
         stack.translatesAutoresizingMaskIntoConstraints = false
852
         sidebar.addSubview(stack)
933
         sidebar.addSubview(stack)
@@ -856,46 +937,106 @@ class ViewController: NSViewController {
856
             row.translatesAutoresizingMaskIntoConstraints = false
937
             row.translatesAutoresizingMaskIntoConstraints = false
857
             row.wantsLayer = true
938
             row.wantsLayer = true
858
             let selectedRow = item == selected
939
             let selectedRow = item == selected
859
-            row.layer?.backgroundColor = selectedRow ? sidebarActiveBackground.cgColor : NSColor.clear.cgColor
860
-            row.layer?.cornerRadius = 10
861
-            row.widthAnchor.constraint(equalToConstant: 70).isActive = true
940
+            row.layer?.backgroundColor = selectedRow ? sidebarActiveBackground.withAlphaComponent(0.95).cgColor : NSColor.clear.cgColor
941
+            row.layer?.cornerRadius = style == .home ? 12 : 10
942
+            row.widthAnchor.constraint(equalToConstant: style == .home ? 68 : 70).isActive = true
862
 
943
 
863
-            let icon = makeLabel(selectedRow ? "⌂" : "◻︎", size: 15, color: primaryText, weight: .regular, centered: true)
864
-            let label = makeLabel(item, size: 11, color: selectedRow ? primaryText : secondaryText, weight: .regular, centered: true)
944
+            let icon = makeLabel(selectedRow ? "⌂" : "◻︎", size: style == .home ? 14 : 15, color: primaryText, weight: .regular, centered: true)
945
+            let label = makeLabel(item, size: style == .home ? 10 : 11, color: selectedRow ? primaryText : secondaryText, weight: .regular, centered: true)
865
             [icon, label].forEach {
946
             [icon, label].forEach {
866
                 $0.translatesAutoresizingMaskIntoConstraints = false
947
                 $0.translatesAutoresizingMaskIntoConstraints = false
867
                 row.addSubview($0)
948
                 row.addSubview($0)
868
             }
949
             }
869
             NSLayoutConstraint.activate([
950
             NSLayoutConstraint.activate([
870
-                icon.topAnchor.constraint(equalTo: row.topAnchor, constant: 10),
951
+                icon.topAnchor.constraint(equalTo: row.topAnchor, constant: style == .home ? 8 : 10),
871
                 icon.centerXAnchor.constraint(equalTo: row.centerXAnchor),
952
                 icon.centerXAnchor.constraint(equalTo: row.centerXAnchor),
872
-                label.topAnchor.constraint(equalTo: icon.bottomAnchor, constant: 5),
953
+                label.topAnchor.constraint(equalTo: icon.bottomAnchor, constant: style == .home ? 4 : 5),
873
                 label.centerXAnchor.constraint(equalTo: row.centerXAnchor),
954
                 label.centerXAnchor.constraint(equalTo: row.centerXAnchor),
874
-                label.bottomAnchor.constraint(equalTo: row.bottomAnchor, constant: -8)
955
+                label.bottomAnchor.constraint(equalTo: row.bottomAnchor, constant: style == .home ? -7 : -8)
875
             ])
956
             ])
876
             stack.addArrangedSubview(row)
957
             stack.addArrangedSubview(row)
877
         }
958
         }
959
+        
960
+        if style == .home {
961
+            let spacer = NSView()
962
+            spacer.translatesAutoresizingMaskIntoConstraints = false
963
+            spacer.heightAnchor.constraint(greaterThanOrEqualToConstant: 12).isActive = true
964
+            stack.addArrangedSubview(spacer)
965
+            
966
+            let settingsBadge = NSView()
967
+            settingsBadge.wantsLayer = true
968
+            settingsBadge.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.03).cgColor
969
+            settingsBadge.layer?.cornerRadius = 12
970
+            settingsBadge.translatesAutoresizingMaskIntoConstraints = false
971
+            settingsBadge.widthAnchor.constraint(equalToConstant: 40).isActive = true
972
+            settingsBadge.heightAnchor.constraint(equalToConstant: 40).isActive = true
973
+            let gear = makeLabel("⚙︎", size: 14, color: secondaryText, weight: .regular, centered: true)
974
+            gear.translatesAutoresizingMaskIntoConstraints = false
975
+            settingsBadge.addSubview(gear)
976
+            NSLayoutConstraint.activate([
977
+                gear.centerXAnchor.constraint(equalTo: settingsBadge.centerXAnchor),
978
+                gear.centerYAnchor.constraint(equalTo: settingsBadge.centerYAnchor)
979
+            ])
980
+            stack.addArrangedSubview(settingsBadge)
981
+        }
878
 
982
 
879
         NSLayoutConstraint.activate([
983
         NSLayoutConstraint.activate([
880
-            stack.topAnchor.constraint(equalTo: sidebar.topAnchor, constant: 18),
984
+            stack.topAnchor.constraint(equalTo: sidebar.topAnchor, constant: style == .home ? 16 : 18),
881
             stack.leadingAnchor.constraint(equalTo: sidebar.leadingAnchor, constant: 4),
985
             stack.leadingAnchor.constraint(equalTo: sidebar.leadingAnchor, constant: 4),
882
             stack.trailingAnchor.constraint(equalTo: sidebar.trailingAnchor, constant: -4)
986
             stack.trailingAnchor.constraint(equalTo: sidebar.trailingAnchor, constant: -4)
883
         ])
987
         ])
988
+        if style == .home {
989
+            stack.bottomAnchor.constraint(lessThanOrEqualTo: sidebar.bottomAnchor, constant: -18).isActive = true
990
+        }
884
 
991
 
885
         return sidebar
992
         return sidebar
886
     }
993
     }
994
+    
995
+    private func makeTopBarIconButton(symbol: String, action: Selector?) -> NSButton {
996
+        let button = NSButton(title: "", target: action == nil ? nil : self, action: action)
997
+        button.isBordered = false
998
+        button.wantsLayer = true
999
+        button.layer?.backgroundColor = titleBarControlBackground.cgColor
1000
+        button.layer?.cornerRadius = 10
1001
+        button.layer?.borderWidth = 1
1002
+        button.layer?.borderColor = NSColor.white.withAlphaComponent(0.05).cgColor
1003
+        button.contentTintColor = secondaryText
1004
+        button.image = NSImage(systemSymbolName: symbol, accessibilityDescription: symbol)
1005
+        button.imageScaling = .scaleProportionallyUpOrDown
1006
+        button.translatesAutoresizingMaskIntoConstraints = false
1007
+        button.widthAnchor.constraint(equalToConstant: 30).isActive = true
1008
+        button.heightAnchor.constraint(equalToConstant: 30).isActive = true
1009
+        return button
1010
+    }
1011
+    
1012
+    private func makeTopBarGlyphButton(symbol: String, action: Selector?) -> NSButton {
1013
+        let button = NSButton(title: "", target: action == nil ? nil : self, action: action)
1014
+        button.isBordered = false
1015
+        button.wantsLayer = true
1016
+        button.layer?.backgroundColor = titleBarLightControlBackground.cgColor
1017
+        button.layer?.cornerRadius = 9
1018
+        button.layer?.borderWidth = 1
1019
+        button.layer?.borderColor = NSColor.white.withAlphaComponent(0.03).cgColor
1020
+        button.contentTintColor = secondaryText
1021
+        button.image = NSImage(systemSymbolName: symbol, accessibilityDescription: symbol)
1022
+        button.imageScaling = .scaleProportionallyUpOrDown
1023
+        button.translatesAutoresizingMaskIntoConstraints = false
1024
+        button.widthAnchor.constraint(equalToConstant: 26).isActive = true
1025
+        button.heightAnchor.constraint(equalToConstant: 26).isActive = true
1026
+        return button
1027
+    }
887
 
1028
 
888
     private func makeActionTile(title: String, symbol: String, color: NSColor, action: Selector? = nil) -> NSView {
1029
     private func makeActionTile(title: String, symbol: String, color: NSColor, action: Selector? = nil) -> NSView {
889
         let root = NSView()
1030
         let root = NSView()
890
         root.translatesAutoresizingMaskIntoConstraints = false
1031
         root.translatesAutoresizingMaskIntoConstraints = false
891
-        root.widthAnchor.constraint(equalToConstant: 100).isActive = true
892
-        root.heightAnchor.constraint(equalToConstant: 96).isActive = true
1032
+        root.widthAnchor.constraint(equalToConstant: 104).isActive = true
1033
+        root.heightAnchor.constraint(equalToConstant: 100).isActive = true
893
 
1034
 
894
         let iconButton = NSButton(title: "", target: action == nil ? nil : self, action: action)
1035
         let iconButton = NSButton(title: "", target: action == nil ? nil : self, action: action)
895
         iconButton.isBordered = false
1036
         iconButton.isBordered = false
896
         iconButton.wantsLayer = true
1037
         iconButton.wantsLayer = true
897
         iconButton.layer?.backgroundColor = color.cgColor
1038
         iconButton.layer?.backgroundColor = color.cgColor
898
-        iconButton.layer?.cornerRadius = 18
1039
+        iconButton.layer?.cornerRadius = 20
899
         iconButton.layer?.shadowOpacity = 0.2
1040
         iconButton.layer?.shadowOpacity = 0.2
900
         iconButton.layer?.shadowRadius = 7
1041
         iconButton.layer?.shadowRadius = 7
901
         iconButton.layer?.shadowOffset = NSSize(width: 0, height: -1)
1042
         iconButton.layer?.shadowOffset = NSSize(width: 0, height: -1)
@@ -911,9 +1052,9 @@ class ViewController: NSViewController {
911
         NSLayoutConstraint.activate([
1052
         NSLayoutConstraint.activate([
912
             iconButton.topAnchor.constraint(equalTo: root.topAnchor),
1053
             iconButton.topAnchor.constraint(equalTo: root.topAnchor),
913
             iconButton.centerXAnchor.constraint(equalTo: root.centerXAnchor),
1054
             iconButton.centerXAnchor.constraint(equalTo: root.centerXAnchor),
914
-            iconButton.widthAnchor.constraint(equalToConstant: 62),
915
-            iconButton.heightAnchor.constraint(equalToConstant: 62),
916
-            label.topAnchor.constraint(equalTo: iconButton.bottomAnchor, constant: 9),
1055
+            iconButton.widthAnchor.constraint(equalToConstant: 64),
1056
+            iconButton.heightAnchor.constraint(equalToConstant: 64),
1057
+            label.topAnchor.constraint(equalTo: iconButton.bottomAnchor, constant: 10),
917
             label.centerXAnchor.constraint(equalTo: root.centerXAnchor),
1058
             label.centerXAnchor.constraint(equalTo: root.centerXAnchor),
918
             label.bottomAnchor.constraint(equalTo: root.bottomAnchor)
1059
             label.bottomAnchor.constraint(equalTo: root.bottomAnchor)
919
         ])
1060
         ])
@@ -923,10 +1064,12 @@ class ViewController: NSViewController {
923
     private func makeMeetingRowCard(_ meeting: ScheduledMeeting) -> NSView {
1064
     private func makeMeetingRowCard(_ meeting: ScheduledMeeting) -> NSView {
924
         let card = NSView()
1065
         let card = NSView()
925
         card.wantsLayer = true
1066
         card.wantsLayer = true
926
-        card.layer?.backgroundColor = NSColor(calibratedRed: 35 / 255, green: 40 / 255, blue: 56 / 255, alpha: 1).cgColor
927
-        card.layer?.cornerRadius = 14
1067
+        card.layer?.backgroundColor = meetingCardBackground.cgColor
1068
+        card.layer?.cornerRadius = 13
1069
+        card.layer?.borderWidth = 1
1070
+        card.layer?.borderColor = NSColor.white.withAlphaComponent(0.06).cgColor
928
         card.translatesAutoresizingMaskIntoConstraints = false
1071
         card.translatesAutoresizingMaskIntoConstraints = false
929
-        card.heightAnchor.constraint(equalToConstant: 110).isActive = true
1072
+        card.heightAnchor.constraint(equalToConstant: 116).isActive = true
930
 
1073
 
931
         let dateFormatter = DateFormatter()
1074
         let dateFormatter = DateFormatter()
932
         dateFormatter.dateFormat = "EEE, MMM d"
1075
         dateFormatter.dateFormat = "EEE, MMM d"
@@ -936,7 +1079,7 @@ class ViewController: NSViewController {
936
         let endText = meeting.end.map { timeFormatter.string(from: $0) } ?? ""
1079
         let endText = meeting.end.map { timeFormatter.string(from: $0) } ?? ""
937
         let range = endText.isEmpty ? startText : "\(startText) - \(endText)"
1080
         let range = endText.isEmpty ? startText : "\(startText) - \(endText)"
938
 
1081
 
939
-        let title = makeLabel(meeting.title, size: 17, color: primaryText, weight: .semibold, centered: false)
1082
+        let title = makeLabel(meeting.title, size: 26, color: primaryText, weight: .regular, centered: false)
940
         let detail = makeLabel("\(dateFormatter.string(from: meeting.start))\n\(range)", size: 14, color: secondaryText, weight: .regular, centered: false)
1083
         let detail = makeLabel("\(dateFormatter.string(from: meeting.start))\n\(range)", size: 14, color: secondaryText, weight: .regular, centered: false)
941
         detail.maximumNumberOfLines = 2
1084
         detail.maximumNumberOfLines = 2
942
         let host = makeLabel("Host: \(meeting.host) • \(meeting.source)", size: 13, color: secondaryText, weight: .regular, centered: false)
1085
         let host = makeLabel("Host: \(meeting.host) • \(meeting.source)", size: 13, color: secondaryText, weight: .regular, centered: false)
@@ -947,15 +1090,15 @@ class ViewController: NSViewController {
947
         }
1090
         }
948
 
1091
 
949
         NSLayoutConstraint.activate([
1092
         NSLayoutConstraint.activate([
950
-            title.topAnchor.constraint(equalTo: card.topAnchor, constant: 12),
951
-            title.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 14),
1093
+            title.topAnchor.constraint(equalTo: card.topAnchor, constant: 11),
1094
+            title.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
952
             title.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -14),
1095
             title.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -14),
953
 
1096
 
954
-            detail.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 6),
1097
+            detail.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 3),
955
             detail.leadingAnchor.constraint(equalTo: title.leadingAnchor),
1098
             detail.leadingAnchor.constraint(equalTo: title.leadingAnchor),
956
             detail.trailingAnchor.constraint(equalTo: title.trailingAnchor),
1099
             detail.trailingAnchor.constraint(equalTo: title.trailingAnchor),
957
 
1100
 
958
-            host.topAnchor.constraint(equalTo: detail.bottomAnchor, constant: 6),
1101
+            host.topAnchor.constraint(equalTo: detail.bottomAnchor, constant: 7),
959
             host.leadingAnchor.constraint(equalTo: title.leadingAnchor),
1102
             host.leadingAnchor.constraint(equalTo: title.leadingAnchor),
960
             host.trailingAnchor.constraint(equalTo: title.trailingAnchor)
1103
             host.trailingAnchor.constraint(equalTo: title.trailingAnchor)
961
         ])
1104
         ])