// // MainWindowView.swift // Zyquo Router // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // App shell: 240pt translucent left navigator (sections, footer settings // gear, permanent status pill with Start/Stop) + detail area. // import SwiftUI struct MainWindowView: View { @EnvironmentObject private var appearance: AppearanceStore // Stored so the ⌘1–6 menu commands can drive it from the Commands scene. @AppStorage("selectedSection") private var sectionRaw = AppSection.dashboard.rawValue private var section: Binding { Binding( get: { AppSection(rawValue: sectionRaw) ?? .dashboard }, set: { sectionRaw = $0.rawValue } ) } @State private var paletteShown = false var body: some View { HStack(spacing: 0) { Navigator(section: section) .frame(width: ZyquoMetrics.navigatorWidth) Rectangle() .fill(ZyquoColor.border) .frame(width: ZyquoMetrics.hairline) detail .frame(maxWidth: .infinity, maxHeight: .infinity) .background(ZyquoColor.background) } .frame( minWidth: ZyquoMetrics.windowMinWidth, minHeight: ZyquoMetrics.windowMinHeight ) .overlay(alignment: .top) { if paletteShown { CommandPalette(isPresented: $paletteShown) .padding(.top, 90) .transition(.opacity.combined(with: .scale(scale: 0.98, anchor: .top))) } } .background( Button("") { withAnimation(ZyquoMotion.state) { paletteShown.toggle() } } .keyboardShortcut("k", modifiers: .command) .hidden() ) .preferredColorScheme(appearance.themeMode.colorScheme) } @ViewBuilder private var detail: some View { switch section.wrappedValue { case .dashboard: DashboardView() case .models: ModelsView() case .requests: RequestsView() case .keys: KeysView() case .playground: PlaygroundView() case .docs: DocsView() } } } // MARK: - Navigator private struct Navigator: View { @Binding var section: AppSection var body: some View { VStack(alignment: .leading, spacing: 0) { // Wordmark HStack(spacing: ZyquoSpacing.xs) { RouterZGlyph(size: 20) Text("Zyquo Router") .font(ZyquoFont.heading) .foregroundStyle(ZyquoColor.textPrimary) } .padding(.horizontal, ZyquoSpacing.md) .padding(.top, ZyquoSpacing.xl) .padding(.bottom, ZyquoSpacing.lg) ForEach(AppSection.allCases) { item in NavigatorRow(item: item, selected: section == item) { withAnimation(ZyquoMotion.state) { section = item } } } Spacer() StatusPill() .padding(.horizontal, ZyquoSpacing.md) .padding(.bottom, ZyquoSpacing.sm) HStack { Button { SettingsOpener.open() } label: { Image(systemName: "gearshape") .font(.system(size: 13, weight: .medium)) .foregroundStyle(ZyquoColor.textSecondary) } .buttonStyle(.plain) .help("Settings") Spacer() } .padding(.horizontal, ZyquoSpacing.md) .padding(.bottom, ZyquoSpacing.md) } .background(NavigatorMaterial()) } } private struct NavigatorRow: View { let item: AppSection let selected: Bool let action: () -> Void @State private var hovering = false var body: some View { Button(action: action) { HStack(spacing: ZyquoSpacing.xs) { Image(systemName: item.systemImage) .font(.system(size: 13, weight: .medium)) .frame(width: 18) Text(item.rawValue) .font(ZyquoFont.bodyEmphasis()) Spacer() } .foregroundStyle(selected ? ZyquoColor.accent : ZyquoColor.textSecondary) .padding(.horizontal, ZyquoSpacing.sm) .padding(.vertical, 7) .background( RoundedRectangle(cornerRadius: ZyquoRadius.small, style: .continuous) .fill(selected ? ZyquoColor.accentSubtle : (hovering ? ZyquoColor.surfaceSecondary : .clear)) ) } .buttonStyle(.plain) .padding(.horizontal, ZyquoSpacing.xs) .onHover { hover in withAnimation(ZyquoMotion.hover) { hovering = hover } } } } /// Translucent sidebar material behind the navigator. private struct NavigatorMaterial: NSViewRepresentable { func makeNSView(context: Context) -> NSVisualEffectView { let view = NSVisualEffectView() view.material = .sidebar view.blendingMode = .behindWindow view.state = .followsWindowActiveState return view } func updateNSView(_ nsView: NSVisualEffectView, context: Context) {} } /// Mini Z-with-signal-dot wordmark glyph (Phase 5 derives the full icon). struct RouterZGlyph: View { let size: CGFloat var body: some View { ZStack(alignment: .bottomTrailing) { Text("Z") .font(.system(size: size, weight: .heavy, design: .rounded)) .foregroundStyle(ZyquoColor.textPrimary) Circle() .fill(ZyquoColor.accent) .frame(width: size * 0.28, height: size * 0.28) .offset(x: size * 0.18, y: 0) } } }