krz/moonrise

Night-sky companion for iPhone, Apple Watch and Vision Pro: what's up tonight, the Moon, planets, launches and missions, and a Library of the solar system. SwiftUI app with a Cloudflare Workers backend. astronomy cloudflare-workers ios night-sky space swiftui visionos watchos

Commit 24212ca649

24212ca6491bfebc44aad929e1ea84e8108cd8c4

parent: 12b2f378a9

Unsigned

cmc <hello@cleberg.net> · 2026-10-19 05:00 UTC

Redesign the guided tours sheet (!43)

Guided tours sheet in the app's style, like Events: title, accent bar and intro; one card per tour with its months, stop count, first four stops as the headline, intro and Start tour. This month's tour is marked and outlined in red. Tour titles shortened to "Sep–Nov evenings" etc. Blue tint removed.

Layout: unified · split

App/Sources/SkyScreen.swift +1 −1
@@ -94,7 +94,7 @@ struct SkyScreen: View {
9494 .preferredColorScheme(.dark)
9595 }
9696 .sheet(isPresented: $showTours) {
97 TourPicker { tour = $0; tourStep = 0; aim(at: $0.steps[0].target) }.preferredColorScheme(.dark).tint(.blue)
97 TourPicker { tour = $0; tourStep = 0; aim(at: $0.steps[0].target) }.preferredColorScheme(.dark)
9898 }
9999 .onChange(of: tourStep) { if let tour { aim(at: tour.steps[tourStep].target) } }
100100 .sheet(isPresented: $showYear) {
App/Sources/TourViews.swift +56 −14
@@ -6,26 +6,68 @@ struct TourPicker: View {
66 var onPick: (Tour) -> Void
77 @Environment(\.dismiss) private var dismiss
88
9 private let ink = Color(red: 0.95, green: 0.95, blue: 0.93)
10 private let accent = Color(red: 0.88, green: 0.24, blue: 0.19)
11 private let month = Calendar.current.component(.month, from: .now)
12
913 var body: some View {
1014 NavigationStack {
11 List(Tour.ordered(month: Calendar.current.component(.month, from: .now))) { tour in
12 Button {
13 onPick(tour)
14 dismiss()
15 } label: {
16 VStack(alignment: .leading, spacing: 4) {
17 Text(tour.title).font(.body.weight(.medium))
18 Text(tour.intro).font(.footnote).foregroundStyle(.secondary)
19 Text("\(tour.steps.count) stops").font(.caption).foregroundStyle(.secondary)
20 }
21 .padding(.vertical, 4)
15 ScrollView {
16 VStack(alignment: .leading, spacing: 0) {
17 Text("Guided tours").font(.title.weight(.medium))
18 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 8).padding(.bottom, 12)
19 Text("A walk around the evening sky, one sight at a time. The view turns to each stop and marks what to look for. Best outside after dark, but they work anywhere.")
20 .font(.subheadline).foregroundStyle(ink.opacity(0.75))
21 .padding(.bottom, 20)
22 ForEach(Tour.ordered(month: month)) { tour in card(tour) }
2223 }
23 .buttonStyle(.plain)
24 .padding(20)
25 .readableWidth()
2426 }
25 .navigationTitle("Guided tours")
26 .navigationBarTitleDisplayMode(.inline)
27 .background(Color.black.ignoresSafeArea())
2728 .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } }
2829 }
30 .foregroundStyle(ink)
31 .tint(accent)
32 }
33
34 private func card(_ tour: Tour) -> some View {
35 let now = tour.months.contains(month)
36 return Button {
37 onPick(tour)
38 dismiss()
39 } label: {
40 VStack(alignment: .leading, spacing: 10) {
41 HStack {
42 if now {
43 Text("THIS MONTH").font(.caption2.weight(.bold)).tracking(0.8)
44 .padding(.horizontal, 6).padding(.vertical, 2)
45 .background(accent, in: RoundedRectangle(cornerRadius: 3))
46 }
47 Text(tour.title).font(.caption.weight(.medium)).foregroundStyle(ink.opacity(0.7))
48 Spacer()
49 Text("\(tour.steps.count) stops").font(.caption).foregroundStyle(ink.opacity(0.6))
50 }
51 Text(tour.steps.prefix(4).map(\.title).joined(separator: " · ") + (tour.steps.count > 4 ? " …" : ""))
52 .font(.title3.weight(.medium))
53 .multilineTextAlignment(.leading)
54 Text(tour.intro).font(.footnote).foregroundStyle(ink.opacity(0.75)).multilineTextAlignment(.leading)
55 HStack(spacing: 6) {
56 Text("Start tour").font(.subheadline.weight(.semibold))
57 Image(systemName: "arrow.right").font(.footnote.weight(.semibold))
58 }
59 .foregroundStyle(now ? accent : ink.opacity(0.8))
60 .padding(.top, 2)
61 }
62 .padding(18)
63 .frame(maxWidth: .infinity, alignment: .leading)
64 .background(ink.opacity(now ? 0.09 : 0.05), in: RoundedRectangle(cornerRadius: 18))
65 .overlay(RoundedRectangle(cornerRadius: 18).stroke(now ? accent.opacity(0.6) : ink.opacity(0.1), lineWidth: now ? 1 : 0.5))
66 .contentShape(RoundedRectangle(cornerRadius: 18))
67 }
68 .buttonStyle(.plain)
69 .padding(.bottom, 14)
70 .accessibilityHint("Starts the tour")
2971 }
3072}
3173
Packages/MoonriseKit/Sources/Library/Resources/tours/tours.json +4 −4
@@ -1,7 +1,7 @@
11[
22 {
33 "id": "winter",
4 "title": "December to February evenings",
4 "title": "Dec–Feb evenings",
55 "months": [
66 12,
77 1,
@@ -70,7 +70,7 @@
7070 },
7171 {
7272 "id": "spring",
73 "title": "March to May evenings",
73 "title": "Mar–May evenings",
7474 "months": [
7575 3,
7676 4,
@@ -117,7 +117,7 @@
117117 },
118118 {
119119 "id": "summer",
120 "title": "June to August evenings",
120 "title": "Jun–Aug evenings",
121121 "months": [
122122 6,
123123 7,
@@ -171,7 +171,7 @@
171171 },
172172 {
173173 "id": "autumn",
174 "title": "September to November evenings",
174 "title": "Sep–Nov evenings",
175175 "months": [
176176 9,
177177 10,