diff --git a/docs/TASKS/app-shell/004-global-frontend-branding.md b/docs/TASKS/app-shell/004-global-frontend-branding.md new file mode 100644 index 0000000..100f89b --- /dev/null +++ b/docs/TASKS/app-shell/004-global-frontend-branding.md @@ -0,0 +1,26 @@ +# Task: Add global frontend branding configuration + +## Goal + +Centralize product branding for the frontend so product name, visible brand marks, brand assets, and theme colors can be changed from one module instead of being hardcoded across shell and auth surfaces. + +## Relevant Files + +- `frontend/src/branding/branding.js` +- `frontend/src/branding/applyBranding.js` +- `frontend/src/components/branding/BrandMark.vue` +- `frontend/src/components/branding/BrandLogo.vue` +- `frontend/src/main.js` +- `frontend/src/assets/main.css` +- `frontend/src/layouts/main/AppSidebar.vue` +- `frontend/src/static/components/LandingSiteMenu.vue` +- `frontend/src/features/auth/views/RegisterView.vue` +- `frontend/src/features/auth/views/LoginView.vue` +- `frontend/public/images/brand/*` + +## Validation + +```bash +cd frontend +npm run build +``` diff --git a/frontend/public/images/brand/auth-illustration.svg b/frontend/public/images/brand/auth-illustration.svg new file mode 100644 index 0000000..50fcfd3 --- /dev/null +++ b/frontend/public/images/brand/auth-illustration.svg @@ -0,0 +1,16 @@ + + Socialize brand illustration + + + + + + + + + + + + + + diff --git a/frontend/public/images/brand/logo-mark.svg b/frontend/public/images/brand/logo-mark.svg new file mode 100644 index 0000000..f52fed6 --- /dev/null +++ b/frontend/public/images/brand/logo-mark.svg @@ -0,0 +1,11 @@ + + Socialize mark + + + + + + + + + diff --git a/frontend/public/images/brand/logo.svg b/frontend/public/images/brand/logo.svg new file mode 100644 index 0000000..c7f068b --- /dev/null +++ b/frontend/public/images/brand/logo.svg @@ -0,0 +1,12 @@ + + Socialize + + + + + + + + + SOCIALIZE + diff --git a/frontend/src/branding/applyBranding.js b/frontend/src/branding/applyBranding.js new file mode 100644 index 0000000..8825d0a --- /dev/null +++ b/frontend/src/branding/applyBranding.js @@ -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})`; +} diff --git a/frontend/src/branding/branding.js b/frontend/src/branding/branding.js new file mode 100644 index 0000000..ca6d552 --- /dev/null +++ b/frontend/src/branding/branding.js @@ -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, + }; +} diff --git a/frontend/src/components/branding/BrandLogo.vue b/frontend/src/components/branding/BrandLogo.vue new file mode 100644 index 0000000..18c6efd --- /dev/null +++ b/frontend/src/components/branding/BrandLogo.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/frontend/src/components/branding/BrandMark.vue b/frontend/src/components/branding/BrandMark.vue new file mode 100644 index 0000000..21ffacd --- /dev/null +++ b/frontend/src/components/branding/BrandMark.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/frontend/src/features/auth/views/LoginView.vue b/frontend/src/features/auth/views/LoginView.vue index c6746da..52f7704 100644 --- a/frontend/src/features/auth/views/LoginView.vue +++ b/frontend/src/features/auth/views/LoginView.vue @@ -5,8 +5,7 @@ class="login-brand" to="/" > - S - Socialize +