Skip to content

fix(theme): light-mode color parity with Wisp Android#162

Merged
barrydeen merged 4 commits into
mainfrom
fix/light-mode-color-parity
May 25, 2026
Merged

fix(theme): light-mode color parity with Wisp Android#162
barrydeen merged 4 commits into
mainfrom
fix/light-mode-color-parity

Conversation

@dmnyc

@dmnyc dmnyc commented May 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

iOS counterpart to Wisp Android PR barrydeen/wisp#558. Light mode has been drifting from the Android counterpart in three places — colors, the wallet logo's visibility, and the in-flight bolt pulse animation. This PR brings all three back into parity.

1. Light-mode primary deepened + zap surfaces aligned

  • Default-theme light primary bumped from #CC7000 to #D9730D. Better contrast on the near-white surface so tinted buttons / icons keep their interactive read.
  • Default-theme light zap + bookmark bumped from #B85C00 to the same #D9730D. They were one shade darker than the primary, producing a visible two-tone mismatch on every zap icon, count, and top-zapper indicator.
  • ResolvedTheme gains resolved zap / bookmark / zapAnimation fields. For the custom theme these track the resolved primary so a user-picked accent flows through to every zap surface as one hue. For every other preset (Nord, Dracula, etc.) they pass through the curated palette values unchanged.
  • Custom-accent light primary derivation: the default Spark-orange accent uses the curated #D9730D directly; any other accent gets darkenColor(accent, 0.18) (HSL lightness × 0.82). Replaces the prior dimmedForLight which capped brightness/saturation asymmetrically.

2. In-flight bolt pulse: vector stroke instead of shadow blur

  • New Color.wispZapAnimationColor accessor, derived from wispZapColor via saturation × 1.15 (capped at 1.0) and brightness ≥ 0.5. Plain wispZapColor reads muddy in light mode because the primary is darkened for button contrast; the celebratory pulse needs a brighter floor.
  • LightningPulseView rewritten: instead of stacked .shadow(radius:) drop shadows (blurred, smeared on near-white surfaces), the bolt is now drawn as a true SwiftUI Shape (BoltGlyph) with .stroke(lineWidth:) for the outer halo and .fill for the body. The stroke is mathematically half-inside / half-outside the silhouette — the fill covers the inside half so only the outer halo is visible. Crisp at every size, no aliasing.
  • Mirrors Wisp Android's LightningAnimation: same bolt geometry, same stroke width / fill / pulse composition, same scale-breath envelope (0.92 → 1.08).

3. SparkBreez wallet logo readable in light mode

  • The SparkBreezLogo SVG is rendered with all-white paths (legacy of the dark-only wallet origin). In light mode that left the logo invisible against the near-white surface in both the wallet dashboard header and the settings Spark-details row.
  • Flipped the asset's template-rendering-intent from original to template so a foregroundStyle modifier tints it. Three sites:
    • Wallet dashboard top bar + Settings Spark-details row → wispOnSurface (near-black on light, light grey on dark).
    • Settings "Powered by Breez SDK" footer → .tertiary so the logo's prominence matches the caption next to it.

See LIGHT_MODE_COLOR_PARITY.md in the Wisp Android repo for the locked cross-platform contract.

Test plan

  • Switch to light mode (default theme). Post you've already zapped → bolt + count is #D9730D, same orange as Save / Connect / Confirm buttons.
  • Notifications tab → bolt + sat count on zap rows is #D9730D.
  • Wallet dashboard → Send/Receive button rings are #D9730D. SparkBreez logo in the top bar is clearly readable (near-black, not invisible white).
  • Wallet Settings → Spark details row logo is clearly readable. Footer "Powered by Breez SDK" logo matches the caption's tertiary tone.
  • Trigger a fresh zap on any post. In-flight bolt animation: filled white bolt body with a crisp orange outer halo that pulses. No blur, no aliasing, no muddy smear.
  • Switch to dark mode. Every surface above unchanged from main (no regressions).
  • Pick a non-default custom accent color (e.g. blue) in Theme Settings. Light-mode buttons + zap icon both render the 18%-darkened variant of your accent and match each other.
  • Try a non-default theme preset (Nord, Dracula, Gruvbox, etc.) — their curated light-mode primary / zap / bookmark values stay intact, unaffected by this PR.

dmnyc added 4 commits May 22, 2026 13:16
Mirrors Wisp Android PR barrydeen/wisp#558 on the iOS side. Two
coupled fixes for light mode that were behaving differently from
the Android counterpart:

1. **Default-theme light primary deepened to `#D9730D`** (was
   `#CC7000`). Better contrast against the near-white surface so
   tinted buttons / icons keep their interactive read. Zap +
   bookmark surfaces on the default theme now use the same value
   — previously they sat at `#B85C00`, one shade darker than the
   primary, producing a visible two-tone mismatch on every zap
   icon, zap count, top-zapper indicator, etc.

2. **In-flight bolt animation gets its own color**. Plain
   `wispZapColor` reads muddy when used for the celebratory pulse
   on a light surface (because the primary is darkened for button
   contrast); the burst needs a brighter floor. Added
   `wispZapAnimationColor`, derived from `wispZapColor` via
   saturation × 1.15 (capped at 1.0) and brightness ≥ 0.5.
   `LightningPulseView` is the only consumer — every other zap
   surface stays on plain `wispZapColor`.

`ResolvedTheme` gains resolved `zap` / `bookmark` / `zapAnimation`
fields so a user-picked accent flows through to every zap surface
for the `custom` theme (zap == primary in custom). Other presets
(Nord, Dracula, etc.) keep their curated palette values unchanged.

Custom-accent light primary derivation: default Spark-orange accent
uses the curated `#D9730D` light primary directly; any other accent
gets `darkenColor(accent, 0.18)` (HSL lightness × 0.82). Replaces
the prior `dimmedForLight` which capped brightness/saturation
asymmetrically.

See LIGHT_MODE_COLOR_PARITY.md in the Wisp Android repo for the
locked cross-platform contract.
The `SparkBreezLogo` SVG is rendered with white paths (a hand-off
from the dark-only origin of the wallet dashboard). In light mode
that left the logo invisible against the near-white surface in the
wallet dashboard header and the settings Spark-details row.

Flipped the asset's `template-rendering-intent` from `original` to
`template` so the foregroundStyle modifier tints it. Use cases:

- Wallet dashboard top bar + Settings Spark-details row →
  `wispOnSurface` so the logo auto-adapts (near-black on light,
  light grey on dark) the same way every other text label in the
  wallet UI does.
- Settings "Powered by Breez SDK" footer → `.tertiary` so the
  logo's prominence matches the caption next to it. The prior
  `.saturation(0)` modifier was a no-op (the SVG was already
  monochrome white) so it's dropped.

Mirrors the visual-readability spirit of the Wisp Android PR
barrydeen/wisp#558 launcher-icon swap — both surfaces needed a
light-mode appearance their dark-only assets weren't providing.
Replaces the prior `Canvas` + `boltPath` implementation with a true
SwiftUI `Shape` (`BoltGlyph`) consumed via `.stroke(lineWidth:)` for
the outer halo and `.fill` for the body. Same visual contract as
Wisp Android's `LightningAnimation`: filled silhouette + outer
stroke + animated pulse + scale breath.

Why a `Shape` instead of an `Image`:

- SF-Symbol bolts (`bolt.fill` / `bolt`) are SwiftUI `Image`s, not
  vector paths we can stroke. The only ways to "add an outer
  stroke" to an `Image` are stacking offset copies (produces
  aliased bitmap halos visible at the edges, the user-reported
  "not clean" rendering) or `.shadow(radius:)` (produces a
  blurred drop shadow, the original light-mode "muddy smear").
- A SwiftUI `Shape` strokes its path mathematically. Half the
  stroke width sits inside the silhouette (covered by the fill
  drawn on top) and half outside (the visible outer halo). The
  result is crisp at every scale and free of aliasing artifacts.

Bolt path is hand-built from a normalized 0-1 coordinate space
(see `BoltGlyph`), approximating Wisp Android's `icBoltPath`
viewBox 55×94. The animation always renders the bolt regardless
of the user's chosen zap-icon style — the in-flight pulse is
iconic, not skinnable. Static icon elsewhere still honors
`AppSettings.zapImage`.

`image:` parameter is kept with a default value for source-compat
with both the bare `LightningPulseView()` callsite on main and the
`LightningPulseView(image: settings.zapImage)` form introduced on
`feat/one-tap-zap`.

Pairs with the other two commits on this branch: `wispZapAnimationColor`
(the vivid color this animation consumes) and the SparkBreez logo
light-mode tinting (the other piece of light-mode color parity).
Three light-mode fixes on the login surfaces:

1. `LoginView`'s "Log In" header was hardcoded `.foregroundStyle(.white)`
   so it rendered as white-on-light-grey in light mode (invisible
   against the sheet's `wispBackground`). Switched to
   `Color.wispOnSurface` which adapts: near-black on light surfaces,
   light grey on dark.

2. Same bug on `SplashView`'s `NostrLoginSheet` title
   ("Continue with Nostr"). Same fix.

3. `WispLogo` SVG uses `fill-rule="evenodd"` so the wisp's two eye
   sub-paths render as cut-outs — they reveal whatever's behind the
   logo. On light mode that's the `wispBackground` light grey, so the
   eyes effectively vanished. Added two explicit `fill="#000000"`
   paths for the eyes drawn before the wisp body. The body's
   cut-outs now reveal the black layer beneath instead of the
   surface color, locking the eye color regardless of the theme.
@barrydeen barrydeen merged commit 55a679e into main May 25, 2026
@dmnyc dmnyc deleted the fix/light-mode-color-parity branch June 2, 2026 03:45
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.

2 participants