From 33fcd7d1bd722d510b9380957cd3f703a9cee831 Mon Sep 17 00:00:00 2001 From: senke Date: Mon, 27 Apr 2026 17:08:17 +0200 Subject: [PATCH] feat(branding): scaffold Logo component + Sumi icons + brand assets pipeline (Sprint 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sprint 3 = production assets (logo, icons, hero, textures). Most deliverables are physical artistic work (artist Renaud + Nikola scans). This commit lays the CODE scaffold so assets drop in without friction when delivered. New : apps/web/src/components/branding/ - Logo.tsx — single source of truth for Talas / Veza brand rendering. Replaces ad-hoc inline wordmarks (Sidebar/Navbar/Footer/landing each had their own VEZA

). Variants: wordmark / symbol / lockup. Sizes xs..xl. Colors auto/ink/cyan/inverse. Optional tagline. Horizontal/vertical orient. - assets/SymbolPlaceholder.tsx — geometric ink stroke + arc + dot, monochrome, currentColor inheritance, scalable. Mirrors charte §3.1 brief. Replaced by artist's hand-drawn mark in P0.1 of BRIEF_ARTISTE. - Logo.stories.tsx — full Storybook coverage: variants, sizes, colors, orientation, Talas vs Veza, all-sizes ladder. - index.ts — barrel exports. New : apps/web/src/components/icons/sumi/ - Play.tsx — first calligraphic icon stub (programmatic approximation per charte §6.3). 9 more to come (Pause, Search, Profile, Chat, Upload, Settings, Home, Close, Volume). - index.ts — barrel + commented TODO list per priority. - Used via existing components/icons/SumiIcon.tsx wrapper which falls back to Lucide when no Sumi version exists. Brand alignment of platform metadata : - public/favicon.svg — Mizu cyan placeholder (#0098B5) replacing default vite.svg. Mirrors SymbolPlaceholder geometry. - public/manifest.json — theme_color #1a1a1a -> #0098B5 (SUMI accent), background_color #ffffff -> #0D0D0F (charte §4.4 rule 1: no pure white). - index.html — theme-color meta + msapplication-TileColor aligned to SUMI. Favicon link points to /favicon.svg. New doc : apps/web/docs/BRANDING.md - Architecture map of brand assets in apps/web. - Logo component API + usage examples. - Asset deliverables status table (P0/P1/P2 from brief artiste, all 🟡 placeholders). - Naming convention for raw scans + processed SVGs. - Step-by-step "how to integrate a delivered asset" for wordmark and Sumi icon. - Brand color guard (ESLint rule pointer). Build OK (vite 12.6s). Typecheck clean. No visual regression — Sidebar/Navbar inline wordmarks intentionally NOT migrated yet (they use fontWeight 300 which contradicts charte's Bold requirement; a per-screen migration call later). Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/docs/BRANDING.md | 149 +++++++++++++++ apps/web/index.html | 7 +- apps/web/public/favicon.svg | 13 ++ apps/web/public/manifest.json | 4 +- .../src/components/branding/Logo.stories.tsx | 125 +++++++++++++ apps/web/src/components/branding/Logo.tsx | 170 ++++++++++++++++++ .../branding/assets/SymbolPlaceholder.tsx | 52 ++++++ apps/web/src/components/branding/index.ts | 2 + apps/web/src/components/icons/sumi/Play.tsx | 25 +++ apps/web/src/components/icons/sumi/index.ts | 42 +++++ 10 files changed, 584 insertions(+), 5 deletions(-) create mode 100644 apps/web/docs/BRANDING.md create mode 100644 apps/web/public/favicon.svg create mode 100644 apps/web/src/components/branding/Logo.stories.tsx create mode 100644 apps/web/src/components/branding/Logo.tsx create mode 100644 apps/web/src/components/branding/assets/SymbolPlaceholder.tsx create mode 100644 apps/web/src/components/branding/index.ts create mode 100644 apps/web/src/components/icons/sumi/Play.tsx create mode 100644 apps/web/src/components/icons/sumi/index.ts diff --git a/apps/web/docs/BRANDING.md b/apps/web/docs/BRANDING.md new file mode 100644 index 000000000..4321fa3d6 --- /dev/null +++ b/apps/web/docs/BRANDING.md @@ -0,0 +1,149 @@ +# Branding & assets pipeline — apps/web + +Single source of truth for how Talas / Veza brand assets enter the codebase. +Reference brand spec : [`CHARTE_GRAPHIQUE_TALAS.md`](../../../../Documents/TG__Talas_Group/05_EXPERIENCE_UTILISATEUR/CHARTE_GRAPHIQUE_TALAS.md). + +--- + +## Architecture + +``` +apps/web/ +├── public/ +│ ├── favicon.svg # SVG favicon (Mizu cyan placeholder) +│ ├── icons/ # PWA icons (PNG, 72x72 to 512x512) +│ ├── fonts/ # Self-hosted woff2 (Space Grotesk, Inter, JetBrains Mono) +│ └── manifest.json # PWA manifest (theme_color = #0098B5 SUMI accent) +└── src/ + ├── components/ + │ ├── branding/ + │ │ ├── Logo.tsx # SOLE entry point for Talas / Veza wordmark + symbol + │ │ ├── Logo.stories.tsx + │ │ ├── assets/ + │ │ │ ├── SymbolPlaceholder.tsx # Geometric placeholder, swap for hand-drawn + │ │ │ ├── TalasWordmark.tsx # (P0.1 artist deliverable — 3 variants) + │ │ │ └── VezaWordmark.tsx # (P1.1 artist deliverable — 1 variant) + │ │ └── index.ts + │ └── icons/ + │ ├── SumiIcon.tsx # Wrapper : prefers hand-drawn, falls back to Lucide + │ └── sumi/ # Hand-drawn calligraphic icons (10 prioritaires) + │ ├── Play.tsx + │ ├── Pause.tsx (TODO) + │ └── ... +``` + +--- + +## Logo component + +**Always use ``** instead of inline `

VEZA

` style markup. + +```tsx +import { Logo } from '@/components/branding'; + +// Default (wordmark, md, theme-aware color) + + +// Lockup with tagline + + +// Symbol only (favicon-style usage) + + +// Cyan accent + +``` + +API : see [Logo.stories.tsx](../src/components/branding/Logo.stories.tsx) for all variants in Storybook. + +--- + +## Asset deliverables — current status + +Per `BRIEF_ARTISTE_IDENTITE_VISUELLE.md` (artist Renaud, 15 avril 2026) and Sprint 3 : + +| Asset | Priority | Status | Location | +|-------|----------|--------|----------| +| TALAS wordmark × 3 (propre, sauvage, vertical) | P0.1 | ⏳ awaiting artist | `branding/assets/TalasWordmark.tsx` (pending) | +| Hero image post-apo | P0.2 | ⏳ awaiting artist | `public/hero/` (pending) | +| VEZA wordmark × 1 (tag fluide) | P1.1 | ⏳ awaiting artist | `branding/assets/VezaWordmark.tsx` (pending) | +| 3-5 textures de liaison | P1.2 | ⏳ awaiting artist | `public/textures/` (pending) | +| 3 symboles iconiques (enso, onde, libre) | P1.3 | ⏳ awaiting artist | `branding/assets/Symbol.tsx` (pending) | +| Talas symbole (calligraphique) | — | 🟡 placeholder | `branding/assets/SymbolPlaceholder.tsx` | +| Favicon SVG | — | 🟡 placeholder | `public/favicon.svg` | +| 10 Sumi icons (play/pause/search/...) | — | 🟡 1/10 stubbed | `components/icons/sumi/` | +| washi.png texture | — | ✅ inline SVG (feTurbulence) | `src/index.css:456` (no external file) | +| Fonts (Space Grotesk + Inter + JetBrains Mono) | — | ✅ self-hosted | `public/fonts/*.woff2` | +| PWA icons (PNG, 9 sizes) | — | 🟡 generic placeholders | `public/icons/icon-*.png` | + +### Naming convention + +- Wordmarks : `{brand}_wordmark_{variant}.svg` then exported as React component + - Example : `talas_wordmark_propre.svg` → `TalasWordmarkPropre.tsx` +- Symbols : `{brand}_symbol_{type}.svg` +- Hero / textures : `{kind}_{number}.png` (raw scans), processed to `webp` for prod +- Always store source SVGs (vectorized) ; processed bitmaps in build + +### Format requirements (per BRIEF_ARTISTE §5) + +- **Scan minimum 600 DPI** (1200 if available). PNG/TIFF only — no JPG (bleeding edges on ink). +- **One artwork per file**. Naming : `talas_wordmark_sauvage_01.png` etc. +- **No retouching** before delivery — clean fond, niveaux, détourage handled in apps/web preprocessing. +- **Paper white** (not cream) ; **encre de Chine** (not brown-tinted black) ; aquarelle limited to terreuse palette. + +--- + +## How to integrate a delivered asset + +### Wordmark (e.g. TALAS propre) + +1. Receive `talas_wordmark_propre_01.png` (scan 600+ DPI). +2. Clean fond + isolate ink in Inkscape : `File → Import → Select-by-color (white) → Delete → Trace bitmap`. +3. Export SVG with `currentColor` fills + transparent background. +4. Save as `apps/web/src/components/branding/assets/TalasWordmark.tsx` : + + ```tsx + import type { SVGProps } from 'react'; + export default function TalasWordmark(props: SVGProps) { + return ( + + {/* Pasted SVG paths here, fills set to currentColor */} + + ); + } + ``` + +5. Update `Logo.tsx` to use `` for `brand='talas'` instead of the + text fallback. (Detect via prop or via fallback chain.) +6. Storybook will show it automatically. + +### Sumi icon (e.g. Pause) + +1. Receive `pause_01.png` from artist. +2. Vectorize manually in Inkscape (no auto-trace — preserves irregularity). +3. Save as `apps/web/src/components/icons/sumi/Pause.tsx`. +4. Add export to `components/icons/sumi/index.ts`. +5. At call site : + + ```tsx + import { SumiIcon } from '@/components/icons/SumiIcon'; + import { PauseIcon } from '@/components/icons/sumi'; + import { Pause } from 'lucide-react'; + + + ``` + +The `SumiIcon` wrapper handles the "use hand-drawn if available, else Lucide +fallback" logic, so you can drop hand-drawn icons in progressively. + +--- + +## Brand color guard + +ESLint rule (`eslint.config.js` `no-restricted-syntax` for hex literals) blocks +new hardcoded colors. To fix a warning : + +- CSS context (JSX style/className/template literal) : use `var(--sumi-*)`. +- TS / canvas context : `import { ColorVizIndigo } from '@veza/design-system/tokens-generated';`. + +Source of truth for all colors : `packages/design-system/tokens/primitive/color.json`. diff --git a/apps/web/index.html b/apps/web/index.html index e063419ae..31df1eb69 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -3,7 +3,7 @@ - + Veza - Plateforme de streaming musical @@ -18,7 +18,8 @@ - + + @@ -36,7 +37,7 @@ - + diff --git a/apps/web/public/favicon.svg b/apps/web/public/favicon.svg new file mode 100644 index 000000000..055b3705b --- /dev/null +++ b/apps/web/public/favicon.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/apps/web/public/manifest.json b/apps/web/public/manifest.json index 9e81e157c..7c714b5e6 100644 --- a/apps/web/public/manifest.json +++ b/apps/web/public/manifest.json @@ -2,8 +2,8 @@ "name": "Veza Platform", "short_name": "Veza", "description": "Plateforme de streaming, collaboration et distribution musicale moderne", - "theme_color": "#1a1a1a", - "background_color": "#ffffff", + "theme_color": "#0098B5", + "background_color": "#0D0D0F", "display": "standalone", "orientation": "portrait", "scope": "/", diff --git a/apps/web/src/components/branding/Logo.stories.tsx b/apps/web/src/components/branding/Logo.stories.tsx new file mode 100644 index 000000000..fbd25588e --- /dev/null +++ b/apps/web/src/components/branding/Logo.stories.tsx @@ -0,0 +1,125 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Logo } from './Logo'; + +const meta = { + title: 'Branding/Logo', + component: Logo, + parameters: { + layout: 'centered', + docs: { + description: { + component: + 'Single source of truth for Talas / Veza brand rendering. Use this component everywhere a wordmark, symbol, or lockup is needed. See `CHARTE_GRAPHIQUE_TALAS.md §3` for the logo specifications. The current SVG symbol is a placeholder until the artist (Renaud, P0.1) delivers the hand-drawn calligraphic mark.', + }, + }, + }, + argTypes: { + brand: { + control: 'inline-radio', + options: ['talas', 'veza'], + }, + variant: { + control: 'inline-radio', + options: ['wordmark', 'symbol', 'lockup'], + }, + size: { + control: 'inline-radio', + options: ['xs', 'sm', 'md', 'lg', 'xl'], + }, + color: { + control: 'inline-radio', + options: ['auto', 'ink', 'cyan', 'inverse'], + }, + orientation: { + control: 'inline-radio', + options: ['horizontal', 'vertical'], + }, + }, + args: { + brand: 'veza', + variant: 'wordmark', + size: 'md', + color: 'auto', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const VezaWordmark: Story = { + args: { brand: 'veza', variant: 'wordmark', size: 'lg' }, +}; + +export const TalasWordmark: Story = { + args: { brand: 'talas', variant: 'wordmark', size: 'lg' }, +}; + +export const Symbol: Story = { + args: { brand: 'talas', variant: 'symbol', size: 'xl' }, +}; + +export const LockupHorizontal: Story = { + args: { brand: 'veza', variant: 'lockup', size: 'lg', tagline: 'STREAMING' }, +}; + +export const LockupVertical: Story = { + args: { brand: 'talas', variant: 'lockup', size: 'lg', orientation: 'vertical' }, +}; + +export const Cyan: Story = { + args: { brand: 'veza', variant: 'lockup', size: 'lg', color: 'cyan', tagline: 'BETA' }, +}; + +export const Inverse: Story = { + args: { brand: 'talas', variant: 'wordmark', size: 'xl', color: 'inverse' }, + parameters: { backgrounds: { default: 'dark' } }, +}; + +export const AllSizes: Story = { + render: () => ( +
+ {(['xs', 'sm', 'md', 'lg', 'xl'] as const).map((size) => ( +
+ {size} + +
+ ))} +
+ ), +}; + +export const AllVariants: Story = { + render: () => ( +
+
+ Wordmark + +
+
+ Symbol + +
+
+ Lockup + +
+
+ ), +}; + +export const TalasVsVeza: Story = { + render: () => ( +
+
+ Talas (mother brand) + +
+
+ Veza (sub-brand) + +
+
+ ), +}; diff --git a/apps/web/src/components/branding/Logo.tsx b/apps/web/src/components/branding/Logo.tsx new file mode 100644 index 000000000..5d7d4596d --- /dev/null +++ b/apps/web/src/components/branding/Logo.tsx @@ -0,0 +1,170 @@ +import { cn } from '@/lib/utils'; +import SymbolPlaceholder from './assets/SymbolPlaceholder'; + +export type LogoBrand = 'talas' | 'veza'; +export type LogoVariant = 'wordmark' | 'symbol' | 'lockup'; +export type LogoSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; +export type LogoColor = 'auto' | 'ink' | 'cyan' | 'inverse'; + +export interface LogoProps { + /** Which brand to render. Default 'veza' (sub-brand for the web app). */ + brand?: LogoBrand; + /** Visual variant : wordmark only, symbol only, or both. Default 'wordmark'. */ + variant?: LogoVariant; + /** Size — drives the wordmark font-size and symbol box. Default 'md'. */ + size?: LogoSize; + /** Color treatment : + * - 'auto' : inherits text-foreground (default, theme-aware) + * - 'ink' : forced black ink (--sumi-text-primary) + * - 'cyan' : Mizu cyan accent (--sumi-accent) + * - 'inverse' : washi paper (--sumi-text-inverse — light text on dark bg) + */ + color?: LogoColor; + /** Optional tagline rendered below the wordmark (e.g. "Spectre Astral"). */ + tagline?: string; + /** Layout direction for lockup variant. Default 'horizontal'. */ + orientation?: 'horizontal' | 'vertical'; + className?: string; + /** Override the accessible label. Default = brand name. */ + 'aria-label'?: string; +} + +const SIZE_TO_TEXT_CLASS: Record = { + xs: 'text-xs', + sm: 'text-sm', + md: 'text-base', + lg: 'text-xl', + xl: 'text-3xl', +}; + +const SIZE_TO_SYMBOL_PX: Record = { + xs: 14, + sm: 18, + md: 24, + lg: 32, + xl: 48, +}; + +const SIZE_TO_TAGLINE_CLASS: Record = { + xs: 'text-[8px]', + sm: 'text-[9px]', + md: 'text-[10px]', + lg: 'text-xs', + xl: 'text-sm', +}; + +const COLOR_TO_CLASS: Record = { + auto: 'text-foreground', + ink: 'text-[var(--sumi-text-primary)]', + cyan: 'text-[var(--sumi-accent)]', + inverse: 'text-[var(--sumi-text-inverse)]', +}; + +const BRAND_TO_LABEL: Record = { + talas: 'TALAS', + veza: 'VEZA', +}; + +/** + * Logo — single source of truth for rendering the Talas / Veza brand. + * + * Replaces ad-hoc inline wordmarks scattered across Sidebar, Navbar, Footer, + * landing pages, etc. Aligns with CHARTE_GRAPHIQUE_TALAS §3 (logo specs). + * + * Currently the wordmark renders as Space Grotesk Bold + uppercase + wide + * letter-spacing, per the charte. When the artist's hand-drawn wordmarks + * arrive (P0.1), this component will swap to consume the SVG assets via + * imports from './assets/{Brand}Wordmark.tsx'. + * + * The symbol uses a placeholder (assets/SymbolPlaceholder.tsx) until the real + * calligraphic mark is delivered. + * + * @example + * + * + * + */ +export function Logo({ + brand = 'veza', + variant = 'wordmark', + size = 'md', + color = 'auto', + tagline, + orientation = 'horizontal', + className, + 'aria-label': ariaLabel, +}: LogoProps) { + const label = BRAND_TO_LABEL[brand]; + const accessibleLabel = ariaLabel ?? `${label} logo`; + const colorClass = COLOR_TO_CLASS[color]; + + const wordmark = ( + + {label} + + ); + + const symbol = ( + + ); + + const taglineEl = tagline && ( + + {tagline} + + ); + + if (variant === 'symbol') { + return ( + + {symbol} + + ); + } + + if (variant === 'wordmark') { + return ( + + {wordmark} + {taglineEl} + + ); + } + + // variant === 'lockup' + return ( + + {symbol} + + {wordmark} + {taglineEl} + + + ); +} + +export default Logo; diff --git a/apps/web/src/components/branding/assets/SymbolPlaceholder.tsx b/apps/web/src/components/branding/assets/SymbolPlaceholder.tsx new file mode 100644 index 000000000..71cf4d16c --- /dev/null +++ b/apps/web/src/components/branding/assets/SymbolPlaceholder.tsx @@ -0,0 +1,52 @@ +import type { SVGProps } from 'react'; + +/** + * Talas symbol — placeholder SVG until the artist's hand-drawn version arrives. + * + * The real symbol (per CHARTE_GRAPHIQUE_TALAS §3.1) is a calligraphic gesture + * evoking both an audio waveform and a brush stroke. It will be: + * - Hand-drawn (paper or tablet, then vectorized in Inkscape) + * - Irregular (imperfections preserved, no auto-smoothing) + * - Monochrome (currentColor) + * - Functional from 16x16 (favicon) to engraving size + * + * This placeholder is geometric: an asymmetric ink stroke crossed by a single + * fluid arc — strict monochrome, currentColor inheritance, scalable. It exists + * to keep the Logo component working until P0.1 of BRIEF_ARTISTE delivers. + * + * To replace : drop the artist's vectorized SVG at + * apps/web/src/components/branding/assets/Symbol.tsx + * exporting a default React component with the same SVGProps signature, and + * update Logo.tsx to import from './assets/Symbol' instead. + */ +export default function SymbolPlaceholder(props: SVGProps) { + return ( + + ); +} diff --git a/apps/web/src/components/branding/index.ts b/apps/web/src/components/branding/index.ts new file mode 100644 index 000000000..4584de7a6 --- /dev/null +++ b/apps/web/src/components/branding/index.ts @@ -0,0 +1,2 @@ +export { Logo, type LogoBrand, type LogoColor, type LogoProps, type LogoSize, type LogoVariant } from './Logo'; +export { default as SymbolPlaceholder } from './assets/SymbolPlaceholder'; diff --git a/apps/web/src/components/icons/sumi/Play.tsx b/apps/web/src/components/icons/sumi/Play.tsx new file mode 100644 index 000000000..90b775da7 --- /dev/null +++ b/apps/web/src/components/icons/sumi/Play.tsx @@ -0,0 +1,25 @@ +import type { SVGProps } from 'react'; + +/** + * Play — Sumi calligraphic icon (placeholder). + * + * Per CHARTE_GRAPHIQUE_TALAS §6.3 : "Triangle en un seul trait rapide". + * + * This file's geometry is a programmatic approximation. The artist (P3 of + * BRIEF_ARTISTE_IDENTITE_VISUELLE) should replace it with a scanned + vectorized + * hand-drawn version that preserves the irregularity of a real brush stroke + * (variable thickness, no auto-smoothing). + */ +export default function PlayIcon(props: SVGProps) { + return ( + + {/* Single closed brush stroke approximating a play triangle */} + + + ); +} diff --git a/apps/web/src/components/icons/sumi/index.ts b/apps/web/src/components/icons/sumi/index.ts new file mode 100644 index 000000000..133fcfdd2 --- /dev/null +++ b/apps/web/src/components/icons/sumi/index.ts @@ -0,0 +1,42 @@ +/** + * Sumi calligraphic icon set — placeholders + delivered. + * + * Per CHARTE_GRAPHIQUE_TALAS §6.3, the design system specifies 10 priority + * icons drawn as brush gestures (variable stroke, irregular, monochrome, + * 24x24 viewBox). Each icon here is an SVG React component consumed via + * SumiIcon (`../SumiIcon.tsx`) which falls back to a Lucide icon when no + * Sumi version exists yet. + * + * Workflow to add a new icon : + * 1. Artist draws the icon on paper (or tablet). Hi-res scan, transparent + * background, monochrome. + * 2. Vectorize manually in Inkscape (no auto-trace — preserves irregularity). + * Export as SVG with currentColor and 24x24 viewBox. + * 3. Save as `apps/web/src/components/icons/sumi/{Name}.tsx`, exporting + * default a React functional component receiving SVGProps. + * 4. Add to the barrel below. + * 5. At call sites : ``. + * + * Priority list (CHARTE_GRAPHIQUE §6.3) : + * ✓ Play — triangle en un seul trait rapide + * ☐ Pause — deux traits verticaux paralleles + * ☐ Search — enso (cercle zen ouvert, non ferme) + * ☐ Profile — capsule de micro (ovale + trait de base) + * ☐ Chat — onde sonore (trois arcs concentriques) + * ☐ Upload — trait ascendant avec goutte au sommet + * ☐ Settings — ensui (cercle + trait directionnel) + * ☐ Home — triangle inverse, montagne minimaliste + * ☐ Close — deux traits croises d'un seul geste + * ☐ Volume — arc de cercle avec diffusion + */ + +export { default as PlayIcon } from './Play'; +// export { default as PauseIcon } from './Pause'; +// export { default as SearchIcon } from './Search'; +// export { default as ProfileIcon } from './Profile'; +// export { default as ChatIcon } from './Chat'; +// export { default as UploadIcon } from './Upload'; +// export { default as SettingsIcon } from './Settings'; +// export { default as HomeIcon } from './Home'; +// export { default as CloseIcon } from './Close'; +// export { default as VolumeIcon } from './Volume';