Skip to content

Card transparency: user-configurable opacity for all home cards#14

Merged
ryanbr merged 3 commits into
mainfrom
fork-staging
Jul 7, 2026
Merged

Card transparency: user-configurable opacity for all home cards#14
ryanbr merged 3 commits into
mainfrom
fork-staging

Conversation

@ryanbr

@ryanbr ryanbr commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Adds a Card transparency slider (Settings → Appearance) that fades every card surface toward the background, saved and applied live. Content sits above the surface so it stays fully readable at any level. Default 100% (solid) = no visual change until the user moves it.

Coverage — all card vessels, both platforms

  • Frosted cards (frostedCardSurface / FrostedCardSurface): Heart Rate, Stress, Fitness Age, Key Metrics, Recovery Vitals, etc.
  • Liquid hero cards (the big pinned-dark score card): Today, Stress, Sleep, Coupled, Hydration, Devices, Trends, Breathe, Workouts, Live.
  • Inline surfaceRaised vessels on the liquid Today (metric tiles, card/cardLink) and the Live / LiveSession / Coupled / Hydration screens.

How

  • Android: reactive CardAppearance.opacity (mutableStateOf), read in frostedCardSurface (composed{}) and scaled into each hero LIQUID_HERO_FILL + hairline.
  • iOS: @AppStorage(CardAppearancePrefs.opacityKey), applied via .opacity(op) on FrostedCardSurface and each inline card/hero background stack.
  • Settings key mirrored across platforms (noop.cardOpacityPercent).

Not included (intentional)

TrendsReportView stays solid — it renders to a shared PDF, not an on-screen card.

Verification

  • Android Kotlin compiles clean.
  • iOS/macOS compile via the Testing build CI (Swift can't build on the dev box).

ryanbr added 3 commits July 7, 2026 18:27
Every card (Heart Rate, Stress, Fitness Age, Key Metrics, Recovery Vitals, …) renders through
NoopCard -> Modifier.frostedCardSurface, whose fill is Palette.surfaceRaised. Make that glass opacity
user-adjustable:
- NoopPrefs.cardOpacityPercent (0-100, default 100 = solid) + setter.
- CardAppearance: an app-wide reactive opacity (mutableStateOf, initialised from the pref in
  MainActivity.onCreate). frostedCardSurface is now `composed {}` and scales the fill + border + wash
  (and light-mode shadow) by it — only the SURFACE fades, the card content stays fully readable.
- SettingsScreen: a "Card transparency" slider under the Day-cycle background toggle. Live-previews
  (the cards on the Settings screen update as you drag) via CardAppearance; saved on release.

Android-only for now (iOS frostedCardSurface is the parity twin, follow-up). Compiles clean.
Needs an on-device eyeball — this changes every card's look and I can't render it on WSL.
Parity with the Android card-transparency feature:
- CardAppearancePrefs (StrandDesign/Appearance.swift): the @AppStorage key "noop.cardOpacityPercent"
  (default 100), mirroring NoopPrefs.cardOpacityPercent.
- FrostedCardSurface (StrandCard.swift): reads it via @AppStorage and applies `.opacity(op)` to the
  whole glass surface, so every card (Heart Rate, Key Metrics, Recovery Vitals, …) fades toward the
  background; content sits above and stays readable. @AppStorage is reactive → all cards update live.
- SettingsView: a "Card transparency" slider (shows transparency 0..100, stores opacity) under the
  Day-cycle background toggle. Bound to the same @AppStorage, so dragging live-previews every card.

Swift is CI-gated (no local compile on WSL) — validated by the iOS + macOS build jobs.
The "Card transparency" setting only reached frostedCardSurface / FrostedCardSurface
cards. The liquid HERO cards (the big pinned-dark score card on Today, Stress, Sleep,
Coupled, Hydration, Devices, Trends, Breathe, Workouts, Live) and the inline
surfaceRaised card vessels on the liquid Today (metric tiles, the card/cardLink
helpers) and the Live / LiveSession / Coupled / Hydration screens built their surface
inline, so the slider left them fully opaque.

Route them all through the same setting, surface-only so content stays readable:
- Android: scale LIQUID_HERO_FILL / LIVE_HERO_FILL + the white hairline by
  CardAppearance.opacity at each hero.
- iOS: read @AppStorage(CardAppearancePrefs.opacityKey) per view and apply
  .opacity(cardOpacity) to each inline card/hero background stack.

TrendsReportView is left solid (it renders to a shared PDF, not an on-screen card).
@ryanbr ryanbr merged commit 0d54dfd into main Jul 7, 2026
2 of 4 checks passed
ryanbr pushed a commit that referenced this pull request Jul 7, 2026
PR #14 broke the iOS/macOS compile — three card surfaces live in nested sub-structs
(LiveLogCard, SignalTrustTile, LiveSessionSummarySheet) that did not carry the
cardOpacity property, so .opacity(cardOpacity) was out of scope there. Give each its
own @AppStorage(CardAppearancePrefs.opacityKey) + cardOpacity, matching the parent
views. Android was unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant