Parcourir la Source

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 il y a 6 jours
Parent
commit
2f751d4e5f
1 fichiers modifiés avec 240 ajouts et 97 suppressions
  1. 240 97
      zoom_app/ViewController.swift

+ 240 - 97
zoom_app/ViewController.swift

@@ -20,6 +20,14 @@ class ViewController: NSViewController {
20 20
     private let sidebarActiveBackground = NSColor(calibratedRed: 22 / 255, green: 23 / 255, blue: 26 / 255, alpha: 1)
21 21
     private let cardBackground = NSColor(calibratedRed: 20 / 255, green: 21 / 255, blue: 24 / 255, alpha: 1)
22 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 31
     private let accentBlue = NSColor(calibratedRed: 56 / 255, green: 132 / 255, blue: 255 / 255, alpha: 1)
24 32
     private let accentOrange = NSColor(calibratedRed: 241 / 255, green: 116 / 255, blue: 49 / 255, alpha: 1)
25 33
     private let primaryText = NSColor(calibratedWhite: 0.98, alpha: 1)
@@ -48,6 +56,11 @@ class ViewController: NSViewController {
48 56
     private var lastScrollEdgeRefreshAt = Date.distantPast
49 57
     private let meetingsRefreshInterval: TimeInterval = 8
50 58
     private let scrollRefreshCooldown: TimeInterval = 3
59
+    
60
+    private enum SidebarStyle {
61
+        case login
62
+        case home
63
+    }
51 64
 
52 65
     override func viewDidLoad() {
53 66
         super.viewDidLoad()
@@ -209,6 +222,10 @@ class ViewController: NSViewController {
209 222
         persistLoggedInState(false)
210 223
         showLoginView()
211 224
     }
225
+    
226
+    @objc private func topBarPlaceholderTapped() {
227
+        // Reserved for future titlebar control actions.
228
+    }
212 229
 
213 230
     private func startMeetingsAutoRefresh() {
214 231
         meetingsRefreshTimer?.invalidate()
@@ -520,7 +537,7 @@ class ViewController: NSViewController {
520 537
 
521 538
     private func makeLoginView() -> NSView {
522 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 541
         let content = NSView()
525 542
 
526 543
         root.addSubview(sidebar)
@@ -625,53 +642,91 @@ class ViewController: NSViewController {
625 642
 
626 643
     private func makeHomeView(profile: GoogleUserProfile?) -> NSView {
627 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 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 660
         sidebar.translatesAutoresizingMaskIntoConstraints = false
634 661
         content.translatesAutoresizingMaskIntoConstraints = false
635 662
 
636 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 671
             sidebar.widthAnchor.constraint(equalToConstant: sidebarWidth),
641 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 678
         let topBar = NSView()
648 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 686
         let searchPill = NSView()
655 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 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 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 730
         let dateTitle = makeLabel("-", size: 16, color: secondaryText, weight: .regular, centered: true)
676 731
 
677 732
         let actions = NSStackView(views: [
@@ -688,14 +743,19 @@ class ViewController: NSViewController {
688 743
 
689 744
         let panel = NSView()
690 745
         panel.wantsLayer = true
691
-        panel.layer?.backgroundColor = secondaryCardBackground.cgColor
746
+        panel.layer?.backgroundColor = secondaryCardBackground.withAlphaComponent(0.94).cgColor
692 747
         panel.layer?.cornerRadius = 16
693 748
         panel.layer?.borderWidth = 1
694 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 756
         let todaysDateFormatter = DateFormatter()
697 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 759
         let meetingsStatus = makeLabel("Upcoming meetings", size: 12, color: secondaryText, weight: .medium, centered: false)
700 760
         let noMeeting = makeLabel("No meetings scheduled for today.", size: 18, color: secondaryText, weight: .regular, centered: true)
701 761
         let meetingsScrollView = NSScrollView()
@@ -714,7 +774,7 @@ class ViewController: NSViewController {
714 774
         openRecordings.font = .systemFont(ofSize: 14, weight: .semibold)
715 775
         openRecordings.contentTintColor = primaryText
716 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 778
         openRecordings.layer?.cornerRadius = 11
719 779
         openRecordings.layer?.borderWidth = 1
720 780
         openRecordings.layer?.borderColor = NSColor.white.withAlphaComponent(0.07).cgColor
@@ -723,7 +783,7 @@ class ViewController: NSViewController {
723 783
         contentColumn.translatesAutoresizingMaskIntoConstraints = false
724 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 787
             $0.translatesAutoresizingMaskIntoConstraints = false
728 788
             contentColumn.addSubview($0)
729 789
         }
@@ -735,64 +795,72 @@ class ViewController: NSViewController {
735 795
         NSLayoutConstraint.activate([
736 796
             contentColumn.topAnchor.constraint(equalTo: content.topAnchor),
737 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 819
             searchPill.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
748 820
             searchPill.heightAnchor.constraint(equalToConstant: 32),
749 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 824
             search.leadingAnchor.constraint(equalTo: searchPill.leadingAnchor, constant: 12),
751 825
             search.trailingAnchor.constraint(equalTo: searchPill.trailingAnchor, constant: -12),
752 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 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 834
             timeTitle.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
772 835
             dateTitle.topAnchor.constraint(equalTo: timeTitle.bottomAnchor, constant: 6),
773 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 839
             actions.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
777 840
             actions.leadingAnchor.constraint(greaterThanOrEqualTo: contentColumn.leadingAnchor, constant: 18),
778 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 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 859
             noMeeting.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 18),
792 860
             noMeeting.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -18),
793 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 864
             meetingsScrollView.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 14),
797 865
             meetingsScrollView.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -14),
798 866
             meetingsScrollView.bottomAnchor.constraint(equalTo: openRecordings.topAnchor, constant: -14),
@@ -806,7 +874,7 @@ class ViewController: NSViewController {
806 874
             openRecordings.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 14),
807 875
             openRecordings.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -14),
808 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 880
         timeLabel = timeTitle
@@ -839,14 +907,27 @@ class ViewController: NSViewController {
839 907
 
840 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 911
         let sidebar = NSView()
844 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 928
         let stack = NSStackView()
848 929
         stack.orientation = .vertical
849
-        stack.spacing = 16
930
+        stack.spacing = style == .home ? 12 : 16
850 931
         stack.alignment = .centerX
851 932
         stack.translatesAutoresizingMaskIntoConstraints = false
852 933
         sidebar.addSubview(stack)
@@ -856,46 +937,106 @@ class ViewController: NSViewController {
856 937
             row.translatesAutoresizingMaskIntoConstraints = false
857 938
             row.wantsLayer = true
858 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 946
             [icon, label].forEach {
866 947
                 $0.translatesAutoresizingMaskIntoConstraints = false
867 948
                 row.addSubview($0)
868 949
             }
869 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 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 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 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 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 985
             stack.leadingAnchor.constraint(equalTo: sidebar.leadingAnchor, constant: 4),
882 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 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 1029
     private func makeActionTile(title: String, symbol: String, color: NSColor, action: Selector? = nil) -> NSView {
889 1030
         let root = NSView()
890 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 1035
         let iconButton = NSButton(title: "", target: action == nil ? nil : self, action: action)
895 1036
         iconButton.isBordered = false
896 1037
         iconButton.wantsLayer = true
897 1038
         iconButton.layer?.backgroundColor = color.cgColor
898
-        iconButton.layer?.cornerRadius = 18
1039
+        iconButton.layer?.cornerRadius = 20
899 1040
         iconButton.layer?.shadowOpacity = 0.2
900 1041
         iconButton.layer?.shadowRadius = 7
901 1042
         iconButton.layer?.shadowOffset = NSSize(width: 0, height: -1)
@@ -911,9 +1052,9 @@ class ViewController: NSViewController {
911 1052
         NSLayoutConstraint.activate([
912 1053
             iconButton.topAnchor.constraint(equalTo: root.topAnchor),
913 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 1058
             label.centerXAnchor.constraint(equalTo: root.centerXAnchor),
918 1059
             label.bottomAnchor.constraint(equalTo: root.bottomAnchor)
919 1060
         ])
@@ -923,10 +1064,12 @@ class ViewController: NSViewController {
923 1064
     private func makeMeetingRowCard(_ meeting: ScheduledMeeting) -> NSView {
924 1065
         let card = NSView()
925 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 1071
         card.translatesAutoresizingMaskIntoConstraints = false
929
-        card.heightAnchor.constraint(equalToConstant: 110).isActive = true
1072
+        card.heightAnchor.constraint(equalToConstant: 116).isActive = true
930 1073
 
931 1074
         let dateFormatter = DateFormatter()
932 1075
         dateFormatter.dateFormat = "EEE, MMM d"
@@ -936,7 +1079,7 @@ class ViewController: NSViewController {
936 1079
         let endText = meeting.end.map { timeFormatter.string(from: $0) } ?? ""
937 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 1083
         let detail = makeLabel("\(dateFormatter.string(from: meeting.start))\n\(range)", size: 14, color: secondaryText, weight: .regular, centered: false)
941 1084
         detail.maximumNumberOfLines = 2
942 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 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 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 1098
             detail.leadingAnchor.constraint(equalTo: title.leadingAnchor),
956 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 1102
             host.leadingAnchor.constraint(equalTo: title.leadingAnchor),
960 1103
             host.trailingAnchor.constraint(equalTo: title.trailingAnchor)
961 1104
         ])