diff --git a/apps/Mac/Auth/MacForgotPasswordView.swift b/apps/Mac/Auth/MacForgotPasswordView.swift index c4914f2..6d6f010 100644 --- a/apps/Mac/Auth/MacForgotPasswordView.swift +++ b/apps/Mac/Auth/MacForgotPasswordView.swift @@ -15,7 +15,7 @@ struct MacForgotPasswordView: View { @State private var resetEmailSent = false var body: some View { - VStack(spacing: 20) { + VStack(spacing: ApproachNoteTheme.spacingLG) { if resetEmailSent { // Success state successView @@ -26,13 +26,13 @@ struct MacForgotPasswordView: View { Spacer() } - .padding(24) + .padding(ApproachNoteTheme.spacingXL) .frame(minWidth: 350, maxWidth: 400, minHeight: 300) } @ViewBuilder private var successView: some View { - VStack(spacing: 16) { + VStack(spacing: ApproachNoteTheme.spacingMD) { Image(systemName: "envelope.circle.fill") .font(.system(size: 60)) .foregroundColor(ApproachNoteTheme.brand) @@ -54,21 +54,21 @@ struct MacForgotPasswordView: View { .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) .multilineTextAlignment(.center) - .padding(.top, 8) + .padding(.top, ApproachNoteTheme.spacingXS) ApproachNoteButton("Done") { dismiss() } - .padding(.top, 16) + .padding(.top, ApproachNoteTheme.spacingMD) } .padding(.top, 40) } @ViewBuilder private var requestFormView: some View { - VStack(spacing: 20) { + VStack(spacing: ApproachNoteTheme.spacingLG) { // Header - VStack(spacing: 8) { + VStack(spacing: ApproachNoteTheme.spacingXS) { Text("Reset Password") .font(ApproachNoteTheme.title()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -78,10 +78,10 @@ struct MacForgotPasswordView: View { .foregroundColor(.secondary) .multilineTextAlignment(.center) } - .padding(.top, 20) + .padding(.top, ApproachNoteTheme.spacingLG) // Email field - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Email") .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) diff --git a/apps/Mac/Auth/MacLoginView.swift b/apps/Mac/Auth/MacLoginView.swift index 3668d64..5ac673e 100644 --- a/apps/Mac/Auth/MacLoginView.swift +++ b/apps/Mac/Auth/MacLoginView.swift @@ -22,9 +22,9 @@ struct MacLoginView: View { var isInline: Bool = false var body: some View { - VStack(spacing: 20) { + VStack(spacing: ApproachNoteTheme.spacingLG) { // Header - VStack(spacing: 8) { + VStack(spacing: ApproachNoteTheme.spacingXS) { Text("Welcome Back") .font(ApproachNoteTheme.title()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -87,15 +87,15 @@ struct MacLoginView: View { Text("or") .foregroundColor(.secondary) .font(ApproachNoteTheme.caption()) - .padding(.horizontal, 8) + .padding(.horizontal, ApproachNoteTheme.spacingXS) Rectangle() .frame(height: 1) .foregroundColor(.gray.opacity(0.3)) } - .padding(.vertical, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) // Email field - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Email") .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) @@ -107,7 +107,7 @@ struct MacLoginView: View { } // Password field - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Password") .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) @@ -156,7 +156,7 @@ struct MacLoginView: View { .frame(height: 1) .foregroundColor(.gray.opacity(0.3)) } - .padding(.vertical, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) // Create account button ApproachNoteButton("Create Account", style: .secondary) { diff --git a/apps/Mac/Auth/MacRegisterView.swift b/apps/Mac/Auth/MacRegisterView.swift index 3f3e112..497fef9 100644 --- a/apps/Mac/Auth/MacRegisterView.swift +++ b/apps/Mac/Auth/MacRegisterView.swift @@ -30,9 +30,9 @@ struct MacRegisterView: View { } var body: some View { - VStack(spacing: 20) { + VStack(spacing: ApproachNoteTheme.spacingLG) { // Header - VStack(spacing: 8) { + VStack(spacing: ApproachNoteTheme.spacingXS) { Text("Create Account") .font(ApproachNoteTheme.title()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -41,10 +41,10 @@ struct MacRegisterView: View { .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) } - .padding(.top, 20) + .padding(.top, ApproachNoteTheme.spacingLG) // Display name - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Display Name") .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) @@ -54,7 +54,7 @@ struct MacRegisterView: View { } // Email - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Email") .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) @@ -66,7 +66,7 @@ struct MacRegisterView: View { } // Password - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Password") .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) @@ -82,7 +82,7 @@ struct MacRegisterView: View { } // Confirm password - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Confirm Password") .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) @@ -129,7 +129,7 @@ struct MacRegisterView: View { Spacer() } - .padding(24) + .padding(ApproachNoteTheme.spacingXL) .frame(minWidth: 350, maxWidth: 450, minHeight: 500) } diff --git a/apps/Mac/Auth/MacResetPasswordView.swift b/apps/Mac/Auth/MacResetPasswordView.swift index d77d747..65a4196 100644 --- a/apps/Mac/Auth/MacResetPasswordView.swift +++ b/apps/Mac/Auth/MacResetPasswordView.swift @@ -28,7 +28,7 @@ struct MacResetPasswordView: View { } var body: some View { - VStack(spacing: 20) { + VStack(spacing: ApproachNoteTheme.spacingLG) { if resetComplete { // Success state successView @@ -39,13 +39,13 @@ struct MacResetPasswordView: View { Spacer() } - .padding(24) + .padding(ApproachNoteTheme.spacingXL) .frame(minWidth: 350, maxWidth: 400, minHeight: 350) } @ViewBuilder private var successView: some View { - VStack(spacing: 16) { + VStack(spacing: ApproachNoteTheme.spacingMD) { Image(systemName: "checkmark.circle.fill") .font(.system(size: 60)) .foregroundColor(.green) @@ -62,16 +62,16 @@ struct MacResetPasswordView: View { ApproachNoteButton("Done") { dismiss() } - .padding(.top, 16) + .padding(.top, ApproachNoteTheme.spacingMD) } .padding(.top, 40) } @ViewBuilder private var resetFormView: some View { - VStack(spacing: 20) { + VStack(spacing: ApproachNoteTheme.spacingLG) { // Header - VStack(spacing: 8) { + VStack(spacing: ApproachNoteTheme.spacingXS) { Text("Set New Password") .font(ApproachNoteTheme.title()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -80,10 +80,10 @@ struct MacResetPasswordView: View { .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) } - .padding(.top, 20) + .padding(.top, ApproachNoteTheme.spacingLG) // New password - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("New Password") .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) @@ -99,7 +99,7 @@ struct MacResetPasswordView: View { } // Confirm password - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Confirm Password") .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) diff --git a/apps/Mac/Components/BackingTrackRow.swift b/apps/Mac/Components/BackingTrackRow.swift index c516d47..ea6d95c 100644 --- a/apps/Mac/Components/BackingTrackRow.swift +++ b/apps/Mac/Components/BackingTrackRow.swift @@ -16,7 +16,7 @@ struct BackingTrackRow: View { var body: some View { Button(action: openYouTube) { - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { // Play button thumbnail ZStack { RoundedRectangle(cornerRadius: 8) @@ -29,15 +29,15 @@ struct BackingTrackRow: View { } // Video info - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(video.title ?? "Backing Track") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) .lineLimit(2) - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { if let duration = video.durationSeconds { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: "clock") .foregroundColor(ApproachNoteTheme.textSecondary) .font(ApproachNoteTheme.caption()) @@ -48,7 +48,7 @@ struct BackingTrackRow: View { } if let tempo = video.tempo { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: "metronome") .foregroundColor(ApproachNoteTheme.textSecondary) .font(ApproachNoteTheme.caption()) @@ -59,7 +59,7 @@ struct BackingTrackRow: View { } if let key = video.keySignature { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: "music.note") .foregroundColor(ApproachNoteTheme.textSecondary) .font(ApproachNoteTheme.caption()) diff --git a/apps/Mac/Components/FeaturedRecordingCard.swift b/apps/Mac/Components/FeaturedRecordingCard.swift index 307dcbd..1a158df 100644 --- a/apps/Mac/Components/FeaturedRecordingCard.swift +++ b/apps/Mac/Components/FeaturedRecordingCard.swift @@ -40,7 +40,7 @@ struct FeaturedRecordingCard: View { } var body: some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { // Album Art Group { if let frontUrl = frontCoverUrl { @@ -81,7 +81,7 @@ struct FeaturedRecordingCard: View { .shadow(color: .black.opacity(0.15), radius: 8, x: 0, y: 4) // Recording Info — Year → Artist → Album → (Song Title) - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { // Year Text(recording.recordingYear.map { String($0) } ?? " ") .font(ApproachNoteTheme.subheadline(weight: .bold)) diff --git a/apps/Mac/Components/MacCommunityDataSection.swift b/apps/Mac/Components/MacCommunityDataSection.swift index f6fd892..af18e41 100644 --- a/apps/Mac/Components/MacCommunityDataSection.swift +++ b/apps/Mac/Components/MacCommunityDataSection.swift @@ -16,7 +16,7 @@ struct MacCommunityDataSection: View { let onEditTapped: () -> Void var body: some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { // Header HStack { Image(systemName: "person.3.fill") @@ -32,7 +32,7 @@ struct MacCommunityDataSection: View { Button { onEditTapped() } label: { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: userContribution != nil ? "pencil" : "plus") Text(userContribution != nil ? "Edit" : "Contribute") } @@ -45,7 +45,7 @@ struct MacCommunityDataSection: View { // Data rows if let data = communityData, hasAnyData(data) { - VStack(spacing: 8) { + VStack(spacing: ApproachNoteTheme.spacingXS) { // Performance Key MacCommunityDataRow( icon: "music.note", @@ -82,7 +82,7 @@ struct MacCommunityDataSection: View { .cornerRadius(8) } else { // No data yet - VStack(alignment: .center, spacing: 8) { + VStack(alignment: .center, spacing: ApproachNoteTheme.spacingXS) { Text("No community data yet") .font(ApproachNoteTheme.body()) .bodyLineSpacing() @@ -135,7 +135,7 @@ struct MacCommunityDataRow: View { var helpText: String? = nil var body: some View { - HStack(alignment: .center, spacing: 12) { + HStack(alignment: .center, spacing: ApproachNoteTheme.spacingSM) { Image(systemName: icon) .foregroundColor(isEmpty ? ApproachNoteTheme.textSecondary.opacity(0.5) : ApproachNoteTheme.textSecondary) .frame(width: 20) @@ -147,7 +147,7 @@ struct MacCommunityDataRow: View { Spacer() - VStack(alignment: .trailing, spacing: 2) { + VStack(alignment: .trailing, spacing: ApproachNoteTheme.spacingXXS) { if let help = helpText { Text(value) .font(ApproachNoteTheme.body()) @@ -177,7 +177,7 @@ struct MacCommunityDataRow: View { } } } - .padding(.vertical, 4) + .padding(.vertical, ApproachNoteTheme.spacingXXS) } } diff --git a/apps/Mac/Components/MacRecordingContributionEditView.swift b/apps/Mac/Components/MacRecordingContributionEditView.swift index 2b6822f..4e22ee8 100644 --- a/apps/Mac/Components/MacRecordingContributionEditView.swift +++ b/apps/Mac/Components/MacRecordingContributionEditView.swift @@ -41,14 +41,14 @@ struct MacRecordingContributionEditView: View { // Form content ScrollView { - VStack(alignment: .leading, spacing: 24) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXL) { // Recording info Text(recordingTitle) .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textSecondary) // Performance Key Section - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Performance Key") .font(ApproachNoteTheme.subheadline(weight: .medium)) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -68,7 +68,7 @@ struct MacRecordingContributionEditView: View { } // Tempo Section - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Tempo") .font(ApproachNoteTheme.subheadline(weight: .medium)) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -88,7 +88,7 @@ struct MacRecordingContributionEditView: View { } // Instrumental/Vocal Section - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Instrumental or Vocal") .font(ApproachNoteTheme.subheadline(weight: .medium)) .foregroundColor(ApproachNoteTheme.textPrimary) diff --git a/apps/Mac/Components/MacResearchStatusBanner.swift b/apps/Mac/Components/MacResearchStatusBanner.swift index 5f1189d..eefd02c 100644 --- a/apps/Mac/Components/MacResearchStatusBanner.swift +++ b/apps/Mac/Components/MacResearchStatusBanner.swift @@ -19,8 +19,8 @@ struct MacResearchStatusBanner: View { @State private var isHovering = false var body: some View { - VStack(alignment: .leading, spacing: 4) { - HStack(spacing: 10) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { + HStack(spacing: ApproachNoteTheme.spacingXS) { // Animated or static icon if isAnimating { Image(systemName: icon) @@ -33,7 +33,7 @@ struct MacResearchStatusBanner: View { .foregroundColor(iconColor) } - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(title) .font(ApproachNoteTheme.subheadline()) .fontWeight(.semibold) @@ -50,7 +50,7 @@ struct MacResearchStatusBanner: View { .font(.system(size: 12)) .foregroundColor(ApproachNoteTheme.textSecondary) } - .padding(10) + .padding(ApproachNoteTheme.spacingXS) .background(iconColor.opacity(0.1)) .cornerRadius(6) .onHover { hovering in @@ -63,11 +63,11 @@ struct MacResearchStatusBanner: View { Text(helperText) .font(ApproachNoteTheme.caption()) .foregroundColor(ApproachNoteTheme.textSecondary) - .padding(.horizontal, 10) + .padding(.horizontal, ApproachNoteTheme.spacingXS) .transition(.opacity) } } - .padding(.top, 8) + .padding(.top, ApproachNoteTheme.spacingXS) .animation(.easeInOut(duration: 0.15), value: isHovering) } } diff --git a/apps/Mac/Components/RecordingCard.swift b/apps/Mac/Components/RecordingCard.swift index 93e1d7d..91ad9b1 100644 --- a/apps/Mac/Components/RecordingCard.swift +++ b/apps/Mac/Components/RecordingCard.swift @@ -55,7 +55,7 @@ struct RecordingCard: View { } var body: some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { // Album art Group { if let frontUrl = frontCoverUrl { @@ -96,7 +96,7 @@ struct RecordingCard: View { .shadow(color: .black.opacity(0.15), radius: 6, x: 0, y: 3) // Recording info below artwork — Year → Artist → Album → (Song Title) - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { // Year if let year = recording.recordingYear { Text(String(year)) @@ -128,7 +128,7 @@ struct RecordingCard: View { } .frame(width: artworkSize, alignment: .leading) } - .padding(12) + .padding(ApproachNoteTheme.spacingSM) .background(isHovering ? ApproachNoteTheme.background : ApproachNoteTheme.surface) .clipShape(RoundedRectangle(cornerRadius: 12)) .overlay( diff --git a/apps/Mac/Components/StreamingButtons.swift b/apps/Mac/Components/StreamingButtons.swift index f9de0c5..74962ad 100644 --- a/apps/Mac/Components/StreamingButtons.swift +++ b/apps/Mac/Components/StreamingButtons.swift @@ -29,7 +29,7 @@ struct StreamingButtons: View { } var body: some View { - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { if let urlString = spotifyUrl, let url = URL(string: urlString) { Link(destination: url) { StreamingIcon(service: .spotify, size: 22) diff --git a/apps/Mac/Components/TranscriptionRow.swift b/apps/Mac/Components/TranscriptionRow.swift index aebc90e..4a92029 100644 --- a/apps/Mac/Components/TranscriptionRow.swift +++ b/apps/Mac/Components/TranscriptionRow.swift @@ -16,7 +16,7 @@ struct TranscriptionRow: View { var body: some View { Button(action: openYouTube) { - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { // Play button thumbnail ZStack { RoundedRectangle(cornerRadius: 8) @@ -29,15 +29,15 @@ struct TranscriptionRow: View { } // Transcription info - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(transcription.albumTitle ?? "Solo Transcription") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) .lineLimit(2) - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { if let year = transcription.recordingYear { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: "calendar") .foregroundColor(ApproachNoteTheme.textSecondary) .font(ApproachNoteTheme.caption()) @@ -48,7 +48,7 @@ struct TranscriptionRow: View { } if let label = transcription.label { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: "opticaldisc") .foregroundColor(ApproachNoteTheme.textSecondary) .font(ApproachNoteTheme.caption()) diff --git a/apps/Mac/Views/ArtistsListView.swift b/apps/Mac/Views/ArtistsListView.swift index 905c0e0..a99aa32 100644 --- a/apps/Mac/Views/ArtistsListView.swift +++ b/apps/Mac/Views/ArtistsListView.swift @@ -128,7 +128,7 @@ struct ArtistsListView: View { .foregroundColor(.white) Spacer() } - .padding(.horizontal, 12) + .padding(.horizontal, ApproachNoteTheme.spacingSM) .padding(.vertical, 6) .frame(maxWidth: .infinity) .background( @@ -223,7 +223,7 @@ struct ArtistRowView: View { } var body: some View { - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { formattedName(for: performer) if let instrument = performer.instrument { Text(instrument) @@ -231,8 +231,8 @@ struct ArtistRowView: View { .foregroundStyle(isSelected ? Color.white.opacity(0.85) : ApproachNoteTheme.textSecondary) } } - .padding(.vertical, 4) - .padding(.horizontal, 8) + .padding(.vertical, ApproachNoteTheme.spacingXXS) + .padding(.horizontal, ApproachNoteTheme.spacingXS) } } diff --git a/apps/Mac/Views/CommunitySettingsView.swift b/apps/Mac/Views/CommunitySettingsView.swift index c10602a..387c394 100644 --- a/apps/Mac/Views/CommunitySettingsView.swift +++ b/apps/Mac/Views/CommunitySettingsView.swift @@ -34,12 +34,12 @@ struct CommunitySettingsView: View { Form { Section { // Header with user info - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { Image(systemName: "person.3.fill") .font(.system(size: 32)) .foregroundColor(ApproachNoteTheme.textSecondary) - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text("Your Contributions") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -50,7 +50,7 @@ struct CommunitySettingsView: View { Spacer() } - .padding(.vertical, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) } Section("Contribution Statistics") { @@ -63,7 +63,7 @@ struct CommunitySettingsView: View { .foregroundColor(ApproachNoteTheme.textSecondary) Spacer() } - .padding(.vertical, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) } else if let stats = contributionStats { contributionStatsView(stats: stats) } else if let error = errorMessage { @@ -73,7 +73,7 @@ struct CommunitySettingsView: View { Text(error) .foregroundColor(ApproachNoteTheme.textSecondary) } - .padding(.vertical, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) } } } @@ -149,7 +149,7 @@ struct CommunitySettingsView: View { .foregroundColor(ApproachNoteTheme.textSecondary) .fontWeight(.medium) } - .padding(.top, 8) + .padding(.top, ApproachNoteTheme.spacingXS) } } @@ -157,14 +157,14 @@ struct CommunitySettingsView: View { @ViewBuilder private var notAuthenticatedView: some View { - VStack(spacing: 24) { + VStack(spacing: ApproachNoteTheme.spacingXL) { Spacer() Image(systemName: "person.3.fill") .font(.system(size: 60)) .foregroundColor(ApproachNoteTheme.textSecondary.opacity(0.5)) - VStack(spacing: 8) { + VStack(spacing: ApproachNoteTheme.spacingXS) { Text("Sign In to View Contributions") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -218,13 +218,13 @@ struct ContributionStatRow: View { let description: String var body: some View { - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { Image(systemName: icon) .font(.system(size: 18)) .foregroundColor(iconColor) .frame(width: 28) - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(label) .font(ApproachNoteTheme.body()) .bodyLineSpacing() @@ -242,7 +242,7 @@ struct ContributionStatRow: View { .fontWeight(.semibold) .foregroundColor(count > 0 ? ApproachNoteTheme.textPrimary : ApproachNoteTheme.textSecondary.opacity(0.5)) } - .padding(.vertical, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) } } diff --git a/apps/Mac/Views/ContentView.swift b/apps/Mac/Views/ContentView.swift index 54b79a2..1eea79d 100644 --- a/apps/Mac/Views/ContentView.swift +++ b/apps/Mac/Views/ContentView.swift @@ -72,7 +72,7 @@ struct ContentView: View { Label("Create New Repertoire", systemImage: "plus.circle") } } label: { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: "text.badge.checkmark") Text(repertoireManager.currentRepertoireDisplayName) .lineLimit(1) @@ -80,7 +80,7 @@ struct ContentView: View { } } else { Button(action: { showRepertoirePopover = true }) { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: "text.badge.checkmark") Text("All Songs") Image(systemName: "chevron.down") @@ -88,7 +88,7 @@ struct ContentView: View { } } .popover(isPresented: $showRepertoirePopover, arrowEdge: .bottom) { - VStack(spacing: 16) { + VStack(spacing: ApproachNoteTheme.spacingMD) { Text("Create and select repertoires to focus on a subset of songs.") .font(ApproachNoteTheme.body()) .bodyLineSpacing() @@ -104,8 +104,8 @@ struct ContentView: View { .bodyLineSpacing() .foregroundColor(.white) .frame(minWidth: 100) - .padding(.horizontal, 16) - .padding(.vertical, 8) + .padding(.horizontal, ApproachNoteTheme.spacingMD) + .padding(.vertical, ApproachNoteTheme.spacingXS) .background(ApproachNoteTheme.brand) .cornerRadius(6) } @@ -249,7 +249,7 @@ struct AccountSettingsView: View { VStack(spacing: 0) { Form { Section { - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { // Profile image or placeholder if let imageUrl = authManager.currentUser?.profileImageUrl, let url = URL(string: imageUrl) { @@ -270,7 +270,7 @@ struct AccountSettingsView: View { .foregroundColor(ApproachNoteTheme.textSecondary) } - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(authManager.currentUser?.displayName ?? "User") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -286,13 +286,13 @@ struct AccountSettingsView: View { } .foregroundColor(.red) } - .padding(.vertical, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) } } .formStyle(.grouped) // Favorites Section - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { HStack { Image(systemName: "heart.fill") .foregroundColor(.red) @@ -325,7 +325,7 @@ struct AccountSettingsView: View { .padding(.horizontal) } else { ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { ForEach(favoritesManager.favoriteRecordings, id: \.id) { recording in FavoriteRecordingCard(recording: recording) .onTapGesture { @@ -364,7 +364,7 @@ struct FavoriteRecordingCard: View { @State private var isHovering = false var body: some View { - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { // Album art AsyncImage(url: URL(string: recording.bestAlbumArtSmall ?? "")) { image in image @@ -388,7 +388,7 @@ struct FavoriteRecordingCard: View { .lineLimit(2) .frame(width: 80, alignment: .leading) } - .padding(8) + .padding(ApproachNoteTheme.spacingXS) .background(isHovering ? ApproachNoteTheme.surface : Color.clear) .cornerRadius(8) .onHover { hovering in @@ -476,7 +476,7 @@ struct GeneralSettingsView: View { } if let progress = progress { - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { HStack { Text(progress.phaseLabel) .font(ApproachNoteTheme.caption()) diff --git a/apps/Mac/Views/Import/MacYouTubeImportView.swift b/apps/Mac/Views/Import/MacYouTubeImportView.swift index 9c49974..0b4e8a7 100644 --- a/apps/Mac/Views/Import/MacYouTubeImportView.swift +++ b/apps/Mac/Views/Import/MacYouTubeImportView.swift @@ -143,7 +143,7 @@ struct MacYouTubeImportView: View { // MARK: - Auth Required View private var authRequiredView: some View { - VStack(spacing: 24) { + VStack(spacing: ApproachNoteTheme.spacingXL) { Spacer() Image(systemName: "lock.fill") @@ -173,21 +173,21 @@ struct MacYouTubeImportView: View { // MARK: - Video Info Header private var videoInfoHeader: some View { - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { Image(systemName: "play.rectangle.fill") .font(.title) .foregroundColor(.red) - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(youtubeData.title) .font(ApproachNoteTheme.headline()) .lineLimit(2) - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Text(youtubeData.videoType.displayName) .font(ApproachNoteTheme.caption()) .foregroundColor(.white) - .padding(.horizontal, 8) + .padding(.horizontal, ApproachNoteTheme.spacingXS) .padding(.vertical, 2) .background(youtubeData.videoType == .transcription ? Color.blue : Color.green) .cornerRadius(4) @@ -209,7 +209,7 @@ struct MacYouTubeImportView: View { // MARK: - Song Search Section private var songSearchSection: some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { Text("Search for a song to link this video to:") .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) @@ -263,7 +263,7 @@ struct MacYouTubeImportView: View { } }) { HStack { - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(song.title) .font(ApproachNoteTheme.headline()) @@ -285,7 +285,7 @@ struct MacYouTubeImportView: View { } } } else if !searchText.isEmpty && !isSearching { - VStack(spacing: 8) { + VStack(spacing: ApproachNoteTheme.spacingXS) { Image(systemName: "music.note") .font(.largeTitle) .foregroundColor(.secondary) @@ -451,7 +451,7 @@ struct MacRecordingPickerView: View { Spacer() } else if let error = loadError { Spacer() - VStack(spacing: 12) { + VStack(spacing: ApproachNoteTheme.spacingSM) { Image(systemName: "exclamationmark.triangle") .font(.largeTitle) .foregroundColor(.orange) @@ -461,7 +461,7 @@ struct MacRecordingPickerView: View { Spacer() } else if recordings.isEmpty { Spacer() - VStack(spacing: 12) { + VStack(spacing: ApproachNoteTheme.spacingSM) { Image(systemName: "opticaldisc") .font(.largeTitle) .foregroundColor(.secondary) @@ -502,12 +502,12 @@ struct MacRecordingPickerView: View { .foregroundColor(.secondary) } .padding(.horizontal) - .padding(.vertical, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) // Recordings list if filteredRecordings.isEmpty { Spacer() - VStack(spacing: 8) { + VStack(spacing: ApproachNoteTheme.spacingXS) { Image(systemName: "magnifyingglass") .font(.largeTitle) .foregroundColor(.secondary) @@ -535,7 +535,7 @@ struct MacRecordingPickerView: View { } private func recordingRow(_ recording: Recording) -> some View { - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { // Album art placeholder if let artUrl = recording.bestAlbumArtSmall, let url = URL(string: artUrl) { AsyncImage(url: url) { image in @@ -555,11 +555,11 @@ struct MacRecordingPickerView: View { } } - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(recording.albumTitle ?? "Unknown Album") .font(ApproachNoteTheme.headline()) - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { if let artist = recording.artistCredit { Text(artist) .font(ApproachNoteTheme.caption()) @@ -579,7 +579,7 @@ struct MacRecordingPickerView: View { Image(systemName: "chevron.right") .foregroundColor(.secondary) } - .padding(.vertical, 4) + .padding(.vertical, ApproachNoteTheme.spacingXXS) } private func loadRecordings() async { diff --git a/apps/Mac/Views/MacAddStreamingLinkSheet.swift b/apps/Mac/Views/MacAddStreamingLinkSheet.swift index 1770c7d..4918bf9 100644 --- a/apps/Mac/Views/MacAddStreamingLinkSheet.swift +++ b/apps/Mac/Views/MacAddStreamingLinkSheet.swift @@ -32,7 +32,7 @@ struct MacAddStreamingLinkSheet: View { // Content ScrollView { - VStack(alignment: .leading, spacing: 20) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingLG) { // Instructions instructionsView @@ -48,7 +48,7 @@ struct MacAddStreamingLinkSheet: View { successView(success) } } - .padding(20) + .padding(ApproachNoteTheme.spacingLG) } Divider() @@ -64,7 +64,7 @@ struct MacAddStreamingLinkSheet: View { private var headerView: some View { HStack { - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text("Add Streaming Link") .font(ApproachNoteTheme.title2()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -92,13 +92,13 @@ struct MacAddStreamingLinkSheet: View { // MARK: - Instructions private var instructionsView: some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { Text("Paste a streaming service URL") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) - VStack(alignment: .leading, spacing: 8) { - HStack(spacing: 8) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) .font(.caption) @@ -107,7 +107,7 @@ struct MacAddStreamingLinkSheet: View { .foregroundColor(ApproachNoteTheme.textSecondary) } - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) .font(.caption) @@ -116,7 +116,7 @@ struct MacAddStreamingLinkSheet: View { .foregroundColor(ApproachNoteTheme.textSecondary) } - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) .font(.caption) @@ -125,7 +125,7 @@ struct MacAddStreamingLinkSheet: View { .foregroundColor(ApproachNoteTheme.textSecondary) } - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Image(systemName: "info.circle") .foregroundColor(ApproachNoteTheme.textSecondary) .font(.caption) @@ -134,7 +134,7 @@ struct MacAddStreamingLinkSheet: View { .foregroundColor(ApproachNoteTheme.textSecondary) } } - .padding(12) + .padding(ApproachNoteTheme.spacingSM) .background(ApproachNoteTheme.surface) .cornerRadius(8) } @@ -143,7 +143,7 @@ struct MacAddStreamingLinkSheet: View { // MARK: - URL Input private var urlInputView: some View { - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Track URL or ID") .font(ApproachNoteTheme.subheadline(weight: .medium)) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -179,7 +179,7 @@ struct MacAddStreamingLinkSheet: View { @ViewBuilder private var serviceIndicator: some View { let detection = detectService(from: urlInput) - HStack(spacing: 6) { + HStack(spacing: ApproachNoteTheme.spacingXS) { switch detection { case .valid(let service): Image(systemName: "checkmark.circle.fill") @@ -267,28 +267,28 @@ struct MacAddStreamingLinkSheet: View { // MARK: - Messages private func errorView(_ message: String) -> some View { - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Image(systemName: "exclamationmark.triangle.fill") .foregroundColor(.red) Text(message) .font(ApproachNoteTheme.subheadline()) .foregroundColor(.red) } - .padding(12) + .padding(ApproachNoteTheme.spacingSM) .frame(maxWidth: .infinity, alignment: .leading) .background(Color.red.opacity(0.1)) .cornerRadius(8) } private func successView(_ message: String) -> some View { - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) Text(message) .font(ApproachNoteTheme.subheadline()) .foregroundColor(.green) } - .padding(12) + .padding(ApproachNoteTheme.spacingSM) .frame(maxWidth: .infinity, alignment: .leading) .background(Color.green.opacity(0.1)) .cornerRadius(8) diff --git a/apps/Mac/Views/MacOnboardingView.swift b/apps/Mac/Views/MacOnboardingView.swift index 4362d33..cfa51ee 100644 --- a/apps/Mac/Views/MacOnboardingView.swift +++ b/apps/Mac/Views/MacOnboardingView.swift @@ -44,7 +44,7 @@ struct MacOnboardingView: View { currentPage -= 1 } }) { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: "chevron.left") Text("Back") } @@ -57,7 +57,7 @@ struct MacOnboardingView: View { Spacer() // Page indicators - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { ForEach(0.. some View { - HStack(alignment: .top, spacing: 12) { + HStack(alignment: .top, spacing: ApproachNoteTheme.spacingSM) { // Score indicator scoreIndicator(score: work.score) - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(work.title) .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -214,7 +214,7 @@ struct MusicBrainzSearchSheet: View { Spacer() } - .padding(.vertical, 4) + .padding(.vertical, ApproachNoteTheme.spacingXXS) } private func scoreIndicator(score: Int?) -> some View { @@ -225,7 +225,7 @@ struct MusicBrainzSearchSheet: View { return ApproachNoteTheme.textSecondary }() - return VStack(spacing: 2) { + return VStack(spacing: ApproachNoteTheme.spacingXXS) { Circle() .fill(color) .frame(width: 8, height: 8) diff --git a/apps/Mac/Views/PerformerDetailView.swift b/apps/Mac/Views/PerformerDetailView.swift index 6f9e366..8686d3a 100644 --- a/apps/Mac/Views/PerformerDetailView.swift +++ b/apps/Mac/Views/PerformerDetailView.swift @@ -43,7 +43,8 @@ struct PerformerDetailView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .padding(.top, 100) } else if let performer = performer { - VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXL) { + // Section rhythm matches SongDetailView (spacingMD between sections). + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingMD) { // Header with image carousel performerHeader(performer) @@ -329,6 +330,7 @@ struct PerformerDetailView: View { ) } .menuStyle(.borderlessButton) + .menuIndicator(.hidden) } // MARK: Role picker (brand-outlined segmented, matches iOS) @@ -662,11 +664,11 @@ private struct PerformerImageCarousel: View { Image(systemName: systemImage) .font(.system(size: 14, weight: .semibold)) .foregroundColor(.white) - .padding(8) + .padding(ApproachNoteTheme.spacingXS) .background(Circle().fill(Color.black.opacity(0.45))) } .buttonStyle(.plain) - .padding(8) + .padding(ApproachNoteTheme.spacingXS) } private var placeholder: some View { diff --git a/apps/Mac/Views/RecordingDetailView.swift b/apps/Mac/Views/RecordingDetailView.swift index 06b2e45..7088192 100644 --- a/apps/Mac/Views/RecordingDetailView.swift +++ b/apps/Mac/Views/RecordingDetailView.swift @@ -107,7 +107,7 @@ struct RecordingDetailView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .padding(.top, 100) } else if let recording = recording { - VStack(alignment: .leading, spacing: 24) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXL) { // Header with album art recordingHeader(recording) @@ -156,7 +156,7 @@ struct RecordingDetailView: View { Button { viewModel.handleFavoriteTap() } label: { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: isFavorited ? "heart.fill" : "heart") if displayFavoriteCount > 0 { Text("\(displayFavoriteCount)") @@ -219,7 +219,7 @@ struct RecordingDetailView: View { @ViewBuilder private func recordingHeader(_ recording: Recording) -> some View { - VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingMD) { // Full-width album art Group { if let frontUrl = displayAlbumArtLarge { @@ -247,9 +247,9 @@ struct RecordingDetailView: View { .animation(.easeInOut(duration: 0.3), value: selectedReleaseId) // Song title, album title, and artist below the image - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { // Recording Name (Year) — matches SongDetailView title pattern - HStack(alignment: .firstTextBaseline, spacing: 8) { + HStack(alignment: .firstTextBaseline, spacing: ApproachNoteTheme.spacingXS) { if let songTitle = recording.songTitle { ( Text(songTitle) @@ -311,7 +311,7 @@ struct RecordingDetailView: View { recording.musicbrainzId != nil if hasMetadata { - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { if let year = displayReleaseYear { metadataLine(label: "RELEASE YEAR", value: String(year)) } @@ -327,7 +327,7 @@ struct RecordingDetailView: View { .bodyLineSpacing() .foregroundColor(ApproachNoteTheme.textPrimary) .fixedSize(horizontal: false, vertical: true) - .padding(.top, 4) + .padding(.top, ApproachNoteTheme.spacingXXS) } if let mbId = recording.musicbrainzId, let mbUrl = URL(string: "https://musicbrainz.org/recording/\(mbId)") { @@ -335,10 +335,10 @@ struct RecordingDetailView: View { .font(ApproachNoteTheme.body()) .bodyLineSpacing() .foregroundColor(ApproachNoteTheme.brand) - .padding(.top, 4) + .padding(.top, ApproachNoteTheme.spacingXXS) } } - .padding(.top, 4) + .padding(.top, ApproachNoteTheme.spacingXXS) } } @@ -386,12 +386,12 @@ struct RecordingDetailView: View { @ViewBuilder private func streamingSection(_ recording: Recording) -> some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { Text("Listen") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { if let spotifyUrlString = spotifyUrl(for: recording), let url = URL(string: spotifyUrlString) { Link(destination: url) { @@ -424,15 +424,15 @@ struct RecordingDetailView: View { @ViewBuilder private func performersSection(_ performers: [Performer]) -> some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { Text("Personnel (\(performers.count.formatted()))") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) - LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 12) { + LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: ApproachNoteTheme.spacingSM) { ForEach(performers) { performer in HStack { - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(performer.name) .font(ApproachNoteTheme.subheadline(weight: .medium)) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -456,7 +456,7 @@ struct RecordingDetailView: View { .cornerRadius(4) } } - .padding(10) + .padding(ApproachNoteTheme.spacingXS) .background(ApproachNoteTheme.surface) .cornerRadius(8) } @@ -479,7 +479,7 @@ struct RecordingDetailView: View { @ViewBuilder private func releasesSection(_ releases: [Release]) -> some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { HStack { Text("Releases (\(releases.count.formatted()))") .font(ApproachNoteTheme.headline()) @@ -505,7 +505,7 @@ struct RecordingDetailView: View { } } } label: { - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { // Selection indicator Image(systemName: isSelected ? "checkmark.circle.fill" : "circle") .font(ApproachNoteTheme.title3()) @@ -531,13 +531,13 @@ struct RecordingDetailView: View { .stroke(isSelected ? ApproachNoteTheme.brand : Color.clear, lineWidth: 2) ) - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(release.title) .font(ApproachNoteTheme.subheadline(weight: isSelected ? .bold : .medium)) .foregroundColor(isSelected ? ApproachNoteTheme.brand : ApproachNoteTheme.textPrimary) .lineLimit(1) - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Text(release.yearDisplay) .font(ApproachNoteTheme.caption()) .foregroundColor(ApproachNoteTheme.textSecondary) @@ -564,7 +564,7 @@ struct RecordingDetailView: View { Spacer() // Streaming indicators - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { if release.hasSpotify { Image(systemName: "music.note") .foregroundColor(.green) @@ -586,7 +586,7 @@ struct RecordingDetailView: View { } } } - .padding(10) + .padding(ApproachNoteTheme.spacingXS) .background(isSelected ? ApproachNoteTheme.brand.opacity(0.1) : ApproachNoteTheme.surface) .cornerRadius(8) .contentShape(Rectangle()) diff --git a/apps/Mac/Views/RecordingsListView.swift b/apps/Mac/Views/RecordingsListView.swift index e6ffa64..f253128 100644 --- a/apps/Mac/Views/RecordingsListView.swift +++ b/apps/Mac/Views/RecordingsListView.swift @@ -178,13 +178,13 @@ struct RecordingsListView: View { .buttonStyle(.plain) } .padding(.horizontal) - .padding(.vertical, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) .background(ApproachNoteTheme.surface) } // No results message if !searchText.isEmpty && filteredRecordings.isEmpty && !recordingService.isLoading { - VStack(spacing: 12) { + VStack(spacing: ApproachNoteTheme.spacingSM) { Image(systemName: "magnifyingglass") .font(.system(size: 32)) .foregroundColor(ApproachNoteTheme.textSecondary.opacity(0.5)) @@ -205,7 +205,7 @@ struct RecordingsListView: View { LazyVStack(spacing: 0) { ForEach(filteredRecordings) { recording in RecordingRowView(recording: recording) - .padding(.horizontal, 12) + .padding(.horizontal, ApproachNoteTheme.spacingSM) .padding(.vertical, 6) .background(selectedRecordingId == recording.id ? ApproachNoteTheme.brand.opacity(0.15) : Color.clear) .contentShape(Rectangle()) @@ -256,7 +256,7 @@ struct RecordingsListView: View { @ViewBuilder private var filterToolbar: some View { - VStack(spacing: 8) { + VStack(spacing: ApproachNoteTheme.spacingXS) { MacSearchBar( text: $searchText, placeholder: "Search recordings...", @@ -264,7 +264,7 @@ struct RecordingsListView: View { ) // Availability row - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { Text("Availability:") .font(ApproachNoteTheme.subheadline()) .foregroundColor(.white) @@ -285,7 +285,7 @@ struct RecordingsListView: View { } } } label: { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: availabilityFilter.icon) .foregroundColor(availabilityFilter == .all ? ApproachNoteTheme.textPrimary : availabilityFilter.iconColor) Text(availabilityFilter.rawValue) @@ -295,7 +295,7 @@ struct RecordingsListView: View { .font(ApproachNoteTheme.caption2()) .foregroundColor(ApproachNoteTheme.textPrimary) } - .padding(.horizontal, 10) + .padding(.horizontal, ApproachNoteTheme.spacingXS) .padding(.vertical, 6) .background(Color.white) .cornerRadius(8) @@ -339,7 +339,7 @@ struct RecordingsListView: View { } } } label: { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: vocalFilter.icon) .foregroundColor(vocalFilter == .all ? ApproachNoteTheme.textPrimary : vocalFilter.iconColor) Text(vocalFilter.rawValue) @@ -349,7 +349,7 @@ struct RecordingsListView: View { .font(ApproachNoteTheme.caption2()) .foregroundColor(ApproachNoteTheme.textPrimary) } - .padding(.horizontal, 10) + .padding(.horizontal, ApproachNoteTheme.spacingXS) .padding(.vertical, 6) .background(Color.white) .cornerRadius(8) @@ -374,7 +374,7 @@ struct RecordingsListView: View { .padding(.horizontal) // Search scope picker - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { Text("Search in:") .font(ApproachNoteTheme.subheadline()) .foregroundColor(.white) @@ -390,7 +390,7 @@ struct RecordingsListView: View { Spacer() } .padding(.horizontal) - .padding(.bottom, 8) + .padding(.bottom, ApproachNoteTheme.spacingXS) } .background(ApproachNoteTheme.textSecondary) .environment(\.colorScheme, .light) @@ -411,7 +411,7 @@ struct RecordingRowView: View { let recording: Recording var body: some View { - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { // Album art Group { if let albumArtUrl = recording.bestAlbumArtSmall ?? recording.bestAlbumArtMedium { @@ -436,7 +436,7 @@ struct RecordingRowView: View { .frame(width: 50, height: 50) .cornerRadius(4) - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(recording.albumTitle ?? "Unknown Album") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -469,7 +469,7 @@ struct RecordingRowView: View { Spacer() // Streaming indicators - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { if recording.hasSpotifyAvailable { Image(systemName: "play.circle.fill") .foregroundColor(.green) @@ -492,7 +492,7 @@ struct RecordingRowView: View { AuthorityBadge(text: badgeText, source: recording.primaryAuthoritySource) } } - .padding(.vertical, 4) + .padding(.vertical, ApproachNoteTheme.spacingXXS) } } diff --git a/apps/Mac/Views/Repertoire/MacAddToRepertoireSheet.swift b/apps/Mac/Views/Repertoire/MacAddToRepertoireSheet.swift index 1290b2e..37e4442 100644 --- a/apps/Mac/Views/Repertoire/MacAddToRepertoireSheet.swift +++ b/apps/Mac/Views/Repertoire/MacAddToRepertoireSheet.swift @@ -64,7 +64,7 @@ struct MacAddToRepertoireSheet: View { private var headerView: some View { HStack { - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text("Add to Repertoire") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -90,7 +90,7 @@ struct MacAddToRepertoireSheet: View { // MARK: - Auth Required View private var authRequiredView: some View { - VStack(spacing: 20) { + VStack(spacing: ApproachNoteTheme.spacingLG) { Image(systemName: "lock.fill") .font(.system(size: 48)) .foregroundColor(ApproachNoteTheme.brand.opacity(0.6)) @@ -118,7 +118,7 @@ struct MacAddToRepertoireSheet: View { // MARK: - Loading View private var loadingView: some View { - VStack(spacing: 16) { + VStack(spacing: ApproachNoteTheme.spacingMD) { ProgressView() .controlSize(.large) Text("Loading repertoires...") @@ -131,7 +131,7 @@ struct MacAddToRepertoireSheet: View { // MARK: - Empty Repertoires View private var emptyRepertoiresView: some View { - VStack(spacing: 20) { + VStack(spacing: ApproachNoteTheme.spacingLG) { Image(systemName: "music.note.list") .font(.system(size: 48)) .foregroundColor(ApproachNoteTheme.textSecondary.opacity(0.5)) @@ -162,16 +162,16 @@ struct MacAddToRepertoireSheet: View { VStack(spacing: 0) { // Quick add section if let lastUsed = repertoireManager.lastUsedRepertoire { - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Quick Add") .font(ApproachNoteTheme.caption()) .foregroundColor(ApproachNoteTheme.textPrimary.opacity(0.6)) .padding(.horizontal) - .padding(.top, 12) + .padding(.top, ApproachNoteTheme.spacingSM) Button(action: { addToRepertoire(lastUsed) }) { HStack { - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text("Add to \(lastUsed.name)") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -195,17 +195,17 @@ struct MacAddToRepertoireSheet: View { } // All repertoires section - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("All Repertoires") .font(ApproachNoteTheme.caption()) .foregroundColor(ApproachNoteTheme.textPrimary.opacity(0.6)) .padding(.horizontal) - .padding(.top, 16) + .padding(.top, ApproachNoteTheme.spacingMD) ForEach(repertoireManager.addableRepertoires) { repertoire in Button(action: { addToRepertoire(repertoire) }) { HStack { - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(repertoire.name) .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -246,7 +246,7 @@ struct MacAddToRepertoireSheet: View { } .buttonStyle(.plain) .padding(.horizontal) - .padding(.vertical, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) } .padding(.bottom) } @@ -259,7 +259,7 @@ struct MacAddToRepertoireSheet: View { ZStack { Color.black.opacity(0.3) - VStack(spacing: 16) { + VStack(spacing: ApproachNoteTheme.spacingMD) { ProgressView() .controlSize(.large) Text("Adding to repertoire...") diff --git a/apps/Mac/Views/Repertoire/MacCreateRepertoireView.swift b/apps/Mac/Views/Repertoire/MacCreateRepertoireView.swift index 96bb03a..d03ec3e 100644 --- a/apps/Mac/Views/Repertoire/MacCreateRepertoireView.swift +++ b/apps/Mac/Views/Repertoire/MacCreateRepertoireView.swift @@ -22,17 +22,17 @@ struct MacCreateRepertoireView: View { } var body: some View { - VStack(spacing: 20) { + VStack(spacing: ApproachNoteTheme.spacingLG) { // Header Text("Create Repertoire") .font(ApproachNoteTheme.title()) .foregroundColor(ApproachNoteTheme.textPrimary) - .padding(.top, 20) + .padding(.top, ApproachNoteTheme.spacingLG) // Form - VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingMD) { // Name field - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Name") .font(ApproachNoteTheme.subheadline()) .foregroundColor(ApproachNoteTheme.textPrimary.opacity(0.7)) @@ -46,7 +46,7 @@ struct MacCreateRepertoireView: View { } // Description field - VStack(alignment: .leading, spacing: 6) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Description (optional)") .font(ApproachNoteTheme.subheadline()) .foregroundColor(ApproachNoteTheme.textPrimary.opacity(0.7)) @@ -78,7 +78,7 @@ struct MacCreateRepertoireView: View { } // Buttons - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { Button("Cancel") { dismiss() } @@ -93,7 +93,7 @@ struct MacCreateRepertoireView: View { .controlSize(.large) .disabled(!isFormValid || isCreating) } - .padding(.bottom, 20) + .padding(.bottom, ApproachNoteTheme.spacingLG) } .frame(width: 350, height: 350) .overlay { @@ -101,13 +101,13 @@ struct MacCreateRepertoireView: View { ZStack { Color.black.opacity(0.3) - VStack(spacing: 16) { + VStack(spacing: ApproachNoteTheme.spacingMD) { ProgressView() .controlSize(.large) Text("Creating repertoire...") .font(ApproachNoteTheme.subheadline()) } - .padding(24) + .padding(ApproachNoteTheme.spacingXL) .background(ApproachNoteTheme.surface) .cornerRadius(12) } diff --git a/apps/Mac/Views/SongBulkEditRecordingsView.swift b/apps/Mac/Views/SongBulkEditRecordingsView.swift index 97cb3ed..4325630 100644 --- a/apps/Mac/Views/SongBulkEditRecordingsView.swift +++ b/apps/Mac/Views/SongBulkEditRecordingsView.swift @@ -150,18 +150,18 @@ struct SongBulkEditRecordingsView: View { private var header: some View { HStack { - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text("Bulk Edit Recordings") .font(ApproachNoteTheme.title2(weight: .semibold)) .foregroundColor(.primary) - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Text(songTitle) .font(ApproachNoteTheme.subheadline()) .foregroundColor(.secondary) Text("\(recordings.count) recordings") .font(ApproachNoteTheme.caption()) .foregroundColor(.secondary) - .padding(.horizontal, 8) + .padding(.horizontal, ApproachNoteTheme.spacingXS) .padding(.vertical, 2) .background(Color.accentColor.opacity(0.1)) .cornerRadius(4) @@ -181,7 +181,7 @@ struct SongBulkEditRecordingsView: View { // MARK: - Column Headers private var columnHeaders: some View { - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { sortableHeader("Artist", column: .artist) .frame(maxWidth: .infinity, alignment: .leading) sortableHeader("Release", column: .release) @@ -199,7 +199,7 @@ struct SongBulkEditRecordingsView: View { } .font(ApproachNoteTheme.caption(weight: .semibold)) .padding(.horizontal) - .padding(.vertical, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) .background(Color(NSColor.controlBackgroundColor)) } @@ -213,7 +213,7 @@ struct SongBulkEditRecordingsView: View { sortOrder = .ascending } } label: { - HStack(spacing: 2) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Text(title) if sortColumn == column { Image(systemName: sortOrder == .ascending ? "chevron.up" : "chevron.down") @@ -231,7 +231,7 @@ struct SongBulkEditRecordingsView: View { private func recordingRow(_ recording: Recording) -> some View { let state = rowStates[recording.id] ?? BulkEditRowState() - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { // Artist Text(recording.artistCredit ?? "Unknown Artist") .font(ApproachNoteTheme.caption()) @@ -298,7 +298,7 @@ struct SongBulkEditRecordingsView: View { Button { openURL(url) } label: { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: "play.circle.fill") .font(.system(size: 12)) Text(title) diff --git a/apps/Mac/Views/SongDetailView.swift b/apps/Mac/Views/SongDetailView.swift index bc1c0a5..e96284d 100644 --- a/apps/Mac/Views/SongDetailView.swift +++ b/apps/Mac/Views/SongDetailView.swift @@ -81,7 +81,7 @@ struct SongDetailView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .padding(.top, 100) } else if let song = song { - VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingMD) { // Header songHeader(song) @@ -124,7 +124,7 @@ struct SongDetailView: View { } .padding() } else { - VStack(spacing: 16) { + VStack(spacing: ApproachNoteTheme.spacingMD) { Image(systemName: "exclamationmark.triangle") .font(.system(size: 50)) .foregroundColor(ApproachNoteTheme.accent) @@ -200,7 +200,7 @@ struct SongDetailView: View { @ViewBuilder private func songHeader(_ song: Song) -> some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { // Title row with Add to Repertoire button HStack(alignment: .firstTextBaseline) { // Title with composed year @@ -224,7 +224,7 @@ struct SongDetailView: View { } } label: { Label("Refresh", systemImage: isRefreshing ? "arrow.triangle.2.circlepath" : "arrow.clockwise") - .padding(.vertical, 4) + .padding(.vertical, ApproachNoteTheme.spacingXXS) } .menuStyle(.borderlessButton) .help(canQueueForRefresh ? "Quick: uses cached data (faster). Full: re-fetches everything." : researchStatusHelperText) @@ -243,7 +243,7 @@ struct SongDetailView: View { ) }) { Label("Bulk Edit", systemImage: "tablecells") - .padding(.vertical, 4) + .padding(.vertical, ApproachNoteTheme.spacingXXS) } .buttonStyle(.bordered) .disabled(song.recordings == nil || song.recordings?.isEmpty == true || isRecordingsLoading) @@ -253,7 +253,7 @@ struct SongDetailView: View { // Add to Repertoire button Button(action: { showAddToRepertoire = true }) { Label("Add to Repertoire", systemImage: "plus.circle") - .padding(.vertical, 4) + .padding(.vertical, ApproachNoteTheme.spacingXXS) } .buttonStyle(.borderedProminent) .tint(ApproachNoteTheme.brand) @@ -270,7 +270,7 @@ struct SongDetailView: View { // Song Reference (if available) if let songRef = song.songReference { - HStack(alignment: .top, spacing: 8) { + HStack(alignment: .top, spacing: ApproachNoteTheme.spacingXS) { Image(systemName: "book.closed.fill") .foregroundColor(ApproachNoteTheme.textSecondary) .font(ApproachNoteTheme.subheadline()) @@ -279,7 +279,7 @@ struct SongDetailView: View { .foregroundColor(ApproachNoteTheme.textSecondary) .fixedSize(horizontal: false, vertical: true) } - .padding(.top, 4) + .padding(.top, ApproachNoteTheme.spacingXXS) } // Success/Error messages @@ -291,7 +291,7 @@ struct SongDetailView: View { .foregroundColor(.green) } .font(ApproachNoteTheme.subheadline()) - .padding(.vertical, 4) + .padding(.vertical, ApproachNoteTheme.spacingXXS) } if let message = errorMessage { @@ -302,7 +302,7 @@ struct SongDetailView: View { .foregroundColor(.red) } .font(ApproachNoteTheme.subheadline()) - .padding(.vertical, 4) + .padding(.vertical, ApproachNoteTheme.spacingXXS) } // Research status indicator @@ -384,7 +384,7 @@ struct SongDetailView: View { @ViewBuilder private func structureSection(_ song: Song) -> some View { if let structure = song.structure, !structure.isEmpty { - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text(structure) .font(ApproachNoteTheme.body()) .bodyLineSpacing() @@ -405,7 +405,7 @@ struct SongDetailView: View { @ViewBuilder private func composedKeyRow(_ composedKey: String) -> some View { - HStack(spacing: 8) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Image(systemName: "tuningfork") .foregroundColor(ApproachNoteTheme.textSecondary) Text("Original Key:") @@ -420,13 +420,13 @@ struct SongDetailView: View { @ViewBuilder private func learnMoreSection(_ song: Song) -> some View { - VStack(alignment: .leading, spacing: 10) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Learn More:") .font(ApproachNoteTheme.body(weight: .semibold)) .bodyLineSpacing() .foregroundColor(ApproachNoteTheme.textPrimary) - HStack(spacing: 12) { + HStack(spacing: ApproachNoteTheme.spacingSM) { if let wikipediaUrl = song.wikipediaUrl, let url = URL(string: wikipediaUrl) { compactExternalLink(label: "Wikipedia", url: url) } @@ -465,9 +465,9 @@ struct SongDetailView: View { let grouped = RecordingGrouping.grouped(filtered, sortOrder: sortOrder) let availableInstruments = RecordingGrouping.availableInstruments(in: recordings) - VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingMD) { // Heading - HStack(alignment: .firstTextBaseline, spacing: 6) { + HStack(alignment: .firstTextBaseline, spacing: ApproachNoteTheme.spacingXS) { Text("ALL RECORDINGS") .font(ApproachNoteTheme.title3()) .bold() @@ -481,17 +481,17 @@ struct SongDetailView: View { } // Filter + Sort row: Filter on the left, Sort right-justified. - HStack(spacing: 10) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Button(action: { showFilterPopover = true }) { - HStack(spacing: 6) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Text("Filter") .font(ApproachNoteTheme.subheadline(weight: .bold)) Image(systemName: "slider.horizontal.3") .font(.caption) } .foregroundColor(ApproachNoteTheme.textPrimary) - .padding(.horizontal, 12) - .padding(.vertical, 8) + .padding(.horizontal, ApproachNoteTheme.spacingSM) + .padding(.vertical, ApproachNoteTheme.spacingXS) .background(ApproachNoteTheme.surface) .cornerRadius(8) .overlay( @@ -516,7 +516,7 @@ struct SongDetailView: View { } } } label: { - HStack(spacing: 6) { + HStack(spacing: ApproachNoteTheme.spacingXS) { ( Text("Sort:") .font(ApproachNoteTheme.subheadline(weight: .bold)) @@ -534,8 +534,8 @@ struct SongDetailView: View { // Decorate the outer Menu, not the label: .borderlessButton // strips a custom background/overlay applied inside `label:`, // so the box has to live here to match the Filter button. - .padding(.horizontal, 12) - .padding(.vertical, 8) + .padding(.horizontal, ApproachNoteTheme.spacingSM) + .padding(.vertical, ApproachNoteTheme.spacingXS) .background(ApproachNoteTheme.surface) .cornerRadius(8) .overlay( @@ -548,7 +548,7 @@ struct SongDetailView: View { // Playable Only toggle (always visible) Toggle(isOn: $playableOnly) { - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text("Playable only?") .font(ApproachNoteTheme.callout(weight: .semibold)) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -561,7 +561,7 @@ struct SongDetailView: View { .tint(ApproachNoteTheme.brand) // Performance Type segmented (always visible) - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Performance Type") .font(ApproachNoteTheme.callout(weight: .semibold)) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -571,7 +571,7 @@ struct SongDetailView: View { // Recordings list if isRecordingsLoading { - VStack(spacing: 12) { + VStack(spacing: ApproachNoteTheme.spacingSM) { ProgressView() .scaleEffect(1.2) Text("Loading recordings...") @@ -581,7 +581,7 @@ struct SongDetailView: View { .frame(maxWidth: .infinity) .padding(.vertical, 40) } else if filtered.isEmpty { - VStack(spacing: 12) { + VStack(spacing: ApproachNoteTheme.spacingSM) { Image(systemName: "music.note") .font(.system(size: 40)) .foregroundColor(ApproachNoteTheme.textSecondary.opacity(0.5)) @@ -693,7 +693,7 @@ struct SongDetailView: View { .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.brand) } - .padding(.vertical, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) .contentShape(Rectangle()) } .buttonStyle(.plain) @@ -703,7 +703,7 @@ struct SongDetailView: View { recording.displayTitle(comparedTo: parentSongTitle) != nil } ScrollView(.horizontal, showsIndicators: false) { - HStack(alignment: .top, spacing: 16) { + HStack(alignment: .top, spacing: ApproachNoteTheme.spacingMD) { ForEach(group.recordings) { recording in RecordingCard( recording: recording, @@ -719,10 +719,10 @@ struct SongDetailView: View { } } } - .padding(.horizontal, 4) - .padding(.vertical, 4) + .padding(.horizontal, ApproachNoteTheme.spacingXXS) + .padding(.vertical, ApproachNoteTheme.spacingXXS) } - .padding(.bottom, 8) + .padding(.bottom, ApproachNoteTheme.spacingXS) } } } @@ -740,9 +740,9 @@ struct SongDetailView: View { @ViewBuilder private func filterPopoverContent(availableInstruments: [InstrumentFamily]) -> some View { ScrollView { - VStack(alignment: .leading, spacing: 24) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXL) { // Playback availability (multi-select) - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("Playback availability") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -751,7 +751,7 @@ struct SongDetailView: View { .foregroundColor(ApproachNoteTheme.textSecondary) .fixedSize(horizontal: false, vertical: true) - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { ForEach(StreamingService.allCases) { service in Toggle(isOn: Binding( get: { selectedServices.contains(service) }, @@ -771,12 +771,12 @@ struct SongDetailView: View { .tint(ApproachNoteTheme.brand) } } - .padding(.top, 4) + .padding(.top, ApproachNoteTheme.spacingXXS) } // By Instrument if !availableInstruments.isEmpty { - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXS) { Text("By Instrument") .font(ApproachNoteTheme.headline()) .foregroundColor(ApproachNoteTheme.textPrimary) @@ -789,12 +789,12 @@ struct SongDetailView: View { GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()) - ], spacing: 8) { + ], spacing: ApproachNoteTheme.spacingXS) { ForEach(availableInstruments, id: \.self) { family in Button(action: { selectedInstrument = (selectedInstrument == family) ? nil : family }) { - HStack(spacing: 6) { + HStack(spacing: ApproachNoteTheme.spacingXS) { Image(systemName: family.icon) .font(ApproachNoteTheme.caption()) Text(family.rawValue) @@ -803,8 +803,8 @@ struct SongDetailView: View { .minimumScaleFactor(0.8) } .frame(maxWidth: .infinity) - .padding(.vertical, 8) - .padding(.horizontal, 8) + .padding(.vertical, ApproachNoteTheme.spacingXS) + .padding(.horizontal, ApproachNoteTheme.spacingXS) .background(selectedInstrument == family ? ApproachNoteTheme.textSecondary : Color.white) .foregroundColor(selectedInstrument == family ? .white : ApproachNoteTheme.textPrimary) .cornerRadius(8) @@ -816,7 +816,7 @@ struct SongDetailView: View { .buttonStyle(.plain) } } - .padding(.top, 4) + .padding(.top, ApproachNoteTheme.spacingXXS) } } @@ -833,9 +833,9 @@ struct SongDetailView: View { Button("Done") { showFilterPopover = false } .keyboardShortcut(.defaultAction) } - .padding(.top, 8) + .padding(.top, ApproachNoteTheme.spacingXS) } - .padding(16) + .padding(ApproachNoteTheme.spacingMD) } .frame(width: 360, height: 420) } @@ -844,7 +844,7 @@ struct SongDetailView: View { @ViewBuilder private func featuredRecordingsSection(_ recordings: [Recording]) -> some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { Text("FEATURED RECORDINGS") .font(ApproachNoteTheme.title2()) .bold() @@ -859,7 +859,7 @@ struct SongDetailView: View { recording.displayTitle(comparedTo: parentSongTitle) != nil } ScrollView(.horizontal, showsIndicators: false) { - HStack(alignment: .top, spacing: 20) { + HStack(alignment: .top, spacing: ApproachNoteTheme.spacingLG) { ForEach(recordings) { recording in FeaturedRecordingCard( recording: recording, @@ -872,7 +872,7 @@ struct SongDetailView: View { } } } - .padding(.horizontal, 4) + .padding(.horizontal, ApproachNoteTheme.spacingXXS) } } } @@ -881,7 +881,7 @@ struct SongDetailView: View { @ViewBuilder private var transcriptionsSection: some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { HStack { Image(systemName: "music.quarternote.3") .foregroundColor(ApproachNoteTheme.accent) @@ -894,8 +894,8 @@ struct SongDetailView: View { Text("\(transcriptions.count)") .font(ApproachNoteTheme.subheadline()) .foregroundColor(ApproachNoteTheme.textSecondary) - .padding(.horizontal, 8) - .padding(.vertical, 4) + .padding(.horizontal, ApproachNoteTheme.spacingXS) + .padding(.vertical, ApproachNoteTheme.spacingXXS) .background(ApproachNoteTheme.accent.opacity(0.1)) .cornerRadius(6) } @@ -904,7 +904,7 @@ struct SongDetailView: View { TranscriptionRow(transcription: transcription) } } - .padding(16) + .padding(ApproachNoteTheme.spacingMD) .background(ApproachNoteTheme.surface) .cornerRadius(12) } @@ -913,7 +913,7 @@ struct SongDetailView: View { @ViewBuilder private var backingTracksSection: some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingSM) { HStack { Image(systemName: "play.circle.fill") .foregroundColor(ApproachNoteTheme.accent) @@ -926,8 +926,8 @@ struct SongDetailView: View { Text("\(backingTracks.count)") .font(ApproachNoteTheme.subheadline()) .foregroundColor(ApproachNoteTheme.textSecondary) - .padding(.horizontal, 8) - .padding(.vertical, 4) + .padding(.horizontal, ApproachNoteTheme.spacingXS) + .padding(.vertical, ApproachNoteTheme.spacingXXS) .background(ApproachNoteTheme.accent.opacity(0.1)) .cornerRadius(6) } @@ -936,7 +936,7 @@ struct SongDetailView: View { BackingTrackRow(video: video) } } - .padding(16) + .padding(ApproachNoteTheme.spacingMD) .background(ApproachNoteTheme.surface) .cornerRadius(12) } diff --git a/apps/Mac/Views/SongsListView.swift b/apps/Mac/Views/SongsListView.swift index 59b63e1..a847526 100644 --- a/apps/Mac/Views/SongsListView.swift +++ b/apps/Mac/Views/SongsListView.swift @@ -100,7 +100,7 @@ struct SongsListView: View { .foregroundColor(.white) Spacer() } - .padding(.horizontal, 12) + .padding(.horizontal, ApproachNoteTheme.spacingSM) .padding(.vertical, 6) .frame(maxWidth: .infinity) .background( @@ -164,7 +164,7 @@ struct SongsListView: View { } private var emptySearchResultsView: some View { - VStack(spacing: 12) { + VStack(spacing: ApproachNoteTheme.spacingSM) { Image(systemName: "magnifyingglass") .font(.system(size: 40)) .foregroundColor(ApproachNoteTheme.textSecondary.opacity(0.5)) @@ -181,7 +181,7 @@ struct SongsListView: View { Button(action: { showMusicBrainzSearch = true }) { - HStack(spacing: 4) { + HStack(spacing: ApproachNoteTheme.spacingXXS) { Image(systemName: "waveform") Text("Search MusicBrainz") } @@ -189,7 +189,7 @@ struct SongsListView: View { } .buttonStyle(.borderedProminent) .tint(ApproachNoteTheme.brand) - .padding(.top, 4) + .padding(.top, ApproachNoteTheme.spacingXXS) } .padding() .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -204,7 +204,7 @@ struct SongRowView: View { var isSelected: Bool = false var body: some View { - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: ApproachNoteTheme.spacingXXS) { Text(song.title) .font(ApproachNoteTheme.headline()) .foregroundStyle(isSelected ? Color.white : ApproachNoteTheme.textPrimary) @@ -214,8 +214,8 @@ struct SongRowView: View { .foregroundStyle(isSelected ? Color.white.opacity(0.85) : ApproachNoteTheme.textSecondary) } } - .padding(.vertical, 4) - .padding(.horizontal, 8) + .padding(.vertical, ApproachNoteTheme.spacingXXS) + .padding(.horizontal, ApproachNoteTheme.spacingXS) } }