feat: centralize frontend branding
This commit is contained in:
16
frontend/public/images/brand/auth-illustration.svg
Normal file
16
frontend/public/images/brand/auth-illustration.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420 240" role="img" aria-labelledby="title">
|
||||
<title id="title">Socialize brand illustration</title>
|
||||
<defs>
|
||||
<linearGradient id="brand-gradient" x1="102" y1="52" x2="324" y2="194" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#ff8a3d"/>
|
||||
<stop offset="1" stop-color="#ef4444"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="420" height="240" rx="36" fill="#fbfaf6"/>
|
||||
<rect x="58" y="44" width="304" height="152" rx="30" fill="#f4f6f3" stroke="#c7d2cc"/>
|
||||
<rect x="82" y="68" width="112" height="104" rx="28" fill="url(#brand-gradient)"/>
|
||||
<path d="M113 137c5.7 5.8 13.2 8.8 22.5 8.8 10.4 0 16.3-3.4 16.3-9.4 0-5.2-4.4-7.4-16-9.1-15.2-2.3-24.2-7.8-24.2-20.2 0-12.1 10.4-20.3 25.5-20.3 10.9 0 19.2 3.3 25.4 10.1l-9.6 8.9c-4.2-4.3-9.4-6.5-15.7-6.5-6.9 0-10.7 2.7-10.7 7.2 0 4.4 4.4 6.4 15.2 8 15.9 2.4 24.6 8.5 24.6 21.1 0 13.6-11 22-29.4 22-12.8 0-23-4-30.2-12z" fill="#fffaf2"/>
|
||||
<path d="M224 86h72" stroke="#172033" stroke-width="12" stroke-linecap="round"/>
|
||||
<path d="M224 118h112" stroke="#2fa58d" stroke-width="12" stroke-linecap="round"/>
|
||||
<path d="M224 150h84" stroke="#ff8a3d" stroke-width="12" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
11
frontend/public/images/brand/logo-mark.svg
Normal file
11
frontend/public/images/brand/logo-mark.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" role="img" aria-labelledby="title">
|
||||
<title id="title">Socialize mark</title>
|
||||
<defs>
|
||||
<linearGradient id="brand-gradient" x1="16" y1="12" x2="82" y2="88" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#ff8a3d"/>
|
||||
<stop offset="1" stop-color="#ef4444"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="96" height="96" rx="28" fill="url(#brand-gradient)"/>
|
||||
<path d="M31 61.5c3.9 4.3 9.3 6.5 16.2 6.5 7.8 0 12.4-2.7 12.4-7.3 0-4.1-3.4-5.9-12.2-7.2-12.1-1.8-19-6.2-19-16.1 0-9.7 8.2-16.4 20.2-16.4 8.7 0 15.3 2.7 20.3 8.2l-8.3 8c-3.3-3.5-7.5-5.2-12.5-5.2-5.4 0-8.3 2.1-8.3 5.6 0 3.6 3.4 5.1 11.9 6.4 12.7 1.9 19.5 6.8 19.5 16.8 0 11-8.8 17.8-23.4 17.8-10.2 0-18.3-3.2-24.2-9.7z" fill="#fffaf2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 791 B |
12
frontend/public/images/brand/logo.svg
Normal file
12
frontend/public/images/brand/logo.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 96" role="img" aria-labelledby="title">
|
||||
<title id="title">Socialize</title>
|
||||
<defs>
|
||||
<linearGradient id="brand-gradient" x1="16" y1="12" x2="82" y2="88" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#ff8a3d"/>
|
||||
<stop offset="1" stop-color="#ef4444"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="96" height="96" rx="28" fill="url(#brand-gradient)"/>
|
||||
<path d="M31 61.5c3.9 4.3 9.3 6.5 16.2 6.5 7.8 0 12.4-2.7 12.4-7.3 0-4.1-3.4-5.9-12.2-7.2-12.1-1.8-19-6.2-19-16.1 0-9.7 8.2-16.4 20.2-16.4 8.7 0 15.3 2.7 20.3 8.2l-8.3 8c-3.3-3.5-7.5-5.2-12.5-5.2-5.4 0-8.3 2.1-8.3 5.6 0 3.6 3.4 5.1 11.9 6.4 12.7 1.9 19.5 6.8 19.5 16.8 0 11-8.8 17.8-23.4 17.8-10.2 0-18.3-3.2-24.2-9.7z" fill="#fffaf2"/>
|
||||
<text x="126" y="61" fill="#172033" font-family="Inter, Arial, sans-serif" font-size="35" font-weight="900" letter-spacing="6">SOCIALIZE</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 933 B |
63
frontend/src/branding/applyBranding.js
Normal file
63
frontend/src/branding/applyBranding.js
Normal file
@@ -0,0 +1,63 @@
|
||||
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})`;
|
||||
}
|
||||
50
frontend/src/branding/branding.js
Normal file
50
frontend/src/branding/branding.js
Normal file
@@ -0,0 +1,50 @@
|
||||
export const branding = Object.freeze({
|
||||
productName: 'Socialize',
|
||||
shortName: 'S',
|
||||
assets: {
|
||||
logo: '/images/brand/logo.svg',
|
||||
logoMark: '/images/brand/logo-mark.svg',
|
||||
authIllustration: '/images/brand/auth-illustration.svg',
|
||||
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,
|
||||
};
|
||||
}
|
||||
35
frontend/src/components/branding/BrandLogo.vue
Normal file
35
frontend/src/components/branding/BrandLogo.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<span class="brand-logo-root">
|
||||
<img
|
||||
v-if="branding.assets.logo"
|
||||
:src="branding.assets.logo"
|
||||
:alt="branding.productName"
|
||||
class="brand-logo-image"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="brand-logo-text"
|
||||
>
|
||||
{{ branding.productName }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { branding } from '@/branding/branding.js';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.brand-logo-root {
|
||||
@apply inline-flex items-center;
|
||||
}
|
||||
|
||||
.brand-logo-image {
|
||||
@apply block h-auto max-h-full w-auto max-w-full;
|
||||
}
|
||||
|
||||
.brand-logo-text {
|
||||
@apply text-lg font-black uppercase tracking-[0.18em];
|
||||
color: var(--h-primary);
|
||||
}
|
||||
</style>
|
||||
26
frontend/src/components/branding/BrandMark.vue
Normal file
26
frontend/src/components/branding/BrandMark.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<span class="brand-mark-root">
|
||||
<img
|
||||
v-if="branding.assets.logoMark"
|
||||
:src="branding.assets.logoMark"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="brand-mark-image"
|
||||
/>
|
||||
<span v-else>{{ branding.shortName }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { branding } from '@/branding/branding.js';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.brand-mark-root {
|
||||
@apply inline-flex items-center justify-center overflow-hidden;
|
||||
}
|
||||
|
||||
.brand-mark-image {
|
||||
@apply h-full w-full object-contain;
|
||||
}
|
||||
</style>
|
||||
@@ -5,8 +5,7 @@
|
||||
class="login-brand"
|
||||
to="/"
|
||||
>
|
||||
<span class="login-brand-mark">S</span>
|
||||
<span class="login-brand-text">Socialize</span>
|
||||
<BrandLogo class="login-brand-logo" />
|
||||
</router-link>
|
||||
|
||||
<section class="login-card">
|
||||
@@ -131,6 +130,7 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { mdiEye, mdiEyeOff, mdiFacebook, mdiGoogle } from '@mdi/js';
|
||||
import BrandLogo from '@/components/branding/BrandLogo.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
@@ -212,18 +212,11 @@
|
||||
}
|
||||
|
||||
.login-brand {
|
||||
@apply mx-auto flex items-center gap-3 px-4 pt-6 no-underline sm:px-0 sm:pt-0;
|
||||
color: #172033;
|
||||
@apply mx-auto flex items-center px-4 pt-6 no-underline sm:px-0 sm:pt-0;
|
||||
}
|
||||
|
||||
.login-brand-mark {
|
||||
@apply flex h-11 w-11 items-center justify-center rounded-2xl text-lg font-black;
|
||||
background: var(--socialize-brand-gradient);
|
||||
color: #fffaf2;
|
||||
}
|
||||
|
||||
.login-brand-text {
|
||||
@apply text-lg font-black uppercase tracking-[0.18em];
|
||||
.login-brand-logo {
|
||||
@apply h-12 max-w-[180px];
|
||||
}
|
||||
|
||||
.login-card {
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
>
|
||||
<img
|
||||
:alt="t('alt')"
|
||||
src="/images/hutopymedia/loginpage/hutopylogin.svg"
|
||||
:src="branding.assets.authIllustration"
|
||||
class="auth-illustration"
|
||||
/>
|
||||
<div class="flex flex-col gap-10 text-center">
|
||||
<h1 class="login-text text-2xl font-bold text-green-600">
|
||||
@@ -41,7 +42,8 @@
|
||||
>
|
||||
<img
|
||||
:alt="t('alt')"
|
||||
src="/images/hutopymedia/loginpage/hutopylogin.svg"
|
||||
:src="branding.assets.authIllustration"
|
||||
class="auth-illustration"
|
||||
/>
|
||||
<div class="flex flex-col gap-10">
|
||||
<h1 class="login-text text-center text-2xl font-bold">
|
||||
@@ -134,6 +136,7 @@
|
||||
import { useClient } from '@/plugins/api.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { mdiEye, mdiEyeOff } from '@mdi/js';
|
||||
import { branding } from '@/branding/branding.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const clientApi = useClient();
|
||||
@@ -185,6 +188,10 @@
|
||||
@apply z-10;
|
||||
}
|
||||
|
||||
.auth-illustration {
|
||||
@apply h-auto w-full max-w-xs;
|
||||
}
|
||||
|
||||
/* Override Vuetify's default padding to accommodate our icon */
|
||||
:deep(.v-field__append-inner) {
|
||||
padding-inline-start: 0;
|
||||
@@ -195,7 +202,7 @@
|
||||
{
|
||||
"en": {
|
||||
"title": "Create your account",
|
||||
"alt": "Hutopy Registration",
|
||||
"alt": "Socialize registration",
|
||||
"name": "Full Name",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
@@ -215,7 +222,7 @@
|
||||
},
|
||||
"fr": {
|
||||
"title": "Créer votre compte",
|
||||
"alt": "Inscription Hutopy",
|
||||
"alt": "Inscription Socialize",
|
||||
"name": "Nom complet",
|
||||
"email": "Email",
|
||||
"password": "Mot de passe",
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
import { getNotificationRoute } from '@/features/notifications/notificationRoutes.js';
|
||||
import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
|
||||
import { useCampaignsStore } from '@/features/campaigns/stores/campaignsStore.js';
|
||||
import { branding } from '@/branding/branding.js';
|
||||
import BrandMark from '@/components/branding/BrandMark.vue';
|
||||
import SidebarUserMenu from './SidebarUserMenu.vue';
|
||||
import {
|
||||
mdiBellOutline,
|
||||
@@ -253,14 +255,14 @@
|
||||
:class="{ 'brand-link-collapsed': !isExpanded }"
|
||||
to="/"
|
||||
>
|
||||
<span class="brand-mark">S</span>
|
||||
<BrandMark class="brand-mark" />
|
||||
<div
|
||||
v-if="isExpanded"
|
||||
class="brand-copy"
|
||||
>
|
||||
<div class="brand-heading">
|
||||
<span class="brand-name-wrap">
|
||||
<span class="brand-name">Socialize</span>
|
||||
<span class="brand-name">{{ branding.productName }}</span>
|
||||
<span
|
||||
class="brand-stage-badge"
|
||||
:aria-label="t('nav.brandStageLabel')"
|
||||
@@ -664,9 +666,7 @@
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
@apply flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-[1.1rem] text-xl font-black;
|
||||
background: var(--socialize-brand-gradient);
|
||||
color: #fffaf2;
|
||||
@apply h-11 w-11 flex-shrink-0 rounded-[1.1rem];
|
||||
}
|
||||
|
||||
.brand-name-wrap {
|
||||
@@ -675,7 +675,7 @@
|
||||
|
||||
.brand-name {
|
||||
@apply min-w-0 text-lg font-black uppercase tracking-[0.18em];
|
||||
color: #172033;
|
||||
color: var(--h-primary);
|
||||
line-height: 2.75rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,10 @@ import { useChannelsStore } from '@/features/channels/stores/channelsStore.js';
|
||||
import { i18n } from '@/plugins/i18n.js';
|
||||
import config from '@/config.js';
|
||||
import { createHead } from '@vueuse/head';
|
||||
import { applyBranding } from '@/branding/applyBranding.js';
|
||||
import { getVuetifyThemeColors } from '@/branding/branding.js';
|
||||
|
||||
applyBranding();
|
||||
|
||||
const vuetify = createVuetify({
|
||||
components: {
|
||||
@@ -62,17 +66,7 @@ const vuetify = createVuetify({
|
||||
themes: {
|
||||
socializeLight: {
|
||||
dark: false,
|
||||
colors: {
|
||||
background: '#f4f6f3',
|
||||
surface: '#fbfaf6',
|
||||
primary: '#172033',
|
||||
secondary: '#fff3e2',
|
||||
accent: '#ff8a3d',
|
||||
error: '#bc2f2f',
|
||||
info: '#2563eb',
|
||||
success: '#2fa58d',
|
||||
warning: '#b45309',
|
||||
},
|
||||
colors: getVuetifyThemeColors(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
class="site-brand"
|
||||
to="/"
|
||||
>
|
||||
<span class="site-brand-mark">S</span>
|
||||
<BrandMark class="site-brand-mark" />
|
||||
<span class="site-brand-heading">
|
||||
<span class="site-brand-text-wrap">
|
||||
<span class="site-brand-text">Socialize</span>
|
||||
<span class="site-brand-text">{{ branding.productName }}</span>
|
||||
<span
|
||||
class="site-brand-stage-badge"
|
||||
:aria-label="t('nav.brandStageLabel')"
|
||||
@@ -136,6 +136,8 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { productFeatureItems } from '@/static/productFeatures.js';
|
||||
import { branding } from '@/branding/branding.js';
|
||||
import BrandMark from '@/components/branding/BrandMark.vue';
|
||||
|
||||
const allowedLocales = ['en', 'fr'];
|
||||
const localeStorageKey = 'user-locale';
|
||||
@@ -273,13 +275,11 @@
|
||||
|
||||
.site-brand {
|
||||
@apply flex min-w-0 items-start gap-3 no-underline;
|
||||
color: #172033;
|
||||
color: var(--h-primary);
|
||||
}
|
||||
|
||||
.site-brand-mark {
|
||||
@apply flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-2xl text-base font-black;
|
||||
background: var(--socialize-brand-gradient);
|
||||
color: #fffaf2;
|
||||
@apply h-10 w-10 flex-shrink-0 rounded-2xl;
|
||||
}
|
||||
|
||||
.site-brand-heading {
|
||||
|
||||
@@ -8,8 +8,6 @@ export default {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
hutopyPrimary: "var(--hutopy-primary)",
|
||||
hutopySecondary: "var(--hutopy-secondary)",
|
||||
hBackground: "var(--h-background)",
|
||||
hOnBackground: "var(--h-on-background)",
|
||||
hSurface: "var(--h-surface)",
|
||||
@@ -27,4 +25,3 @@ export default {
|
||||
},
|
||||
plugins: []
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user