diff --git a/.codeyam/design/site-redesign/01-gummy-mockup.html b/.codeyam/design/site-redesign/01-gummy-mockup.html new file mode 100644 index 0000000..084fde3 --- /dev/null +++ b/.codeyam/design/site-redesign/01-gummy-mockup.html @@ -0,0 +1,175 @@ + + + + + +Otterpace — Gummy + + + + + +
+
+ 🐾 Available now on the App Store +

A running coach who cheers, never shames.

+

Meet Buddy — a kind, injury-aware otter coach that keeps you moving toward your goal, one gentle nudge at a time.

+
+ Download on the App Store + Coming soon to Google Play +
+
Free · No accounts · Your health data never leaves your phone · Android coming!
+
+
+
+
Goal crushed
+
Buddy is proud
+ Otterpace Today screen +
+
+ +
+

One app. Five whole looks.

Pick a design that feels like you — the entire app re-skins, mascot and all. Change it anytime.

+
+
Otter theme
Otter
Warm & friendly
+
Bolt theme
Bolt
Dark & focused
+
Orbit theme
Orbit
Cool & cosmic
+
Fieldnote theme
Fieldnote
Warm analog
+
Garden theme
Garden
Quiet & natural
+
+
+ +
+

Coaching that's actually kind

Practical, injury-aware guidance — no diagnoses, no shame, no nagging.

+
+
🩺

Injury-aware

Backs off when your training load spikes. Sensible run/rest calls, never a diagnosis.

+
💬

Ask Buddy anything

Conversational coaching that answers in plain language. Works offline; smarter with your own AI key.

+
👟

Gentle daily nudges

Move toward your step goal with reminders that encourage — never guilt-trip.

+
📅

Weekly review

What went well, what changed, and one kind focus for next week.

+
🔒

On-device & private

Apple Health is read on your phone and never uploaded. No accounts, no ads, no data sold.

+
💛

Open source

Built in the open as a CodeYam showcase — check the code yourself.

+
+
+ +
+
+

Say hi to Buddy.

+

Buddy reads your steps, runs and rest days, then coaches you like a friend who wants you healthy for the long run.

+
“You blew past your step goal today 🎉 Tomorrow, keep it easy — a relaxed walk lets today settle in.”
+
+ Buddy coaching card +
+ +
+

Start moving with Buddy.

+

Otterpace 1.0 is live on the App Store. Android is on the way.

+
+ Download on the App Store + Coming soon to Google Play +
+
+ + + diff --git a/.codeyam/design/site-redesign/02-editorial-mockup.html b/.codeyam/design/site-redesign/02-editorial-mockup.html new file mode 100644 index 0000000..63b3db3 --- /dev/null +++ b/.codeyam/design/site-redesign/02-editorial-mockup.html @@ -0,0 +1,129 @@ + + + + + +Otterpace — Editorial + + + +
+ + +
+ +
+
Now available on the App Store · No. 001iPhone · Android coming
+

A kinder way to keep running.

+
+
+

Meet Buddy — an injury-aware otter coach that reads your steps and runs, then guides you like a friend who wants you healthy for the long haul. No diagnoses. No shame. Five whole-app looks to make it yours.

+
+ Download on the App Store + Coming soon to Google Play +
+ +
+
Otterpace Today
Fig. 1 — Today, in the Otter look
+
+
+ +
+
The Collection — five whole-app themes
+

Otterpace ships as one app in five complete identities — colors, type and mascot all shift together. Wear the one that fits your mood.

+
+
Otter
Nº 01
Otter
Warm & friendly
+
Bolt
Nº 02
Bolt
Dark & focused
+
Orbit
Nº 03
Orbit
Cool & cosmic
+
Fieldnote
Nº 04
Fieldnote
Warm analog
+
Garden
Nº 05
Garden
Quiet & natural
+
+
+ +
+

“A coach should back off when you’re tired, cheer when you win, and never make you feel bad for a rest day.”

+
+ +
+
The Features — what Buddy does
+
+
01

Injury-aware coaching

Practical run/rest guidance that eases off when your training load spikes.

+
02

Ask Buddy anything

Conversational answers in plain language. Works offline; smarter with your own AI key.

+
03

Gentle daily nudges

Move toward your step goal with reminders that encourage, not scold.

+
04

Weekly review

What went well, what changed, and one kind focus for the week ahead.

+
05

On-device & private

Apple Health read locally, never uploaded. No accounts, no ads, no data sold.

+
06

Open source

Built in the open as a CodeYam showcase — read every line.

+
+
+ + + diff --git a/.codeyam/design/site-redesign/03-marquee-mockup.html b/.codeyam/design/site-redesign/03-marquee-mockup.html new file mode 100644 index 0000000..e96858e --- /dev/null +++ b/.codeyam/design/site-redesign/03-marquee-mockup.html @@ -0,0 +1,139 @@ + + + + + +Otterpace — Marquee + + + + + +
+ Available on the App Store🐾Android coming soon🐾Never-shame coaching🐾Five whole-app themes🐾Your data stays on-device🐾 + Available on the App Store🐾Android coming soon🐾Never-shame coaching🐾Five whole-app themes🐾Your data stays on-device🐾 +
+ +
+ Now live on the App Store +

Run happy.
Coach kind.

+

Meet Buddy — the injury-aware otter coach who nudges, cheers, and never once makes you feel bad about a rest day.

+
+ Download on the App Store + Coming soon to Google Play +
+
+
Goal crushed 🎉
+
+ Orbit theme + Otter theme + Garden theme +
+
+
+ +
+
Five looks.
One otter.
+

The whole app re-skins — colors, type, mascot. Wear the mood you’re in.

+
+
Otter
Otter
+
Bolt
Bolt
+
Orbit
Orbit
+
Fieldnote
Fieldnote
+
Garden
Garden
+
+
+ +
+
Coaching, not nagging.
+

Practical, injury-aware, and always on your side.

+
+
🩺

Injury-aware

Backs off when your load spikes. Run/rest calls, never a diagnosis.

+
💬

Ask Buddy

Conversational coaching in plain language. Works offline too.

+
👟

Gentle nudges

Move toward your step goal with encouragement, not guilt.

+
📅

Weekly review

What went well, what changed, one kind focus for next week.

+
🔒

On-device

Apple Health stays on your phone. No accounts, no ads, no data sold.

+
💛

Open source

Built in the open as a CodeYam showcase.

+
+
+ + + diff --git a/.codeyam/design/site-redesign/04-neo-brutalist-mockup.html b/.codeyam/design/site-redesign/04-neo-brutalist-mockup.html new file mode 100644 index 0000000..ab2c194 --- /dev/null +++ b/.codeyam/design/site-redesign/04-neo-brutalist-mockup.html @@ -0,0 +1,152 @@ + + + + + +Otterpace — Bolt + + + + + +
+ v1.0 LIVE ON THE APP STORE +
+
+

Dark mode
for your discipline.

+

Otterpace is a running coach with range — from cozy coral to this: Bolt, true-black and built for focus. Same kind coaching. Whole new look.

+
+ Download on the App Store + Coming soon to Google Play +
+
+
// preview a theme
+
+
Bolt
+
Otter
+
Orbit
+
Fieldnote
+
Garden
+
+
+
+
Otterpace in Bolt theme
+
+
+ +
+
// the theme system
+
Five whole-app looks.
+

One codebase, five complete identities — colors, type, and mascot all shift together. Set it in onboarding or Settings; change it anytime.

+
+
Bolt
Bolt
dark · aqua
+
Otter
Otter
warm · coral
+
Orbit
Orbit
cosmic · ice
+
Fieldnote
Fieldnote
analog · teal
+
Garden
Garden
calm · sage
+
+
+ +
+
// coaching engine
+
Kind, not soft.
+
+
01

Injury-aware

Eases off when your training load spikes. Run/rest calls, never a diagnosis.

+
02

Ask Buddy

Conversational coaching. Offline by default; bring your own AI key to go deeper.

+
03

Daily nudges

Step-goal progress and movement reminders that encourage, not scold.

+
04

Weekly review

What went well, what changed, one focus for next week.

+
05

On-device

Apple Health read locally, never uploaded. No accounts. No ads.

+
06

Open source

A CodeYam showcase — audit every line on GitHub.

+
+
+ +
+

Ship your streak in Bolt.

+
+ Download on the App Store + Coming soon to Google Play +
+
+ + + diff --git a/.codeyam/design/site-redesign/05-space-odyssey-mockup.html b/.codeyam/design/site-redesign/05-space-odyssey-mockup.html new file mode 100644 index 0000000..29b1d61 --- /dev/null +++ b/.codeyam/design/site-redesign/05-space-odyssey-mockup.html @@ -0,0 +1,152 @@ + + + + + +Otterpace — Orbit + + + + + +
+
+ ◍ Live on the App Store · Android incoming +

Your training,
in orbit.

+

Otterpace is a kind, injury-aware running coach — and it comes in five whole-app looks. This is Orbit: cool, calm, cosmic.

+
+ Download on the App Store + Coming soon to Google Play +
+
Free · privacy-first · Apple Health stays on your device · Android coming!
+
+
+
+
+
Otterpace in Orbit theme
+
+
+ +
+
The theme system
+
Five constellations, one app.
+

Every theme reskins the entire app — colors, type, and mascot shift together. Orbit is just one of five.

+
+
Orbit
Orbit
cosmic · ice
+
Otter
Otter
warm · coral
+
Bolt
Bolt
dark · aqua
+
Fieldnote
Fieldnote
analog · teal
+
Garden
Garden
calm · sage
+
+
+ +
+
Coaching, grounded
+
Kind guidance, cosmic calm.
+
+
🩺

Injury-aware

Backs off when your load spikes. Sensible run/rest calls, never a diagnosis.

+
💬

Ask Buddy

Conversational coaching in plain language. Works offline; smarter with your key.

+
👟

Gentle nudges

Move toward your step goal with encouragement, not guilt.

+
📅

Weekly review

What went well, what changed, one focus for next week.

+
🔒

On-device

Apple Health stays on your phone. No accounts, no ads, no data sold.

+
💛

Open source

Built in the open as a CodeYam showcase.

+
+
+ +
+
Ready for liftoff
+

Bring Buddy into orbit.

+
+ Download on the App Store + Coming soon to Google Play +
+
+ + + diff --git a/.codeyam/design/site-redesign/06-letters-to-sean-mockup.html b/.codeyam/design/site-redesign/06-letters-to-sean-mockup.html new file mode 100644 index 0000000..0277eda --- /dev/null +++ b/.codeyam/design/site-redesign/06-letters-to-sean-mockup.html @@ -0,0 +1,131 @@ + + + + + +Otterpace — A note from Buddy + + + + + +
+
Buddy
Now on
App Store
+
Hey, it’s Buddy 🐾
+

I’m your running coach — a small otter who lives in your phone and genuinely just wants you to feel good moving. Not fast. Not perfect. Good.

+

Here’s my whole deal: I read your steps and runs from Apple Health (on your device — I never send it anywhere), I nudge you gently toward your goal, and when your training load spikes, I’m the one who says “hey, take it easy today.” No diagnoses. No guilt. Never any shame.

+

Ask me anything and I’ll answer like a friend, not a spreadsheet. And if you want a different vibe, I come in five whole looks — pick the one that feels like you.

+
Come run with me,Buddy
+
+ Download on the App Store + Coming soon to Google Play +
+
P.S. I’m free, I’m open source, and Android friends — I’m coming for you soon. 💛
+
+ +
A quick look inside
+ +
+
+ Garden theme + Otterpace Today +
+
+

Five looks. Same Buddy.

+

The entire app re-skins — colors, type, and even my face change with the theme. Set it when you start, switch it whenever.

+
+ Otter + Bolt + Orbit + Fieldnote + Garden +
+
+
+ +
+
🩺

Injury-aware

I back off when your load spikes — run/rest guidance, never a diagnosis.

+
💬

Ask me anything

Conversational coaching in plain words. I work offline; smarter with your own AI key.

+
👟

Gentle daily nudges

Little encouragements toward your step goal. No nagging, promise.

+
📅

Weekly review

What went well, what changed, and one kind focus for next week.

+
🔒

On-device & private

Apple Health stays on your phone. No accounts, no ads, no data sold.

+
💛

Open source

Built in the open as a CodeYam showcase — peek at how I work.

+
+ +
+

Let’s go for a walk.

+

Otterpace is live on the App Store. Android is on the way.

+
+ Download on the App Store + Coming soon to Google Play +
+
+ + + diff --git a/.codeyam/design/site-redesign/07-offcatalog-mockup.html b/.codeyam/design/site-redesign/07-offcatalog-mockup.html new file mode 100644 index 0000000..9ae97ab --- /dev/null +++ b/.codeyam/design/site-redesign/07-offcatalog-mockup.html @@ -0,0 +1,161 @@ + + + + + +Otterpace — Theme Chameleon + + + + + +
+
+ 🐾 Live on the App Store · Android coming! +

Your coach.
Your colors.

+

Otterpace ships in five whole-app looks. Tap one below — watch the entire app and this page change with it.

+
+
👇 Try it — pick a theme
+
+ + + + + +
+
+
+ Download on the App Store + Coming soon to Google Play +
+
Free · No accounts · Your health data never leaves your phone.
+
+
+
+
Goal crushed
+
Buddy is proud
+ Otterpace app +
+
+ +
+
The coaching

Kind, injury-aware, always on your side.

The look changes. The care doesn’t.

+
+
🩺

Injury-aware

Backs off when your load spikes. Run/rest calls, never a diagnosis.

+
💬

Ask Buddy

Conversational coaching in plain language. Offline by default; smarter with your key.

+
👟

Gentle nudges

Move toward your step goal with encouragement, not guilt.

+
📅

Weekly review

What went well, what changed, one focus for next week.

+
🔒

On-device

Apple Health read locally, never uploaded. No accounts, no ads.

+
💛

Open source

Built in the open as a CodeYam showcase.

+
+
+ +
+

Find your look. Start moving.

+

Otterpace 1.0 is live on the App Store. Android is coming.

+
+ Download on the App Store + Coming soon to Google Play +
+
+ + + + + diff --git a/.codeyam/design/site-redesign/08-offcatalog-mockup.html b/.codeyam/design/site-redesign/08-offcatalog-mockup.html new file mode 100644 index 0000000..0027b0f --- /dev/null +++ b/.codeyam/design/site-redesign/08-offcatalog-mockup.html @@ -0,0 +1,181 @@ + + + + + +Otterpace — Coach's Notebook + + + + + +
+
+
+ ◐ Now live on the App Store +

Keep the streak.
Keep it kind.

+

Otterpace is a running coach that reads your day, closes your goal ring, and coaches you like a friend — injury-aware, never-shame.

+
+ Download on the App Store + Coming soon to Google Play +
+
+
+ +
Otterpace Today
+
+
+
+
This weekwith Buddy
+
11,240
STEPS TODAY
+
4.2 mi
LAST RUN
+
3
RUN DAYS
+
Low
INJURY RISK
+
+
+ +
+ +
+
// the theme system
+
Five looks. Pick your pace.
+

The whole app re-skins — colors, type, and mascot shift together. Set it in onboarding or Settings.

+
+
Otter
Otter
warm · coral
+
Bolt
Bolt
dark · aqua
+
Orbit
Orbit
cosmic · ice
+
Fieldnote
Fieldnote
analog · teal
+
Garden
Garden
calm · sage
+
+
+ +
+ +
+
// weekly review
+
Buddy writes you a recap.
+
+
What went well

A consistent, honest week

4 runs for 22 miles, capped by an 8-mile long run. That rhythm is exactly what builds durable fitness.

+
What changed

Load trending up — gently

More total miles than a typical week, but at a sustainable pace rather than a spike.

+
Training risk

Low — keep it easy

Two rest days and honest runs. Keep most runs conversational and you’ll stay clear of the injury tax.

+
Next week

One kind focus

Repeat the shape. No hero weeks — just show up and let it compound.

+
+
+ +
+ +
+
// the essentials
+
Everything Buddy brings.
+
+
🩺

Injury-aware

Backs off when your load spikes — run/rest calls, never a diagnosis.

+
💬

Ask Buddy

Conversational coaching. Offline by default; bring your own AI key to go deeper.

+
🔒

On-device

Apple Health read locally, never uploaded. No accounts, no ads, no data sold.

+
+
+ +
+

Close today’s ring with Buddy.

+

Otterpace 1.0 is live on the App Store · Android coming · Open source

+
+ Download on the App Store + Coming soon to Google Play +
+
+ + + diff --git a/.codeyam/design/site-redesign/index.html b/.codeyam/design/site-redesign/index.html new file mode 100644 index 0000000..1dff8b4 --- /dev/null +++ b/.codeyam/design/site-redesign/index.html @@ -0,0 +1,32 @@ + + +Otterpace — site redesign directions + +
+

🐾 Otterpace — 8 landing-page directions

+

Open each in a new tab and compare. Then tell me “let’s use N”, or mix (“#4’s vibe with #1’s warmth”).

+
+
01 · Gummy
Soft & playful coral
The warm, huggable, on-brand take. Theme device gallery.
Anchored
+
02 · Editorial
Refined magazine
Big serif display, lookbook theme index. Sophisticated.
Anchored
+
03 · Marquee
Energetic display type
Bold headlines + a running-pace marquee band.
Anchored
+
04 · Bolt / Neo-brutalist
Black + electric aqua
The Bolt theme as a site — live theme-switcher.
Exploratory
+
05 · Orbit / Cosmic
Immersive dark cosmos
The Orbit theme — atmospheric, planetary hero.
Exploratory
+
06 · Letter from Buddy
Warm & personal
The site as a kind note from your coach.
Exploratory
+
07 · Theme Chameleon
The whole site re-skins
Tap a theme — page + app morph across all five.
Off-catalog
+
08 · Coach’s Notebook
Fitness-editorial
Step-ring + pace-line motif, weekly-review spine.
Off-catalog
+
+
diff --git a/.codeyam/design/site-redesign/shots/app-store-badge.svg b/.codeyam/design/site-redesign/shots/app-store-badge.svg new file mode 100644 index 0000000..d98eed4 --- /dev/null +++ b/.codeyam/design/site-redesign/shots/app-store-badge.svg @@ -0,0 +1,9 @@ + + Download on the App Store + + + + + Download on the + App Store + diff --git a/.codeyam/design/site-redesign/shots/icon.png b/.codeyam/design/site-redesign/shots/icon.png new file mode 100644 index 0000000..8ba8b41 Binary files /dev/null and b/.codeyam/design/site-redesign/shots/icon.png differ diff --git a/.codeyam/design/site-redesign/shots/play-badge-soon.svg b/.codeyam/design/site-redesign/shots/play-badge-soon.svg new file mode 100644 index 0000000..dc38881 --- /dev/null +++ b/.codeyam/design/site-redesign/shots/play-badge-soon.svg @@ -0,0 +1,12 @@ + + Coming soon to Google Play + + + + + + + + COMING SOON TO + Google Play + diff --git a/.codeyam/design/site-redesign/shots/shot-bolt.png b/.codeyam/design/site-redesign/shots/shot-bolt.png new file mode 100644 index 0000000..125a421 Binary files /dev/null and b/.codeyam/design/site-redesign/shots/shot-bolt.png differ diff --git a/.codeyam/design/site-redesign/shots/shot-fieldnote.png b/.codeyam/design/site-redesign/shots/shot-fieldnote.png new file mode 100644 index 0000000..671b394 Binary files /dev/null and b/.codeyam/design/site-redesign/shots/shot-fieldnote.png differ diff --git a/.codeyam/design/site-redesign/shots/shot-garden.png b/.codeyam/design/site-redesign/shots/shot-garden.png new file mode 100644 index 0000000..067bf2a Binary files /dev/null and b/.codeyam/design/site-redesign/shots/shot-garden.png differ diff --git a/.codeyam/design/site-redesign/shots/shot-onboarding.png b/.codeyam/design/site-redesign/shots/shot-onboarding.png new file mode 100644 index 0000000..7474b4c Binary files /dev/null and b/.codeyam/design/site-redesign/shots/shot-onboarding.png differ diff --git a/.codeyam/design/site-redesign/shots/shot-orbit.png b/.codeyam/design/site-redesign/shots/shot-orbit.png new file mode 100644 index 0000000..1b1d708 Binary files /dev/null and b/.codeyam/design/site-redesign/shots/shot-orbit.png differ diff --git a/.codeyam/design/site-redesign/shots/shot-otter.png b/.codeyam/design/site-redesign/shots/shot-otter.png new file mode 100644 index 0000000..218070c Binary files /dev/null and b/.codeyam/design/site-redesign/shots/shot-otter.png differ diff --git a/.codeyam/design/site-redesign/shots/shot-settings.png b/.codeyam/design/site-redesign/shots/shot-settings.png new file mode 100644 index 0000000..026aa2d Binary files /dev/null and b/.codeyam/design/site-redesign/shots/shot-settings.png differ diff --git a/.codeyam/design/site-redesign/shots/shot-themes.png b/.codeyam/design/site-redesign/shots/shot-themes.png new file mode 100644 index 0000000..edc49ae Binary files /dev/null and b/.codeyam/design/site-redesign/shots/shot-themes.png differ diff --git a/Sources/AppCore/AskCoachInputBar.swift b/Sources/AppCore/AskCoachInputBar.swift index 0c01e72..dcfb853 100644 --- a/Sources/AppCore/AskCoachInputBar.swift +++ b/Sources/AppCore/AskCoachInputBar.swift @@ -31,7 +31,7 @@ struct AskCoachInputBar: View { Button(action: onSend) { Image(systemName: "arrow.up") .font(.system(size: 17, weight: .bold)) - .foregroundColor(.white) + .foregroundColor(Palette.onAccent) .frame(width: 40, height: 40) .background( Circle().fill(canSend diff --git a/Sources/AppCore/AskCoachLockedState.swift b/Sources/AppCore/AskCoachLockedState.swift index d4ae387..d2557f7 100644 --- a/Sources/AppCore/AskCoachLockedState.swift +++ b/Sources/AppCore/AskCoachLockedState.swift @@ -21,7 +21,7 @@ struct AskCoachLockedState: View { Button(action: onAddKey) { Text("Connect your AI key") .font(Typography.headline) - .foregroundColor(.white) + .foregroundColor(Palette.onAccent) .padding(.horizontal, 22).padding(.vertical, 12) .background(Capsule().fill(Palette.brand)) } diff --git a/Sources/AppCore/ChatBubble.swift b/Sources/AppCore/ChatBubble.swift index 7e5673e..d054ed3 100644 --- a/Sources/AppCore/ChatBubble.swift +++ b/Sources/AppCore/ChatBubble.swift @@ -13,7 +13,7 @@ struct ChatBubble: View { Spacer(minLength: 40) Text(message.text) .font(Typography.body) - .foregroundColor(.white) + .foregroundColor(Palette.onAccent) .padding(.horizontal, 14).padding(.vertical, 10) .background( RoundedRectangle(cornerRadius: 18, style: .continuous) diff --git a/Sources/AppCore/CoachCard.swift b/Sources/AppCore/CoachCard.swift index 147cbc4..5848ee1 100644 --- a/Sources/AppCore/CoachCard.swift +++ b/Sources/AppCore/CoachCard.swift @@ -17,7 +17,7 @@ struct CoachCard: View { Spacer() Text(coach.recommendationType.uppercased()) .font(Typography.caption2) - .foregroundColor(.white) + .foregroundColor(Palette.onAccent) .padding(.horizontal, 8).padding(.vertical, 3) .background(Capsule().fill(coach.safetyFlag ? Palette.amber : Palette.go)) } diff --git a/Sources/AppCore/ConnectHero.swift b/Sources/AppCore/ConnectHero.swift index 5d1a469..c1ff5d7 100644 --- a/Sources/AppCore/ConnectHero.swift +++ b/Sources/AppCore/ConnectHero.swift @@ -42,7 +42,7 @@ struct ConnectHero: View { Text("Connect Apple Health") .font(Typography.headline) } - .foregroundColor(.white) + .foregroundColor(Palette.onAccent) .frame(maxWidth: .infinity) .padding(.vertical, 16) .background( diff --git a/Sources/AppCore/ContentView.swift b/Sources/AppCore/ContentView.swift index d69729e..4a86f09 100644 --- a/Sources/AppCore/ContentView.swift +++ b/Sources/AppCore/ContentView.swift @@ -114,11 +114,22 @@ public struct ContentView: View { .onAppear { Analytics.shared.capture("onboarding_started") } } } + // Re-key the whole visual tree on the selected theme so a live switch + // rebuilds every module — leaf cards read the static `Palette` (which + // resolves the current theme) but don't individually observe `ThemeStore`, + // so without this identity change SwiftUI keeps their cached (default, + // white-card) render and only the theme-observing screen roots retint. + // `model`/`session` live above this `.id`, so app state survives the + // switch; scenario captures pin one theme at launch, so `.id` is constant + // there and never triggers a rebuild. + .id(themeStore.themeID) .modifier(DynamicTypeOverride(raw: contentSizeOverride)) - // Light-only design: also pin the scheme here so SwiftUI previews and - // isolated-component captures (which mount ContentView directly, not via - // the App scene) render light too. The App scene sets this as well. - .preferredColorScheme(.light) + // Match the system color scheme to the selected theme so system chrome + // (TabView bar, SecureField, scroll backgrounds, default Text) tracks the + // painted surfaces — light for Default/Fieldnote/Garden, dark for + // Bolt/Orbit. Default is light, so SwiftUI previews and isolated-component + // captures (which mount ContentView directly) still render light. + .preferredColorScheme(themeStore.current.isDark ? .dark : .light) .onAppear { if previewMode.isEmpty { Analytics.shared.capture("app_opened") } revalidateSessionIfNeeded() diff --git a/Sources/AppCore/HealthDeniedView.swift b/Sources/AppCore/HealthDeniedView.swift index 4fad659..530d205 100644 --- a/Sources/AppCore/HealthDeniedView.swift +++ b/Sources/AppCore/HealthDeniedView.swift @@ -41,7 +41,7 @@ struct HealthDeniedView: View { Text("Open Settings") .font(Typography.headline) } - .foregroundColor(.white) + .foregroundColor(Palette.onAccent) .frame(maxWidth: .infinity) .padding(.vertical, 16) .background( diff --git a/Sources/AppCore/Onboarding/OnboardingFlowView.swift b/Sources/AppCore/Onboarding/OnboardingFlowView.swift index 051e30d..c8918f9 100644 --- a/Sources/AppCore/Onboarding/OnboardingFlowView.swift +++ b/Sources/AppCore/Onboarding/OnboardingFlowView.swift @@ -461,7 +461,7 @@ struct OnboardingFlowView: View { Button(action: action) { Text(title) .font(Typography.headline) - .foregroundColor(.white) + .foregroundColor(Palette.onAccent) .frame(maxWidth: .infinity) .padding(.vertical, 16) .background( diff --git a/Sources/AppCore/RacePromptBanner.swift b/Sources/AppCore/RacePromptBanner.swift index 294ebd6..98154ca 100644 --- a/Sources/AppCore/RacePromptBanner.swift +++ b/Sources/AppCore/RacePromptBanner.swift @@ -13,14 +13,14 @@ struct RacePromptBanner: View { HStack(spacing: 12) { Image(systemName: "flag.checkered") .font(.system(size: 20, weight: .bold)) - .foregroundColor(.white) + .foregroundColor(Palette.onAccent) VStack(alignment: .leading, spacing: 3) { Text("Got a race coming up?") .font(Typography.headline) - .foregroundColor(.white) + .foregroundColor(Palette.onAccent) Text("Tell Buddy and I'll tailor your training toward it.") .font(Typography.caption) - .foregroundColor(.white.opacity(0.92)) + .foregroundColor(Palette.onAccent.opacity(0.92)) .fixedSize(horizontal: false, vertical: true) } Spacer(minLength: 24) @@ -39,7 +39,7 @@ struct RacePromptBanner: View { Button(action: onDismiss) { Image(systemName: "xmark") .font(.system(size: 12, weight: .bold)) - .foregroundColor(.white.opacity(0.9)) + .foregroundColor(Palette.onAccent.opacity(0.9)) .padding(10) } .accessibilityLabel("Dismiss race prompt") diff --git a/Sources/AppCore/Theme.swift b/Sources/AppCore/Theme.swift index 8f81b4c..8762016 100644 --- a/Sources/AppCore/Theme.swift +++ b/Sources/AppCore/Theme.swift @@ -27,6 +27,11 @@ public enum Palette { public static var card: Color { t.card } public static var bgTop: Color { t.bgTop } public static var bgBottom: Color { t.bgBottom } + + // Readable text/icon color to place on a brand/go/amber accent fill. White on + // the light themes; a near-black on the dark themes (whose accents are light), + // so on-accent text always contrasts instead of blending in. + public static var onAccent: Color { t.onAccent } } // MARK: - Typography diff --git a/Sources/AppCore/Theming/ThemeSystem.swift b/Sources/AppCore/Theming/ThemeSystem.swift index b5c0bca..c1eeb01 100644 --- a/Sources/AppCore/Theming/ThemeSystem.swift +++ b/Sources/AppCore/Theming/ThemeSystem.swift @@ -30,6 +30,12 @@ public struct Theme: Equatable { public let card: Color public let bgTop: Color public let bgBottom: Color + + // The readable text/icon color to place *on* a brand/go/amber accent fill + // (buttons, pills, chat bubbles). Light themes use white; the dark themes + // carry deliberately *light* accents (bright teal/blue), so white would blend + // — they use a near-black instead so on-accent text always contrasts. + public let onAccent: Color } /// The five selectable looks. `.default` is the shipping identity and the default. @@ -68,35 +74,40 @@ public enum ThemeID: String, CaseIterable, Identifiable { go: Color(hex: 0x3DBC7D), sky: Color(hex: 0x5B9EED), amber: Color(hex: 0xF7B03B), gold: Color(hex: 0xFACC4D), lilac: Color(hex: 0x8C82DC), ink: Color(hex: 0x292B38), subtle: Color(hex: 0x575C6E), - card: .white, bgTop: Color(hex: 0xFDF5EE), bgBottom: Color(hex: 0xF5F0FA)) + card: .white, bgTop: Color(hex: 0xFDF5EE), bgBottom: Color(hex: 0xF5F0FA), + onAccent: .white) case .bolt: return Theme(id: self, isDark: true, brand: Color(hex: 0x2FE3D0), brandDeep: Color(hex: 0x17B7A6), go: Color(hex: 0x3DD68C), sky: Color(hex: 0x5AB0FF), amber: Color(hex: 0xFFB65A), gold: Color(hex: 0xFFD65A), lilac: Color(hex: 0xB49CFF), ink: Color(hex: 0xF5F5F7), subtle: Color(hex: 0xAEAEB4), - card: Color(hex: 0x1B1B1D), bgTop: Color(hex: 0x0A0B0D), bgBottom: Color(hex: 0x000000)) + card: Color(hex: 0x1B1B1D), bgTop: Color(hex: 0x0A0B0D), bgBottom: Color(hex: 0x000000), + onAccent: Color(hex: 0x08110F)) case .orbit: return Theme(id: self, isDark: true, brand: Color(hex: 0x74D6FF), brandDeep: Color(hex: 0x3E9FD6), go: Color(hex: 0x5FD6C4), sky: Color(hex: 0x9AB8FF), amber: Color(hex: 0xFFC46F), gold: Color(hex: 0xFFDA7A), lilac: Color(hex: 0xA79CFF), ink: Color(hex: 0xEAEFFA), subtle: Color(hex: 0x9AA5BE), - card: Color(hex: 0x111524), bgTop: Color(hex: 0x0A0E1C), bgBottom: Color(hex: 0x05070E)) + card: Color(hex: 0x111524), bgTop: Color(hex: 0x0A0E1C), bgBottom: Color(hex: 0x05070E), + onAccent: Color(hex: 0x061019)) case .fieldnote: return Theme(id: self, isDark: false, brand: Color(hex: 0xE0562F), brandDeep: Color(hex: 0xC0421F), go: Color(hex: 0x1F7E8C), sky: Color(hex: 0x4E8FA0), amber: Color(hex: 0xD99A3C), gold: Color(hex: 0xE0B24A), lilac: Color(hex: 0x9C7B9E), ink: Color(hex: 0x2A2620), subtle: Color(hex: 0x7C7263), - card: Color(hex: 0xFBF5E6), bgTop: Color(hex: 0xEFE6D2), bgBottom: Color(hex: 0xE9DFC8)) + card: Color(hex: 0xFBF5E6), bgTop: Color(hex: 0xEFE6D2), bgBottom: Color(hex: 0xE9DFC8), + onAccent: .white) case .garden: return Theme(id: self, isDark: false, brand: Color(hex: 0x4E6B54), brandDeep: Color(hex: 0x3A5340), go: Color(hex: 0x6F9E8B), sky: Color(hex: 0x7FB0AE), amber: Color(hex: 0xC9A24A), gold: Color(hex: 0xD6B85C), lilac: Color(hex: 0xC98BA8), ink: Color(hex: 0x26312A), subtle: Color(hex: 0x69756A), - card: .white, bgTop: Color(hex: 0xECEFE8), bgBottom: Color(hex: 0xE4EADD)) + card: .white, bgTop: Color(hex: 0xECEFE8), bgBottom: Color(hex: 0xE4EADD), + onAccent: .white) } } }