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 (
+
+ );
+ }
+ ```
+
+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 (
+
+ );
+}
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';