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 5b4208dbee

5b4208dbee8c606479836b541a105e9013ff317a

parent: 906cb6df4f

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-18 05:54 UTC

Use Dynamic Type text styles throughout the app

Replace fixed point sizes with text styles (caption2 through
largeTitle) in the Sky, Today, System, Sun, Launches and Library
screens; generated drawings keep fixed sizes. The Today summary wraps
to two lines. The draw-time readout shows only in Debug builds.

Layout: unified · split

App/Sources/LaunchesScreen.swift +18 −18
@@ -18,11 +18,11 @@ struct LaunchesScreen: View {
18 var body: some View { 18 var body: some View {
19 ScrollView { 19 ScrollView {
20 VStack(alignment: .leading, spacing: 0) { 20 VStack(alignment: .leading, spacing: 0) {
21 Text("Launches").font(.system(size: 28, weight: .medium)) 21 Text("Launches").font(.title.weight(.medium))
22 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 8).padding(.bottom, 8) 22 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 8).padding(.bottom, 8)
23 23
24 if let error { 24 if let error {
25 Text(error).font(.system(size: 13)).foregroundStyle(accent).padding(.vertical, 12) 25 Text(error).font(.footnote).foregroundStyle(accent).padding(.vertical, 12)
26 } 26 }
27 if let list { 27 if let list {
28 ForEach(Array(list.launches.enumerated()), id: \.element.id) { i, launch in 28 ForEach(Array(list.launches.enumerated()), id: \.element.id) { i, launch in
@@ -30,7 +30,7 @@ struct LaunchesScreen: View {
30 .buttonStyle(.plain) 30 .buttonStyle(.plain)
31 } 31 }
32 Text("Launch data: The Space Devs, Launch Library 2.") 32 Text("Launch data: The Space Devs, Launch Library 2.")
33 .font(.system(size: 10)).opacity(0.5).padding(.vertical, 16) 33 .font(.caption2).opacity(0.5).padding(.vertical, 16)
34 } else if error == nil { 34 } else if error == nil {
35 ProgressView().tint(ink).padding(.top, 40).frame(maxWidth: .infinity) 35 ProgressView().tint(ink).padding(.top, 40).frame(maxWidth: .infinity)
36 } 36 }
@@ -57,18 +57,18 @@ struct LaunchesScreen: View {
57 private func row(_ l: Launch, index: Int) -> some View { 57 private func row(_ l: Launch, index: Int) -> some View {
58 VStack(alignment: .leading, spacing: 6) { 58 VStack(alignment: .leading, spacing: 6) {
59 HStack(alignment: .firstTextBaseline) { 59 HStack(alignment: .firstTextBaseline) {
60 Text(String(format: "%02d", index)).font(.system(size: 10).monospacedDigit()).opacity(0.5) 60 Text(String(format: "%02d", index)).font(.caption2.monospacedDigit()).opacity(0.5)
61 Text(l.net.formatted(.dateTime.weekday(.abbreviated).day().month(.abbreviated).hour().minute())) 61 Text(l.net.formatted(.dateTime.weekday(.abbreviated).day().month(.abbreviated).hour().minute()))
62 .font(.system(size: 11)).opacity(0.7) 62 .font(.caption2).opacity(0.7)
63 Spacer() 63 Spacer()
64 StatusTag(status: l.status, ink: ink, accent: accent) 64 StatusTag(status: l.status, ink: ink, accent: accent)
65 } 65 }
66 Text(l.rocket).font(.system(size: 22, weight: .medium)) 66 Text(l.rocket).font(.title2.weight(.medium))
67 if let m = l.mission { Text(m).font(.system(size: 14)).lineLimit(1) } 67 if let m = l.mission { Text(m).font(.subheadline).lineLimit(1) }
68 HStack { 68 HStack {
69 Text("\(l.provider) · \(l.location)").font(.system(size: 11)).opacity(0.6).lineLimit(1) 69 Text("\(l.provider) · \(l.location)").font(.caption2).opacity(0.6).lineLimit(1)
70 Spacer() 70 Spacer()
71 Countdown(to: l.net).font(.system(size: 13, weight: .medium).monospacedDigit()) 71 Countdown(to: l.net).font(.footnote.weight(.medium).monospacedDigit())
72 } 72 }
73 } 73 }
74 .padding(.vertical, 14) 74 .padding(.vertical, 14)
@@ -84,7 +84,7 @@ struct StatusTag: View {
84 84
85 var body: some View { 85 var body: some View {
86 Text(status.uppercased()) 86 Text(status.uppercased())
87 .font(.system(size: 10, weight: .medium)) 87 .font(.caption2.weight(.medium))
88 .tracking(0.6) 88 .tracking(0.6)
89 .padding(.horizontal, 6).padding(.vertical, 2) 89 .padding(.horizontal, 6).padding(.vertical, 2)
90 .foregroundStyle(status == "Go" ? Color.black : ink) 90 .foregroundStyle(status == "Go" ? Color.black : ink)
@@ -121,11 +121,11 @@ struct LaunchDetail: View {
121 VStack(alignment: .leading, spacing: 10) { 121 VStack(alignment: .leading, spacing: 10) {
122 HStack { 122 HStack {
123 StatusTag(status: launch.status, ink: ink, accent: accent) 123 StatusTag(status: launch.status, ink: ink, accent: accent)
124 Text(launch.statusName).font(.system(size: 12)).opacity(0.7) 124 Text(launch.statusName).font(.caption).opacity(0.7)
125 } 125 }
126 Text(launch.rocket).font(.system(size: 28, weight: .medium)) 126 Text(launch.rocket).font(.title.weight(.medium))
127 if let m = launch.mission { Text(m).font(.system(size: 17)) } 127 if let m = launch.mission { Text(m).font(.body) }
128 Countdown(to: launch.net).font(.system(size: 34, weight: .medium).monospacedDigit()).padding(.vertical, 6) 128 Countdown(to: launch.net).font(.largeTitle.weight(.medium).monospacedDigit()).padding(.vertical, 6)
129 Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 6) { 129 Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 6) {
130 fact("Provider", launch.provider) 130 fact("Provider", launch.provider)
131 fact("Launch time", launch.net.formatted(date: .abbreviated, time: .shortened)) 131 fact("Launch time", launch.net.formatted(date: .abbreviated, time: .shortened))
@@ -137,11 +137,11 @@ struct LaunchDetail: View {
137 fact("Pad", launch.pad) 137 fact("Pad", launch.pad)
138 fact("Location", launch.location) 138 fact("Location", launch.location)
139 } 139 }
140 .font(.system(size: 14)) 140 .font(.subheadline)
141 followButton.padding(.top, 8) 141 followButton.padding(.top, 8)
142 if let message { Text(message).font(.system(size: 12)).opacity(0.7) } 142 if let message { Text(message).font(.caption).opacity(0.7) }
143 if let d = launch.missionDescription { 143 if let d = launch.missionDescription {
144 Text(d).font(.system(size: 14)).opacity(0.85).padding(.top, 8) 144 Text(d).font(.subheadline).opacity(0.85).padding(.top, 8)
145 } 145 }
146 } 146 }
147 .padding(20) 147 .padding(20)
@@ -160,7 +160,7 @@ struct LaunchDetail: View {
160 following ? unfollow() : follow() 160 following ? unfollow() : follow()
161 } label: { 161 } label: {
162 Label(following ? "Stop following" : "Follow on Lock Screen", systemImage: following ? "bell.slash" : "bell") 162 Label(following ? "Stop following" : "Follow on Lock Screen", systemImage: following ? "bell.slash" : "bell")
163 .font(.system(size: 15, weight: .medium)) 163 .font(.subheadline.weight(.medium))
164 .frame(maxWidth: .infinity) 164 .frame(maxWidth: .infinity)
165 .padding(.vertical, 10) 165 .padding(.vertical, 10)
166 .overlay(RoundedRectangle(cornerRadius: 6).stroke(ink.opacity(0.5), lineWidth: 0.5)) 166 .overlay(RoundedRectangle(cornerRadius: 6).stroke(ink.opacity(0.5), lineWidth: 0.5))
App/Sources/LibraryScreen.swift +8 −8
@@ -17,9 +17,9 @@ struct LibraryScreen: View {
17 if !pages.isEmpty { 17 if !pages.isEmpty {
18 NavigationLink(value: section) { 18 NavigationLink(value: section) {
19 HStack { 19 HStack {
20 Text(section.title).font(.system(size: 17)) 20 Text(section.title).font(.body)
21 Spacer() 21 Spacer()
22 Text("\(pages.count)").font(.system(size: 13).monospacedDigit()).opacity(0.5) 22 Text("\(pages.count)").font(.footnote.monospacedDigit()).opacity(0.5)
23 } 23 }
24 } 24 }
25 .listRowBackground(pageBackground) 25 .listRowBackground(pageBackground)
@@ -66,7 +66,7 @@ struct PageRow: View {
66 var body: some View { 66 var body: some View {
67 VStack(alignment: .leading, spacing: 2) { 67 VStack(alignment: .leading, spacing: 2) {
68 Text(page.title).font(.body) 68 Text(page.title).font(.body)
69 Text(page.subtitle).font(.system(size: 12)).opacity(0.6).lineLimit(1) 69 Text(page.subtitle).font(.caption).opacity(0.6).lineLimit(1)
70 } 70 }
71 .padding(.vertical, 2) 71 .padding(.vertical, 2)
72 } 72 }
@@ -78,7 +78,7 @@ struct LibraryPageView: View {
78 var body: some View { 78 var body: some View {
79 ScrollView { 79 ScrollView {
80 VStack(alignment: .leading, spacing: 0) { 80 VStack(alignment: .leading, spacing: 0) {
81 Text(page.section.title).font(.system(size: 11, weight: .medium)).textCase(.uppercase).tracking(0.8).opacity(0.6) 81 Text(page.section.title).font(.caption2.weight(.medium)).textCase(.uppercase).tracking(0.8).opacity(0.6)
82 Text(page.title).font(.largeTitle.weight(.medium)).padding(.top, 4) 82 Text(page.title).font(.largeTitle.weight(.medium)).padding(.top, 4)
83 Text(page.subtitle).font(.subheadline).opacity(0.75).padding(.top, 2) 83 Text(page.subtitle).font(.subheadline).opacity(0.75).padding(.top, 2)
84 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 10).padding(.bottom, 16) 84 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 10).padding(.bottom, 16)
@@ -121,23 +121,23 @@ struct LibraryPageView: View {
121 AsyncImage(url: image.url) { phase in 121 AsyncImage(url: image.url) { phase in
122 switch phase { 122 switch phase {
123 case .success(let img): img.resizable().scaledToFit() 123 case .success(let img): img.resizable().scaledToFit()
124 case .failure: Color.black.frame(height: 120).overlay(Text("Image unavailable").font(.system(size: 12)).opacity(0.6)) 124 case .failure: Color.black.frame(height: 120).overlay(Text("Image unavailable").font(.caption).opacity(0.6))
125 default: Color.black.frame(height: 200).overlay(ProgressView().tint(ink)) 125 default: Color.black.frame(height: 200).overlay(ProgressView().tint(ink))
126 } 126 }
127 } 127 }
128 Text(image.caption).font(.footnote).opacity(0.75) 128 Text(image.caption).font(.footnote).opacity(0.75)
129 Link(destination: image.page) { 129 Link(destination: image.page) {
130 Text("\(image.credit) · \(image.licence)").font(.system(size: 10)).opacity(0.55) 130 Text("\(image.credit) · \(image.licence)").font(.caption2).opacity(0.55)
131 } 131 }
132 } 132 }
133 } 133 }
134 134
135 private var sources: some View { 135 private var sources: some View {
136 VStack(alignment: .leading, spacing: 6) { 136 VStack(alignment: .leading, spacing: 6) {
137 Text("Sources").font(.system(size: 11, weight: .medium)).textCase(.uppercase).tracking(0.8).opacity(0.6) 137 Text("Sources").font(.caption2.weight(.medium)).textCase(.uppercase).tracking(0.8).opacity(0.6)
138 ForEach(Array(page.sources.enumerated()), id: \.offset) { i, s in 138 ForEach(Array(page.sources.enumerated()), id: \.offset) { i, s in
139 Link(destination: s.url) { 139 Link(destination: s.url) {
140 Text("\(i + 1). \(s.title)").font(.system(size: 12)).underline().opacity(0.8) 140 Text("\(i + 1). \(s.title)").font(.caption).underline().opacity(0.8)
141 } 141 }
142 } 142 }
143 } 143 }
App/Sources/SkyScreen.swift +12 −10
@@ -123,13 +123,15 @@ struct SkyScreen: View {
123 HStack(alignment: .top) { 123 HStack(alignment: .top) {
124 VStack(alignment: .leading, spacing: 4) { 124 VStack(alignment: .leading, spacing: 4) {
125 Text(model.offsetHours == 0 ? "Tonight" : "Sky at \(model.displayedDate.formatted(date: .omitted, time: .shortened))") 125 Text(model.offsetHours == 0 ? "Tonight" : "Sky at \(model.displayedDate.formatted(date: .omitted, time: .shortened))")
126 .font(.system(size: 22, weight: .medium)) 126 .font(.title2.weight(.medium))
127 Text(model.placeName) 127 Text(model.placeName)
128 .font(.system(size: 11)).opacity(0.7) 128 .font(.caption2).opacity(0.7)
129 #if DEBUG
129 TimelineView(.periodic(from: .now, by: 1)) { _ in 130 TimelineView(.periodic(from: .now, by: 1)) { _ in
130 Text(stats.summary(build: model.lastBuild)) 131 Text(stats.summary(build: model.lastBuild))
131 .font(.system(size: 10).monospacedDigit()).opacity(0.7) 132 .font(.caption2.monospacedDigit()).opacity(0.7)
132 } 133 }
134 #endif
133 } 135 }
134 Spacer() 136 Spacer()
135 if pointer.available { 137 if pointer.available {
@@ -137,14 +139,14 @@ struct SkyScreen: View {
137 pointer.active ? pointer.stop() : pointer.start() 139 pointer.active ? pointer.stop() : pointer.start()
138 } label: { 140 } label: {
139 Image(systemName: pointer.active ? "scope" : "hand.draw") 141 Image(systemName: pointer.active ? "scope" : "hand.draw")
140 .font(.system(size: 17)) 142 .font(.body)
141 .frame(width: 44, height: 44) 143 .frame(width: 44, height: 44)
142 } 144 }
143 .accessibilityLabel(pointer.active ? "Stop following the phone" : "Point the phone at the sky") 145 .accessibilityLabel(pointer.active ? "Stop following the phone" : "Point the phone at the sky")
144 } 146 }
145 Button { showSettings = true } label: { 147 Button { showSettings = true } label: {
146 Image(systemName: "gearshape") 148 Image(systemName: "gearshape")
147 .font(.system(size: 17)) 149 .font(.body)
148 .frame(width: 44, height: 44) 150 .frame(width: 44, height: 44)
149 } 151 }
150 .accessibilityLabel("Settings") 152 .accessibilityLabel("Settings")
@@ -152,7 +154,7 @@ struct SkyScreen: View {
152 nightMode = night ? "off" : "on" 154 nightMode = night ? "off" : "on"
153 } label: { 155 } label: {
154 Image(systemName: night ? "eye.fill" : "eye") 156 Image(systemName: night ? "eye.fill" : "eye")
155 .font(.system(size: 17)) 157 .font(.body)
156 .frame(width: 44, height: 44) 158 .frame(width: 44, height: 44)
157 } 159 }
158 .accessibilityLabel(night ? "Turn off night mode" : "Turn on night mode") 160 .accessibilityLabel(night ? "Turn off night mode" : "Turn on night mode")
@@ -165,7 +167,7 @@ struct SkyScreen: View {
165 private func card(for object: SkyObject) -> some View { 167 private func card(for object: SkyObject) -> some View {
166 VStack(alignment: .leading, spacing: 8) { 168 VStack(alignment: .leading, spacing: 8) {
167 HStack { 169 HStack {
168 Text(title(for: object)).font(.system(size: 20, weight: .medium)) 170 Text(title(for: object)).font(.title3.weight(.medium))
169 Spacer() 171 Spacer()
170 Button { selection = nil } label: { Image(systemName: "xmark").frame(width: 32, height: 32) } 172 Button { selection = nil } label: { Image(systemName: "xmark").frame(width: 32, height: 32) }
171 .accessibilityLabel("Close") 173 .accessibilityLabel("Close")
@@ -174,11 +176,11 @@ struct SkyScreen: View {
174 case .body: 176 case .body:
175 if let d = details { bodyFacts(d) } else { ProgressView().tint(ink) } 177 if let d = details { bodyFacts(d) } else { ProgressView().tint(ink) }
176 case .constellation(let id): 178 case .constellation(let id):
177 Text("Constellation · \(id)").font(.system(size: 13)).opacity(0.7) 179 Text("Constellation · \(id)").font(.footnote).opacity(0.7)
178 } 180 }
179 if let page = libraryPage(for: object) { 181 if let page = libraryPage(for: object) {
180 Button { libraryPage = page } label: { 182 Button { libraryPage = page } label: {
181 Label("Read more", systemImage: "book").font(.system(size: 13, weight: .medium)) 183 Label("Read more", systemImage: "book").font(.footnote.weight(.medium))
182 } 184 }
183 .buttonStyle(.plain) 185 .buttonStyle(.plain)
184 .padding(.top, 4) 186 .padding(.top, 4)
@@ -223,7 +225,7 @@ struct SkyScreen: View {
223 Text(row.0).opacity(0.7) 225 Text(row.0).opacity(0.7)
224 Text(row.1).monospacedDigit() 226 Text(row.1).monospacedDigit()
225 } 227 }
226 .font(.system(size: 13)) 228 .font(.footnote)
227 } 229 }
228 } 230 }
229 } 231 }
App/Sources/SunScreen.swift +15 −15
@@ -19,7 +19,7 @@ struct SunScreen: View {
19 var body: some View { 19 var body: some View {
20 ScrollView { 20 ScrollView {
21 VStack(alignment: .leading, spacing: 0) { 21 VStack(alignment: .leading, spacing: 0) {
22 Text("Sun").font(.system(size: 28, weight: .medium)) 22 Text("Sun").font(.title.weight(.medium))
23 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 8).padding(.bottom, 16) 23 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 8).padding(.bottom, 16)
24 24
25 image 25 image
@@ -28,17 +28,17 @@ struct SunScreen: View {
28 } 28 }
29 .pickerStyle(.segmented) 29 .pickerStyle(.segmented)
30 .padding(.top, 12) 30 .padding(.top, 12)
31 Text(wavelength.caption).font(.system(size: 13)).opacity(0.75).padding(.top, 8) 31 Text(wavelength.caption).font(.footnote).opacity(0.75).padding(.top, 8)
32 Text(SolarImage.credit).font(.system(size: 10)).opacity(0.5).padding(.top, 4) 32 Text(SolarImage.credit).font(.caption2).opacity(0.5).padding(.top, 4)
33 33
34 section("Aurora") { auroraMap } 34 section("Aurora") { auroraMap }
35 section("Solar flares, last 24 h") { flares } 35 section("Solar flares, last 24 h") { flares }
36 section("Geomagnetic activity (Kp)") { kpChart } 36 section("Geomagnetic activity (Kp)") { kpChart }
37 37
38 if failed { 38 if failed {
39 Text("Couldn't load space weather. Pull to retry.").font(.system(size: 13)).foregroundStyle(accent).padding(.top, 12) 39 Text("Couldn't load space weather. Pull to retry.").font(.footnote).foregroundStyle(accent).padding(.top, 12)
40 } 40 }
41 Text("Space weather data: NOAA Space Weather Prediction Center. Coastlines: Natural Earth.").font(.system(size: 10)).opacity(0.5).padding(.vertical, 16) 41 Text("Space weather data: NOAA Space Weather Prediction Center. Coastlines: Natural Earth.").font(.caption2).opacity(0.5).padding(.vertical, 16)
42 } 42 }
43 .padding(.horizontal, 20) 43 .padding(.horizontal, 20)
44 } 44 }
@@ -64,7 +64,7 @@ struct SunScreen: View {
64 AsyncImage(url: wavelength.url(size: 1024)) { phase in 64 AsyncImage(url: wavelength.url(size: 1024)) { phase in
65 switch phase { 65 switch phase {
66 case .success(let img): img.resizable().scaledToFit() 66 case .success(let img): img.resizable().scaledToFit()
67 case .failure: Color.black.overlay(Text("Image unavailable").font(.system(size: 13)).opacity(0.6)) 67 case .failure: Color.black.overlay(Text("Image unavailable").font(.footnote).opacity(0.6))
68 default: Color.black.overlay(ProgressView().tint(ink)) 68 default: Color.black.overlay(ProgressView().tint(ink))
69 } 69 }
70 } 70 }
@@ -75,7 +75,7 @@ struct SunScreen: View {
75 75
76 private func section(_ title: String, @ViewBuilder content: () -> some View) -> some View { 76 private func section(_ title: String, @ViewBuilder content: () -> some View) -> some View {
77 VStack(alignment: .leading, spacing: 10) { 77 VStack(alignment: .leading, spacing: 10) {
78 Text(title).font(.system(size: 11, weight: .medium)).textCase(.uppercase).tracking(0.8).opacity(0.6) 78 Text(title).font(.caption2.weight(.medium)).textCase(.uppercase).tracking(0.8).opacity(0.6)
79 content() 79 content()
80 } 80 }
81 .padding(.top, 24) 81 .padding(.top, 24)
@@ -94,9 +94,9 @@ struct SunScreen: View {
94 .accessibilityElement() 94 .accessibilityElement()
95 .accessibilityLabel(auroraDescription(ovation)) 95 .accessibilityLabel(auroraDescription(ovation))
96 Text("Chance of visible aurora overhead, OVATION model, for \(ovation.forecastTime.formatted(date: .omitted, time: .shortened)). Brighter green is more likely.") 96 Text("Chance of visible aurora overhead, OVATION model, for \(ovation.forecastTime.formatted(date: .omitted, time: .shortened)). Brighter green is more likely.")
97 .font(.system(size: 11)).opacity(0.6) 97 .font(.caption2).opacity(0.6)
98 } else { 98 } else {
99 Text("Loading").font(.system(size: 13)).opacity(0.6) 99 Text("Loading").font(.footnote).opacity(0.6)
100 } 100 }
101 } 101 }
102 102
@@ -127,7 +127,7 @@ struct SunScreen: View {
127 .foregroundStyle(c.0 == "C" ? ink.opacity(0.25) : accent.opacity(0.6)) 127 .foregroundStyle(c.0 == "C" ? ink.opacity(0.25) : accent.opacity(0.6))
128 .lineStyle(StrokeStyle(lineWidth: 0.5, dash: [3, 3])) 128 .lineStyle(StrokeStyle(lineWidth: 0.5, dash: [3, 3]))
129 .annotation(position: .top, alignment: .leading) { 129 .annotation(position: .top, alignment: .leading) {
130 Text(c.0).font(.system(size: 10)).opacity(0.7) 130 Text(c.0).font(.caption2).opacity(0.7)
131 } 131 }
132 } 132 }
133 } 133 }
@@ -141,14 +141,14 @@ struct SunScreen: View {
141 } 141 }
142 .frame(height: 160) 142 .frame(height: 160)
143 } else { 143 } else {
144 Text("Loading").font(.system(size: 13)).opacity(0.6) 144 Text("Loading").font(.footnote).opacity(0.6)
145 } 145 }
146 } 146 }
147 147
148 @ViewBuilder 148 @ViewBuilder
149 private var kpChart: some View { 149 private var kpChart: some View {
150 if kp.isEmpty { 150 if kp.isEmpty {
151 Text("Loading").font(.system(size: 13)).opacity(0.6) 151 Text("Loading").font(.footnote).opacity(0.6)
152 } else { 152 } else {
153 Chart(kp) { bar in 153 Chart(kp) { bar in
154 BarMark(x: .value("Start", bar.start, unit: .hour), y: .value("Kp", bar.kp), width: .ratio(0.8)) 154 BarMark(x: .value("Start", bar.start, unit: .hour), y: .value("Kp", bar.kp), width: .ratio(0.8))
@@ -169,14 +169,14 @@ struct SunScreen: View {
169 } 169 }
170 .frame(height: 140) 170 .frame(height: 140)
171 Text("Solid: observed. Faint: forecast. Red: storm level (Kp 5 or more).") 171 Text("Solid: observed. Faint: forecast. Red: storm level (Kp 5 or more).")
172 .font(.system(size: 11)).opacity(0.6) 172 .font(.caption2).opacity(0.6)
173 } 173 }
174 } 174 }
175 175
176 private func stat(_ label: String, _ value: String) -> some View { 176 private func stat(_ label: String, _ value: String) -> some View {
177 VStack(alignment: .leading, spacing: 2) { 177 VStack(alignment: .leading, spacing: 2) {
178 Text(label).font(.system(size: 10)).opacity(0.6) 178 Text(label).font(.caption2).opacity(0.6)
179 Text(value).font(.system(size: 20, weight: .medium).monospacedDigit()) 179 Text(value).font(.title3.weight(.medium).monospacedDigit())
180 } 180 }
181 } 181 }
182} 182}
App/Sources/SystemScreen.swift +8 −8
@@ -22,7 +22,7 @@ struct SystemScreen: View {
22 var body: some View { 22 var body: some View {
23 ScrollView { 23 ScrollView {
24 VStack(alignment: .leading, spacing: 0) { 24 VStack(alignment: .leading, spacing: 0) {
25 Text("Solar system").font(.system(size: 28, weight: .medium)) 25 Text("Solar system").font(.title.weight(.medium))
26 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 8).padding(.bottom, 16) 26 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 8).padding(.bottom, 16)
27 27
28 OrreryView(date: date, ink: ink, accent: accent) 28 OrreryView(date: date, ink: ink, accent: accent)
@@ -35,17 +35,17 @@ struct SystemScreen: View {
35 } 35 }
36 .accessibilityLabel(playing ? "Pause" : "Play") 36 .accessibilityLabel(playing ? "Pause" : "Play")
37 Text(date.formatted(.dateTime.year().month(.abbreviated))) 37 Text(date.formatted(.dateTime.year().month(.abbreviated)))
38 .font(.system(size: 15, weight: .medium).monospacedDigit()) 38 .font(.subheadline.weight(.medium).monospacedDigit())
39 .frame(width: 80, alignment: .leading) 39 .frame(width: 80, alignment: .leading)
40 Slider(value: $offsetDays, in: range).tint(ink) 40 Slider(value: $offsetDays, in: range).tint(ink)
41 Button("Today") { playing = false; offsetDays = 0 } 41 Button("Today") { playing = false; offsetDays = 0 }
42 .font(.system(size: 13, weight: .medium)) 42 .font(.footnote.weight(.medium))
43 .opacity(abs(offsetDays) < 1 ? 0.4 : 1) 43 .opacity(abs(offsetDays) < 1 ? 0.4 : 1)
44 } 44 }
45 .padding(.top, 12) 45 .padding(.top, 12)
46 46
47 Text("Distances beyond 1 AU are drawn on a logarithmic scale. Rings: planet orbits, then 50, 100 and 200 AU.") 47 Text("Distances beyond 1 AU are drawn on a logarithmic scale. Rings: planet orbits, then 50, 100 and 200 AU.")
48 .font(.system(size: 11)).opacity(0.6).padding(.top, 8) 48 .font(.caption2).opacity(0.6).padding(.top, 8)
49 49
50 section("Spacecraft") { 50 section("Spacecraft") {
51 Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 6) { 51 Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 6) {
@@ -54,7 +54,7 @@ struct SystemScreen: View {
54 Text("From Sun").opacity(0.6) 54 Text("From Sun").opacity(0.6)
55 Text("Light time").opacity(0.6) 55 Text("Light time").opacity(0.6)
56 } 56 }
57 .font(.system(size: 11)) 57 .font(.caption2)
58 ForEach(Spacecraft.all) { craft in 58 ForEach(Spacecraft.all) { craft in
59 GridRow { 59 GridRow {
60 if let page = Library.page(spacecraft: craft.name) { 60 if let page = Library.page(spacecraft: craft.name) {
@@ -70,12 +70,12 @@ struct SystemScreen: View {
70 Text("") 70 Text("")
71 } 71 }
72 } 72 }
73 .font(.system(size: 14)) 73 .font(.subheadline)
74 } 74 }
75 } 75 }
76 } 76 }
77 Text("Spacecraft positions: NASA/JPL Horizons. Pioneer 10 and 11 positions after contact was lost are predictions.") 77 Text("Spacecraft positions: NASA/JPL Horizons. Pioneer 10 and 11 positions after contact was lost are predictions.")
78 .font(.system(size: 10)).opacity(0.5).padding(.vertical, 16) 78 .font(.caption2).opacity(0.5).padding(.vertical, 16)
79 } 79 }
80 .padding(.horizontal, 20) 80 .padding(.horizontal, 20)
81 } 81 }
@@ -102,7 +102,7 @@ struct SystemScreen: View {
102 102
103 private func section(_ title: String, @ViewBuilder content: () -> some View) -> some View { 103 private func section(_ title: String, @ViewBuilder content: () -> some View) -> some View {
104 VStack(alignment: .leading, spacing: 10) { 104 VStack(alignment: .leading, spacing: 10) {
105 Text(title).font(.system(size: 11, weight: .medium)).textCase(.uppercase).tracking(0.8).opacity(0.6) 105 Text(title).font(.caption2.weight(.medium)).textCase(.uppercase).tracking(0.8).opacity(0.6)
106 content() 106 content()
107 } 107 }
108 .padding(.top, 24) 108 .padding(.top, 24)
App/Sources/TodaySheet.swift +8 −8
@@ -18,7 +18,7 @@ struct TodaySheet: View {
18 } 18 }
19 section("Space weather") { weather } 19 section("Space weather") { weather }
20 Text("Space weather data: NOAA Space Weather Prediction Center.") 20 Text("Space weather data: NOAA Space Weather Prediction Center.")
21 .font(.system(size: 11)).opacity(0.5).padding(.top, 8) 21 .font(.caption2).opacity(0.5).padding(.top, 8)
22 } 22 }
23 .padding(.horizontal, 20) 23 .padding(.horizontal, 20)
24 .padding(.top, 18) 24 .padding(.top, 18)
@@ -29,7 +29,7 @@ struct TodaySheet: View {
29 29
30 private func section(_ title: String, @ViewBuilder content: () -> some View) -> some View { 30 private func section(_ title: String, @ViewBuilder content: () -> some View) -> some View {
31 VStack(alignment: .leading, spacing: 8) { 31 VStack(alignment: .leading, spacing: 8) {
32 Text(title).font(.system(size: 11, weight: .medium)).textCase(.uppercase).tracking(0.8).opacity(0.6) 32 Text(title).font(.caption2.weight(.medium)).textCase(.uppercase).tracking(0.8).opacity(0.6)
33 content() 33 content()
34 } 34 }
35 .frame(maxWidth: .infinity, alignment: .leading) 35 .frame(maxWidth: .infinity, alignment: .leading)
@@ -86,7 +86,7 @@ struct TodaySheet: View {
86 ("Updated", time(w.observedAt)), 86 ("Updated", time(w.observedAt)),
87 ]) 87 ])
88 } else { 88 } else {
89 Text("Loading").font(.system(size: 13)).opacity(0.6) 89 Text("Loading").font(.footnote).opacity(0.6)
90 } 90 }
91 } 91 }
92 92
@@ -97,7 +97,7 @@ struct TodaySheet: View {
97 Text(item.0).opacity(0.7) 97 Text(item.0).opacity(0.7)
98 Text(item.1).monospacedDigit() 98 Text(item.1).monospacedDigit()
99 } 99 }
100 .font(.system(size: 14)) 100 .font(.subheadline)
101 } 101 }
102 } 102 }
103 } 103 }
@@ -121,9 +121,9 @@ struct TodayBar: View {
121 scrubber 121 scrubber
122 Button(action: onExpand) { 122 Button(action: onExpand) {
123 HStack { 123 HStack {
124 Text(summary).font(.system(size: 13)).opacity(0.75).lineLimit(1) 124 Text(summary).font(.footnote).opacity(0.75).lineLimit(2)
125 Spacer() 125 Spacer()
126 Image(systemName: "chevron.up").font(.system(size: 12, weight: .medium)).opacity(0.6) 126 Image(systemName: "chevron.up").font(.caption.weight(.medium)).opacity(0.6)
127 } 127 }
128 } 128 }
129 .buttonStyle(.plain) 129 .buttonStyle(.plain)
@@ -154,11 +154,11 @@ struct TodayBar: View {
154 private var scrubber: some View { 154 private var scrubber: some View {
155 HStack(spacing: 12) { 155 HStack(spacing: 12) {
156 Text(model.displayedDate.formatted(date: .omitted, time: .shortened)) 156 Text(model.displayedDate.formatted(date: .omitted, time: .shortened))
157 .font(.system(size: 13).monospacedDigit()) 157 .font(.footnote.monospacedDigit())
158 .frame(width: 70, alignment: .leading) 158 .frame(width: 70, alignment: .leading)
159 Slider(value: $model.offsetHours, in: -12...12, step: 0.25).tint(ink) 159 Slider(value: $model.offsetHours, in: -12...12, step: 0.25).tint(ink)
160 Button("Now") { model.offsetHours = 0 } 160 Button("Now") { model.offsetHours = 0 }
161 .font(.system(size: 13, weight: .medium)) 161 .font(.footnote.weight(.medium))
162 .opacity(model.offsetHours == 0 ? 0.4 : 1) 162 .opacity(model.offsetHours == 0 ? 0.4 : 1)
163 } 163 }
164 } 164 }