feat(webui): adopt lavender mascot branding

This commit is contained in:
DrSmoothl
2026-11-03 10:41:11 +08:00
parent a8f460499b
commit 1cd838e7a1
14 changed files with 64 additions and 37 deletions
+2
View File
@@ -121,6 +121,8 @@ After changing an action name, parameter, result description/schema, example or
SDK breaking changes also update [CHANGELOG.md](packages/sdk/CHANGELOG.md) and [VERSIONING.md](packages/sdk/VERSIONING.md). WebUI changes follow its [interaction conventions](packages/webui/README.md). Keep user-facing documentation aligned with behavior.
Standalone WebUI branding uses the [SnowLuma mascot](assets/readme/mascot.png) and a lavender default accent. Keep server defaults, frontend presets and pre-mount CSS aligned as described in [WebUI default appearance](packages/webui/README.md#default-appearance), while preserving saved user choices and host appearance overlays.
The root [README.md](README.md) presents the project's purpose, overall capabilities, quick start and documentation entry points. Update it only when those overview details change; adding or fixing an individual feature does not justify adding a dedicated section there. Put feature-specific API usage, configuration, limitations and troubleshooting in the relevant package README or topic document, preferring existing documentation.
Edit sources rather than `dist` outputs. Native artifacts in `packages/runtime/native` are supplied build inputs; do not substitute arbitrary local binaries to make a build pass. Packaging changes must preserve the checks in [Core's build configuration](packages/core/vite.config.ts) and [the release layout validator](tools/check-release-layout.mjs).
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+7 -7
View File
@@ -44,7 +44,7 @@ export type SidebarStyle = 'follow' | 'panel' | 'accent';
export type BackgroundType = 'none' | 'solid' | 'gradient' | 'image';
export type Density = 'cozy' | 'compact';
export type TimeFormat = '12h' | '24h';
/** Full color scheme. 'default' keeps the sky theme (driven by `mode`); the
/** Full color scheme. 'default' keeps the lavender theme (driven by `mode`); the
* Catppuccin flavors are complete palettes that also fix light/dark. */
export type Palette =
| 'default'
@@ -85,7 +85,7 @@ export interface UiAppearance {
accentCustom: string;
accentScope: AccentScope;
darkIntensity: DarkIntensity;
/** Full color scheme; 'default' = the built-in sky theme. */
/** Full color scheme; 'default' = the built-in lavender theme. */
palette: Palette;
sidebarStyle: SidebarStyle;
background: UiBackground;
@@ -203,12 +203,12 @@ export interface UiConfig {
}
// ─── Defaults ────────────────────────────────────────────────────────────
// Mirror the pre-existing ThemeContext defaults so an upgrade is a no-op
// visually until the operator changes something.
// Keep these defaults aligned with ThemeContext. Saved appearance choices
// are normalized independently and remain the operator's preference.
const DEFAULT_BACKGROUND: UiBackground = {
type: 'none',
color: '#0ea5e9',
color: '#b99ae6',
gradient: 'none',
imageOpacity: 0.15,
imageBlur: 0,
@@ -220,8 +220,8 @@ const DEFAULT_BACKGROUND: UiBackground = {
const DEFAULT_APPEARANCE: UiAppearance = {
mode: 'system',
accentMode: 'preset',
accentPreset: 'sky',
accentCustom: '#38bdf8',
accentPreset: 'lavender',
accentCustom: '#8561bd',
accentScope: 'global',
darkIntensity: 'soft',
palette: 'default',
+17
View File
@@ -3,6 +3,23 @@
SnowLuma's browser console is a Vite + React application for managing runtime
status, OneBot configuration, logs, storage, and server settings.
## Default appearance
New installations and appearance resets use the mascot's lavender accent.
The light variant uses a deeper purple for readable button labels; dark mode
uses a softer lavender. Existing saved accents and custom colors are preserved.
The original sky-blue accent remains available in Settings.
Keep the server defaults in `packages/core/src/webui/ui-config.ts`, the frontend
defaults and preset in `src/contexts/ThemeContext.tsx`, and the pre-mount tokens
in `src/index.css` aligned when changing the default accent.
The browser favicon, home-screen icon and in-app logo share the mascot avatar
in [`assets/brand/mascot-avatar.png`](../../assets/brand/mascot-avatar.png).
Its exported sizes live in `public/`: `logo.png` (512 px), `favicon.png` (64 px),
and `apple-touch-icon.png` (180 px). Export from the master without redrawing
individual sizes.
## Host appearance integration
`SNOWLUMA_WEBUI_TARGET=maibot` opts a deployment into a reversible millennium
+3 -2
View File
@@ -2,9 +2,10 @@
<html lang="zh-CN" class="light" data-theme="light">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/logo.png" />
<link rel="icon" type="image/png" sizes="64x64" href="/favicon.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#0ea5e9" />
<meta name="theme-color" content="#8561bd" />
<!-- WebUI is an admin surface; tell every compliant crawler to skip it. -->
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet" />
<meta name="googlebot" content="noindex, nofollow, noarchive, nosnippet" />
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 269 KiB

@@ -147,7 +147,7 @@ export function Sidebar({ collapsed = false, onItemClick }: SidebarProps) {
<div className="flex h-16 shrink-0 items-center px-2">
<div className="grid w-12 shrink-0 place-items-center">
<div className="relative flex size-9 items-center justify-center overflow-hidden rounded-xl bg-primary/10 ring-1 ring-primary/20">
<img src="/logo.png" alt="SnowLuma" className="size-7 object-contain" />
<img src="/logo.png" alt="SnowLuma" className="size-full object-cover" />
</div>
</div>
<div
@@ -319,7 +319,7 @@ function LoginBrand({ subtitle }: { subtitle: string }) {
return (
<div className="flex flex-col items-center gap-3 text-center">
<div className="flex size-14 items-center justify-center rounded-2xl bg-primary/10 shadow-sm ring-1 ring-primary/20">
<img src="/logo.png" alt="SnowLuma" className="size-10 object-contain" />
<img src="/logo.png" alt="SnowLuma" className="size-10 rounded-xl object-cover" />
</div>
<div>
<div className="flex items-center justify-center gap-2">
@@ -2078,7 +2078,7 @@ function AboutPanel() {
<img
src="/logo.png"
alt="SnowLuma"
className="size-20 object-contain drop-shadow-sm"
className="size-20 rounded-2xl object-cover drop-shadow-sm"
/>
</div>
<div className="flex items-center gap-2">
+14 -6
View File
@@ -46,7 +46,8 @@ export type AppearancePatch = Partial<Omit<UiAppearance, 'background'>> & {
// ─── Frontend catalogues (the server stores only ids; the visuals live here) ───
export type AccentColor = 'sky' | 'blue' | 'violet' | 'rose' | 'emerald' | 'amber' | 'orange';
export type AccentColor =
'lavender' | 'sky' | 'blue' | 'violet' | 'rose' | 'emerald' | 'amber' | 'orange';
export interface AccentSpec {
id: AccentColor;
@@ -57,6 +58,13 @@ export interface AccentSpec {
}
export const ACCENTS: AccentSpec[] = [
{
id: 'lavender',
label: '薰衣草',
swatch: '#8561bd',
light: { primary: 'oklch(57% 0.14 300)', ring: 'oklch(57% 0.14 300)' },
dark: { primary: 'oklch(78% 0.115 300)', ring: 'oklch(78% 0.115 300)' },
},
{
id: 'sky',
label: '天蓝',
@@ -198,15 +206,15 @@ export const UI_SCALE = { min: 0.9, max: 1.2, step: 0.05 } as const;
export const DEFAULT_APPEARANCE: UiAppearance = {
mode: 'system',
accentMode: 'preset',
accentPreset: 'sky',
accentCustom: '#38bdf8',
accentPreset: 'lavender',
accentCustom: '#8561bd',
accentScope: 'global',
darkIntensity: 'soft',
palette: 'default',
sidebarStyle: 'follow',
background: {
type: 'none',
color: '#0ea5e9',
color: '#b99ae6',
gradient: 'none',
imageOpacity: 0.15,
imageBlur: 0,
@@ -327,7 +335,7 @@ function accentVarsCss(a: UiAppearance): string {
const surf = a.palette === 'default';
if (a.accentMode === 'custom') {
const hex = safeHex(a.accentCustom, '#38bdf8');
const hex = safeHex(a.accentCustom, DEFAULT_APPEARANCE.accentCustom);
const lightFg = readableForeground(hex);
// Dark mode: lighten the accent for contrast on dark surfaces, and pick
// the foreground from the *lightened* colour (a light accent needs dark text).
@@ -590,7 +598,7 @@ export const PALETTE_OPTIONS: PaletteSpec[] = [
{
id: 'default',
label: '默认',
preview: { bg: '#f1f5f9', surface: '#ffffff', dots: ['#0ea5e9', '#10b981', '#f43f5e'] },
preview: { bg: '#f1f5f9', surface: '#ffffff', dots: ['#8561bd', '#10b981', '#f43f5e'] },
},
{
id: 'catppuccin-latte',
+18 -18
View File
@@ -4,7 +4,7 @@
@custom-variant dark (&:where(.dark, .dark *));
/* ──────────────────────────────────────────────
Theme tokens (sky-blue accent)
Theme tokens (mascot lavender accent)
────────────────────────────────────────────── */
:root {
--radius: 0.75rem;
@@ -18,8 +18,8 @@
--popover: oklch(100% 0 0);
--popover-foreground: oklch(20% 0.03 250);
/* sky-500 */
--primary: oklch(68.5% 0.155 230);
/* A deeper lavender keeps light-mode button labels readable. */
--primary: oklch(57% 0.14 300);
--primary-foreground: oklch(99% 0.005 240);
--secondary: oklch(96% 0.012 240);
@@ -28,8 +28,8 @@
--muted: oklch(96% 0.012 240);
--muted-foreground: oklch(50% 0.02 250);
--accent: oklch(94% 0.03 230);
--accent-foreground: oklch(35% 0.07 230);
--accent: oklch(94% 0.03 300);
--accent-foreground: oklch(35% 0.07 300);
--destructive: oklch(60% 0.22 27);
--destructive-foreground: oklch(99% 0.005 240);
@@ -49,16 +49,16 @@
--border: oklch(92% 0.01 240);
--input: oklch(94% 0.012 240);
--ring: oklch(68.5% 0.155 230);
--ring: oklch(57% 0.14 300);
--sidebar: oklch(94% 0.008 240);
--sidebar-foreground: oklch(25% 0.03 250);
--sidebar-primary: oklch(68.5% 0.155 230);
--sidebar-primary: oklch(57% 0.14 300);
--sidebar-primary-foreground: oklch(99% 0.005 240);
--sidebar-accent: oklch(90% 0.025 230);
--sidebar-accent-foreground: oklch(35% 0.08 230);
--sidebar-accent: oklch(90% 0.025 300);
--sidebar-accent-foreground: oklch(35% 0.08 300);
--sidebar-border: oklch(92% 0.01 240);
--sidebar-ring: oklch(68.5% 0.155 230);
--sidebar-ring: oklch(57% 0.14 300);
--media-outline: oklch(0% 0 0 / 0.1);
/* Cards need depth without turning every surface into a hard outlined box.
@@ -79,7 +79,7 @@
--popover: oklch(20% 0.025 250);
--popover-foreground: oklch(96% 0.01 240);
--primary: oklch(75% 0.14 230);
--primary: oklch(78% 0.115 300);
--primary-foreground: oklch(15% 0.02 250);
--secondary: oklch(25% 0.025 250);
@@ -88,8 +88,8 @@
--muted: oklch(24% 0.02 250);
--muted-foreground: oklch(68% 0.02 240);
--accent: oklch(28% 0.05 230);
--accent-foreground: oklch(85% 0.1 230);
--accent: oklch(28% 0.05 300);
--accent-foreground: oklch(85% 0.1 300);
--destructive: oklch(62% 0.2 27);
--destructive-foreground: oklch(98% 0.005 240);
@@ -106,16 +106,16 @@
--border: oklch(28% 0.02 250);
--input: oklch(26% 0.02 250);
--ring: oklch(75% 0.14 230);
--ring: oklch(78% 0.115 300);
--sidebar: oklch(12% 0.018 250);
--sidebar-foreground: oklch(92% 0.01 240);
--sidebar-primary: oklch(75% 0.14 230);
--sidebar-primary: oklch(78% 0.115 300);
--sidebar-primary-foreground: oklch(15% 0.02 250);
--sidebar-accent: oklch(28% 0.05 230);
--sidebar-accent-foreground: oklch(85% 0.1 230);
--sidebar-accent: oklch(28% 0.05 300);
--sidebar-accent-foreground: oklch(85% 0.1 300);
--sidebar-border: oklch(26% 0.02 250);
--sidebar-ring: oklch(75% 0.14 230);
--sidebar-ring: oklch(78% 0.115 300);
--media-outline: oklch(100% 0 0 / 0.12);
--shadow-card: