From 224dc04696b18b9c3e863da8f2f5d77aba6d4637 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 28 Jul 2026 18:16:01 -0700 Subject: [PATCH] [eric] theme: stock wallpaper is a designed contrasting gradient (canvas + shell + onboarding stage), never flat white when no accent picked --- frontend/src/app/components/Layout/AppShell.tsx | 4 ++-- .../app/components/OnboardingV3/beats/BeatShell.tsx | 3 ++- .../src/app/pages/Dashboard/canvas/DashboardCanvas.tsx | 4 ++-- frontend/src/shared/styles/washBackground.ts | 10 ++++++++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/Layout/AppShell.tsx b/frontend/src/app/components/Layout/AppShell.tsx index 1fb781dd..08db74c3 100644 --- a/frontend/src/app/components/Layout/AppShell.tsx +++ b/frontend/src/app/components/Layout/AppShell.tsx @@ -38,7 +38,7 @@ import { findBrowserByWebContentsId } from '@/shared/browserRegistry'; import { byPreviewRecency } from '@/shared/previewOrder'; import { useClaudeTokens, useThemeAccent, useThemeWash } from '@/shared/styles/ThemeContext'; import SpacesStrip from '@/app/pages/Dashboard/desktop/SpacesStrip'; -import { washBackgroundUrl } from '@/shared/styles/washBackground'; +import { washBackgroundUrl, effectiveWashStops } from '@/shared/styles/washBackground'; import { ErrorSlime } from '@/app/components/feedback/ErrorSlime'; const UPDATE_DISMISS_KEY = 'openswarm-update-dismissed'; @@ -96,7 +96,7 @@ const AppShell: React.FC = () => { // the content floats as a rounded card). Mirrors the DashboardCanvas wash formula. const { accent: themeAccent, gradient: themeGradient } = useThemeAccent(); const { washOpacity: themeWashOpacity } = useThemeWash(); - const fsWashStops = themeGradient ?? (themeAccent ? [themeAccent] : null); + const fsWashStops = effectiveWashStops(themeGradient, themeAccent); // During an active free trial the user CAN run things, so a red "no model connected" warning is misleading and discouraging (it sits right above the working starter chips). The trial flips connection_mode back to own_key the moment it's spent, so this banner returns then, landing the connect-a-model nudge after the win, not before it. const freeTrialActive = useAppSelector((s) => { const d = s.settings.data as any; diff --git a/frontend/src/app/components/OnboardingV3/beats/BeatShell.tsx b/frontend/src/app/components/OnboardingV3/beats/BeatShell.tsx index 77b963b1..bbf74393 100644 --- a/frontend/src/app/components/OnboardingV3/beats/BeatShell.tsx +++ b/frontend/src/app/components/OnboardingV3/beats/BeatShell.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { effectiveWashStops } from '@/shared/styles/washBackground'; import { motion } from 'framer-motion'; import { ArrowLeft } from 'lucide-react'; import { useThemeAccent, useThemeWash } from '@/shared/styles/ThemeContext'; @@ -53,7 +54,7 @@ const BeatShell: React.FC<{ // Once the user has picked stops the stage wears them (our theme beat repaints live); before that it stays Arc-mauve. const { accent, gradient } = useThemeAccent(); const { washOpacity, grain } = useThemeWash(); - const stops = gradient ?? (accent ? [accent] : null); + const stops = effectiveWashStops(gradient, accent); // Picked color reads VIVID on the stage (alpha floor over near-white), not muddied into the mauve. const washAlpha = Math.round(Math.max(0.5, washOpacity) * 255).toString(16).padStart(2, '0'); const stageBg = stageDark diff --git a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx index 0fb54e64..e39cce7d 100644 --- a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx +++ b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx @@ -14,7 +14,7 @@ import ApplicationsWindow from '../desktop/ApplicationsWindow'; import type { ClaudeTokens } from '@/shared/styles/claudeTokens'; import { useThemeAccent, useThemeWash } from '@/shared/styles/ThemeContext'; import { GRAIN_URL } from '@/shared/styles/grainTexture'; -import { washBackgroundUrl } from '@/shared/styles/washBackground'; +import { washBackgroundUrl, effectiveWashStops } from '@/shared/styles/washBackground'; import type { AgentSession } from '@/shared/state/agentsSlice'; import type { CardPosition, @@ -168,7 +168,7 @@ const DashboardCanvas: React.FC = ({ const { accent, gradient } = useThemeAccent(); const { washOpacity, grain } = useThemeWash(); // A single picked color stores gradient=null, so fall back to the accent (mirrors BeatShell). - const washStops = gradient ?? (accent ? [accent] : null); + const washStops = effectiveWashStops(gradient, accent); const dotSize = Math.max(1, 1.5 * canvas.zoom); const dotSpacing = 24 * canvas.zoom; diff --git a/frontend/src/shared/styles/washBackground.ts b/frontend/src/shared/styles/washBackground.ts index 1fd76341..890da23c 100644 --- a/frontend/src/shared/styles/washBackground.ts +++ b/frontend/src/shared/styles/washBackground.ts @@ -12,3 +12,13 @@ export function washBackgroundUrl(stops: string[], washOpacity: number): string const svg = `${stopEls}`; return `url("data:image/svg+xml,${svg.replace(/#/g, '%23').replace(/'/g, '%27')}")`; } + +// Stock wallpaper when the user hasn't picked an accent yet: a designed blue-to-cream-to-pink +// gradient (contrasting stops), so a fresh install and the onboarding stage never look flat/white. +export const DEFAULT_WASH_STOPS = ['#B7CDEA', '#EFE0D2', '#E7BDD1']; + +export function effectiveWashStops(gradient: string[] | null, accent: string | null): string[] { + if (gradient && gradient.length > 0) return gradient; + if (accent) return [accent]; + return DEFAULT_WASH_STOPS; +}