From 9e4d5655549f1768449e1bf526c0416f70f48aad Mon Sep 17 00:00:00 2001 From: InstaZDLL Date: Sat, 20 Jun 2026 20:06:37 +0200 Subject: [PATCH 1/2] fix(skins): readable Mood Radio + About hero in Liquid light MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related regressions reported on the Liquid light skin after v1.5.0: 1. **Mood Radio cards still pale** — the previous fix (#275) bumped the `::before` opacity from 0.55 to 0.95, but the gradient itself uses `rgb(X Y Z / 0.55)` per stop, and the two alphas multiply (0.55 × 0.95 = 0.52, basically the original). Bump the gradient internal alphas to 1.0 in light mode so the 5 cards become saturated colored surfaces that the hardcoded white text actually reads on. Dark mode keeps the original translucent gradients — the dark page makes them pop on its own. 2. **About hero invisible** — `AboutView` ships the hero with `
`. The dark gradient body is the design intent (white text on dark block, Studio works perfectly). In Liquid light the surrounding aurora bleeds through and inherited `text-white` propagates down to every text child → invisible. Remap `.text-white` to `--liq-ink` in light mode, excluding elements that ALSO carry a saturated colored bg (emerald pills, mood tiles, etc.) where the white is intentional. The exclusion chain matches the colors already used by intentional-white components across the app. Both ship under `:not(.dark)` so dark Liquid stays unchanged. --- src/styles/skins/liquid.css | 67 ++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/styles/skins/liquid.css b/src/styles/skins/liquid.css index e293c8d..576b3a1 100644 --- a/src/styles/skins/liquid.css +++ b/src/styles/skins/liquid.css @@ -478,13 +478,33 @@ gradient blends into the background and the hardcoded white text below (title/subtitle/count) dissolves into the card. Dark mode stays at the original 0.55 → 0.75 because the dark page makes the - gradient pop on its own. */ + gradient pop on its own. + We ALSO need to bump the gradient internal alphas from 0.55 → 1.0 + in light mode — the ::before opacity bump alone wasn't enough + because the two alphas multiply (0.55 × 0.95 = 0.52, basically + unchanged from 0.55). Solid-alpha gradients give the saturated + cards that white text actually reads on. */ :root[data-skin="liquid"]:not(.dark) .liquid-mood-tile::before { opacity: 0.95; } :root[data-skin="liquid"]:not(.dark) .liquid-mood-tile:hover::before { opacity: 1; } +:root[data-skin="liquid"]:not(.dark) .liquid-mood-focus::before { + background: linear-gradient(135deg, rgb(99 102 241), rgb(37 99 235)); +} +:root[data-skin="liquid"]:not(.dark) .liquid-mood-chill::before { + background: linear-gradient(135deg, rgb(245 158 11), rgb(217 119 6)); +} +:root[data-skin="liquid"]:not(.dark) .liquid-mood-workout::before { + background: linear-gradient(135deg, rgb(244 63 94), rgb(219 39 119)); +} +:root[data-skin="liquid"]:not(.dark) .liquid-mood-party::before { + background: linear-gradient(135deg, rgb(168 85 247), rgb(217 70 239)); +} +:root[data-skin="liquid"]:not(.dark) .liquid-mood-sleep::before { + background: linear-gradient(135deg, rgb(8 145 178), rgb(30 64 175)); +} :root[data-skin="liquid"] .liquid-mood-icon { width: 2.25rem !important; @@ -790,6 +810,51 @@ color: var(--liq-ink) !important; } +/* Light-mode `.text-white` neutralization. Components like AboutView's + hero ship `
` — the dark gradient body keeps white text readable + in Studio. Liquid light mode keeps the same `text-white` on those + elements while the surrounding aurora bleeds through, and the + inherited white propagates down to every text child → invisible. + Remap `.text-white` to `--liq-ink` (dark in light, near-white in + dark, so it stays white when it should). The `:not(...)` chain + excludes elements that ALSO carry a saturated colored background + (emerald pills, action buttons, mood tiles) where the white is + intentional. */ +:root[data-skin="liquid"]:not(.dark) :where(.text-white):not( + [class*="bg-emerald"] +):not( + [class*="bg-violet"] +):not( + [class*="bg-indigo"] +):not( + [class*="bg-blue-5"] +):not( + [class*="bg-blue-6"] +):not( + [class*="bg-purple"] +):not( + [class*="bg-fuchsia"] +):not( + [class*="bg-pink"] +):not( + [class*="bg-rose"] +):not( + [class*="bg-amber"] +):not( + [class*="bg-orange"] +):not( + [class*="bg-sky"] +):not( + [class*="bg-cyan"] +):not( + .liquid-mood-tile +):not( + .liquid-mood-tile * +) { + color: var(--liq-ink) !important; +} + :root[data-skin="liquid"] :where( .text-zinc-700, .text-zinc-600, From a2c0cb4ec8d37890a735b6f3f4c856ac2b746793 Mon Sep 17 00:00:00 2001 From: InstaZDLL Date: Sat, 20 Jun 2026 20:15:51 +0200 Subject: [PATCH 2/2] fix(skins): paint Mood Radio gradient on tile, not ::before pseudo (Liquid light) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User reports the Mood Radio tiles flicker — sometimes saturated as designed, sometimes pale-white again. Investigation pointed at the ::before pseudo route being fragile against three runtime races: 1. **Theme/skin re-application on profile load** — ThemeContext reads the active profile's DB row on mount + on every profile switch (ThemeContext.tsx:111), which calls applyTheme() and toggles the `.dark` class. During the gap between cached-theme apply and DB-theme apply, `:not(.dark)` can stop matching and the ::before-based rule falls back to the 0.55-alpha gradient. 2. **View transitions on theme toggle** — setThemeId triggers `document.startViewTransition` which snapshots the paint for ~300ms. If the snapshot caught a state where my rule wasn't matching, the tile stays pale until the transition tears down. 3. **240ms opacity transition on ::before** — the pseudo has a transition: opacity, so even when the rule matches again the tiles fade back in slowly, giving a "blinks white" impression. Fix: paint the saturated gradient directly on the tile element (background-image, !important) instead of via the pseudo, and display: none the ::before in light mode so the two layers don't double-stack. Element-level paint isn't subject to z-index/stacking gymnastics, isn't affected by the transition on the pseudo, and survives the .dark flips during profile load. Dark mode keeps the existing ::before route unchanged — there it works because the dark page makes the translucent gradient pop on its own. --- src/styles/skins/liquid.css | 54 ++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/src/styles/skins/liquid.css b/src/styles/skins/liquid.css index 576b3a1..6754781 100644 --- a/src/styles/skins/liquid.css +++ b/src/styles/skins/liquid.css @@ -484,26 +484,42 @@ because the two alphas multiply (0.55 × 0.95 = 0.52, basically unchanged from 0.55). Solid-alpha gradients give the saturated cards that white text actually reads on. */ -:root[data-skin="liquid"]:not(.dark) .liquid-mood-tile::before { - opacity: 0.95; -} -:root[data-skin="liquid"]:not(.dark) .liquid-mood-tile:hover::before { - opacity: 1; -} -:root[data-skin="liquid"]:not(.dark) .liquid-mood-focus::before { - background: linear-gradient(135deg, rgb(99 102 241), rgb(37 99 235)); +/* Paint the gradient DIRECTLY on the tile element (not the ::before + pseudo) for the light variant. The pseudo route was fragile in + three ways: + - it sits behind the tile body at z-index -1 and depends on the + parent's stacking context staying as expected, + - the `opacity` transition (240ms) on the pseudo races with + theme/skin re-application during profile load — `.dark` can flip + on briefly while the DB row is being read by ThemeContext, and + the `:not(.dark)` rule stops matching → cards fade to the + 0.55-alpha gradient and look white, + - view transitions snapshot the paint at the moment of click; if + the snapshot caught the wrong state the tile stays pale until + the transition tears down. + Painting on the element itself bypasses all three traps — the + tile renders as a saturated colored surface from the first paint, + and stays that way regardless of cascade ordering, .dark flips, + or view-transition snapshots. We also `display: none` the ::before + so the two layers don't double-stack and re-introduce the flicker + when the user picks an interactive state mid-transition. */ +:root[data-skin="liquid"]:not(.dark) .liquid-mood-focus { + background-image: linear-gradient(135deg, rgb(99 102 241), rgb(37 99 235)) !important; +} +:root[data-skin="liquid"]:not(.dark) .liquid-mood-chill { + background-image: linear-gradient(135deg, rgb(245 158 11), rgb(217 119 6)) !important; +} +:root[data-skin="liquid"]:not(.dark) .liquid-mood-workout { + background-image: linear-gradient(135deg, rgb(244 63 94), rgb(219 39 119)) !important; +} +:root[data-skin="liquid"]:not(.dark) .liquid-mood-party { + background-image: linear-gradient(135deg, rgb(168 85 247), rgb(217 70 239)) !important; +} +:root[data-skin="liquid"]:not(.dark) .liquid-mood-sleep { + background-image: linear-gradient(135deg, rgb(8 145 178), rgb(30 64 175)) !important; } -:root[data-skin="liquid"]:not(.dark) .liquid-mood-chill::before { - background: linear-gradient(135deg, rgb(245 158 11), rgb(217 119 6)); -} -:root[data-skin="liquid"]:not(.dark) .liquid-mood-workout::before { - background: linear-gradient(135deg, rgb(244 63 94), rgb(219 39 119)); -} -:root[data-skin="liquid"]:not(.dark) .liquid-mood-party::before { - background: linear-gradient(135deg, rgb(168 85 247), rgb(217 70 239)); -} -:root[data-skin="liquid"]:not(.dark) .liquid-mood-sleep::before { - background: linear-gradient(135deg, rgb(8 145 178), rgb(30 64 175)); +:root[data-skin="liquid"]:not(.dark) .liquid-mood-tile::before { + display: none; } :root[data-skin="liquid"] .liquid-mood-icon {