fix(app): keep brand colour text readable on every surface (#3504)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-16 03:06:24 +02:00
1 parent 3224f2113a
commit 275cc73f7a
102 files changed
+1045 -419

No files matched your search

+94 -206
View File
@@ -2,24 +2,17 @@
import {mkdirSync, writeFileSync} from 'node:fs'; import {mkdirSync, writeFileSync} from 'node:fs';
import {dirname, join, relative} from 'node:path'; import {dirname, join, relative} from 'node:path';
import {
interface ColorFamily { BRAND_PRESSED_RATIO,
hue: number; brandTintMix,
saturation: number; COLOR_FAMILIES,
useSaturationFactor: boolean; COLOR_SCALES,
} type ColorFamily,
type Scale,
interface ScaleStop { scaleStopLightness,
name: string; THEME_PALETTES,
position?: number; } from '@app/features/theme/utils/ColorSystemPalette';
} import {ThemeTypes} from '@fluxer/constants/src/UserConstants';
interface Scale {
family: string;
range: [number, number];
curve: 'linear' | 'easeIn' | 'easeOut' | 'easeInOut';
stops: Array<ScaleStop>;
}
interface TokenDef { interface TokenDef {
name?: string; name?: string;
@@ -295,142 +288,8 @@ const LIGHT_CODE_TOKENS: Array<TokenDef> = [
{name: '--ansi-bg-bright-white', value: 'color-mix(in srgb, hsl(0, 0%, 90%) 90%, var(--code-block-bg) 10%)'}, {name: '--ansi-bg-bright-white', value: 'color-mix(in srgb, hsl(0, 0%, 90%) 90%, var(--code-block-bg) 10%)'},
]; ];
const CONFIG: Config = { const CONFIG: Config = {
families: { families: COLOR_FAMILIES,
neutralDark: {hue: 258, saturation: 10, useSaturationFactor: true}, scales: COLOR_SCALES,
neutralLight: {hue: 220, saturation: 10, useSaturationFactor: true},
brand: {hue: 242, saturation: 70, useSaturationFactor: true},
link: {hue: 198, saturation: 92, useSaturationFactor: true},
accentPurple: {hue: 278, saturation: 85, useSaturationFactor: true},
statusOnline: {hue: 152, saturation: 72, useSaturationFactor: true},
legacyDark: {hue: 220, saturation: 13, useSaturationFactor: true},
legacyLink: {hue: 210, saturation: 100, useSaturationFactor: true},
legacyAccentPurple: {hue: 270, saturation: 80, useSaturationFactor: true},
legacyStatusOnline: {hue: 142, saturation: 76, useSaturationFactor: true},
statusIdle: {hue: 45, saturation: 93, useSaturationFactor: true},
statusDnd: {hue: 0, saturation: 84, useSaturationFactor: true},
statusOffline: {hue: 218, saturation: 11, useSaturationFactor: true},
statusDanger: {hue: 1, saturation: 77, useSaturationFactor: true},
textCode: {hue: 340, saturation: 50, useSaturationFactor: true},
brandIcon: {hue: 38, saturation: 92, useSaturationFactor: true},
},
scales: {
darkSurface: {
family: 'neutralDark',
range: [5, 24],
curve: 'easeOut',
stops: [
{name: '--background-primary', position: 0},
{name: '--background-secondary', position: 0.16},
{name: '--background-secondary-lighter', position: 0.22},
{name: '--background-secondary-alt', position: 0.28},
{name: '--background-tertiary', position: 0.4},
{name: '--background-channel-header', position: 0.34},
{name: '--guild-list-foreground', position: 0.38},
{name: '--background-header-secondary', position: 0.5},
{name: '--background-header-primary', position: 0.5},
{name: '--background-textarea', position: 0.3},
{name: '--background-header-primary-hover', position: 0.85},
],
},
coalSurface: {
family: 'neutralDark',
range: [1, 12],
curve: 'easeOut',
stops: [
{name: '--background-primary', position: 0},
{name: '--background-secondary', position: 0.16},
{name: '--background-secondary-alt', position: 0.28},
{name: '--background-tertiary', position: 0.4},
{name: '--background-channel-header', position: 0.34},
{name: '--guild-list-foreground', position: 0.38},
{name: '--background-header-secondary', position: 0.5},
{name: '--background-header-primary', position: 0.5},
{name: '--background-textarea', position: 0.3},
{name: '--background-header-primary-hover', position: 0.85},
],
},
darkText: {
family: 'neutralDark',
range: [60, 96],
curve: 'easeInOut',
stops: [
{name: '--text-tertiary-secondary', position: 0},
{name: '--text-tertiary-muted', position: 0.2},
{name: '--text-tertiary', position: 0.38},
{name: '--text-primary-muted', position: 0.55},
{name: '--text-chat-muted', position: 0.55},
{name: '--text-secondary', position: 0.72},
{name: '--text-chat', position: 0.82},
{name: '--text-primary', position: 1},
],
},
lightSurface: {
family: 'neutralLight',
range: [86, 98.5],
curve: 'easeIn',
stops: [
{name: '--background-header-primary-hover', position: 0},
{name: '--background-header-primary', position: 0.12},
{name: '--background-header-secondary', position: 0.2},
{name: '--guild-list-foreground', position: 0.35},
{name: '--background-tertiary', position: 0.42},
{name: '--background-channel-header', position: 0.5},
{name: '--background-secondary-alt', position: 0.63},
{name: '--background-secondary', position: 0.74},
{name: '--background-secondary-lighter', position: 0.83},
{name: '--background-textarea', position: 0.88},
{name: '--background-primary', position: 1},
],
},
lightText: {
family: 'neutralLight',
range: [15, 54],
curve: 'easeOut',
stops: [
{name: '--text-primary', position: 0},
{name: '--text-chat', position: 0.08},
{name: '--text-secondary', position: 0.28},
{name: '--text-chat-muted', position: 0.45},
{name: '--text-primary-muted', position: 0.45},
{name: '--text-tertiary', position: 0.6},
{name: '--text-tertiary-secondary', position: 0.75},
{name: '--text-tertiary-muted', position: 0.85},
],
},
legacyDarkSurface: {
family: 'legacyDark',
range: [5, 26],
curve: 'easeOut',
stops: [
{name: '--background-primary', position: 0},
{name: '--background-secondary', position: 0.16},
{name: '--background-secondary-lighter', position: 0.22},
{name: '--background-secondary-alt', position: 0.28},
{name: '--background-tertiary', position: 0.4},
{name: '--background-channel-header', position: 0.34},
{name: '--guild-list-foreground', position: 0.38},
{name: '--background-header-secondary', position: 0.5},
{name: '--background-header-primary', position: 0.5},
{name: '--background-textarea', position: 0.3},
{name: '--background-header-primary-hover', position: 0.85},
],
},
legacyDarkText: {
family: 'legacyDark',
range: [52, 96],
curve: 'easeInOut',
stops: [
{name: '--text-tertiary-secondary', position: 0},
{name: '--text-tertiary-muted', position: 0.2},
{name: '--text-tertiary', position: 0.38},
{name: '--text-primary-muted', position: 0.55},
{name: '--text-chat-muted', position: 0.55},
{name: '--text-secondary', position: 0.72},
{name: '--text-chat', position: 0.82},
{name: '--text-primary', position: 1},
],
},
},
tokens: { tokens: {
root: [ root: [
{scale: 'darkSurface'}, {scale: 'darkSurface'},
@@ -447,7 +306,7 @@ hsl(258, calc(10% * var(--saturation-factor)), 2%) 10%
{name: '--panel-control-divider', family: 'neutralDark', saturation: 30, lightness: 55, alpha: 0.35}, {name: '--panel-control-divider', family: 'neutralDark', saturation: 30, lightness: 55, alpha: 0.35},
{name: '--panel-control-highlight', value: 'hsla(0, 0%, 100%, 0.04)'}, {name: '--panel-control-highlight', value: 'hsla(0, 0%, 100%, 0.04)'},
{name: '--background-modifier-hover', family: 'neutralDark', lightness: 100, alpha: 0.05}, {name: '--background-modifier-hover', family: 'neutralDark', lightness: 100, alpha: 0.05},
{name: '--background-modifier-selected', family: 'neutralDark', lightness: 100, alpha: 0.1}, {name: '--background-modifier-selected', ...THEME_PALETTES[ThemeTypes.DARK].selectedOverlay},
{name: '--background-modifier-accent', family: 'neutralDark', saturation: 13, lightness: 80, alpha: 0.15}, {name: '--background-modifier-accent', family: 'neutralDark', saturation: 13, lightness: 80, alpha: 0.15},
{name: '--background-modifier-accent-focus', family: 'neutralDark', saturation: 13, lightness: 80, alpha: 0.22}, {name: '--background-modifier-accent-focus', family: 'neutralDark', saturation: 13, lightness: 80, alpha: 0.22},
{name: '--control-button-normal-bg', value: 'transparent'}, {name: '--control-button-normal-bg', value: 'transparent'},
@@ -480,6 +339,41 @@ hsl(258, calc(10% * var(--saturation-factor)), 2%) 10%
lightness: 100, lightness: 100,
instanceOverride: '--instance-brand-primary-fill', instanceOverride: '--instance-brand-primary-fill',
}, },
{
name: '--brand-primary-shade',
hue: 0,
saturation: 0,
lightness: 0,
instanceOverride: '--instance-brand-primary-shade',
},
{
name: '--brand-primary-pressed',
value: `color-mix(in srgb, var(--brand-primary) ${BRAND_PRESSED_RATIO}%, var(--brand-primary-shade))`,
},
{
name: '--brand-primary-foreground',
family: 'brand',
lightness: 55,
instanceOverride: '--instance-brand-primary-foreground',
},
{
name: '--brand-primary-light-fill',
hue: 0,
saturation: 0,
lightness: 100,
instanceOverride: '--instance-brand-primary-light-fill',
},
{
name: '--brand-tint',
value: brandTintMix(THEME_PALETTES[ThemeTypes.DARK].brandTint, 'rest'),
instanceOverride: '--instance-brand-tint',
},
{
name: '--brand-tint-hover',
value: brandTintMix(THEME_PALETTES[ThemeTypes.DARK].brandTint, 'hover'),
instanceOverride: '--instance-brand-tint-hover',
},
{name: '--brand-tint-border', value: 'var(--brand-primary)', instanceOverride: '--instance-brand-tint-border'},
{name: '--status-online', family: 'statusOnline', lightness: 40}, {name: '--status-online', family: 'statusOnline', lightness: 40},
{name: '--status-idle', family: 'statusIdle', lightness: 50}, {name: '--status-idle', family: 'statusIdle', lightness: 50},
{name: '--status-dnd', family: 'statusDnd', lightness: 60}, {name: '--status-dnd', family: 'statusDnd', lightness: 60},
@@ -500,6 +394,19 @@ hsl(258, calc(10% * var(--saturation-factor)), 2%) 10%
lightness: 98, lightness: 98,
instanceOverride: '--instance-text-on-brand-primary', instanceOverride: '--instance-text-on-brand-primary',
}, },
{
name: '--text-on-brand-primary-light',
hue: 0,
saturation: 0,
lightness: 98,
instanceOverride: '--instance-text-on-brand-primary-light',
},
{
name: '--text-on-brand-tint',
value: 'var(--text-on-brand-primary)',
instanceOverride: '--instance-text-on-brand-tint',
},
{name: '--text-on-dark', hue: 0, saturation: 0, lightness: 98},
...DARK_CODE_TOKENS, ...DARK_CODE_TOKENS,
{name: '--text-selection', hue: 198, saturation: 92, useSaturationFactor: true, lightness: 70, alpha: 0.35}, {name: '--text-selection', hue: 198, saturation: 92, useSaturationFactor: true, lightness: 70, alpha: 0.35},
{name: '--markup-mention-text', value: 'var(--text-link)'}, {name: '--markup-mention-text', value: 'var(--text-link)'},
@@ -623,7 +530,7 @@ hsl(245, calc(100% * var(--saturation-factor)), 80%) 40%
{name: '--panel-control-divider', family: 'neutralLight', saturation: 30, lightness: 35, alpha: 0.2}, {name: '--panel-control-divider', family: 'neutralLight', saturation: 30, lightness: 35, alpha: 0.2},
{name: '--panel-control-highlight', value: 'hsla(0, 0%, 100%, 0.65)'}, {name: '--panel-control-highlight', value: 'hsla(0, 0%, 100%, 0.65)'},
{name: '--background-modifier-hover', family: 'neutralLight', saturation: 10, lightness: 10, alpha: 0.05}, {name: '--background-modifier-hover', family: 'neutralLight', saturation: 10, lightness: 10, alpha: 0.05},
{name: '--background-modifier-selected', family: 'neutralLight', saturation: 10, lightness: 10, alpha: 0.1}, {name: '--background-modifier-selected', ...THEME_PALETTES[ThemeTypes.LIGHT].selectedOverlay},
{name: '--background-modifier-accent', family: 'neutralLight', saturation: 10, lightness: 40, alpha: 0.22}, {name: '--background-modifier-accent', family: 'neutralLight', saturation: 10, lightness: 40, alpha: 0.22},
{name: '--background-modifier-accent-focus', family: 'neutralLight', saturation: 10, lightness: 40, alpha: 0.32}, {name: '--background-modifier-accent-focus', family: 'neutralLight', saturation: 10, lightness: 40, alpha: 0.32},
{name: '--control-button-normal-bg', value: 'transparent'}, {name: '--control-button-normal-bg', value: 'transparent'},
@@ -667,6 +574,22 @@ hsl(245, calc(100% * var(--saturation-factor)), 80%) 40%
{name: '--plutonium-text', value: 'var(--text-on-brand-primary)'}, {name: '--plutonium-text', value: 'var(--text-on-brand-primary)'},
{name: '--plutonium-icon', family: 'brandIcon', lightness: 45}, {name: '--plutonium-icon', family: 'brandIcon', lightness: 45},
{name: '--invite-verified-icon-color', value: 'var(--brand-primary)'}, {name: '--invite-verified-icon-color', value: 'var(--brand-primary)'},
{
name: '--brand-tint',
value: brandTintMix(THEME_PALETTES[ThemeTypes.LIGHT].brandTint, 'rest'),
instanceOverride: '--instance-brand-tint',
},
{
name: '--brand-tint-hover',
value: brandTintMix(THEME_PALETTES[ThemeTypes.LIGHT].brandTint, 'hover'),
instanceOverride: '--instance-brand-tint-hover',
},
{
name: '--brand-tint-border',
value: 'color-mix(in srgb, var(--brand-primary) 45%, transparent 55%)',
instanceOverride: '--instance-brand-tint-border',
},
{name: '--text-on-brand-tint', value: 'var(--brand-primary)', instanceOverride: '--instance-text-on-brand-tint'},
{name: '--border-color', family: 'neutralLight', lightness: 40, alpha: 0.15}, {name: '--border-color', family: 'neutralLight', lightness: 40, alpha: 0.15},
{name: '--border-color-hover', family: 'neutralLight', lightness: 40, alpha: 0.25}, {name: '--border-color-hover', family: 'neutralLight', lightness: 40, alpha: 0.25},
{name: '--border-color-focus', hue: 210, saturation: 90, useSaturationFactor: true, lightness: 50, alpha: 0.4}, {name: '--border-color-focus', hue: 210, saturation: 90, useSaturationFactor: true, lightness: 50, alpha: 0.4},
@@ -707,8 +630,10 @@ hsl(245, calc(100% * var(--saturation-factor)), 80%) 40%
], ],
coal: [ coal: [
{scale: 'coalSurface'}, {scale: 'coalSurface'},
{name: '--background-secondary', value: 'var(--background-primary)'}, ...Object.entries(THEME_PALETTES[ThemeTypes.COAL].surfaceAliases).map(([name, target]) => ({
{name: '--background-secondary-lighter', value: 'var(--background-primary)'}, name,
value: `var(${target})`,
})),
{ {
name: '--panel-control-bg', name: '--panel-control-bg',
value: `color-mix( value: `color-mix(
@@ -721,7 +646,7 @@ hsl(258, calc(10% * var(--saturation-factor)), 0%) 10%
{name: '--panel-control-divider', family: 'neutralDark', saturation: 20, lightness: 25, alpha: 0.28}, {name: '--panel-control-divider', family: 'neutralDark', saturation: 20, lightness: 25, alpha: 0.28},
{name: '--panel-control-highlight', value: 'hsla(0, 0%, 100%, 0.06)'}, {name: '--panel-control-highlight', value: 'hsla(0, 0%, 100%, 0.06)'},
{name: '--background-modifier-hover', family: 'neutralDark', lightness: 100, alpha: 0.04}, {name: '--background-modifier-hover', family: 'neutralDark', lightness: 100, alpha: 0.04},
{name: '--background-modifier-selected', family: 'neutralDark', lightness: 100, alpha: 0.08}, {name: '--background-modifier-selected', ...THEME_PALETTES[ThemeTypes.COAL].selectedOverlay},
{name: '--background-modifier-accent', family: 'neutralDark', saturation: 10, lightness: 65, alpha: 0.18}, {name: '--background-modifier-accent', family: 'neutralDark', saturation: 10, lightness: 65, alpha: 0.18},
{name: '--background-modifier-accent-focus', family: 'neutralDark', saturation: 10, lightness: 70, alpha: 0.26}, {name: '--background-modifier-accent-focus', family: 'neutralDark', saturation: 10, lightness: 70, alpha: 0.26},
{name: '--control-button-normal-bg', value: 'transparent'}, {name: '--control-button-normal-bg', value: 'transparent'},
@@ -776,7 +701,7 @@ hsl(220, calc(13% * var(--saturation-factor)), 2%) 10%
{name: '--panel-control-border', family: 'legacyDark', saturation: 30, lightness: 65, alpha: 0.45}, {name: '--panel-control-border', family: 'legacyDark', saturation: 30, lightness: 65, alpha: 0.45},
{name: '--panel-control-divider', family: 'legacyDark', saturation: 30, lightness: 55, alpha: 0.35}, {name: '--panel-control-divider', family: 'legacyDark', saturation: 30, lightness: 55, alpha: 0.35},
{name: '--background-modifier-hover', family: 'legacyDark', lightness: 100, alpha: 0.05}, {name: '--background-modifier-hover', family: 'legacyDark', lightness: 100, alpha: 0.05},
{name: '--background-modifier-selected', family: 'legacyDark', lightness: 100, alpha: 0.1}, {name: '--background-modifier-selected', ...THEME_PALETTES[ThemeTypes.DARK_LEGACY].selectedOverlay},
{name: '--background-modifier-accent', family: 'legacyDark', saturation: 13, lightness: 80, alpha: 0.15}, {name: '--background-modifier-accent', family: 'legacyDark', saturation: 13, lightness: 80, alpha: 0.15},
{name: '--background-modifier-accent-focus', family: 'legacyDark', saturation: 13, lightness: 80, alpha: 0.22}, {name: '--background-modifier-accent-focus', family: 'legacyDark', saturation: 13, lightness: 80, alpha: 0.22},
{name: '--control-button-hover-bg', family: 'legacyDark', lightness: 22}, {name: '--control-button-hover-bg', family: 'legacyDark', lightness: 22},
@@ -820,48 +745,13 @@ interface OutputToken {
instanceOverride?: string; instanceOverride?: string;
} }
function clamp01(value: number): number {
return Math.min(1, Math.max(0, value));
}
function applyCurve(curve: Scale['curve'], t: number): number {
switch (curve) {
case 'easeIn':
return t * t;
case 'easeOut':
return 1 - (1 - t) * (1 - t);
case 'easeInOut':
if (t < 0.5) {
return 2 * t * t;
}
return 1 - 2 * (1 - t) * (1 - t);
default:
return t;
}
}
function buildScaleTokens(scale: Scale): Array<OutputToken> { function buildScaleTokens(scale: Scale): Array<OutputToken> {
const lastIndex = Math.max(scale.stops.length - 1, 1); return scale.stops.map((stop, index) => ({
const tokens: Array<OutputToken> = []; type: 'tone',
for (let i = 0; i < scale.stops.length; i++) { name: stop.name,
const stop = scale.stops[i]; family: scale.family,
let pos: number; lightness: scaleStopLightness(scale, index),
if (stop.position !== undefined) { }));
pos = clamp01(stop.position);
} else {
pos = i / lastIndex;
}
const eased = applyCurve(scale.curve, pos);
let lightness = scale.range[0] + (scale.range[1] - scale.range[0]) * eased;
lightness = Math.round(lightness * 1000) / 1000;
tokens.push({
type: 'tone',
name: stop.name,
family: scale.family,
lightness,
});
}
return tokens;
} }
function expandTokens(defs: Array<TokenDef>, scales: Record<string, Scale>): Array<OutputToken> { function expandTokens(defs: Array<TokenDef>, scales: Record<string, Scale>): Array<OutputToken> {
@@ -881,6 +771,7 @@ function expandTokens(defs: Array<TokenDef>, scales: Record<string, Scale>): Arr
type: 'literal', type: 'literal',
name: def.name!, name: def.name!,
value: def.value.trim(), value: def.value.trim(),
instanceOverride: def.instanceOverride,
}); });
} else { } else {
tokens.push({ tokens.push({
@@ -945,11 +836,8 @@ function formatTone(token: OutputToken, families: Record<string, ColorFamily>):
} }
function formatValue(token: OutputToken, families: Record<string, ColorFamily>): string { function formatValue(token: OutputToken, families: Record<string, ColorFamily>): string {
if (token.type === 'tone') { const value = token.type === 'tone' ? formatTone(token, families) : token.value!.trim();
const tone = formatTone(token, families); return token.instanceOverride ? `var(${token.instanceOverride}, ${value})` : value;
return token.instanceOverride ? `var(${token.instanceOverride}, ${tone})` : tone;
}
return token.value!.trim();
} }
function renderBlock(selector: string, tokens: Array<OutputToken>, families: Record<string, ColorFamily>): string { function renderBlock(selector: string, tokens: Array<OutputToken>, families: Record<string, ColorFamily>): string {
+2 -2
View File
@@ -201,7 +201,7 @@ html.platform-native .fluxer-boot-app {
.fluxer-boot-item[data-selected='1'] .fluxer-boot-fluxer-icon { .fluxer-boot-item[data-selected='1'] .fluxer-boot-fluxer-icon {
border-radius: 30%; border-radius: 30%;
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
.fluxer-boot-action-icon { .fluxer-boot-action-icon {
display: flex; display: flex;
@@ -218,7 +218,7 @@ html.platform-native .fluxer-boot-app {
.fluxer-boot-item[data-selected='1'] .fluxer-boot-action-icon { .fluxer-boot-item[data-selected='1'] .fluxer-boot-action-icon {
border-radius: 30%; border-radius: 30%;
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
.fluxer-boot-glyph-lg { .fluxer-boot-glyph-lg {
height: 1.75rem; height: 1.75rem;
@@ -137,5 +137,5 @@
height: 1.25rem; height: 1.25rem;
border-radius: 50%; border-radius: 50%;
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
@@ -298,7 +298,7 @@
} }
.channelItemAutocompleteHighlight { .channelItemAutocompleteHighlight {
box-shadow: 0 0 0 0.125rem var(--brand-primary); box-shadow: 0 0 0 0.125rem var(--brand-primary-foreground);
} }
.categoryContent { .categoryContent {
@@ -27,5 +27,5 @@
} }
:global(.theme-light) .dropIndicatorValid { :global(.theme-light) .dropIndicatorValid {
background-color: var(--brand-primary); background-color: var(--brand-primary-foreground);
} }
@@ -13,7 +13,7 @@
} }
:global(.theme-light) .channelBadgeSelectedIcon { :global(.theme-light) .channelBadgeSelectedIcon {
color: #fff; color: var(--brand-primary-fill);
} }
.favoriteItemContainer { .favoriteItemContainer {
@@ -85,7 +85,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 624.9375rem; border-radius: 624.9375rem;
background-color: white; background-color: var(--brand-primary-fill);
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
color: var(--brand-primary); color: var(--brand-primary);
@@ -219,7 +219,7 @@
.guildIconSelected.guildIconNoImage { .guildIconSelected.guildIconNoImage {
border-radius: 30%; border-radius: 30%;
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
@media (hover: hover) and (pointer: fine) { @media (hover: hover) and (pointer: fine) {
@@ -231,7 +231,7 @@
.guildListItem.contextMenuHover .guildIconNoImage { .guildListItem.contextMenuHover .guildIconNoImage {
border-radius: 30%; border-radius: 30%;
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
} }
@@ -363,7 +363,7 @@
.fluxerButtonIconSelected { .fluxerButtonIconSelected {
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
@media (hover: hover) and (pointer: fine) { @media (hover: hover) and (pointer: fine) {
@@ -374,7 +374,7 @@
.fluxerButtonIcon:hover, .fluxerButtonIcon:hover,
.fluxerButton.contextMenuHover .fluxerButtonIcon { .fluxerButton.contextMenuHover .fluxerButtonIcon {
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
} }
@@ -782,10 +782,10 @@
:global(.theme-light) .dropIndicatorTop::before, :global(.theme-light) .dropIndicatorTop::before,
:global(.theme-light) .dropIndicatorBottom::after, :global(.theme-light) .dropIndicatorBottom::after,
:global(.theme-light) .guildListDropZoneActive::after { :global(.theme-light) .guildListDropZoneActive::after {
background: var(--brand-primary); background: var(--brand-primary-foreground);
} }
:global(.theme-light) .combinePreviewGrid { :global(.theme-light) .combinePreviewGrid {
background-color: color-mix(in srgb, var(--background-secondary) 92%, var(--brand-primary) 8%); background-color: color-mix(in srgb, var(--background-secondary) 92%, var(--brand-primary) 8%);
outline-color: var(--brand-primary); outline-color: var(--brand-primary-foreground);
} }
@@ -37,7 +37,7 @@ export const NAGBAR_TONES: Readonly<Record<NagbarToneKind, NagbarTone>> = Object
}), }),
[NagbarToneKind.DANGER]: Object.freeze({ [NagbarToneKind.DANGER]: Object.freeze({
backgroundColor: 'var(--status-danger)', backgroundColor: 'var(--status-danger)',
textColor: 'var(--text-on-brand-primary)', textColor: 'var(--text-on-dark)',
}), }),
[NagbarToneKind.ALERT]: Object.freeze({backgroundColor: '#ea580c', textColor: '#ffffff'}), [NagbarToneKind.ALERT]: Object.freeze({backgroundColor: '#ea580c', textColor: '#ffffff'}),
[NagbarToneKind.PREMIUM]: Object.freeze({backgroundColor: '#f97316', textColor: '#ffffff'}), [NagbarToneKind.PREMIUM]: Object.freeze({backgroundColor: '#f97316', textColor: '#ffffff'}),
@@ -22,7 +22,7 @@
height: 0.125rem; height: 0.125rem;
width: 100%; width: 100%;
border-radius: 624.9375rem; border-radius: 624.9375rem;
background-color: var(--brand-primary); background-color: var(--brand-primary-foreground);
transition: transition:
transform 150ms, transform 150ms,
opacity 150ms; opacity 150ms;
@@ -19,5 +19,5 @@
.iconTextSelected { .iconTextSelected {
height: 1.25rem; height: 1.25rem;
width: 1.25rem; width: 1.25rem;
color: white; color: var(--brand-primary-fill);
} }
@@ -66,7 +66,9 @@ export const DiscoveryButton = observer(() => {
animate={{borderRadius: isHovering || isSelected ? '30%' : '50%'}} animate={{borderRadius: isHovering || isSelected ? '30%' : '50%'}}
initial={{borderRadius: isHovering || isSelected ? '30%' : '50%'}} initial={{borderRadius: isHovering || isSelected ? '30%' : '50%'}}
transition={{duration: Accessibility.useReducedMotion ? 0 : 0.07, ease: 'easeOut'}} transition={{duration: Accessibility.useReducedMotion ? 0 : 0.07, ease: 'easeOut'}}
style={isSelected ? {backgroundColor: 'var(--brand-primary)', color: 'white'} : undefined} style={
isSelected ? {backgroundColor: 'var(--brand-primary)', color: 'var(--brand-primary-fill)'} : undefined
}
data-flx="app.sidebar-nav.discovery-button.div--2" data-flx="app.sidebar-nav.discovery-button.div--2"
> >
<CompassIcon <CompassIcon
@@ -269,10 +269,10 @@
:global(.theme-light) .dropIndicatorTop::before, :global(.theme-light) .dropIndicatorTop::before,
:global(.theme-light) .dropIndicatorBottom::after { :global(.theme-light) .dropIndicatorBottom::after {
background: var(--brand-primary); background: var(--brand-primary-foreground);
} }
:global(.theme-light) .dropIndicatorInside .collapsedFolder, :global(.theme-light) .dropIndicatorInside .collapsedFolder,
:global(.theme-light) .dropIndicatorInside .folderHeaderButton { :global(.theme-light) .dropIndicatorInside .folderHeaderButton {
outline-color: var(--brand-primary); outline-color: var(--brand-primary-foreground);
} }
@@ -15,7 +15,7 @@
width: 6rem; width: 6rem;
height: 6rem; height: 6rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.content { .content {
@@ -157,12 +157,12 @@
.editIcon { .editIcon {
height: 1.25rem; height: 1.25rem;
width: 1.25rem; width: 1.25rem;
color: white; color: var(--brand-primary-fill);
} }
.editLabel { .editLabel {
font-weight: 500; font-weight: 500;
color: white; color: var(--brand-primary-fill);
} }
.section { .section {
@@ -32,7 +32,7 @@
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
background-color: var(--brand-primary); background-color: var(--brand-primary);
border-radius: 624.9375rem; border-radius: 624.9375rem;
color: white; color: var(--brand-primary-fill);
font-size: 0.875rem; font-size: 0.875rem;
line-height: 1; line-height: 1;
} }
@@ -205,7 +205,7 @@
.item[data-selected='true'] .fluxerIcon { .item[data-selected='true'] .fluxerIcon {
border-radius: 30%; border-radius: 30%;
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
.fluxerIconGlyph { .fluxerIconGlyph {
@@ -248,7 +248,7 @@
.item[data-selected='true'] .actionIcon { .item[data-selected='true'] .actionIcon {
border-radius: 30%; border-radius: 30%;
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
.actionIconGlyph { .actionIconGlyph {
@@ -6,7 +6,7 @@
--account-profile-slot-width: 14.875rem; --account-profile-slot-width: 14.875rem;
--account-profile-instance-badge-size: 3rem; --account-profile-instance-badge-size: 3rem;
--account-profile-state-badge-size: 2.25rem; --account-profile-state-badge-size: 2.25rem;
--account-profile-name-color: var(--text-on-brand-primary); --account-profile-name-color: var(--text-on-dark);
--account-profile-domain-height: 0; --account-profile-domain-height: 0;
--account-profile-domain-gap: 0; --account-profile-domain-gap: 0;
--account-profile-name-gap: 0.625rem; --account-profile-name-gap: 0.625rem;
@@ -100,7 +100,7 @@
.noAccounts { .noAccounts {
margin-bottom: 1rem; margin-bottom: 1rem;
color: var(--text-on-brand-primary); color: var(--text-on-dark);
font-size: 0.9375rem; font-size: 0.9375rem;
font-weight: 600; font-weight: 600;
line-height: 1.25rem; line-height: 1.25rem;
@@ -49,7 +49,7 @@
.checkIcon { .checkIcon {
height: 0.625rem; height: 0.625rem;
width: 0.625rem; width: 0.625rem;
color: white; color: var(--brand-primary-fill);
} }
.code { .code {
@@ -157,7 +157,7 @@ img.entityIcon {
.themeIcon { .themeIcon {
height: 2.5rem; height: 2.5rem;
width: 2.5rem; width: 2.5rem;
color: white; color: var(--brand-primary-fill);
} }
.form { .form {
@@ -98,7 +98,7 @@
width: 1.5rem; width: 1.5rem;
height: 1.5rem; height: 1.5rem;
border: 0.1875rem solid var(--background-modifier-accent); border: 0.1875rem solid var(--background-modifier-accent);
border-top-color: var(--brand-primary); border-top-color: var(--brand-primary-foreground);
border-radius: 50%; border-radius: 50%;
animation: spin 1s linear infinite; animation: spin 1s linear infinite;
} }
@@ -53,5 +53,5 @@
.audioIcon { .audioIcon {
height: 1rem; height: 1rem;
width: 1rem; width: 1rem;
color: white; color: var(--brand-primary-fill);
} }
@@ -58,11 +58,11 @@
} }
:global(.theme-light) .gifButton:hover { :global(.theme-light) .gifButton:hover {
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
} }
:global(.theme-light) .gifButtonSelected { :global(.theme-light) .gifButtonSelected {
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
} }
.gifVideoWrapper { .gifVideoWrapper {
@@ -317,5 +317,5 @@
:global(.theme-light) .dropIndicatorLeft::before, :global(.theme-light) .dropIndicatorLeft::before,
:global(.theme-light) .dropIndicatorRight::after { :global(.theme-light) .dropIndicatorRight::after {
background: var(--brand-primary); background: var(--brand-primary-foreground);
} }
@@ -155,7 +155,7 @@
background: none; background: none;
border: none; border: none;
padding: 0 0.75rem; padding: 0 0.75rem;
color: white; color: var(--brand-primary-fill);
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 500; font-weight: 500;
overflow: hidden; overflow: hidden;
@@ -172,7 +172,7 @@
background: none; background: none;
border: none; border: none;
padding: 0 0.75rem; padding: 0 0.75rem;
color: white; color: var(--brand-primary-fill);
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 600; font-weight: 600;
flex-shrink: 0; flex-shrink: 0;
@@ -270,6 +270,16 @@
text-shadow: 0 0.0625rem 0.0625rem rgba(0, 0, 0, 0.06); text-shadow: 0 0.0625rem 0.0625rem rgba(0, 0, 0, 0.06);
} }
.gridItemCategoryEmpty .gridItemIcon,
.gridItemCategoryEmpty .gridItemCategoryTitleText {
color: var(--text-on-brand-primary-light);
}
.gridItemFavorites .gridItemIcon,
.gridItemFavorites .gridItemCategoryTitleText {
color: var(--text-on-brand-primary);
}
.gif { .gif {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -445,6 +455,8 @@
color: var(--text-primary); color: var(--text-primary);
} }
:global(.theme-light) .gridItemCategoryEmpty .gridItemCategoryTitleText,
:global(.theme-light) .gridItemCategoryEmpty .gridItemIcon,
:global(.theme-light) .gridItemFavorites .gridItemCategoryTitleText, :global(.theme-light) .gridItemFavorites .gridItemCategoryTitleText,
:global(.theme-light) .gridItemFavorites .gridItemIcon { :global(.theme-light) .gridItemFavorites .gridItemIcon {
color: var(--text-on-brand-primary); color: var(--text-on-brand-primary);
@@ -459,8 +471,8 @@
} }
:global(.theme-light) .gridItemFocused { :global(.theme-light) .gridItemFocused {
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
box-shadow: inset 0 0 0 0.125rem var(--brand-primary); box-shadow: inset 0 0 0 0.125rem var(--brand-primary-foreground);
} }
:global(.theme-light) .gridItemFavorites .gridItemBackdrop { :global(.theme-light) .gridItemFavorites .gridItemBackdrop {
@@ -195,7 +195,7 @@
} }
:global(.theme-light) .progressBar { :global(.theme-light) .progressBar {
background-color: var(--brand-primary); background-color: var(--brand-primary-foreground);
} }
.thContent { .thContent {
@@ -138,7 +138,7 @@
.audioIcon { .audioIcon {
height: 3rem; height: 3rem;
width: 3rem; width: 3rem;
color: white; color: var(--brand-primary-fill);
flex-shrink: 0; flex-shrink: 0;
} }
@@ -153,7 +153,7 @@
.audioDuration { .audioDuration {
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 1.125rem; font-size: 1.125rem;
color: white; color: var(--brand-primary-fill);
} }
.audioFilename { .audioFilename {
@@ -164,14 +164,14 @@
text-align: center; text-align: center;
font-weight: 600; font-weight: 600;
font-size: 0.875rem; font-size: 0.875rem;
color: rgba(255, 255, 255, 0.9); color: color-mix(in srgb, var(--brand-primary-fill) 90%, transparent);
} }
.audioBadge { .audioBadge {
padding: 0.125rem 0.5rem; padding: 0.125rem 0.5rem;
border-radius: 0.375rem; border-radius: 0.375rem;
background-color: rgba(0, 0, 0, 0.2); background-color: color-mix(in srgb, var(--brand-primary-shade) 20%, transparent);
font-weight: 700; font-weight: 700;
font-size: 0.75rem; font-size: 0.75rem;
color: white; color: var(--brand-primary-fill);
} }
@@ -36,16 +36,16 @@
} }
.reactionMe .reactionButton { .reactionMe .reactionButton {
background-color: color-mix(in srgb, var(--brand-primary) 36%, var(--background-secondary) 64%); background-color: var(--brand-tint);
border-color: var(--brand-primary); border-color: var(--brand-tint-border);
color: var(--text-on-brand-primary); color: var(--text-on-brand-tint);
} }
:global(:where(html.window-focused, html.unfocused-fully-interactive):not(.window-focus-activation-guard)) :global(:where(html.window-focused, html.unfocused-fully-interactive):not(.window-focus-activation-guard))
.reactionMe .reactionMe
.reactionButton:hover { .reactionButton:hover {
background-color: color-mix(in srgb, var(--brand-primary) 48%, var(--background-secondary) 52%); background-color: var(--brand-tint-hover);
border-color: var(--brand-primary); border-color: var(--brand-tint-border);
} }
.reactionInner { .reactionInner {
@@ -80,7 +80,7 @@
} }
.reactionMe .countWrapper { .reactionMe .countWrapper {
color: var(--text-on-brand-primary); color: var(--text-on-brand-tint);
} }
:global(.theme-light) .reactionButton { :global(.theme-light) .reactionButton {
@@ -96,20 +96,20 @@
} }
:global(.theme-light) .reactionMe .reactionButton { :global(.theme-light) .reactionMe .reactionButton {
background-color: color-mix(in srgb, var(--brand-primary) 12%, var(--background-primary) 88%); background-color: var(--brand-tint);
border-color: color-mix(in srgb, var(--brand-primary) 45%, transparent 55%); border-color: var(--brand-tint-border);
color: var(--brand-primary); color: var(--text-on-brand-tint);
} }
:global(.theme-light:where(.window-focused, .unfocused-fully-interactive):not(.window-focus-activation-guard)) :global(.theme-light:where(.window-focused, .unfocused-fully-interactive):not(.window-focus-activation-guard))
.reactionMe .reactionMe
.reactionButton:hover { .reactionButton:hover {
background-color: color-mix(in srgb, var(--brand-primary) 18%, var(--background-primary) 82%); background-color: var(--brand-tint-hover);
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
} }
:global(.theme-light) .reactionMe .countWrapper { :global(.theme-light) .reactionMe .countWrapper {
color: var(--brand-primary); color: var(--text-on-brand-tint);
} }
.reactionContainerDisabled { .reactionContainerDisabled {
@@ -127,8 +127,8 @@
.reactionMe .reactionButtonDisabled, .reactionMe .reactionButtonDisabled,
.reactionMe .reactionButtonDisabled:hover { .reactionMe .reactionButtonDisabled:hover {
background-color: color-mix(in srgb, var(--brand-primary) 36%, var(--background-secondary) 64%); background-color: var(--brand-tint);
border-color: var(--brand-primary); border-color: var(--brand-tint-border);
} }
:global(.theme-light) .reactionButtonDisabled, :global(.theme-light) .reactionButtonDisabled,
@@ -139,8 +139,8 @@
:global(.theme-light) .reactionMe .reactionButtonDisabled, :global(.theme-light) .reactionMe .reactionButtonDisabled,
:global(.theme-light) .reactionMe .reactionButtonDisabled:hover { :global(.theme-light) .reactionMe .reactionButtonDisabled:hover {
background-color: color-mix(in srgb, var(--brand-primary) 12%, var(--background-primary) 88%); background-color: var(--brand-tint);
border-color: color-mix(in srgb, var(--brand-primary) 45%, transparent 55%); border-color: var(--brand-tint-border);
} }
.reactionPickerButton { .reactionPickerButton {
@@ -110,5 +110,5 @@
:global(.theme-light) .progressBarIndeterminate, :global(.theme-light) .progressBarIndeterminate,
:global(.theme-light) .progressBarNormal { :global(.theme-light) .progressBarNormal {
background-color: var(--brand-primary); background-color: var(--brand-primary-foreground);
} }
@@ -170,7 +170,7 @@
} }
:global(.theme-light) .tabButtonActive { :global(.theme-light) .tabButtonActive {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.tabButtonInactive { .tabButtonInactive {
@@ -367,7 +367,7 @@
} }
.newGroupIconWhite { .newGroupIconWhite {
color: white; color: var(--brand-primary-fill);
} }
.newGroupContent { .newGroupContent {
@@ -555,12 +555,13 @@
.quickActionButtonActive { .quickActionButtonActive {
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
@media (hover: hover) and (pointer: fine) { @media (hover: hover) and (pointer: fine) {
.quickActionButtonActive:hover { .quickActionButtonActive:hover {
background-color: var(--brand-primary-light); background-color: var(--brand-primary-light);
color: var(--brand-primary-light-fill);
} }
} }
@@ -35,7 +35,7 @@
} }
:global(.theme-light) .heroIcon { :global(.theme-light) .heroIcon {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.title { .title {
@@ -90,7 +90,7 @@
:global(.theme-light) .dmListHeaderShortcut kbd { :global(.theme-light) .dmListHeaderShortcut kbd {
background-color: hsl(220, calc(13% * var(--saturation-factor)), 18.44%); background-color: hsl(220, calc(13% * var(--saturation-factor)), 18.44%);
color: var(--text-on-brand-primary); color: var(--text-on-dark);
box-shadow: 0 0.0625rem 0 rgba(0, 0, 0, 0.4); box-shadow: 0 0.0625rem 0 rgba(0, 0, 0, 0.4);
} }
@@ -280,7 +280,7 @@
.clickableItemSelected .clickableItemIcon { .clickableItemSelected .clickableItemIcon {
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
@media (hover: hover) and (pointer: fine) { @media (hover: hover) and (pointer: fine) {
@@ -313,7 +313,7 @@
.sendIcon { .sendIcon {
height: 1.5rem; height: 1.5rem;
width: 1.5rem; width: 1.5rem;
color: white; color: var(--brand-primary-fill);
} }
.clickableItemText { .clickableItemText {
@@ -725,7 +725,7 @@
.mobileSpecialButtonSelected .mobileSpecialButtonIcon { .mobileSpecialButtonSelected .mobileSpecialButtonIcon {
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
.mobileSpecialButtonText { .mobileSpecialButtonText {
@@ -403,13 +403,17 @@
.languageItemSelected { .languageItemSelected {
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: #fff; color: var(--brand-primary-fill);
} }
.languageItemSelected.languageItemHighlighted { .languageItemSelected.languageItemHighlighted {
background-color: var(--brand-primary); background-color: var(--brand-primary);
} }
.languageItemSelected .languageOptionCanonical {
color: inherit;
}
.languageItemIndicator { .languageItemIndicator {
position: absolute; position: absolute;
top: 50%; top: 50%;
@@ -322,7 +322,7 @@ export const collectGalleryImages = ({
}; };
export const getBorderColor = (color: number | undefined) => { export const getBorderColor = (color: number | undefined) => {
if (color === undefined || color === 0) { if (color === undefined || color === 0) {
return 'var(--brand-primary)'; return 'var(--brand-primary-foreground)';
} }
return ColorUtils.int2rgb(color); return ColorUtils.int2rgb(color);
}; };
@@ -149,7 +149,7 @@
border: none; border: none;
border-radius: var(--radius-full); border-radius: var(--radius-full);
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6);
color: var(--text-on-brand-primary); color: var(--text-on-dark);
cursor: pointer; cursor: pointer;
z-index: 2; z-index: 2;
transition: background-color 150ms ease; transition: background-color 150ms ease;
@@ -14,5 +14,5 @@
font-weight: 600; font-weight: 600;
font-size: 0.875rem; font-size: 0.875rem;
line-height: 1; line-height: 1;
color: var(--text-on-brand-primary); color: var(--text-on-dark);
} }
@@ -10,7 +10,7 @@
--action-toolbar-item-size: 2rem; --action-toolbar-item-size: 2rem;
--action-toolbar-item-border-width: 0px; --action-toolbar-item-border-width: 0px;
--action-toolbar-item-background: rgba(0, 0, 0, 0.75); --action-toolbar-item-background: rgba(0, 0, 0, 0.75);
--action-toolbar-item-color: var(--text-on-brand-primary); --action-toolbar-item-color: var(--text-on-dark);
} }
.overlayButton svg { .overlayButton svg {
@@ -25,5 +25,5 @@
:global(:where(html.window-focused, html.unfocused-fully-interactive):not(.window-focus-activation-guard)) :global(:where(html.window-focused, html.unfocused-fully-interactive):not(.window-focus-activation-guard))
.overlayButton.overlayButtonHover:hover .overlayButton.overlayButtonHover:hover
svg { svg {
color: color-mix(in srgb, var(--text-on-brand-primary) 95%, transparent); color: color-mix(in srgb, var(--text-on-dark) 95%, transparent);
} }
@@ -46,11 +46,15 @@
inset: -0.1875rem; inset: -0.1875rem;
border-radius: var(--radius-full); border-radius: var(--radius-full);
border: 0.125rem solid transparent; border: 0.125rem solid transparent;
border-top-color: var(--text-on-brand-primary); border-top-color: var(--brand-primary-foreground);
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
pointer-events: none; pointer-events: none;
} }
.containerActive .loadingSpinner {
border-top-color: var(--text-on-brand-primary);
}
@keyframes spin { @keyframes spin {
from { from {
transform: rotate(0deg); transform: rotate(0deg);
@@ -84,7 +88,7 @@
} }
:global(.theme-light) .waveformBarPast { :global(.theme-light) .waveformBarPast {
background-color: var(--brand-primary); background-color: var(--brand-primary-foreground);
} }
.timestamp { .timestamp {
@@ -211,13 +211,13 @@
.actionButtonAccept { .actionButtonAccept {
--action-toolbar-item-background: var(--friend-action-brand-background); --action-toolbar-item-background: var(--friend-action-brand-background);
--action-toolbar-item-color: color-mix(in srgb, var(--brand-primary) 76%, var(--text-primary) 24%); --action-toolbar-item-color: color-mix(in srgb, var(--brand-primary-foreground) 76%, var(--text-primary) 24%);
} }
:global(:where(html.window-focused, html.unfocused-fully-interactive):not(.window-focus-activation-guard)) :global(:where(html.window-focused, html.unfocused-fully-interactive):not(.window-focus-activation-guard))
.actionButtonAccept:hover { .actionButtonAccept:hover {
--action-toolbar-item-background: var(--friend-action-brand-hover-background); --action-toolbar-item-background: var(--friend-action-brand-hover-background);
--action-toolbar-item-color: color-mix(in srgb, var(--brand-primary) 82%, var(--text-primary) 18%); --action-toolbar-item-color: color-mix(in srgb, var(--brand-primary-foreground) 82%, var(--text-primary) 18%);
} }
.actionButtonIgnore { .actionButtonIgnore {
@@ -41,5 +41,5 @@
} }
:global(.theme-light) .progressBar { :global(.theme-light) .progressBar {
background-color: var(--brand-primary); background-color: var(--brand-primary-foreground);
} }
@@ -114,7 +114,7 @@
.actionButton:hover { .actionButton:hover {
border-color: var(--brand-primary); border-color: var(--brand-primary);
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
.deleteButton:hover { .deleteButton:hover {
@@ -61,10 +61,10 @@
} }
:global(.theme-light) .card:hover { :global(.theme-light) .card:hover {
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
} }
:global(.theme-light) .cardIcon { :global(.theme-light) .cardIcon {
background: color-mix(in srgb, var(--brand-primary) 12%, transparent); background: color-mix(in srgb, var(--brand-primary) 12%, transparent);
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
@@ -36,7 +36,7 @@
padding: 0.375rem 0.75rem; padding: 0.375rem 0.75rem;
border-radius: 624.9375rem; border-radius: 624.9375rem;
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
font-size: 0.875rem; font-size: 0.875rem;
} }
@@ -570,14 +570,14 @@
} }
:global(.theme-light) .cardUnreadLabel { :global(.theme-light) .cardUnreadLabel {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.newBadge { .newBadge {
padding: 0 0.375rem; padding: 0 0.375rem;
border-radius: 0.25rem; border-radius: 0.25rem;
background-color: var(--status-danger); background-color: var(--status-danger);
color: var(--text-on-brand-primary); color: var(--text-on-dark);
font-size: 0.6875rem; font-size: 0.6875rem;
font-weight: 700; font-weight: 700;
line-height: 1.125rem; line-height: 1.125rem;
@@ -625,7 +625,7 @@
padding: 0.5rem; padding: 0.5rem;
border-radius: 50%; border-radius: 50%;
background-color: rgb(0 0 0 / 0.6); background-color: rgb(0 0 0 / 0.6);
color: var(--text-on-brand-primary); color: var(--text-on-dark);
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
@@ -664,14 +664,14 @@
} }
.reactionActive { .reactionActive {
background-color: color-mix(in srgb, var(--brand-primary) 36%, var(--background-secondary) 64%); background-color: var(--brand-tint);
border-color: var(--brand-primary); border-color: var(--brand-tint-border);
color: var(--text-on-brand-primary); color: var(--text-on-brand-tint);
} }
.reactionActive:hover:not(:disabled) { .reactionActive:hover:not(:disabled) {
background-color: color-mix(in srgb, var(--brand-primary) 48%, var(--background-secondary) 52%); background-color: var(--brand-tint-hover);
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
} }
:global(.theme-light) .reaction { :global(.theme-light) .reaction {
@@ -681,9 +681,9 @@
} }
:global(.theme-light) .reactionActive { :global(.theme-light) .reactionActive {
background-color: color-mix(in srgb, var(--brand-primary) 12%, var(--background-primary) 88%); background-color: var(--brand-tint);
border-color: color-mix(in srgb, var(--brand-primary) 45%, transparent 55%); border-color: var(--brand-tint-border);
color: var(--brand-primary); color: var(--text-on-brand-tint);
} }
.notice { .notice {
@@ -7,31 +7,31 @@
.badge { .badge {
flex-shrink: 0; flex-shrink: 0;
--guild-badge-seal: white; --guild-badge-seal: var(--brand-primary-fill);
--guild-badge-icon: var(--brand-primary); --guild-badge-icon: var(--brand-primary);
} }
.badgeOnLightSurface { .badgeOnLightSurface {
flex-shrink: 0; flex-shrink: 0;
--guild-badge-seal: white; --guild-badge-seal: var(--brand-primary-fill);
--guild-badge-icon: var(--brand-primary); --guild-badge-icon: var(--brand-primary);
} }
:global(.theme-light) .badgeOnLightSurface { :global(.theme-light) .badgeOnLightSurface {
--guild-badge-seal: var(--brand-primary); --guild-badge-seal: var(--brand-primary);
--guild-badge-icon: white; --guild-badge-icon: var(--brand-primary-fill);
} }
.badgeBanner { .badgeBanner {
flex-shrink: 0; flex-shrink: 0;
filter: drop-shadow(0 0.0625rem 0.1875rem rgba(0, 0, 0, 0.9)); filter: drop-shadow(0 0.0625rem 0.1875rem rgba(0, 0, 0, 0.9));
--guild-badge-seal: white; --guild-badge-seal: var(--brand-primary-fill);
--guild-badge-icon: var(--brand-primary); --guild-badge-icon: var(--brand-primary);
} }
.badgeBannerDark { .badgeBannerDark {
flex-shrink: 0; flex-shrink: 0;
filter: drop-shadow(0 0.0625rem 0.1875rem rgba(0, 0, 0, 0.9)); filter: drop-shadow(0 0.0625rem 0.1875rem rgba(0, 0, 0, 0.9));
--guild-badge-seal: white; --guild-badge-seal: var(--brand-primary-fill);
--guild-badge-icon: var(--brand-primary); --guild-badge-icon: var(--brand-primary);
} }
@@ -204,6 +204,10 @@
color: var(--brand-primary-fill); color: var(--brand-primary-fill);
} }
.overwriteItemSelected .lockIcon {
color: inherit;
}
.roleDot { .roleDot {
width: 0.75rem; width: 0.75rem;
height: 0.75rem; height: 0.75rem;
@@ -144,7 +144,7 @@
position: absolute; position: absolute;
inset: 0; inset: 0;
margin: auto; margin: auto;
color: var(--text-on-brand-primary); color: var(--text-on-dark);
} }
.overflowCount { .overflowCount {
@@ -109,10 +109,14 @@
} }
.statusIndicatorActive .statusIcon { .statusIndicatorActive .statusIcon {
color: var(--brand-primary-fill); color: var(--brand-primary-light-fill);
} }
:global(.theme-light) .statusIndicatorActive { :global(.theme-light) .statusIndicatorActive {
border-color: var(--brand-primary); border-color: var(--brand-primary);
background-color: var(--brand-primary); background-color: var(--brand-primary);
} }
:global(.theme-light) .statusIndicatorActive .statusIcon {
color: var(--brand-primary-fill);
}
@@ -95,7 +95,7 @@
.breadcrumbActive .breadcrumbNumber { .breadcrumbActive .breadcrumbNumber {
background: var(--brand-primary); background: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
.breadcrumbLabel { .breadcrumbLabel {
@@ -21,7 +21,7 @@
} }
:global(.theme-light) .tile { :global(.theme-light) .tile {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.title { .title {
@@ -143,7 +143,7 @@
} }
:global(.theme-light) .checkIcon { :global(.theme-light) .checkIcon {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.emptyState { .emptyState {
@@ -50,7 +50,7 @@
} }
:global(.theme-light) .optionSelected { :global(.theme-light) .optionSelected {
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
background-color: color-mix(in srgb, var(--brand-primary) 10%, transparent); background-color: color-mix(in srgb, var(--brand-primary) 10%, transparent);
} }
@@ -71,7 +71,7 @@
} }
:global(.theme-light) .optionIconSelected { :global(.theme-light) .optionIconSelected {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.optionText { .optionText {
@@ -103,7 +103,7 @@
} }
:global(.theme-light) .checkIcon { :global(.theme-light) .checkIcon {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.footer { .footer {
@@ -43,7 +43,7 @@
} }
:global(.theme-light) .optionSelected { :global(.theme-light) .optionSelected {
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
background-color: color-mix(in srgb, var(--brand-primary) 10%, transparent); background-color: color-mix(in srgb, var(--brand-primary) 10%, transparent);
} }
@@ -64,7 +64,7 @@
} }
:global(.theme-light) .optionIconSelected { :global(.theme-light) .optionIconSelected {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.optionText { .optionText {
@@ -94,5 +94,5 @@
} }
:global(.theme-light) .checkIcon { :global(.theme-light) .checkIcon {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
@@ -34,7 +34,7 @@
} }
:global(.theme-light) .optionSelected { :global(.theme-light) .optionSelected {
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
background-color: color-mix(in srgb, var(--brand-primary) 10%, transparent); background-color: color-mix(in srgb, var(--brand-primary) 10%, transparent);
} }
@@ -55,7 +55,7 @@
} }
:global(.theme-light) .optionIconSelected { :global(.theme-light) .optionIconSelected {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.optionText { .optionText {
@@ -85,5 +85,5 @@
} }
:global(.theme-light) .checkIcon { :global(.theme-light) .checkIcon {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
@@ -140,7 +140,7 @@
} }
:global(.theme-light) .checkIcon { :global(.theme-light) .checkIcon {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.emptyState { .emptyState {
@@ -16,7 +16,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 0.5rem; border-radius: 0.5rem;
border: 0.125rem dashed var(--brand-primary); border: 0.125rem dashed var(--brand-primary-foreground);
background: color-mix(in srgb, var(--brand-primary) 10%, transparent); background: color-mix(in srgb, var(--brand-primary) 10%, transparent);
} }
@@ -28,13 +28,13 @@
} }
.dragIcon { .dragIcon {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.dragText { .dragText {
font-weight: 500; font-weight: 500;
font-size: 1.125rem; font-size: 1.125rem;
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.freeUserContainer { .freeUserContainer {
@@ -131,7 +131,7 @@
border: none; border: none;
background: none; background: none;
font-size: 0.75rem; font-size: 0.75rem;
color: var(--brand-primary); color: var(--brand-primary-foreground);
cursor: pointer; cursor: pointer;
} }
@@ -218,7 +218,7 @@
animation: spin 1s linear infinite; animation: spin 1s linear infinite;
border-radius: 50%; border-radius: 50%;
border: 0.125rem solid var(--background-modifier-accent); border: 0.125rem solid var(--background-modifier-accent);
border-top-color: var(--brand-primary); border-top-color: var(--brand-primary-foreground);
} }
@keyframes spin { @keyframes spin {
@@ -314,7 +314,7 @@
} }
.selectedIcon { .selectedIcon {
color: white; color: var(--brand-primary-fill);
} }
.statsText { .statsText {
@@ -291,7 +291,7 @@ const BackgroundItem: React.FC<BackgroundItemProps> = React.memo(
ref={visibilityRef} ref={visibilityRef}
className={styles.backgroundItem} className={styles.backgroundItem}
style={{ style={{
borderColor: isSelected ? 'var(--brand-primary)' : 'var(--background-modifier-accent)', borderColor: isSelected ? 'var(--brand-primary-foreground)' : 'var(--background-modifier-accent)',
}} }}
onClick={handleClick} onClick={handleClick}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
@@ -68,7 +68,7 @@ export function useThemeCssVariables({
]); ]);
useLayoutEffect(() => { useLayoutEffect(() => {
if (instanceThemeColor === null) return; if (instanceThemeColor === null) return;
const variables = getInstanceBrandVariables(instanceThemeColor); const variables = getInstanceBrandVariables(instanceThemeColor, effectiveTheme, saturationFactor);
if (variables === null) return; if (variables === null) return;
const htmlNode = document.documentElement; const htmlNode = document.documentElement;
for (const name of INSTANCE_BRAND_VARIABLES) { for (const name of INSTANCE_BRAND_VARIABLES) {
@@ -79,7 +79,7 @@ export function useThemeCssVariables({
htmlNode.style.removeProperty(name); htmlNode.style.removeProperty(name);
} }
}; };
}, [instanceThemeColor]); }, [instanceThemeColor, effectiveTheme, saturationFactor]);
useLayoutEffect(() => { useLayoutEffect(() => {
const htmlNode = document.documentElement; const htmlNode = document.documentElement;
return () => { return () => {
@@ -25,7 +25,7 @@
@media (hover: hover) { @media (hover: hover) {
.deleteButton:hover { .deleteButton:hover {
background-color: var(--status-danger); background-color: var(--status-danger);
color: var(--text-on-brand-primary); color: var(--text-on-dark);
} }
} }
@@ -117,7 +117,7 @@
} }
.playButton svg { .playButton svg {
color: var(--text-on-brand-primary); color: var(--text-on-dark);
} }
.audioPlaceholder { .audioPlaceholder {
@@ -152,6 +152,6 @@
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 600; font-weight: 600;
color: var(--text-on-brand-primary); color: var(--text-on-dark);
line-height: 1; line-height: 1;
} }
@@ -0,0 +1,259 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {ThemeTypes} from '@fluxer/constants/src/UserConstants';
export interface ColorFamily {
hue: number;
saturation: number;
useSaturationFactor: boolean;
}
interface ScaleStop {
name: string;
position?: number;
}
export interface Scale {
family: string;
range: [number, number];
curve: 'linear' | 'easeIn' | 'easeOut' | 'easeInOut';
stops: Array<ScaleStop>;
}
export interface ColorTone {
family?: string;
hue?: number;
saturation?: number;
lightness?: number;
alpha?: number;
useSaturationFactor?: boolean;
}
export type ColorSystemTheme =
| typeof ThemeTypes.DARK
| typeof ThemeTypes.DARK_LEGACY
| typeof ThemeTypes.COAL
| typeof ThemeTypes.LIGHT;
interface BrandTint {
surface: string;
rest: number;
hover: number;
}
interface ThemePalette {
surfaceScale: string;
surfaceAliases: Record<string, string>;
selectedOverlay: ColorTone;
brandTint: BrandTint;
}
export const COLOR_FAMILIES: Record<string, ColorFamily> = {
neutralDark: {hue: 258, saturation: 10, useSaturationFactor: true},
neutralLight: {hue: 220, saturation: 10, useSaturationFactor: true},
brand: {hue: 242, saturation: 70, useSaturationFactor: true},
link: {hue: 198, saturation: 92, useSaturationFactor: true},
accentPurple: {hue: 278, saturation: 85, useSaturationFactor: true},
statusOnline: {hue: 152, saturation: 72, useSaturationFactor: true},
legacyDark: {hue: 220, saturation: 13, useSaturationFactor: true},
legacyLink: {hue: 210, saturation: 100, useSaturationFactor: true},
legacyAccentPurple: {hue: 270, saturation: 80, useSaturationFactor: true},
legacyStatusOnline: {hue: 142, saturation: 76, useSaturationFactor: true},
statusIdle: {hue: 45, saturation: 93, useSaturationFactor: true},
statusDnd: {hue: 0, saturation: 84, useSaturationFactor: true},
statusOffline: {hue: 218, saturation: 11, useSaturationFactor: true},
statusDanger: {hue: 1, saturation: 77, useSaturationFactor: true},
textCode: {hue: 340, saturation: 50, useSaturationFactor: true},
brandIcon: {hue: 38, saturation: 92, useSaturationFactor: true},
};
export const COLOR_SCALES: Record<string, Scale> = {
darkSurface: {
family: 'neutralDark',
range: [5, 24],
curve: 'easeOut',
stops: [
{name: '--background-primary', position: 0},
{name: '--background-secondary', position: 0.16},
{name: '--background-secondary-lighter', position: 0.22},
{name: '--background-secondary-alt', position: 0.28},
{name: '--background-tertiary', position: 0.4},
{name: '--background-channel-header', position: 0.34},
{name: '--guild-list-foreground', position: 0.38},
{name: '--background-header-secondary', position: 0.5},
{name: '--background-header-primary', position: 0.5},
{name: '--background-textarea', position: 0.3},
{name: '--background-header-primary-hover', position: 0.85},
],
},
coalSurface: {
family: 'neutralDark',
range: [1, 12],
curve: 'easeOut',
stops: [
{name: '--background-primary', position: 0},
{name: '--background-secondary', position: 0.16},
{name: '--background-secondary-alt', position: 0.28},
{name: '--background-tertiary', position: 0.4},
{name: '--background-channel-header', position: 0.34},
{name: '--guild-list-foreground', position: 0.38},
{name: '--background-header-secondary', position: 0.5},
{name: '--background-header-primary', position: 0.5},
{name: '--background-textarea', position: 0.3},
{name: '--background-header-primary-hover', position: 0.85},
],
},
darkText: {
family: 'neutralDark',
range: [60, 96],
curve: 'easeInOut',
stops: [
{name: '--text-tertiary-secondary', position: 0},
{name: '--text-tertiary-muted', position: 0.2},
{name: '--text-tertiary', position: 0.38},
{name: '--text-primary-muted', position: 0.55},
{name: '--text-chat-muted', position: 0.55},
{name: '--text-secondary', position: 0.72},
{name: '--text-chat', position: 0.82},
{name: '--text-primary', position: 1},
],
},
lightSurface: {
family: 'neutralLight',
range: [86, 98.5],
curve: 'easeIn',
stops: [
{name: '--background-header-primary-hover', position: 0},
{name: '--background-header-primary', position: 0.12},
{name: '--background-header-secondary', position: 0.2},
{name: '--guild-list-foreground', position: 0.35},
{name: '--background-tertiary', position: 0.42},
{name: '--background-channel-header', position: 0.5},
{name: '--background-secondary-alt', position: 0.63},
{name: '--background-secondary', position: 0.74},
{name: '--background-secondary-lighter', position: 0.83},
{name: '--background-textarea', position: 0.88},
{name: '--background-primary', position: 1},
],
},
lightText: {
family: 'neutralLight',
range: [15, 54],
curve: 'easeOut',
stops: [
{name: '--text-primary', position: 0},
{name: '--text-chat', position: 0.08},
{name: '--text-secondary', position: 0.28},
{name: '--text-chat-muted', position: 0.45},
{name: '--text-primary-muted', position: 0.45},
{name: '--text-tertiary', position: 0.6},
{name: '--text-tertiary-secondary', position: 0.75},
{name: '--text-tertiary-muted', position: 0.85},
],
},
legacyDarkSurface: {
family: 'legacyDark',
range: [5, 26],
curve: 'easeOut',
stops: [
{name: '--background-primary', position: 0},
{name: '--background-secondary', position: 0.16},
{name: '--background-secondary-lighter', position: 0.22},
{name: '--background-secondary-alt', position: 0.28},
{name: '--background-tertiary', position: 0.4},
{name: '--background-channel-header', position: 0.34},
{name: '--guild-list-foreground', position: 0.38},
{name: '--background-header-secondary', position: 0.5},
{name: '--background-header-primary', position: 0.5},
{name: '--background-textarea', position: 0.3},
{name: '--background-header-primary-hover', position: 0.85},
],
},
legacyDarkText: {
family: 'legacyDark',
range: [52, 96],
curve: 'easeInOut',
stops: [
{name: '--text-tertiary-secondary', position: 0},
{name: '--text-tertiary-muted', position: 0.2},
{name: '--text-tertiary', position: 0.38},
{name: '--text-primary-muted', position: 0.55},
{name: '--text-chat-muted', position: 0.55},
{name: '--text-secondary', position: 0.72},
{name: '--text-chat', position: 0.82},
{name: '--text-primary', position: 1},
],
},
};
const DARK_BRAND_TINT: BrandTint = {surface: '--background-secondary', rest: 36, hover: 48};
export const THEME_PALETTES: Record<ColorSystemTheme, ThemePalette> = {
[ThemeTypes.DARK]: {
surfaceScale: 'darkSurface',
surfaceAliases: {},
selectedOverlay: {family: 'neutralDark', lightness: 100, alpha: 0.1},
brandTint: DARK_BRAND_TINT,
},
[ThemeTypes.COAL]: {
surfaceScale: 'coalSurface',
surfaceAliases: {
'--background-secondary': '--background-primary',
'--background-secondary-lighter': '--background-primary',
},
selectedOverlay: {family: 'neutralDark', lightness: 100, alpha: 0.08},
brandTint: DARK_BRAND_TINT,
},
[ThemeTypes.DARK_LEGACY]: {
surfaceScale: 'legacyDarkSurface',
surfaceAliases: {},
selectedOverlay: {family: 'legacyDark', lightness: 100, alpha: 0.1},
brandTint: DARK_BRAND_TINT,
},
[ThemeTypes.LIGHT]: {
surfaceScale: 'lightSurface',
surfaceAliases: {},
selectedOverlay: {family: 'neutralLight', saturation: 10, lightness: 10, alpha: 0.1},
brandTint: {surface: '--background-primary', rest: 12, hover: 18},
},
};
export const BRAND_PRESSED_RATIO = 90;
export function getThemePalette(theme: string): ThemePalette {
return THEME_PALETTES[theme as ColorSystemTheme] ?? THEME_PALETTES[ThemeTypes.DARK];
}
export function brandTintMix(tint: BrandTint, state: 'rest' | 'hover'): string {
const ratio = tint[state];
return `color-mix(in srgb, var(--brand-primary) ${ratio}%, var(${tint.surface}) ${100 - ratio}%)`;
}
function clamp01(value: number): number {
return Math.min(1, Math.max(0, value));
}
function applyCurve(curve: Scale['curve'], t: number): number {
switch (curve) {
case 'easeIn':
return t * t;
case 'easeOut':
return 1 - (1 - t) * (1 - t);
case 'easeInOut':
if (t < 0.5) {
return 2 * t * t;
}
return 1 - 2 * (1 - t) * (1 - t);
default:
return t;
}
}
export function scaleStopLightness(scale: Scale, index: number): number {
const lastIndex = Math.max(scale.stops.length - 1, 1);
const stop = scale.stops[index];
const position = stop.position !== undefined ? clamp01(stop.position) : index / lastIndex;
const eased = applyCurve(scale.curve, position);
const lightness = scale.range[0] + (scale.range[1] - scale.range[0]) * eased;
return Math.round(lightness * 1000) / 1000;
}
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {BRAND_PRESSED_RATIO, THEME_PALETTES} from '@app/features/theme/utils/ColorSystemPalette';
import {
contrastRatio,
FOREGROUND_BRAND_TINT_RATIO,
FOREGROUND_SELF_TINT_RATIO,
getInstanceBrandVariables,
hslToRgb,
MIN_TEXT_CONTRAST,
mixRgb,
resolveThemeSurface,
resolveThemeSurfaces,
} from '@app/features/theme/utils/InstanceBrandColors';
import {describe, expect, test} from 'vitest';
type Rgb = readonly [number, number, number];
const MIN_UI_CONTRAST = 3;
const THEMES = Object.keys(THEME_PALETTES);
const SATURATION_FACTORS = [1, 0.5, 0];
const NAMED_COLORS = [
'#ffffff',
'#f0f0f0',
'#ffff00',
'#00ff00',
'#00ffff',
'#ff00ff',
'#ff0000',
'#0000ff',
'#e53935',
'#4641d9',
'#7f7f7f',
'#000001',
'#0b1a3a',
'#ffd1dc',
];
function hex(rgb: Rgb): string {
return `#${rgb.map((channel) => Math.round(channel).toString(16).padStart(2, '0')).join('')}`;
}
function gridColors(): Array<string> {
const colors: Array<string> = [];
for (let hue = 0; hue < 360; hue += 30) {
for (const saturation of [100, 35]) {
for (const lightness of [3, 15, 30, 50, 70, 85, 97]) {
colors.push(hex(hslToRgb(hue, saturation, lightness)));
}
}
}
return colors;
}
function parseColor(value: string): Rgb {
const hsl = /^hsl\(([\d.]+), ([\d.]+)%, ([\d.]+)%\)$/.exec(value);
if (hsl) return hslToRgb(Number(hsl[1]), Number(hsl[2]), Number(hsl[3]));
const rgb = /^rgb\(([\d.]+), ([\d.]+), ([\d.]+)\)$/.exec(value);
if (rgb) return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];
throw new Error(`Unparseable colour ${value}`);
}
function minContrast(foreground: Rgb, backgrounds: ReadonlyArray<Rgb>): number {
return Math.min(...backgrounds.map((background) => contrastRatio(foreground, background)));
}
function sameColor(first: Rgb, second: Rgb): boolean {
return first.every((channel, index) => Math.abs(channel - second[index]) < 0.01);
}
const COLORS = [...NAMED_COLORS, ...gridColors()];
const CASES = THEMES.flatMap((theme) =>
SATURATION_FACTORS.flatMap((saturationFactor) => COLORS.map((color) => ({theme, saturationFactor, color}))),
);
describe('getInstanceBrandVariables', () => {
test('rejects anything but a lowercase six digit hex colour', () => {
expect(getInstanceBrandVariables('#FFF', 'dark', 1)).toBeNull();
expect(getInstanceBrandVariables('red', 'dark', 1)).toBeNull();
});
test('keeps the operator colour as the solid fill', () => {
expect(getInstanceBrandVariables('#ffff00', 'light', 1)?.['--instance-brand-primary']).toBe('hsl(60, 100%, 50%)');
expect(getInstanceBrandVariables('#e53935', 'dark', 0.5)?.['--instance-brand-primary']).toBe(
'hsl(1.36, 38.6%, 55.29%)',
);
});
test('treats an unknown theme like the dark root palette', () => {
expect(getInstanceBrandVariables('#ffff00', 'unknown', 1)).toEqual(getInstanceBrandVariables('#ffff00', 'dark', 1));
});
test.each(CASES)(
'$color in $theme at saturation $saturationFactor meets contrast minimums',
({theme, saturationFactor, color}) => {
const variables = getInstanceBrandVariables(color, theme, saturationFactor);
expect(variables).not.toBeNull();
const v = Object.fromEntries(
Object.entries(variables!).map(([name, value]) => [name.replace('--instance-', ''), parseColor(value)]),
);
const surfaces = resolveThemeSurfaces(theme, saturationFactor);
const stops = surfaces.slice(0, surfaces.length / 2);
const pressed = mixRgb(v['brand-primary'], v['brand-primary-shade'], BRAND_PRESSED_RATIO);
const solidFills = [v['brand-primary'], v['brand-secondary'], pressed];
expect(minContrast(v['text-on-brand-primary'], solidFills)).toBeGreaterThanOrEqual(MIN_TEXT_CONTRAST);
expect(minContrast(v['brand-primary-fill'], solidFills)).toBeGreaterThanOrEqual(MIN_TEXT_CONTRAST);
expect(contrastRatio(v['text-on-brand-primary-light'], v['brand-primary-light'])).toBeGreaterThanOrEqual(
MIN_TEXT_CONTRAST,
);
expect(contrastRatio(v['brand-primary-light-fill'], v['brand-primary-light'])).toBeGreaterThanOrEqual(
MIN_TEXT_CONTRAST,
);
for (const name of ['brand-primary-foreground', 'brand-primary-light']) {
const foreground = v[name];
const tinted = [
...stops.map((stop) => mixRgb(foreground, stop, FOREGROUND_SELF_TINT_RATIO)),
...stops.map((stop) => mixRgb(v['brand-primary'], stop, FOREGROUND_BRAND_TINT_RATIO)),
];
expect(minContrast(foreground, surfaces)).toBeGreaterThanOrEqual(MIN_TEXT_CONTRAST);
expect(minContrast(foreground, tinted)).toBeGreaterThanOrEqual(MIN_TEXT_CONTRAST);
}
expect(minContrast(v['brand-tint-border'], surfaces)).toBeGreaterThanOrEqual(MIN_UI_CONTRAST);
expect(minContrast(v['text-on-brand-tint'], [v['brand-tint'], v['brand-tint-hover']])).toBeGreaterThanOrEqual(
MIN_TEXT_CONTRAST,
);
const tint = THEME_PALETTES[theme as keyof typeof THEME_PALETTES].brandTint;
const tintSurface = resolveThemeSurface(theme, tint.surface, saturationFactor);
const matchingRatio = (target: Rgb, max: number) =>
Array.from({length: max + 1}, (_, ratio) => max - ratio).find((ratio) =>
sameColor(target, mixRgb(v['brand-primary'], tintSurface, ratio)),
);
expect(matchingRatio(v['brand-tint'], tint.rest)).toBeDefined();
expect(matchingRatio(v['brand-tint-hover'], tint.hover)).toBeDefined();
},
);
test('keeps the stylesheet tint ratios whenever the text already passes', () => {
const variables = getInstanceBrandVariables('#4641d9', 'dark', 1)!;
const surface = resolveThemeSurface('dark', THEME_PALETTES.dark.brandTint.surface, 1);
const primary = parseColor(variables['--instance-brand-primary']);
expect(sameColor(parseColor(variables['--instance-brand-tint']), mixRgb(primary, surface, 36))).toBe(true);
expect(sameColor(parseColor(variables['--instance-brand-tint-hover']), mixRgb(primary, surface, 48))).toBe(true);
});
test('reads light text on the dark reaction tint even when the brand needs dark text', () => {
const variables = getInstanceBrandVariables('#e53935', 'dark', 1)!;
expect(variables['--instance-text-on-brand-primary']).toBe('rgb(0, 0, 0)');
expect(variables['--instance-text-on-brand-tint']).toBe('hsl(0, 0%, 98%)');
});
});
@@ -1,18 +1,64 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import {
COLOR_FAMILIES,
COLOR_SCALES,
type ColorTone,
getThemePalette,
scaleStopLightness,
} from '@app/features/theme/utils/ColorSystemPalette';
export const INSTANCE_BRAND_VARIABLES = [ export const INSTANCE_BRAND_VARIABLES = [
'--instance-brand-primary', '--instance-brand-primary',
'--instance-brand-secondary', '--instance-brand-secondary',
'--instance-brand-primary-shade',
'--instance-brand-primary-light', '--instance-brand-primary-light',
'--instance-brand-primary-foreground',
'--instance-brand-primary-fill', '--instance-brand-primary-fill',
'--instance-brand-primary-light-fill',
'--instance-text-on-brand-primary', '--instance-text-on-brand-primary',
'--instance-text-on-brand-primary-light',
'--instance-brand-tint',
'--instance-brand-tint-hover',
'--instance-brand-tint-border',
'--instance-text-on-brand-tint',
] as const; ] as const;
type InstanceBrandVariable = (typeof INSTANCE_BRAND_VARIABLES)[number]; type InstanceBrandVariable = (typeof INSTANCE_BRAND_VARIABLES)[number];
const HEX_COLOR_PATTERN = /^#[0-9a-f]{6}$/; export const MIN_TEXT_CONTRAST = 4.5;
const TARGET_CONTRAST = MIN_TEXT_CONTRAST + 0.05;
export const FOREGROUND_SELF_TINT_RATIO = 15;
export const FOREGROUND_BRAND_TINT_RATIO = 22;
function parseHexColor(value: string): [number, number, number] | null { type Rgb = readonly [number, number, number];
interface Color {
css: string;
rgb: Rgb;
}
interface Hsl {
hue: number;
saturation: number;
lightness: number;
}
interface OnColor {
text: Color;
fill: Color;
dark: boolean;
}
const HEX_COLOR_PATTERN = /^#[0-9a-f]{6}$/;
const WHITE: Rgb = [255, 255, 255];
const BLACK: Rgb = [0, 0, 0];
function round2(value: number): number {
return Math.round(value * 100) / 100;
}
function parseHexColor(value: string): Rgb | null {
if (!HEX_COLOR_PATTERN.test(value)) return null; if (!HEX_COLOR_PATTERN.test(value)) return null;
const digits = value.slice(1); const digits = value.slice(1);
return [ return [
@@ -22,7 +68,7 @@ function parseHexColor(value: string): [number, number, number] | null {
]; ];
} }
function rgbToHsl(red: number, green: number, blue: number): [number, number, number] { function rgbToHsl([red, green, blue]: Rgb): Hsl {
const r = red / 255; const r = red / 255;
const g = green / 255; const g = green / 255;
const b = blue / 255; const b = blue / 255;
@@ -30,7 +76,7 @@ function rgbToHsl(red: number, green: number, blue: number): [number, number, nu
const min = Math.min(r, g, b); const min = Math.min(r, g, b);
const lightness = (max + min) / 2; const lightness = (max + min) / 2;
const delta = max - min; const delta = max - min;
if (delta === 0) return [0, 0, lightness * 100]; if (delta === 0) return {hue: 0, saturation: 0, lightness: lightness * 100};
const saturation = delta / (1 - Math.abs(2 * lightness - 1)); const saturation = delta / (1 - Math.abs(2 * lightness - 1));
let hue: number; let hue: number;
if (max === r) { if (max === r) {
@@ -40,36 +86,225 @@ function rgbToHsl(red: number, green: number, blue: number): [number, number, nu
} else { } else {
hue = (r - g) / delta + 4; hue = (r - g) / delta + 4;
} }
return [(hue * 60 + 360) % 360, saturation * 100, lightness * 100]; return {hue: (hue * 60 + 360) % 360, saturation: saturation * 100, lightness: lightness * 100};
} }
function relativeLuminance(red: number, green: number, blue: number): number { export function hslToRgb(hue: number, saturation: number, lightness: number): Rgb {
const s = Math.min(100, Math.max(0, saturation)) / 100;
const l = Math.min(100, Math.max(0, lightness)) / 100;
const chroma = (1 - Math.abs(2 * l - 1)) * s;
const h = (((hue % 360) + 360) % 360) / 60;
const x = chroma * (1 - Math.abs((h % 2) - 1));
const m = l - chroma / 2;
const [r, g, b] =
h < 1
? [chroma, x, 0]
: h < 2
? [x, chroma, 0]
: h < 3
? [0, chroma, x]
: h < 4
? [0, x, chroma]
: h < 5
? [x, 0, chroma]
: [chroma, 0, x];
return [(r + m) * 255, (g + m) * 255, (b + m) * 255];
}
export function mixRgb(first: Rgb, second: Rgb, firstPercent: number): Rgb {
const ratio = firstPercent / 100;
return [
first[0] * ratio + second[0] * (1 - ratio),
first[1] * ratio + second[1] * (1 - ratio),
first[2] * ratio + second[2] * (1 - ratio),
];
}
function relativeLuminance(rgb: Rgb): number {
const linear = (channel: number) => { const linear = (channel: number) => {
const value = channel / 255; const value = Math.round(channel) / 255;
return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4; return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
}; };
return 0.2126 * linear(red) + 0.7152 * linear(green) + 0.0722 * linear(blue); return 0.2126 * linear(rgb[0]) + 0.7152 * linear(rgb[1]) + 0.0722 * linear(rgb[2]);
} }
function formatNumber(value: number): string { export function contrastRatio(first: Rgb, second: Rgb): number {
return String(Math.round(value * 100) / 100); const a = relativeLuminance(first);
const b = relativeLuminance(second);
return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
} }
function hsl(hue: number, saturation: number, lightness: number): string { function hslColor({hue, saturation, lightness}: Hsl): Color {
return `hsl(${formatNumber(hue)}, calc(${formatNumber(saturation)}% * var(--saturation-factor)), ${formatNumber(lightness)}%)`; const h = round2(hue);
const s = round2(saturation);
const l = round2(lightness);
return {css: `hsl(${h}, ${s}%, ${l}%)`, rgb: hslToRgb(h, s, l)};
} }
export function getInstanceBrandVariables(themeColor: string): Record<InstanceBrandVariable, string> | null { function rgbColor(rgb: Rgb): Color {
const [r, g, b] = rgb.map(round2);
return {css: `rgb(${r}, ${g}, ${b})`, rgb: [r, g, b]};
}
function resolveTone(tone: ColorTone, saturationFactor: number): Rgb {
const family = tone.family ? COLOR_FAMILIES[tone.family] : undefined;
const hue = round2(tone.hue ?? family?.hue ?? 0);
const saturation = round2(tone.saturation ?? family?.saturation ?? 0);
const useFactor = tone.useSaturationFactor ?? family?.useSaturationFactor ?? false;
return hslToRgb(hue, useFactor ? saturation * saturationFactor : saturation, round2(tone.lightness ?? 0));
}
export function resolveThemeSurface(theme: string, name: string, saturationFactor: number): Rgb {
const palette = getThemePalette(theme);
const scale = COLOR_SCALES[palette.surfaceScale];
const target = palette.surfaceAliases[name] ?? name;
const index = scale.stops.findIndex((stop) => stop.name === target);
if (index === -1) throw new Error(`${target} is not a stop of ${palette.surfaceScale}`);
return resolveTone({family: scale.family, lightness: scaleStopLightness(scale, index)}, saturationFactor);
}
export function resolveThemeSurfaces(theme: string, saturationFactor: number): Array<Rgb> {
const palette = getThemePalette(theme);
const scale = COLOR_SCALES[palette.surfaceScale];
const overlay = resolveTone(palette.selectedOverlay, saturationFactor);
const overlayPercent = (palette.selectedOverlay.alpha ?? 1) * 100;
const stops = scale.stops.map((_, index) =>
resolveTone({family: scale.family, lightness: scaleStopLightness(scale, index)}, saturationFactor),
);
return [...stops, ...stops.map((stop) => mixRgb(overlay, stop, overlayPercent))];
}
function isLightTheme(theme: string, saturationFactor: number): boolean {
return relativeLuminance(resolveThemeSurface(theme, '--background-primary', saturationFactor)) > 0.5;
}
function passes(foreground: Rgb, backgrounds: ReadonlyArray<Rgb>): boolean {
return backgrounds.every((background) => contrastRatio(foreground, background) >= TARGET_CONTRAST);
}
function shiftLightness(start: Hsl, lighter: boolean, accept: (color: Color) => boolean): Color {
const initial = hslColor(start);
if (accept(initial)) return initial;
const step = lighter ? 1 : -1;
const limit = lighter ? 10000 : 0;
let failing = Math.round(start.lightness * 100);
let passing = limit;
for (let value = failing + step * 100; lighter ? value < limit : value > limit; value += step * 100) {
if (accept(hslColor({...start, lightness: value / 100}))) {
passing = value;
break;
}
failing = value;
}
while (Math.abs(passing - failing) > 1) {
const middle = Math.round((passing + failing) / 2);
if (accept(hslColor({...start, lightness: middle / 100}))) {
passing = middle;
} else {
failing = middle;
}
}
return hslColor({...start, lightness: passing / 100});
}
function neutral(lightness: number): Color {
return hslColor({hue: 0, saturation: 0, lightness});
}
function pickOnColor(background: Rgb): OnColor {
const light = {text: neutral(98), fill: neutral(100), dark: false};
const dark = {text: neutral(8), fill: neutral(8), dark: true};
const best = contrastRatio(light.text.rgb, background) >= contrastRatio(dark.text.rgb, background) ? light : dark;
if (contrastRatio(best.text.rgb, background) >= TARGET_CONTRAST) return best;
const pure = rgbColor(contrastRatio(WHITE, background) >= contrastRatio(BLACK, background) ? WHITE : BLACK);
return {text: pure, fill: pure, dark: pure.rgb[0] === 0};
}
function readableOnSurfaces(start: Hsl, theme: string, saturationFactor: number, primary: Rgb): Color {
const surfaces = resolveThemeSurfaces(theme, saturationFactor);
const stops = surfaces.slice(0, surfaces.length / 2);
const brandTints = stops.map((stop) => mixRgb(primary, stop, FOREGROUND_BRAND_TINT_RATIO));
return shiftLightness(start, !isLightTheme(theme, saturationFactor), (color) => {
const selfTints = stops.map((stop) => mixRgb(color.rgb, stop, FOREGROUND_SELF_TINT_RATIO));
return passes(color.rgb, surfaces) && passes(color.rgb, selfTints) && passes(color.rgb, brandTints);
});
}
function largestPassingRatio(text: Rgb, primary: Rgb, surface: Rgb, ratio: number): number {
for (let candidate = ratio; candidate >= 0; candidate--) {
if (contrastRatio(text, mixRgb(primary, surface, candidate)) >= TARGET_CONTRAST) return candidate;
}
return -1;
}
function brandTint(
brand: Hsl,
primary: Rgb,
theme: string,
saturationFactor: number,
): {rest: Color; hover: Color; text: Color} {
const tint = getThemePalette(theme).brandTint;
const surface = resolveThemeSurface(theme, tint.surface, saturationFactor);
if (isLightTheme(theme, saturationFactor)) {
const rest = rgbColor(mixRgb(primary, surface, tint.rest));
const hover = rgbColor(mixRgb(primary, surface, tint.hover));
const text = shiftLightness(brand, false, (color) => passes(color.rgb, [rest.rgb, hover.rgb]));
return {rest, hover, text};
}
const options = [neutral(98), neutral(8), rgbColor(WHITE), rgbColor(BLACK)].map((text) => {
const hover = largestPassingRatio(text.rgb, primary, surface, tint.hover);
const rest = Math.min(largestPassingRatio(text.rgb, primary, surface, tint.rest), hover);
return {text, rest, hover};
});
const best =
options.find((option) => option.rest === tint.rest && option.hover === tint.hover) ??
options.reduce((current, option) => (option.rest + option.hover > current.rest + current.hover ? option : current));
return {
rest: rgbColor(mixRgb(primary, surface, best.rest)),
hover: rgbColor(mixRgb(primary, surface, best.hover)),
text: best.text,
};
}
export function getInstanceBrandVariables(
themeColor: string,
theme: string,
saturationFactor: number,
): Record<InstanceBrandVariable, string> | null {
const rgb = parseHexColor(themeColor); const rgb = parseHexColor(themeColor);
if (rgb === null) return null; if (rgb === null) return null;
const [hue, saturation, lightness] = rgbToHsl(...rgb); const base = rgbToHsl(rgb);
const luminance = relativeLuminance(...rgb); const brand = {...base, saturation: base.saturation * saturationFactor};
const prefersDarkText = (luminance + 0.05) / 0.05 > 1.05 / (luminance + 0.05); const primary = hslColor(brand);
const onPrimary = pickOnColor(primary.rgb);
const secondary = shiftLightness(
{hue: brand.hue, saturation: brand.saturation * (6 / 7), lightness: brand.lightness * 0.89},
onPrimary.dark,
(color) => passes(onPrimary.text.rgb, [color.rgb]) && passes(onPrimary.fill.rgb, [color.rgb]),
);
const shade = rgbColor(onPrimary.dark ? WHITE : BLACK);
const foreground = readableOnSurfaces(brand, theme, saturationFactor, primary.rgb);
const light = readableOnSurfaces(
{...brand, lightness: brand.lightness + (100 - brand.lightness) * 0.65},
theme,
saturationFactor,
primary.rgb,
);
const onLight = pickOnColor(light.rgb);
const tint = brandTint(brand, primary.rgb, theme, saturationFactor);
return { return {
'--instance-brand-primary': hsl(hue, saturation, lightness), '--instance-brand-primary': primary.css,
'--instance-brand-secondary': hsl(hue, saturation * (6 / 7), lightness * 0.89), '--instance-brand-secondary': secondary.css,
'--instance-brand-primary-light': hsl(hue, saturation, lightness + (100 - lightness) * 0.65), '--instance-brand-primary-shade': shade.css,
'--instance-brand-primary-fill': prefersDarkText ? 'hsl(0, 0%, 8%)' : 'hsl(0, 0%, 100%)', '--instance-brand-primary-light': light.css,
'--instance-text-on-brand-primary': prefersDarkText ? 'hsl(0, 0%, 8%)' : 'hsl(0, 0%, 98%)', '--instance-brand-primary-foreground': foreground.css,
'--instance-brand-primary-fill': onPrimary.fill.css,
'--instance-brand-primary-light-fill': onLight.fill.css,
'--instance-text-on-brand-primary': onPrimary.text.css,
'--instance-text-on-brand-primary-light': onLight.text.css,
'--instance-brand-tint': tint.rest.css,
'--instance-brand-tint-hover': tint.hover.css,
'--instance-brand-tint-border': foreground.css,
'--instance-text-on-brand-tint': tint.text.css,
}; };
} }
@@ -106,8 +106,15 @@ export type ThemeVariableName =
| "--border-color-hover" | "--border-color-hover"
| "--brand-primary" | "--brand-primary"
| "--brand-primary-fill" | "--brand-primary-fill"
| "--brand-primary-foreground"
| "--brand-primary-light" | "--brand-primary-light"
| "--brand-primary-light-fill"
| "--brand-primary-pressed"
| "--brand-primary-shade"
| "--brand-secondary" | "--brand-secondary"
| "--brand-tint"
| "--brand-tint-border"
| "--brand-tint-hover"
| "--button-danger-active-fill" | "--button-danger-active-fill"
| "--button-danger-fill" | "--button-danger-fill"
| "--button-danger-outline-active-border" | "--button-danger-outline-active-border"
@@ -421,6 +428,9 @@ export type ThemeVariableName =
| "--text-code" | "--text-code"
| "--text-link" | "--text-link"
| "--text-on-brand-primary" | "--text-on-brand-primary"
| "--text-on-brand-primary-light"
| "--text-on-brand-tint"
| "--text-on-dark"
| "--text-primary" | "--text-primary"
| "--text-primary-muted" | "--text-primary-muted"
| "--text-secondary" | "--text-secondary"
@@ -570,8 +580,15 @@ export const THEME_VARIABLES: ReadonlyArray<ThemeVariableDefinition> = [
{name: "--border-color-hover", kind: "color", groupId: "borders", groupLabel: "Borders & focus", source: "color-system"}, {name: "--border-color-hover", kind: "color", groupId: "borders", groupLabel: "Borders & focus", source: "color-system"},
{name: "--brand-primary", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"}, {name: "--brand-primary", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"},
{name: "--brand-primary-fill", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"}, {name: "--brand-primary-fill", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"},
{name: "--brand-primary-foreground", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"},
{name: "--brand-primary-light", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"}, {name: "--brand-primary-light", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"},
{name: "--brand-primary-light-fill", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"},
{name: "--brand-primary-pressed", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"},
{name: "--brand-primary-shade", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"},
{name: "--brand-secondary", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"}, {name: "--brand-secondary", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"},
{name: "--brand-tint", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"},
{name: "--brand-tint-border", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"},
{name: "--brand-tint-hover", kind: "color", groupId: "brand", groupLabel: "Brand & accents", source: "color-system"},
{name: "--button-danger-active-fill", kind: "color", groupId: "buttons", groupLabel: "Buttons", source: "color-system"}, {name: "--button-danger-active-fill", kind: "color", groupId: "buttons", groupLabel: "Buttons", source: "color-system"},
{name: "--button-danger-fill", kind: "color", groupId: "buttons", groupLabel: "Buttons", source: "color-system"}, {name: "--button-danger-fill", kind: "color", groupId: "buttons", groupLabel: "Buttons", source: "color-system"},
{name: "--button-danger-outline-active-border", kind: "color", groupId: "buttons", groupLabel: "Buttons", source: "color-system"}, {name: "--button-danger-outline-active-border", kind: "color", groupId: "buttons", groupLabel: "Buttons", source: "color-system"},
@@ -885,6 +902,9 @@ export const THEME_VARIABLES: ReadonlyArray<ThemeVariableDefinition> = [
{name: "--text-code", kind: "color", groupId: "code", groupLabel: "Code & terminal", source: "color-system"}, {name: "--text-code", kind: "color", groupId: "code", groupLabel: "Code & terminal", source: "color-system"},
{name: "--text-link", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"}, {name: "--text-link", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"},
{name: "--text-on-brand-primary", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"}, {name: "--text-on-brand-primary", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"},
{name: "--text-on-brand-primary-light", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"},
{name: "--text-on-brand-tint", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"},
{name: "--text-on-dark", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"},
{name: "--text-primary", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"}, {name: "--text-primary", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"},
{name: "--text-primary-muted", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"}, {name: "--text-primary-muted", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"},
{name: "--text-secondary", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"}, {name: "--text-secondary", kind: "color", groupId: "text", groupLabel: "Text", source: "color-system"},
@@ -1035,8 +1055,15 @@ export const THEME_VARIABLE_NAMES: ReadonlyArray<string> = [
"--border-color-hover", "--border-color-hover",
"--brand-primary", "--brand-primary",
"--brand-primary-fill", "--brand-primary-fill",
"--brand-primary-foreground",
"--brand-primary-light", "--brand-primary-light",
"--brand-primary-light-fill",
"--brand-primary-pressed",
"--brand-primary-shade",
"--brand-secondary", "--brand-secondary",
"--brand-tint",
"--brand-tint-border",
"--brand-tint-hover",
"--button-danger-active-fill", "--button-danger-active-fill",
"--button-danger-fill", "--button-danger-fill",
"--button-danger-outline-active-border", "--button-danger-outline-active-border",
@@ -1350,6 +1377,9 @@ export const THEME_VARIABLE_NAMES: ReadonlyArray<string> = [
"--text-code", "--text-code",
"--text-link", "--text-link",
"--text-on-brand-primary", "--text-on-brand-primary",
"--text-on-brand-primary-light",
"--text-on-brand-tint",
"--text-on-dark",
"--text-primary", "--text-primary",
"--text-primary-muted", "--text-primary-muted",
"--text-secondary", "--text-secondary",
@@ -1500,8 +1530,15 @@ export const THEME_STUDIO_DARK_DEFAULT_VARIABLE_VALUES: Readonly<Record<string,
"--border-color-hover": "hsla(258, calc(10% * var(--saturation-factor)), 50%, 0.3)", "--border-color-hover": "hsla(258, calc(10% * var(--saturation-factor)), 50%, 0.3)",
"--brand-primary": "hsl(242, calc(70% * var(--saturation-factor)), 55%)", "--brand-primary": "hsl(242, calc(70% * var(--saturation-factor)), 55%)",
"--brand-primary-fill": "hsl(0, 0%, 100%)", "--brand-primary-fill": "hsl(0, 0%, 100%)",
"--brand-primary-foreground": "hsl(242, calc(70% * var(--saturation-factor)), 55%)",
"--brand-primary-light": "hsl(242, calc(100% * var(--saturation-factor)), 84%)", "--brand-primary-light": "hsl(242, calc(100% * var(--saturation-factor)), 84%)",
"--brand-primary-light-fill": "hsl(0, 0%, 100%)",
"--brand-primary-pressed": "color-mix(in srgb, hsl(242, calc(70% * var(--saturation-factor)), 55%) 90%, hsl(0, 0%, 0%))",
"--brand-primary-shade": "hsl(0, 0%, 0%)",
"--brand-secondary": "hsl(242, calc(60% * var(--saturation-factor)), 49%)", "--brand-secondary": "hsl(242, calc(60% * var(--saturation-factor)), 49%)",
"--brand-tint": "color-mix(in srgb, var(--brand-primary) 36%, var(--background-secondary) 64%)",
"--brand-tint-border": "var(--brand-primary)",
"--brand-tint-hover": "color-mix(in srgb, var(--brand-primary) 48%, var(--background-secondary) 52%)",
"--button-danger-active-fill": "hsl(359, calc(65% * var(--saturation-factor)), 45%)", "--button-danger-active-fill": "hsl(359, calc(65% * var(--saturation-factor)), 45%)",
"--button-danger-fill": "hsl(359, calc(70% * var(--saturation-factor)), 54%)", "--button-danger-fill": "hsl(359, calc(70% * var(--saturation-factor)), 54%)",
"--button-danger-outline-active-border": "transparent", "--button-danger-outline-active-border": "transparent",
@@ -1815,6 +1852,9 @@ export const THEME_STUDIO_DARK_DEFAULT_VARIABLE_VALUES: Readonly<Record<string,
"--text-code": "color-mix(in srgb, hsl(258, calc(10% * var(--saturation-factor)), 90.36%) 82%, hsl(340, calc(50% * var(--saturation-factor)), 90%) 18%)", "--text-code": "color-mix(in srgb, hsl(258, calc(10% * var(--saturation-factor)), 90.36%) 82%, hsl(340, calc(50% * var(--saturation-factor)), 90%) 18%)",
"--text-link": "hsl(198, calc(92% * var(--saturation-factor)), 70%)", "--text-link": "hsl(198, calc(92% * var(--saturation-factor)), 70%)",
"--text-on-brand-primary": "hsl(0, 0%, 98%)", "--text-on-brand-primary": "hsl(0, 0%, 98%)",
"--text-on-brand-primary-light": "hsl(0, 0%, 98%)",
"--text-on-brand-tint": "var(--text-on-brand-primary)",
"--text-on-dark": "hsl(0, 0%, 98%)",
"--text-primary": "hsl(258, calc(10% * var(--saturation-factor)), 96%)", "--text-primary": "hsl(258, calc(10% * var(--saturation-factor)), 96%)",
"--text-primary-muted": "hsl(258, calc(10% * var(--saturation-factor)), 81.42%)", "--text-primary-muted": "hsl(258, calc(10% * var(--saturation-factor)), 81.42%)",
"--text-secondary": "hsl(258, calc(10% * var(--saturation-factor)), 90.36%)", "--text-secondary": "hsl(258, calc(10% * var(--saturation-factor)), 90.36%)",
@@ -1965,8 +2005,15 @@ export const THEME_STUDIO_LIGHT_DEFAULT_VARIABLE_VALUES: Readonly<Record<string,
"--border-color-hover": "hsla(220, calc(10% * var(--saturation-factor)), 40%, 0.25)", "--border-color-hover": "hsla(220, calc(10% * var(--saturation-factor)), 40%, 0.25)",
"--brand-primary": "hsl(242, calc(70% * var(--saturation-factor)), 55%)", "--brand-primary": "hsl(242, calc(70% * var(--saturation-factor)), 55%)",
"--brand-primary-fill": "hsl(0, 0%, 100%)", "--brand-primary-fill": "hsl(0, 0%, 100%)",
"--brand-primary-foreground": "hsl(242, calc(70% * var(--saturation-factor)), 55%)",
"--brand-primary-light": "hsl(242, calc(100% * var(--saturation-factor)), 84%)", "--brand-primary-light": "hsl(242, calc(100% * var(--saturation-factor)), 84%)",
"--brand-primary-light-fill": "hsl(0, 0%, 100%)",
"--brand-primary-pressed": "color-mix(in srgb, hsl(242, calc(70% * var(--saturation-factor)), 55%) 90%, hsl(0, 0%, 0%))",
"--brand-primary-shade": "hsl(0, 0%, 0%)",
"--brand-secondary": "hsl(242, calc(60% * var(--saturation-factor)), 49%)", "--brand-secondary": "hsl(242, calc(60% * var(--saturation-factor)), 49%)",
"--brand-tint": "color-mix(in srgb, var(--brand-primary) 12%, var(--background-primary) 88%)",
"--brand-tint-border": "color-mix(in srgb, var(--brand-primary) 45%, transparent 55%)",
"--brand-tint-hover": "color-mix(in srgb, var(--brand-primary) 18%, var(--background-primary) 82%)",
"--button-danger-active-fill": "hsl(359, calc(65% * var(--saturation-factor)), 45%)", "--button-danger-active-fill": "hsl(359, calc(65% * var(--saturation-factor)), 45%)",
"--button-danger-fill": "hsl(359, calc(70% * var(--saturation-factor)), 54%)", "--button-danger-fill": "hsl(359, calc(70% * var(--saturation-factor)), 54%)",
"--button-danger-outline-active-border": "transparent", "--button-danger-outline-active-border": "transparent",
@@ -2280,6 +2327,9 @@ export const THEME_STUDIO_LIGHT_DEFAULT_VARIABLE_VALUES: Readonly<Record<string,
"--text-code": "color-mix(in srgb, hsl(220, calc(10% * var(--saturation-factor)), 33.78%) 72%, hsl(340, calc(50% * var(--saturation-factor)), 38%) 28%)", "--text-code": "color-mix(in srgb, hsl(220, calc(10% * var(--saturation-factor)), 33.78%) 72%, hsl(340, calc(50% * var(--saturation-factor)), 38%) 28%)",
"--text-link": "hsl(198, calc(92% * var(--saturation-factor)), 45%)", "--text-link": "hsl(198, calc(92% * var(--saturation-factor)), 45%)",
"--text-on-brand-primary": "hsl(0, 0%, 98%)", "--text-on-brand-primary": "hsl(0, 0%, 98%)",
"--text-on-brand-primary-light": "hsl(0, 0%, 98%)",
"--text-on-brand-tint": "var(--brand-primary)",
"--text-on-dark": "hsl(0, 0%, 98%)",
"--text-primary": "hsl(220, calc(10% * var(--saturation-factor)), 15%)", "--text-primary": "hsl(220, calc(10% * var(--saturation-factor)), 15%)",
"--text-primary-muted": "hsl(220, calc(10% * var(--saturation-factor)), 42.2%)", "--text-primary-muted": "hsl(220, calc(10% * var(--saturation-factor)), 42.2%)",
"--text-secondary": "hsl(220, calc(10% * var(--saturation-factor)), 33.78%)", "--text-secondary": "hsl(220, calc(10% * var(--saturation-factor)), 33.78%)",
@@ -24,6 +24,7 @@
--studio-fg-muted: var(--text-secondary); --studio-fg-muted: var(--text-secondary);
--studio-fg-subtle: var(--text-tertiary); --studio-fg-subtle: var(--text-tertiary);
--studio-fg-on-accent: var(--text-on-brand-primary); --studio-fg-on-accent: var(--text-on-brand-primary);
--studio-fg-on-danger: var(--text-on-dark);
--studio-accent: var(--brand-primary); --studio-accent: var(--brand-primary);
--studio-accent-hover: var(--brand-secondary); --studio-accent-hover: var(--brand-secondary);
@@ -31,7 +32,7 @@
--studio-focus: var(--focus-primary); --studio-focus: var(--focus-primary);
--studio-button-primary: var(--brand-primary); --studio-button-primary: var(--brand-primary);
--studio-button-primary-hover: color-mix(in srgb, var(--brand-primary) 88%, var(--text-primary) 12%); --studio-button-primary-hover: var(--studio-accent-hover);
--studio-button-secondary: var(--button-secondary-fill); --studio-button-secondary: var(--button-secondary-fill);
--studio-button-secondary-hover: var(--button-secondary-active-fill); --studio-button-secondary-hover: var(--button-secondary-active-fill);
@@ -95,7 +95,7 @@ function buildEditorTheme(baseTheme: ThemeStudioBaseTheme): Extension {
}, },
'.cm-tooltip-autocomplete > ul > li[aria-selected]': { '.cm-tooltip-autocomplete > ul > li[aria-selected]': {
backgroundColor: 'var(--studio-accent, rgba(120, 160, 220, 0.3))', backgroundColor: 'var(--studio-accent, rgba(120, 160, 220, 0.3))',
color: 'var(--studio-fg-primary)', color: 'var(--studio-fg-on-accent)',
}, },
'.cm-tooltip-autocomplete > ul > li': { '.cm-tooltip-autocomplete > ul > li': {
fontFamily: 'var(--studio-font-mono)', fontFamily: 'var(--studio-font-mono)',
@@ -80,7 +80,7 @@
.dangerSolid { .dangerSolid {
--bg: var(--studio-danger); --bg: var(--studio-danger);
--bg-hover: var(--studio-danger-hover); --bg-hover: var(--studio-danger-hover);
--fg: var(--studio-fg-on-accent); --fg: var(--studio-fg-on-danger);
} }
.leadingIcon { .leadingIcon {
@@ -47,7 +47,7 @@
} }
:global(.theme-light) .count { :global(.theme-light) .count {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.preview { .preview {
@@ -295,7 +295,7 @@
.checkboxChecked { .checkboxChecked {
background-color: var(--brand-primary); background-color: var(--brand-primary);
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
} }
.checkboxChecked::after { .checkboxChecked::after {
@@ -305,7 +305,7 @@
top: 50%; top: 50%;
width: 0.25rem; width: 0.25rem;
height: 0.5625rem; height: 0.5625rem;
border: solid white; border: solid var(--brand-primary-fill);
border-width: 0 0.15625rem 0.15625rem 0; border-width: 0 0.15625rem 0.15625rem 0;
transform: translate(-50%, -60%) rotate(45deg); transform: translate(-50%, -60%) rotate(45deg);
} }
@@ -14,7 +14,7 @@
} }
.radioButtonSelected { .radioButtonSelected {
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
background-color: var(--brand-primary); background-color: var(--brand-primary);
} }
@@ -27,7 +27,7 @@
height: 0.375rem; height: 0.375rem;
width: 0.375rem; width: 0.375rem;
border-radius: 50%; border-radius: 50%;
background-color: white; background-color: var(--brand-primary-fill);
} }
.labelContent { .labelContent {
@@ -129,7 +129,7 @@
} }
.button.primary:active:not(:disabled):not([data-disabled]) { .button.primary:active:not(:disabled):not([data-disabled]) {
background-color: color-mix(in srgb, var(--brand-primary) 90%, #000); background-color: var(--brand-primary-pressed);
} }
.button.secondary { .button.secondary {
@@ -213,7 +213,7 @@
.button.recording { .button.recording {
background-color: var(--accent-success, #16a34a); background-color: var(--accent-success, #16a34a);
border-color: color-mix(in srgb, var(--accent-success, #16a34a) 80%, white 20%); border-color: color-mix(in srgb, var(--accent-success, #16a34a) 80%, white 20%);
color: var(--brand-primary-fill); color: var(--button-primary-text);
animation: buttonRecordingPulse 1.1s infinite; animation: buttonRecordingPulse 1.1s infinite;
} }
@@ -48,11 +48,11 @@
.checked { .checked {
background-color: var(--brand-primary); background-color: var(--brand-primary);
border: 0.0625rem solid var(--brand-primary); border: 0.0625rem solid var(--brand-primary-foreground);
} }
.checkIcon { .checkIcon {
color: white; color: var(--brand-primary-fill);
} }
.label { .label {
@@ -80,12 +80,12 @@
.menuChecked { .menuChecked {
background-color: var(--brand-primary); background-color: var(--brand-primary);
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
} }
.menuChecked:hover { .menuChecked:hover {
background-color: var(--brand-primary); background-color: var(--brand-primary);
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
} }
.checkboxIndicator { .checkboxIndicator {
@@ -167,7 +167,6 @@ export const Checkbox: React.FC<CheckboxProps> = observer(
<CheckIcon <CheckIcon
size={checkIconSizeRem} size={checkIconSizeRem}
weight="bold" weight="bold"
color={'#ffffff'}
className={styles.checkIcon} className={styles.checkIcon}
data-flx="ui.checkbox.checkbox.check-icon" data-flx="ui.checkbox.checkbox.check-icon"
/> />
@@ -224,7 +223,6 @@ export const Checkbox: React.FC<CheckboxProps> = observer(
<CheckIcon <CheckIcon
size={checkIconSizeRem} size={checkIconSizeRem}
weight="bold" weight="bold"
color={'#ffffff'}
className={styles.checkIcon} className={styles.checkIcon}
data-flx="ui.checkbox.checkbox.check-icon--2" data-flx="ui.checkbox.checkbox.check-icon--2"
/> />
@@ -46,7 +46,7 @@
.pageButtonActive { .pageButtonActive {
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: white; color: var(--brand-primary-fill);
} }
.pageButtonActive:hover { .pageButtonActive:hover {
@@ -160,7 +160,7 @@
.barFill { .barFill {
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
var(--brand-primary, #5865f2), var(--brand-primary-foreground, #5865f2),
color-mix(in srgb, var(--brand-primary-light, #7983f5) 84%, #fff 16%) color-mix(in srgb, var(--brand-primary-light, #7983f5) 84%, #fff 16%)
); );
border-radius: inherit; border-radius: inherit;
@@ -171,7 +171,7 @@
.vertical .barFill { .vertical .barFill {
background: linear-gradient( background: linear-gradient(
0deg, 0deg,
var(--brand-primary, #5865f2), var(--brand-primary-foreground, #5865f2),
color-mix(in srgb, var(--brand-primary-light, #7983f5) 84%, #fff 16%) color-mix(in srgb, var(--brand-primary-light, #7983f5) 84%, #fff 16%)
); );
width: 100%; width: 100%;
@@ -180,8 +180,8 @@
:global(.theme-light) .barFill { :global(.theme-light) .barFill {
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
var(--brand-primary, #5865f2), var(--brand-primary-foreground, #5865f2),
color-mix(in srgb, var(--brand-primary, #5865f2) 72%, #111 28%) color-mix(in srgb, var(--brand-primary-foreground, #5865f2) 72%, #111 28%)
); );
box-shadow: none; box-shadow: none;
} }
@@ -189,8 +189,8 @@
:global(.theme-light) .vertical .barFill { :global(.theme-light) .vertical .barFill {
background: linear-gradient( background: linear-gradient(
0deg, 0deg,
var(--brand-primary, #5865f2), var(--brand-primary-foreground, #5865f2),
color-mix(in srgb, var(--brand-primary, #5865f2) 72%, #111 28%) color-mix(in srgb, var(--brand-primary-foreground, #5865f2) 72%, #111 28%)
); );
} }
@@ -273,13 +273,13 @@
.rdpToday .rdpDayButton { .rdpToday .rdpDayButton {
font-weight: 700; font-weight: 700;
text-decoration: underline; text-decoration: underline;
text-decoration-color: var(--brand-primary); text-decoration-color: var(--brand-primary-foreground);
text-underline-offset: 0.1875rem; text-underline-offset: 0.1875rem;
} }
.rdpSelected .rdpDayButton { .rdpSelected .rdpDayButton {
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: #fff; color: var(--brand-primary-fill);
font-weight: 600; font-weight: 600;
} }
@@ -364,13 +364,17 @@
.itemSelected { .itemSelected {
background-color: var(--brand-primary); background-color: var(--brand-primary);
color: #fff; color: var(--brand-primary-fill);
} }
.itemSelected.itemHighlighted { .itemSelected.itemHighlighted {
background-color: var(--brand-primary); background-color: var(--brand-primary);
} }
.itemSelected .itemText :where(*) {
color: inherit;
}
.itemRendered { .itemRendered {
position: relative; position: relative;
} }
@@ -116,6 +116,7 @@
} }
.switchThumb[data-state='checked'] { .switchThumb[data-state='checked'] {
background-color: var(--brand-primary-fill);
transform: translateX(1.375rem); transform: translateX(1.375rem);
} }
@@ -42,5 +42,5 @@
:global(.theme-light) { :global(.theme-light) {
--keybind-hint-default-background: hsl(0, 0%, 18%); --keybind-hint-default-background: hsl(0, 0%, 18%);
--keybind-hint-default-color: var(--text-on-brand-primary); --keybind-hint-default-color: var(--text-on-dark);
} }
@@ -84,14 +84,14 @@
} }
.checkbox.checked { .checkbox.checked {
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
background-color: var(--brand-primary); background-color: var(--brand-primary);
} }
.checkIcon { .checkIcon {
height: 0.75rem; height: 0.75rem;
width: 0.75rem; width: 0.75rem;
color: white; color: var(--brand-primary-fill);
} }
.radioContainer { .radioContainer {
@@ -115,14 +115,14 @@
} }
.radio.radioSelected { .radio.radioSelected {
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
} }
.radioInner { .radioInner {
height: 0.625rem; height: 0.625rem;
width: 0.625rem; width: 0.625rem;
border-radius: 50%; border-radius: 50%;
background-color: var(--brand-primary); background-color: var(--brand-primary-foreground);
} }
.labelColumn { .labelColumn {
@@ -12,7 +12,7 @@
.icon { .icon {
flex-shrink: 0; flex-shrink: 0;
margin-top: 0.125rem; margin-top: 0.125rem;
color: white; color: var(--brand-primary-fill);
} }
.content { .content {
@@ -25,7 +25,7 @@
.text { .text {
font-size: 0.8125rem; font-size: 0.8125rem;
color: white; color: var(--brand-primary-fill);
margin: 0; margin: 0;
line-height: 1.4; line-height: 1.4;
} }
@@ -40,7 +40,7 @@
.dismissLink { .dismissLink {
font-size: 0.75rem; font-size: 0.75rem;
color: rgba(255, 255, 255, 0.7); color: color-mix(in srgb, var(--brand-primary-fill) 70%, transparent);
background: none; background: none;
border: none; border: none;
padding: 0; padding: 0;
@@ -49,6 +49,6 @@
} }
.dismissLink:hover { .dismissLink:hover {
color: white; color: var(--brand-primary-fill);
text-decoration: underline; text-decoration: underline;
} }
@@ -68,7 +68,7 @@
} }
.optionSelected { .optionSelected {
box-shadow: inset 0 0 0 0.0625rem var(--brand-primary); box-shadow: inset 0 0 0 0.0625rem var(--brand-primary-foreground);
} }
.emptyState { .emptyState {
@@ -7,17 +7,17 @@
gap: var(--spacing-1-5); gap: var(--spacing-1-5);
--radio-indicator-size: 1.125rem; --radio-indicator-size: 1.125rem;
--radio-indicator-border: color-mix(in srgb, var(--border-color) 70%, #fff 30%); --radio-indicator-border: color-mix(in srgb, var(--border-color) 70%, #fff 30%);
--radio-indicator-border-selected: var(--brand-primary); --radio-indicator-border-selected: var(--brand-primary-foreground);
--radio-dot-bg: var(--brand-primary); --radio-dot-bg: var(--brand-primary);
--radio-dot-fill: #fff; --radio-dot-fill: var(--brand-primary-fill);
--radio-transition: var(--transition-normal, 150ms ease); --radio-transition: var(--transition-normal, 150ms ease);
} }
:global(.theme-light) .group { :global(.theme-light) .group {
--radio-indicator-border: color-mix(in srgb, var(--text-secondary) 70%, #000 30%); --radio-indicator-border: color-mix(in srgb, var(--text-secondary) 70%, #000 30%);
--radio-indicator-border-selected: color-mix(in srgb, var(--brand-primary) 85%, var(--text-primary) 15%); --radio-indicator-border-selected: color-mix(in srgb, var(--brand-primary-foreground) 85%, var(--text-primary) 15%);
--radio-dot-bg: var(--brand-primary); --radio-dot-bg: var(--brand-primary);
--radio-dot-fill: #fff; --radio-dot-fill: var(--brand-primary-fill);
} }
.radioGroupOption { .radioGroupOption {
@@ -34,6 +34,6 @@
} }
:global(.theme-light) .tab.selected { :global(.theme-light) .tab.selected {
color: var(--brand-primary); color: var(--brand-primary-foreground);
border-bottom-color: var(--brand-primary); border-bottom-color: var(--brand-primary-foreground);
} }
@@ -99,7 +99,7 @@
.checkIcon { .checkIcon {
width: 0.75rem; width: 0.75rem;
height: 0.75rem; height: 0.75rem;
color: #fff; color: var(--brand-primary-fill);
} }
.chevronIcon { .chevronIcon {
@@ -185,12 +185,12 @@
.editProfileIcon { .editProfileIcon {
height: 1.25rem; height: 1.25rem;
width: 1.25rem; width: 1.25rem;
color: white; color: var(--brand-primary-fill);
} }
.editProfileText { .editProfileText {
font-weight: 500; font-weight: 500;
color: white; color: var(--brand-primary-fill);
} }
.actionButtonsContainer { .actionButtonsContainer {
@@ -234,7 +234,7 @@
.actionIcon { .actionIcon {
height: 1.5rem; height: 1.5rem;
width: 1.5rem; width: 1.5rem;
color: white; color: var(--brand-primary-fill);
} }
.actionIconSecondary { .actionIconSecondary {
@@ -143,9 +143,9 @@
} }
.dropIndicatorTop { .dropIndicatorTop {
border-top: 0.125rem solid var(--brand-primary); border-top: 0.125rem solid var(--brand-primary-foreground);
} }
.dropIndicatorBottom { .dropIndicatorBottom {
border-bottom: 0.125rem solid var(--brand-primary); border-bottom: 0.125rem solid var(--brand-primary-foreground);
} }
@@ -20,7 +20,7 @@
} }
.themeButtonSelected { .themeButtonSelected {
border-color: var(--brand-primary); border-color: var(--brand-primary-foreground);
} }
.themeButtonLight { .themeButtonLight {
@@ -59,5 +59,5 @@
} }
.themeButtonCheckmarkIcon { .themeButtonCheckmarkIcon {
color: white; color: var(--brand-primary-fill);
} }
@@ -86,7 +86,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: white; color: var(--brand-primary-fill);
font-weight: 600; font-weight: 600;
font-size: 0.875rem; font-size: 0.875rem;
} }
@@ -288,7 +288,7 @@
color-mix(in srgb, var(--brand-primary) 90%, transparent), color-mix(in srgb, var(--brand-primary) 90%, transparent),
color-mix(in srgb, var(--brand-secondary) 85%, transparent) color-mix(in srgb, var(--brand-secondary) 85%, transparent)
); );
color: white; color: var(--brand-primary-fill);
font-weight: 800; font-weight: 800;
font-size: 1.85rem; font-size: 1.85rem;
} }
@@ -49,7 +49,7 @@
} }
:global(.theme-light) .sequenceBadge { :global(.theme-light) .sequenceBadge {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
.sequenceBadgeMobile { .sequenceBadgeMobile {
@@ -4,7 +4,7 @@
--profile-skeleton-bg: var(--background-primary); --profile-skeleton-bg: var(--background-primary);
--profile-skeleton-fill: var(--background-secondary-alt); --profile-skeleton-fill: var(--background-secondary-alt);
--profile-skeleton-fill-strong: var(--background-tertiary); --profile-skeleton-fill-strong: var(--background-tertiary);
--profile-skeleton-border-color: var(--brand-primary); --profile-skeleton-border-color: var(--brand-primary-foreground);
--profile-skeleton-radius: 0.375rem; --profile-skeleton-radius: 0.375rem;
--profile-skeleton-border-width: 0px; --profile-skeleton-border-width: 0px;
--profile-skeleton-width: 100%; --profile-skeleton-width: 100%;
@@ -18,7 +18,7 @@
.new, .new,
.experimental { .experimental {
background: var(--brand-primary); background: var(--brand-primary);
color: #fff; color: var(--brand-primary-fill);
} }
.badges { .badges {
@@ -54,6 +54,10 @@
color: white; color: white;
} }
.iconContainerBrand .actionIcon {
color: var(--brand-primary-fill);
}
.actionIconSecondary { .actionIconSecondary {
color: var(--text-primary); color: var(--text-primary);
} }
@@ -80,6 +80,7 @@
.playButton:hover { .playButton:hover {
background-color: var(--brand-primary-light); background-color: var(--brand-primary-light);
color: var(--text-on-brand-primary-light);
} }
.playButton:active { .playButton:active {
@@ -56,7 +56,7 @@
inset: -0.1875rem; inset: -0.1875rem;
border-radius: var(--radius-full); border-radius: var(--radius-full);
border: 0.125rem solid transparent; border: 0.125rem solid transparent;
border-top-color: var(--text-on-brand-primary); border-top-color: var(--brand-primary-foreground);
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
pointer-events: none; pointer-events: none;
} }
@@ -201,11 +201,15 @@
inset: -0.1875rem; inset: -0.1875rem;
border-radius: var(--radius-full); border-radius: var(--radius-full);
border: 0.125rem solid transparent; border: 0.125rem solid transparent;
border-top-color: var(--text-on-brand-primary); border-top-color: var(--brand-primary-foreground);
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
pointer-events: none; pointer-events: none;
} }
.mobileContainerActive .loadingSpinner {
border-top-color: var(--text-on-brand-primary);
}
@keyframes spin { @keyframes spin {
from { from {
transform: rotate(0deg); transform: rotate(0deg);
@@ -292,5 +296,5 @@
} }
:global(.theme-light) .mobileProgressFill:not(.mobileProgressFillActive) { :global(.theme-light) .mobileProgressFill:not(.mobileProgressFillActive) {
background-color: var(--brand-primary); background-color: var(--brand-primary-foreground);
} }
@@ -56,7 +56,7 @@
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6);
border-radius: 50%; border-radius: 50%;
padding: 1rem; padding: 1rem;
color: var(--text-on-brand-primary); color: var(--text-on-dark);
transition: transition:
background-color 150ms ease, background-color 150ms ease,
transform 150ms ease; transform 150ms ease;
@@ -66,5 +66,5 @@
} }
:global(.theme-light) .active { :global(.theme-light) .active {
color: var(--brand-primary); color: var(--brand-primary-foreground);
} }
@@ -144,7 +144,7 @@
:global(.theme-light) .fill, :global(.theme-light) .fill,
:global(.theme-light) .thumb { :global(.theme-light) .thumb {
background-color: var(--brand-primary); background-color: var(--brand-primary-foreground);
} }
.compact .thumb { .compact .thumb {
Loaded 100 of 102 files, more files were not shown because too many files have changed in this diff. Show more