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 d8b3f37247

d8b3f3724778ab7a2138104496aa9ad29f490618

parent: 12b2f378a9

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-19 04:59 UTC

Redesign the guided tours sheet

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,