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 {
1818 var body: some View {
1919 ScrollView {
2020 VStack(alignment: .leading, spacing: 0) {
21 Text("Launches").font(.system(size: 28, weight: .medium))
21 Text("Launches").font(.title.weight(.medium))
2222 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 8).padding(.bottom, 8)
2323
2424 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)
2626 }
2727 if let list {
2828 ForEach(Array(list.launches.enumerated()), id: \.element.id) { i, launch in
@@ -30,7 +30,7 @@ struct LaunchesScreen: View {
3030 .buttonStyle(.plain)
3131 }
3232 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)
3434 } else if error == nil {
3535 ProgressView().tint(ink).padding(.top, 40).frame(maxWidth: .infinity)
3636 }
@@ -57,18 +57,18 @@ struct LaunchesScreen: View {
5757 private func row(_ l: Launch, index: Int) -> some View {
5858 VStack(alignment: .leading, spacing: 6) {
5959 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)
6161 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)
6363 Spacer()
6464 StatusTag(status: l.status, ink: ink, accent: accent)
6565 }
66 Text(l.rocket).font(.system(size: 22, weight: .medium))
67 if let m = l.mission { Text(m).font(.system(size: 14)).lineLimit(1) }
66 Text(l.rocket).font(.title2.weight(.medium))
67 if let m = l.mission { Text(m).font(.subheadline).lineLimit(1) }
6868 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)
7070 Spacer()
71 Countdown(to: l.net).font(.system(size: 13, weight: .medium).monospacedDigit())
71 Countdown(to: l.net).font(.footnote.weight(.medium).monospacedDigit())
7272 }
7373 }
7474 .padding(.vertical, 14)
@@ -84,7 +84,7 @@ struct StatusTag: View {
8484
8585 var body: some View {
8686 Text(status.uppercased())
87 .font(.system(size: 10, weight: .medium))
87 .font(.caption2.weight(.medium))
8888 .tracking(0.6)
8989 .padding(.horizontal, 6).padding(.vertical, 2)
9090 .foregroundStyle(status == "Go" ? Color.black : ink)
@@ -121,11 +121,11 @@ struct LaunchDetail: View {
121121 VStack(alignment: .leading, spacing: 10) {
122122 HStack {
123123 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)
125125 }
126 Text(launch.rocket).font(.system(size: 28, weight: .medium))
127 if let m = launch.mission { Text(m).font(.system(size: 17)) }
128 Countdown(to: launch.net).font(.system(size: 34, weight: .medium).monospacedDigit()).padding(.vertical, 6)
126 Text(launch.rocket).font(.title.weight(.medium))
127 if let m = launch.mission { Text(m).font(.body) }
128 Countdown(to: launch.net).font(.largeTitle.weight(.medium).monospacedDigit()).padding(.vertical, 6)
129129 Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 6) {
130130 fact("Provider", launch.provider)
131131 fact("Launch time", launch.net.formatted(date: .abbreviated, time: .shortened))
@@ -137,11 +137,11 @@ struct LaunchDetail: View {
137137 fact("Pad", launch.pad)
138138 fact("Location", launch.location)
139139 }
140 .font(.system(size: 14))
140 .font(.subheadline)
141141 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) }
143143 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)
145145 }
146146 }
147147 .padding(20)
@@ -160,7 +160,7 @@ struct LaunchDetail: View {
160160 following ? unfollow() : follow()
161161 } label: {
162162 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))
164164 .frame(maxWidth: .infinity)
165165 .padding(.vertical, 10)
166166 .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 {
1717 if !pages.isEmpty {
1818 NavigationLink(value: section) {
1919 HStack {
20 Text(section.title).font(.system(size: 17))
20 Text(section.title).font(.body)
2121 Spacer()
22 Text("\(pages.count)").font(.system(size: 13).monospacedDigit()).opacity(0.5)
22 Text("\(pages.count)").font(.footnote.monospacedDigit()).opacity(0.5)
2323 }
2424 }
2525 .listRowBackground(pageBackground)
@@ -66,7 +66,7 @@ struct PageRow: View {
6666 var body: some View {
6767 VStack(alignment: .leading, spacing: 2) {
6868 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)
7070 }
7171 .padding(.vertical, 2)
7272 }
@@ -78,7 +78,7 @@ struct LibraryPageView: View {
7878 var body: some View {
7979 ScrollView {
8080 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)
8282 Text(page.title).font(.largeTitle.weight(.medium)).padding(.top, 4)
8383 Text(page.subtitle).font(.subheadline).opacity(0.75).padding(.top, 2)
8484 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 10).padding(.bottom, 16)
@@ -121,23 +121,23 @@ struct LibraryPageView: View {
121121 AsyncImage(url: image.url) { phase in
122122 switch phase {
123123 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))
125125 default: Color.black.frame(height: 200).overlay(ProgressView().tint(ink))
126126 }
127127 }
128128 Text(image.caption).font(.footnote).opacity(0.75)
129129 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)
131131 }
132132 }
133133 }
134134
135135 private var sources: some View {
136136 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)
138138 ForEach(Array(page.sources.enumerated()), id: \.offset) { i, s in
139139 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)
141141 }
142142 }
143143 }
App/Sources/SkyScreen.swift +12 −10
@@ -123,13 +123,15 @@ struct SkyScreen: View {
123123 HStack(alignment: .top) {
124124 VStack(alignment: .leading, spacing: 4) {
125125 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))
127127 Text(model.placeName)
128 .font(.system(size: 11)).opacity(0.7)
128 .font(.caption2).opacity(0.7)
129 #if DEBUG
129130 TimelineView(.periodic(from: .now, by: 1)) { _ in
130131 Text(stats.summary(build: model.lastBuild))
131 .font(.system(size: 10).monospacedDigit()).opacity(0.7)
132 .font(.caption2.monospacedDigit()).opacity(0.7)
132133 }
134 #endif
133135 }
134136 Spacer()
135137 if pointer.available {
@@ -137,14 +139,14 @@ struct SkyScreen: View {
137139 pointer.active ? pointer.stop() : pointer.start()
138140 } label: {
139141 Image(systemName: pointer.active ? "scope" : "hand.draw")
140 .font(.system(size: 17))
142 .font(.body)
141143 .frame(width: 44, height: 44)
142144 }
143145 .accessibilityLabel(pointer.active ? "Stop following the phone" : "Point the phone at the sky")
144146 }
145147 Button { showSettings = true } label: {
146148 Image(systemName: "gearshape")
147 .font(.system(size: 17))
149 .font(.body)
148150 .frame(width: 44, height: 44)
149151 }
150152 .accessibilityLabel("Settings")
@@ -152,7 +154,7 @@ struct SkyScreen: View {
152154 nightMode = night ? "off" : "on"
153155 } label: {
154156 Image(systemName: night ? "eye.fill" : "eye")
155 .font(.system(size: 17))
157 .font(.body)
156158 .frame(width: 44, height: 44)
157159 }
158160 .accessibilityLabel(night ? "Turn off night mode" : "Turn on night mode")
@@ -165,7 +167,7 @@ struct SkyScreen: View {
165167 private func card(for object: SkyObject) -> some View {
166168 VStack(alignment: .leading, spacing: 8) {
167169 HStack {
168 Text(title(for: object)).font(.system(size: 20, weight: .medium))
170 Text(title(for: object)).font(.title3.weight(.medium))
169171 Spacer()
170172 Button { selection = nil } label: { Image(systemName: "xmark").frame(width: 32, height: 32) }
171173 .accessibilityLabel("Close")
@@ -174,11 +176,11 @@ struct SkyScreen: View {
174176 case .body:
175177 if let d = details { bodyFacts(d) } else { ProgressView().tint(ink) }
176178 case .constellation(let id):
177 Text("Constellation · \(id)").font(.system(size: 13)).opacity(0.7)
179 Text("Constellation · \(id)").font(.footnote).opacity(0.7)
178180 }
179181 if let page = libraryPage(for: object) {
180182 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))
182184 }
183185 .buttonStyle(.plain)
184186 .padding(.top, 4)
@@ -223,7 +225,7 @@ struct SkyScreen: View {
223225 Text(row.0).opacity(0.7)
224226 Text(row.1).monospacedDigit()
225227 }
226 .font(.system(size: 13))
228 .font(.footnote)
227229 }
228230 }
229231 }
App/Sources/SunScreen.swift +15 −15
@@ -19,7 +19,7 @@ struct SunScreen: View {
1919 var body: some View {
2020 ScrollView {
2121 VStack(alignment: .leading, spacing: 0) {
22 Text("Sun").font(.system(size: 28, weight: .medium))
22 Text("Sun").font(.title.weight(.medium))
2323 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 8).padding(.bottom, 16)
2424
2525 image
@@ -28,17 +28,17 @@ struct SunScreen: View {
2828 }
2929 .pickerStyle(.segmented)
3030 .padding(.top, 12)
31 Text(wavelength.caption).font(.system(size: 13)).opacity(0.75).padding(.top, 8)
32 Text(SolarImage.credit).font(.system(size: 10)).opacity(0.5).padding(.top, 4)
31 Text(wavelength.caption).font(.footnote).opacity(0.75).padding(.top, 8)
32 Text(SolarImage.credit).font(.caption2).opacity(0.5).padding(.top, 4)
3333
3434 section("Aurora") { auroraMap }
3535 section("Solar flares, last 24 h") { flares }
3636 section("Geomagnetic activity (Kp)") { kpChart }
3737
3838 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)
4040 }
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)
4242 }
4343 .padding(.horizontal, 20)
4444 }
@@ -64,7 +64,7 @@ struct SunScreen: View {
6464 AsyncImage(url: wavelength.url(size: 1024)) { phase in
6565 switch phase {
6666 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))
6868 default: Color.black.overlay(ProgressView().tint(ink))
6969 }
7070 }
@@ -75,7 +75,7 @@ struct SunScreen: View {
7575
7676 private func section(_ title: String, @ViewBuilder content: () -> some View) -> some View {
7777 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)
7979 content()
8080 }
8181 .padding(.top, 24)
@@ -94,9 +94,9 @@ struct SunScreen: View {
9494 .accessibilityElement()
9595 .accessibilityLabel(auroraDescription(ovation))
9696 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)
9898 } else {
99 Text("Loading").font(.system(size: 13)).opacity(0.6)
99 Text("Loading").font(.footnote).opacity(0.6)
100100 }
101101 }
102102
@@ -127,7 +127,7 @@ struct SunScreen: View {
127127 .foregroundStyle(c.0 == "C" ? ink.opacity(0.25) : accent.opacity(0.6))
128128 .lineStyle(StrokeStyle(lineWidth: 0.5, dash: [3, 3]))
129129 .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)
131131 }
132132 }
133133 }
@@ -141,14 +141,14 @@ struct SunScreen: View {
141141 }
142142 .frame(height: 160)
143143 } else {
144 Text("Loading").font(.system(size: 13)).opacity(0.6)
144 Text("Loading").font(.footnote).opacity(0.6)
145145 }
146146 }
147147
148148 @ViewBuilder
149149 private var kpChart: some View {
150150 if kp.isEmpty {
151 Text("Loading").font(.system(size: 13)).opacity(0.6)
151 Text("Loading").font(.footnote).opacity(0.6)
152152 } else {
153153 Chart(kp) { bar in
154154 BarMark(x: .value("Start", bar.start, unit: .hour), y: .value("Kp", bar.kp), width: .ratio(0.8))
@@ -169,14 +169,14 @@ struct SunScreen: View {
169169 }
170170 .frame(height: 140)
171171 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)
173173 }
174174 }
175175
176176 private func stat(_ label: String, _ value: String) -> some View {
177177 VStack(alignment: .leading, spacing: 2) {
178 Text(label).font(.system(size: 10)).opacity(0.6)
179 Text(value).font(.system(size: 20, weight: .medium).monospacedDigit())
178 Text(label).font(.caption2).opacity(0.6)
179 Text(value).font(.title3.weight(.medium).monospacedDigit())
180180 }
181181 }
182182}
App/Sources/SystemScreen.swift +8 −8
@@ -22,7 +22,7 @@ struct SystemScreen: View {
2222 var body: some View {
2323 ScrollView {
2424 VStack(alignment: .leading, spacing: 0) {
25 Text("Solar system").font(.system(size: 28, weight: .medium))
25 Text("Solar system").font(.title.weight(.medium))
2626 Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 8).padding(.bottom, 16)
2727
2828 OrreryView(date: date, ink: ink, accent: accent)
@@ -35,17 +35,17 @@ struct SystemScreen: View {
3535 }
3636 .accessibilityLabel(playing ? "Pause" : "Play")
3737 Text(date.formatted(.dateTime.year().month(.abbreviated)))
38 .font(.system(size: 15, weight: .medium).monospacedDigit())
38 .font(.subheadline.weight(.medium).monospacedDigit())
3939 .frame(width: 80, alignment: .leading)
4040 Slider(value: $offsetDays, in: range).tint(ink)
4141 Button("Today") { playing = false; offsetDays = 0 }
42 .font(.system(size: 13, weight: .medium))
42 .font(.footnote.weight(.medium))
4343 .opacity(abs(offsetDays) < 1 ? 0.4 : 1)
4444 }
4545 .padding(.top, 12)
4646
4747 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)
4949
5050 section("Spacecraft") {
5151 Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 6) {
@@ -54,7 +54,7 @@ struct SystemScreen: View {
5454 Text("From Sun").opacity(0.6)
5555 Text("Light time").opacity(0.6)
5656 }
57 .font(.system(size: 11))
57 .font(.caption2)
5858 ForEach(Spacecraft.all) { craft in
5959 GridRow {
6060 if let page = Library.page(spacecraft: craft.name) {
@@ -70,12 +70,12 @@ struct SystemScreen: View {
7070 Text("")
7171 }
7272 }
73 .font(.system(size: 14))
73 .font(.subheadline)
7474 }
7575 }
7676 }
7777 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)
7979 }
8080 .padding(.horizontal, 20)
8181 }
@@ -102,7 +102,7 @@ struct SystemScreen: View {
102102
103103 private func section(_ title: String, @ViewBuilder content: () -> some View) -> some View {
104104 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)
106106 content()
107107 }
108108 .padding(.top, 24)
App/Sources/TodaySheet.swift +8 −8
@@ -18,7 +18,7 @@ struct TodaySheet: View {
1818 }
1919 section("Space weather") { weather }
2020 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)
2222 }
2323 .padding(.horizontal, 20)
2424 .padding(.top, 18)
@@ -29,7 +29,7 @@ struct TodaySheet: View {
2929
3030 private func section(_ title: String, @ViewBuilder content: () -> some View) -> some View {
3131 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)
3333 content()
3434 }
3535 .frame(maxWidth: .infinity, alignment: .leading)
@@ -86,7 +86,7 @@ struct TodaySheet: View {
8686 ("Updated", time(w.observedAt)),
8787 ])
8888 } else {
89 Text("Loading").font(.system(size: 13)).opacity(0.6)
89 Text("Loading").font(.footnote).opacity(0.6)
9090 }
9191 }
9292
@@ -97,7 +97,7 @@ struct TodaySheet: View {
9797 Text(item.0).opacity(0.7)
9898 Text(item.1).monospacedDigit()
9999 }
100 .font(.system(size: 14))
100 .font(.subheadline)
101101 }
102102 }
103103 }
@@ -121,9 +121,9 @@ struct TodayBar: View {
121121 scrubber
122122 Button(action: onExpand) {
123123 HStack {
124 Text(summary).font(.system(size: 13)).opacity(0.75).lineLimit(1)
124 Text(summary).font(.footnote).opacity(0.75).lineLimit(2)
125125 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)
127127 }
128128 }
129129 .buttonStyle(.plain)
@@ -154,11 +154,11 @@ struct TodayBar: View {
154154 private var scrubber: some View {
155155 HStack(spacing: 12) {
156156 Text(model.displayedDate.formatted(date: .omitted, time: .shortened))
157 .font(.system(size: 13).monospacedDigit())
157 .font(.footnote.monospacedDigit())
158158 .frame(width: 70, alignment: .leading)
159159 Slider(value: $model.offsetHours, in: -12...12, step: 0.25).tint(ink)
160160 Button("Now") { model.offsetHours = 0 }
161 .font(.system(size: 13, weight: .medium))
161 .font(.footnote.weight(.medium))
162162 .opacity(model.offsetHours == 0 ? 0.4 : 1)
163163 }
164164 }