refactor(web): make identity tints theme-aware and restore colorful default preset

Derive --color-identity-* tokens from theme palette variables (chart and
status hues) instead of fixed oklch hues, so group names, channel ids and
model badges re-tint automatically when a color preset overrides the
palette. The monochrome simple-large preset collapses identity tints to
the foreground via --identity-color-mix: 0%.

Also port main's blue default palette (light/dark), preview the default
preset as a rainbow swatch, and show the token group as a tinted
GroupBadge on the usage-logs page.
This commit is contained in:
t0ng7u
2026-07-12 15:30:25 +08:00
parent fdcd86c851
commit 31b6832314
5 changed files with 93 additions and 104 deletions
+3 -1
View File
@@ -323,10 +323,12 @@ function PresetConfig() {
aria-hidden='true'
className='absolute inset-0 rounded-md'
style={
// The default preset is the full-color theme; two swatch
// stops cannot convey that, so it previews as a rainbow.
preset.value === 'default'
? {
background:
'linear-gradient(135deg, var(--background) 0%, var(--muted) 50%, var(--foreground) 100%)',
'linear-gradient(135deg, oklch(0.68 0.2 25) 0%, oklch(0.8 0.17 85) 25%, oklch(0.72 0.18 155) 50%, oklch(0.66 0.19 245) 75%, oklch(0.68 0.2 315) 100%)',
}
: {
background: `linear-gradient(135deg, ${preset.swatches[0]} 0%, ${preset.swatches[1] ?? preset.swatches[0]} 100%)`,
@@ -21,6 +21,7 @@ import { GitBranch, Sparkles } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { GroupBadge } from '@/components/group-badge'
import { CopyableStatusBadge, StatusBadge } from '@/components/status-badge'
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
import {
@@ -567,31 +568,33 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
let group = log.group
if (!group) group = other?.group || ''
const metaParts: string[] = []
const groupRatioText = getGroupRatioText(
other,
group ? groupRatios[group] : undefined
)
if (group) {
metaParts.push(sensitiveVisible ? group : '••••')
}
if (groupRatioText) metaParts.push(groupRatioText)
const tokenBadgeClassName =
'max-w-full min-w-0 overflow-hidden [&>[data-slot=status-badge-label]]:max-w-full [&>[data-slot=status-badge-label]]:min-w-0 [&>[data-slot=status-badge-label]]:overflow-hidden [&>[data-slot=status-badge-label]]:text-ellipsis'
return (
<div className='flex w-max flex-col gap-0.5'>
<div className='flex max-w-[200px] flex-col gap-0.5'>
<TooltipProvider delay={300}>
<Tooltip>
<TooltipTrigger render={<div className='shrink-0' />}>
<TooltipTrigger render={<div className='max-w-full' />}>
{sensitiveVisible ? (
<CopyableStatusBadge
value={tokenName}
variant='neutral'
size='sm'
className={tokenBadgeClassName}
>
{displayName}
</CopyableStatusBadge>
) : (
<StatusBadge variant='neutral' size='sm'>
<StatusBadge
variant='neutral'
size='sm'
className={tokenBadgeClassName}
>
{displayName}
</StatusBadge>
)}
@@ -603,12 +606,21 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
)}
</Tooltip>
</TooltipProvider>
{metaParts.length > 0 && (
<span
className='text-subtle-foreground max-w-[150px] truncate text-xs'
title={sensitiveVisible ? metaParts.join(' · ') : undefined}
>
{metaParts.join(' · ')}
{(group || groupRatioText) && (
<span className='flex max-w-full min-w-0 items-baseline gap-1 text-xs leading-none'>
{group && (
<GroupBadge
group={group}
label={sensitiveVisible ? undefined : '••••'}
size='sm'
className='min-w-0 truncate'
/>
)}
{groupRatioText && (
<span className='text-subtle-foreground shrink-0 tabular-nums'>
{groupRatioText}
</span>
)}
</span>
)}
</div>
+1 -1
View File
@@ -27,7 +27,7 @@ export const THEME_PRESETS = [
{
value: 'default',
name: 'Default',
swatches: ['oklch(0.13 0 0)', 'oklch(0.95 0 0)'],
swatches: ['oklch(0.692 0.141 243.716)', 'oklch(0.7 0.12 280)'],
},
{
// Inspired by Anthropic's official brand language: warm cream canvas
+6
View File
@@ -342,6 +342,12 @@ For commercial licensing, please contact support@quantumnous.com
--skeleton-base: oklch(0.9 0 0);
--skeleton-highlight: oklch(0.97 0 0);
/* Identity tints (group names, channel ids, model badges…) are decorative,
* so this monochrome preset collapses them to the foreground by mixing 0%
* of the palette hue. Status colors above stay colored on purpose: they
* carry meaning (error/success), which accessibility asks us to keep. */
--identity-color-mix: 0%;
--radius: 0.5rem;
--text-xs: 0.9rem;
--text-sm: 1rem;
+57 -88
View File
@@ -105,94 +105,66 @@ For commercial licensing, please contact support@quantumnous.com
/* Metric identity accent; intentionally distinct from status semantics. */
--color-metric-rpm: color-mix(
in oklch,
oklch(0.68 calc(0.2 * var(--identity-chroma-scale)) 350)
var(--identity-color-mix),
oklch(0.68 0.2 350) var(--identity-color-mix),
var(--foreground)
);
/* Stable entity hues used by deterministic string-to-color identity tints.
* Mixing toward the achromatic foreground dilutes chroma along with
* lightness, so each hue pre-scales its chroma by the per-mode
* `--identity-chroma-scale` (see :root / .dark). */
/* Stable entity tints used by deterministic string-to-color identity
* coloring. Each tint derives from a theme palette token (chart and status
* hues) instead of a fixed hue, so identity colors re-tint automatically
* when a theme preset overrides the palette. Mixing toward the foreground
* (via the per-mode `--identity-color-mix`) keeps compact text legible in
* both light and dark modes. */
--color-identity-amber: color-mix(
in oklch,
oklch(0.72 calc(0.17 * var(--identity-chroma-scale)) 75)
var(--identity-color-mix),
var(--warning) var(--identity-color-mix),
var(--foreground)
);
--color-identity-blue: color-mix(
in oklch,
oklch(0.62 calc(0.18 * var(--identity-chroma-scale)) 250)
var(--identity-color-mix),
var(--chart-1) var(--identity-color-mix),
var(--foreground)
);
--color-identity-cyan: color-mix(
in oklch,
oklch(0.7 calc(0.14 * var(--identity-chroma-scale)) 205)
var(--identity-color-mix),
var(--chart-2) var(--identity-color-mix),
var(--foreground)
);
--color-identity-green: color-mix(
in oklch,
oklch(0.64 calc(0.16 * var(--identity-chroma-scale)) 145)
var(--identity-color-mix),
var(--success) var(--identity-color-mix),
var(--foreground)
);
--color-identity-grey: var(--muted-foreground);
--color-identity-indigo: color-mix(
in oklch,
oklch(0.6 calc(0.2 * var(--identity-chroma-scale)) 275)
var(--identity-color-mix),
var(--foreground)
);
--color-identity-indigo: var(--color-identity-blue);
--color-identity-light-blue: color-mix(
in oklch,
oklch(0.74 calc(0.12 * var(--identity-chroma-scale)) 225)
var(--identity-color-mix),
var(--info) var(--identity-color-mix),
var(--foreground)
);
--color-identity-lime: color-mix(
in oklch,
oklch(0.75 calc(0.17 * var(--identity-chroma-scale)) 125)
var(--identity-color-mix),
var(--chart-3) var(--identity-color-mix),
var(--foreground)
);
--color-identity-orange: color-mix(
--color-identity-orange: var(--color-identity-amber);
--color-identity-pink: color-mix(
in oklch,
oklch(0.7 calc(0.19 * var(--identity-chroma-scale)) 50)
var(--identity-color-mix),
var(--chart-5) var(--identity-color-mix),
var(--foreground)
);
--color-identity-pink: var(--color-metric-rpm);
--color-identity-purple: color-mix(
in oklch,
oklch(0.62 calc(0.2 * var(--identity-chroma-scale)) 305)
var(--identity-color-mix),
var(--chart-4) var(--identity-color-mix),
var(--foreground)
);
--color-identity-red: color-mix(
in oklch,
oklch(0.64 calc(0.21 * var(--identity-chroma-scale)) 25)
var(--identity-color-mix),
var(--foreground)
);
--color-identity-teal: color-mix(
in oklch,
oklch(0.66 calc(0.14 * var(--identity-chroma-scale)) 175)
var(--identity-color-mix),
var(--foreground)
);
--color-identity-violet: color-mix(
in oklch,
oklch(0.65 calc(0.2 * var(--identity-chroma-scale)) 325)
var(--identity-color-mix),
var(--foreground)
);
--color-identity-yellow: color-mix(
in oklch,
oklch(0.78 calc(0.16 * var(--identity-chroma-scale)) 95)
var(--identity-color-mix),
var(--destructive) var(--identity-color-mix),
var(--foreground)
);
--color-identity-teal: var(--color-identity-cyan);
--color-identity-violet: var(--color-identity-purple);
--color-identity-yellow: var(--color-identity-amber);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
@@ -233,19 +205,19 @@ For commercial licensing, please contact support@quantumnous.com
* `[data-theme-font='...']` blocks in theme-presets.css. Default mirrors
* `--font-sans` so behavior is identical when no font preference is set. */
--font-body: var(--font-sans);
/* Light mode uses a bright pure-white canvas (matching the original
* palette) with soft light-grey borders and accents, so the UI reads
* crisp instead of collapsing into muddy greys. */
/* Light mode uses a bright pure-white canvas with the blue brand palette:
* blue primary/ring/charts and primary-tinted accents, over soft
* light-grey borders, so the default theme reads colorful but calm. */
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.13 0 0);
--primary-foreground: oklch(0.985 0 0);
--primary: oklch(0.692 0.141 243.716);
--primary-foreground: oklch(1 0 0);
--secondary: oklch(0.95 0 0);
--secondary-foreground: oklch(0.145 0 0);
--secondary-foreground: oklch(0.42 0.16 250);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.49 0 0);
/* Low-emphasis text tiers below `muted-foreground`, used for secondary
@@ -263,7 +235,7 @@ For commercial licensing, please contact support@quantumnous.com
var(--muted-foreground) 40%,
transparent
);
--accent: oklch(0.95 0 0);
--accent: color-mix(in oklch, var(--primary) 12%, var(--background));
--accent-foreground: oklch(0.145 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.985 0 0);
@@ -277,20 +249,20 @@ For commercial licensing, please contact support@quantumnous.com
--neutral-foreground: oklch(0.145 0 0);
--border: oklch(0.93 0 0);
--input: oklch(0.93 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.975 0 0);
--ring: oklch(0.708 0.16 249.003);
--chart-1: oklch(0.72 0.18 250);
--chart-2: oklch(0.65 0.15 200);
--chart-3: oklch(0.7 0.12 280);
--chart-4: oklch(0.68 0.19 325);
--chart-5: oklch(0.68 0.16 155);
--sidebar: color-mix(in oklch, var(--foreground) 0.5%, var(--background));
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.13 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.92 0 0);
--sidebar-accent-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.64 0.197 253.892);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: color-mix(in oklch, var(--primary) 12%, var(--background));
--sidebar-accent-foreground: oklch(0.579 0.196 255.909);
--sidebar-border: oklch(0.93 0 0);
--sidebar-ring: oklch(0.708 0 0);
--sidebar-ring: oklch(0.58 0.2 250);
--skeleton-base: oklch(0.97 0 0);
--skeleton-highlight: oklch(0.985 0 0);
/* 100% keeps the original vivid semantic hues (matching the old palette
@@ -298,7 +270,6 @@ For commercial licensing, please contact support@quantumnous.com
* of darkening them toward the near-black foreground. */
--status-color-mix: 100%;
--identity-color-mix: 100%;
--identity-chroma-scale: 1;
--identity-surface-mix: 13%;
--identity-border-mix: 28%;
--table-row: var(--card);
@@ -333,13 +304,13 @@ For commercial licensing, please contact support@quantumnous.com
--card-foreground: oklch(0.965 0 0);
--popover: oklch(0.305 0 0);
--popover-foreground: oklch(0.965 0 0);
--primary: oklch(0.965 0 0);
--primary-foreground: oklch(0.155 0 0);
--primary: oklch(0.54 0.142 248.516);
--primary-foreground: oklch(1 0 0);
--secondary: oklch(0.335 0 0);
--secondary-foreground: oklch(0.965 0 0);
--secondary-foreground: oklch(0.9 0.05 250);
--muted: oklch(0.305 0 0);
--muted-foreground: oklch(0.78 0 0);
--accent: oklch(0.365 0 0);
--accent: color-mix(in oklch, var(--primary) 20%, var(--background));
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--destructive-foreground: oklch(0.985 0 0);
@@ -347,32 +318,30 @@ For commercial licensing, please contact support@quantumnous.com
--success-foreground: oklch(0.145 0 0);
--warning: oklch(0.769 0.188 70.08);
--warning-foreground: oklch(0.145 0 0);
--info: oklch(0.68 0.17 237.323);
--info: oklch(0.613 0.14 239.919);
--info-foreground: oklch(0.145 0 0);
--neutral: oklch(0.76 0 0);
--neutral-foreground: oklch(0.155 0 0);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 17%);
--ring: oklch(0.68 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--ring: oklch(0.554 0.148 250.726);
--chart-1: oklch(0.76 0.14 250);
--chart-2: oklch(0.76 0.13 200);
--chart-3: oklch(0.76 0.13 285);
--chart-4: oklch(0.75 0.16 325);
--chart-5: oklch(0.76 0.14 155);
--sidebar: oklch(0.225 0 0);
--sidebar-foreground: oklch(0.95 0 0);
--sidebar-primary: oklch(0.965 0 0);
--sidebar-primary-foreground: oklch(0.155 0 0);
--sidebar-accent: oklch(0.355 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.588 0.123 246.488);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: color-mix(in oklch, var(--primary) 20%, var(--background));
--sidebar-accent-foreground: oklch(0.9 0.05 250);
--sidebar-border: oklch(1 0 0 / 11%);
--sidebar-ring: oklch(0.68 0 0);
--sidebar-ring: oklch(0.7 0.17 250);
--skeleton-base: oklch(0.335 0 0);
--skeleton-highlight: oklch(0.44 0 0);
--status-color-mix: 55%;
--identity-color-mix: 55%;
/* Dark mode keeps the soft macaron pastels unchanged. */
--identity-chroma-scale: 1;
--identity-surface-mix: 10%;
--identity-border-mix: 20%;
--table-row: var(--background);