refactor: simplify frontend theme setup

This commit is contained in:
2026-05-07 16:35:47 -04:00
parent 9768a37252
commit 6ac05e1a10
9 changed files with 70 additions and 199 deletions

View File

@@ -2,80 +2,25 @@
@custom-variant dark (&:where(.dark, .dark *)); @custom-variant dark (&:where(.dark, .dark *));
@theme inline { @theme inline {
--color-hBackground: var(--h-background); --color-hBackground: rgb(var(--v-theme-background));
--color-hOnBackground: var(--h-on-background); --color-hOnBackground: rgb(var(--v-theme-on-background));
--color-hSurface: var(--h-surface); --color-hSurface: rgb(var(--v-theme-surface));
--color-hOnSurface: var(--h-on-surface); --color-hOnSurface: rgb(var(--v-theme-on-surface));
--color-hPrimary: var(--h-primary); --color-hPrimary: rgb(var(--v-theme-primary));
--color-hOnPrimary: var(--h-on-primary); --color-hOnPrimary: rgb(var(--v-theme-on-primary));
--color-hSecondary: var(--h-secondary); --color-hSecondary: rgb(var(--v-theme-secondary));
--color-hOnSecondary: var(--h-on-secondary); --color-hOnSecondary: rgb(var(--v-theme-on-secondary));
--color-hTertiary: var(--h-tertiary); --color-hTertiary: rgb(var(--v-theme-tertiary));
--color-hOnTertiary: var(--h-on-tertiary); --color-hOnTertiary: rgb(var(--v-theme-on-tertiary));
--color-hError: var(--h-error); --color-hError: rgb(var(--v-theme-error));
--color-hOnError: var(--h-on-error); --color-hOnError: rgb(var(--v-theme-on-error));
}
:root {
--socialize-primary: #172033;
--socialize-accent: #ff8a3d;
--socialize-accent-strong: #ef4444;
--socialize-brand-gradient: linear-gradient(135deg, var(--socialize-accent) 0%, var(--socialize-accent-strong) 100%);
--socialize-accent-shadow: rgba(255, 138, 61, 0.28);
--socialize-accent-strong-shadow: rgba(239, 68, 68, 0.28);
--socialize-highlight: #2fa58d;
--h-background: #f4f6f3;
--h-on-background: #172033;
--h-surface: #fbfaf6;
--h-surface-muted: #f1f5f2;
--h-on-surface: #172033;
--h-control: #eef3ef;
--h-control-hover: #e7eee9;
--h-control-focus: #ffffff;
--h-border: #c7d2cc;
--h-border-strong: #94a39d;
--h-primary: #172033;
--h-on-primary: #fbfaf6;
--h-secondary: #fff3e2;
--h-on-secondary: #172033;
--h-tertiary: #d9f6ee;
--h-on-tertiary: #0f766e;
--h-error: #bc2f2f;
--h-on-error: #ffffff;
} }
html, html,
body, body,
#app { #app {
min-height: 100%; min-height: 100%;
background: var(--h-background); background: #f4f6f3;
}
input:not([type='checkbox']):not([type='radio']):not([type='range']):not([type='file']),
select,
textarea {
background-color: var(--h-control) !important;
border-color: var(--h-border) !important;
color: var(--h-on-surface);
}
input:not([type='checkbox']):not([type='radio']):not([type='range']):not([type='file']):hover,
select:hover,
textarea:hover {
background-color: var(--h-control-hover) !important;
border-color: var(--h-border-strong) !important;
}
input:not([type='checkbox']):not([type='radio']):not([type='range']):not([type='file']):focus,
select:focus,
textarea:focus,
input:not([type='checkbox']):not([type='radio']):not([type='range']):not([type='file']):focus-visible,
select:focus-visible,
textarea:focus-visible {
background-color: var(--h-control-focus) !important;
border-color: var(--socialize-highlight) !important;
box-shadow: 0 0 0 3px rgba(47, 165, 141, 0.16);
outline: none;
} }
input::placeholder, input::placeholder,
@@ -85,8 +30,8 @@ textarea::placeholder {
} }
.v-application { .v-application {
background: var(--h-background) !important; background: rgb(var(--v-theme-background)) !important;
color: var(--h-on-background); color: rgb(var(--v-theme-on-background));
} }
.v-card, .v-card,
@@ -94,41 +39,47 @@ textarea::placeholder {
.v-list, .v-list,
.v-menu > .v-overlay__content, .v-menu > .v-overlay__content,
.v-dialog > .v-overlay__content { .v-dialog > .v-overlay__content {
background-color: var(--h-surface) !important; background-color: rgb(var(--v-theme-surface)) !important;
border: 1px solid var(--h-border); border: 1px solid rgb(var(--v-theme-border));
} }
.v-field { .v-field {
background-color: var(--h-control) !important; background-color: rgb(var(--v-theme-control)) !important;
color: var(--h-on-surface); color: rgb(var(--v-theme-on-surface));
} }
.v-field:hover { .v-field:hover {
background-color: var(--h-control-hover) !important; background-color: rgb(var(--v-theme-control-hover)) !important;
} }
.v-field--focused { .v-field--focused {
background-color: var(--h-control-focus) !important; background-color: rgb(var(--v-theme-control-focus)) !important;
} }
.v-field__outline { .v-field__outline {
color: var(--h-border-strong); color: rgb(var(--v-theme-border-strong));
} }
.v-field--focused .v-field__outline { .v-field--focused .v-field__outline {
color: var(--socialize-highlight); color: rgb(var(--v-theme-highlight));
} }
.v-field__input, .v-field__input,
.v-field-label { .v-field-label {
color: var(--h-on-surface); color: rgb(var(--v-theme-on-surface));
}
.v-select .v-field .v-field__input > input,
.v-select .v-field .v-field__input > input::placeholder {
color: transparent !important;
caret-color: transparent;
} }
.panel, .panel,
[class$='-panel'], [class$='-panel'],
[class$='-card'], [class$='-card'],
div.card { div.card {
border-color: var(--h-border) !important; border-color: rgb(var(--v-theme-border)) !important;
} }
@layer components { @layer components {

View File

@@ -1,63 +0,0 @@
import { branding } from './branding.js';
const cssVariableMap = {
'--socialize-primary': 'primary',
'--socialize-accent': 'accent',
'--socialize-accent-strong': 'accentStrong',
'--socialize-highlight': 'highlight',
'--h-background': 'background',
'--h-on-background': 'onBackground',
'--h-surface': 'surface',
'--h-surface-muted': 'surfaceMuted',
'--h-on-surface': 'onSurface',
'--h-control': 'control',
'--h-control-hover': 'controlHover',
'--h-control-focus': 'controlFocus',
'--h-border': 'border',
'--h-border-strong': 'borderStrong',
'--h-primary': 'primary',
'--h-on-primary': 'onPrimary',
'--h-secondary': 'secondary',
'--h-on-secondary': 'onSecondary',
'--h-tertiary': 'tertiary',
'--h-on-tertiary': 'onTertiary',
'--h-error': 'error',
'--h-on-error': 'onError',
};
export function applyBranding(target = getDefaultTarget()) {
if (!target) {
return;
}
Object.entries(cssVariableMap).forEach(([variableName, colorKey]) => {
target.style.setProperty(variableName, branding.colors[colorKey]);
});
target.style.setProperty(
'--socialize-brand-gradient',
`linear-gradient(135deg, ${branding.colors.accent} 0%, ${branding.colors.accentStrong} 100%)`
);
target.style.setProperty('--socialize-accent-shadow', getRgbShadow(branding.colors.accent, 0.28));
target.style.setProperty('--socialize-accent-strong-shadow', getRgbShadow(branding.colors.accentStrong, 0.28));
}
function getDefaultTarget() {
return typeof document === 'undefined'
? null
: document.documentElement;
}
function getRgbShadow(hexColor, opacity) {
const normalizedHex = hexColor.replace('#', '');
if (normalizedHex.length !== 6) {
return hexColor;
}
const red = Number.parseInt(normalizedHex.slice(0, 2), 16);
const green = Number.parseInt(normalizedHex.slice(2, 4), 16);
const blue = Number.parseInt(normalizedHex.slice(4, 6), 16);
return `rgba(${red}, ${green}, ${blue}, ${opacity})`;
}

View File

@@ -7,44 +7,4 @@ export const branding = Object.freeze({
authIllustration: '/images/brand/auth-illustration.svg', authIllustration: '/images/brand/auth-illustration.svg',
favicon: '/favicon.ico', favicon: '/favicon.ico',
}, },
colors: {
background: '#f4f6f3',
onBackground: '#172033',
surface: '#fbfaf6',
surfaceMuted: '#f1f5f2',
onSurface: '#172033',
control: '#eef3ef',
controlHover: '#e7eee9',
controlFocus: '#ffffff',
border: '#c7d2cc',
borderStrong: '#94a39d',
primary: '#172033',
onPrimary: '#fbfaf6',
secondary: '#fff3e2',
onSecondary: '#172033',
tertiary: '#d9f6ee',
onTertiary: '#0f766e',
accent: '#ff8a3d',
accentStrong: '#ef4444',
highlight: '#2fa58d',
error: '#bc2f2f',
onError: '#ffffff',
info: '#2563eb',
success: '#2fa58d',
warning: '#b45309',
},
}); });
export function getVuetifyThemeColors() {
return {
background: branding.colors.background,
surface: branding.colors.surface,
primary: branding.colors.primary,
secondary: branding.colors.secondary,
accent: branding.colors.accent,
error: branding.colors.error,
info: branding.colors.info,
success: branding.colors.success,
warning: branding.colors.warning,
};
}

View File

@@ -31,6 +31,6 @@
.brand-logo-text { .brand-logo-text {
@apply text-lg font-black uppercase tracking-[0.18em]; @apply text-lg font-black uppercase tracking-[0.18em];
color: var(--h-primary); color: rgb(var(--v-theme-primary));
} }
</style> </style>

View File

@@ -36,19 +36,19 @@
.feedback-entry-button { .feedback-entry-button {
@apply flex h-12 items-center gap-2 rounded-full border px-4 text-sm font-bold shadow-lg transition-colors; @apply flex h-12 items-center gap-2 rounded-full border px-4 text-sm font-bold shadow-lg transition-colors;
background: var(--socialize-accent-strong); background: rgb(var(--v-theme-accent-strong));
border-color: rgba(255, 255, 255, 0.55); border-color: rgba(255, 255, 255, 0.55);
color: #ffffff; color: #ffffff;
box-shadow: 0 16px 34px var(--socialize-accent-strong-shadow); box-shadow: 0 16px 34px rgb(var(--v-theme-accent-strong) / 0.28);
} }
.feedback-entry-button:hover { .feedback-entry-button:hover {
background: color-mix(in srgb, var(--socialize-accent-strong) 82%, var(--socialize-primary)); background: color-mix(in srgb, rgb(var(--v-theme-accent-strong)) 82%, rgb(var(--v-theme-primary)));
box-shadow: 0 18px 38px var(--socialize-accent-strong-shadow); box-shadow: 0 18px 38px rgb(var(--v-theme-accent-strong) / 0.28);
} }
.feedback-entry-button:focus-visible { .feedback-entry-button:focus-visible {
outline: 3px solid color-mix(in srgb, var(--socialize-accent) 35%, transparent); outline: 3px solid color-mix(in srgb, rgb(var(--v-theme-accent)) 35%, transparent);
outline-offset: 3px; outline-offset: 3px;
} }

View File

@@ -676,7 +676,7 @@
.brand-name { .brand-name {
@apply min-w-0 text-lg font-black uppercase tracking-[0.18em]; @apply min-w-0 text-lg font-black uppercase tracking-[0.18em];
color: var(--h-primary); color: rgb(var(--v-theme-primary));
line-height: 2.75rem; line-height: 2.75rem;
} }

View File

@@ -35,10 +35,7 @@ import { useChannelsStore } from '@/features/channels/stores/channelsStore.js';
import { i18n } from '@/plugins/i18n.js'; import { i18n } from '@/plugins/i18n.js';
import config from '@/config.js'; import config from '@/config.js';
import { createHead } from '@vueuse/head'; import { createHead } from '@vueuse/head';
import { applyBranding } from '@/branding/applyBranding.js'; import { socializeTheme } from '@/plugins/theme.js';
import { getVuetifyThemeColors } from '@/branding/branding.js';
applyBranding();
const vuetify = createVuetify({ const vuetify = createVuetify({
components: { components: {
@@ -64,10 +61,7 @@ const vuetify = createVuetify({
theme: { theme: {
defaultTheme: 'socializeLight', defaultTheme: 'socializeLight',
themes: { themes: {
socializeLight: { socializeLight: socializeTheme,
dark: false,
colors: getVuetifyThemeColors(),
},
}, },
}, },
}); });

View File

@@ -0,0 +1,29 @@
export const socializeTheme = {
dark: false,
colors: {
background: '#f4f6f3',
'on-background': '#172033',
surface: '#fbfaf6',
'surface-muted': '#f1f5f2',
'on-surface': '#172033',
control: '#eef3ef',
'control-hover': '#e7eee9',
'control-focus': '#ffffff',
border: '#c7d2cc',
'border-strong': '#94a39d',
primary: '#172033',
'on-primary': '#fbfaf6',
secondary: '#fff3e2',
'on-secondary': '#172033',
tertiary: '#d9f6ee',
'on-tertiary': '#0f766e',
accent: '#ff8a3d',
'accent-strong': '#ef4444',
highlight: '#2fa58d',
error: '#bc2f2f',
'on-error': '#ffffff',
info: '#2563eb',
success: '#2fa58d',
warning: '#b45309',
},
};

View File

@@ -276,7 +276,7 @@
.site-brand { .site-brand {
@apply flex min-w-0 items-start gap-3 no-underline; @apply flex min-w-0 items-start gap-3 no-underline;
color: var(--h-primary); color: rgb(var(--v-theme-primary));
} }
.site-brand-mark { .site-brand-mark {