feat(branding): scaffold Logo component + Sumi icons + brand assets pipeline (Sprint 3)

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 <h2>). 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) <noreply@anthropic.com>
This commit is contained in:
senke 2026-04-27 17:08:17 +02:00
parent cb511afa6e
commit 33fcd7d1bd
10 changed files with 584 additions and 5 deletions

149
apps/web/docs/BRANDING.md Normal file
View file

@ -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 `<Logo />`** instead of inline `<h2>VEZA</h2>` style markup.
```tsx
import { Logo } from '@/components/branding';
// Default (wordmark, md, theme-aware color)
<Logo brand="veza" />
// Lockup with tagline
<Logo brand="veza" variant="lockup" size="lg" tagline="STREAMING" />
// Symbol only (favicon-style usage)
<Logo brand="talas" variant="symbol" size="sm" />
// Cyan accent
<Logo brand="veza" variant="lockup" color="cyan" />
```
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<SVGSVGElement>) {
return (
<svg viewBox="0 0 240 60" xmlns="http://www.w3.org/2000/svg" {...props}>
{/* Pasted SVG paths here, fills set to currentColor */}
</svg>
);
}
```
5. Update `Logo.tsx` to use `<TalasWordmark />` 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';
<SumiIcon sumi={PauseIcon} fallback={Pause} size={24} />
```
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`.

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>Veza - Plateforme de streaming musical</title> <title>Veza - Plateforme de streaming musical</title>
@ -18,7 +18,8 @@
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="theme-color" content="#1a1a1a" /> <!-- theme-color = SUMI Mizu cyan (charte §4.1 — sole UI accent) -->
<meta name="theme-color" content="#0098B5" />
<meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
@ -36,7 +37,7 @@
<link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512x512.png" /> <link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512x512.png" />
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#1a1a1a" /> <meta name="msapplication-TileColor" content="#0D0D0F" />
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png" /> <meta name="msapplication-TileImage" content="/icons/icon-144x144.png" />
<!-- SEO and Social --> <!-- SEO and Social -->

View file

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<!--
Veza / Talas favicon — placeholder.
Awaits the artist's hand-drawn calligraphic mark (P0.1 of BRIEF_ARTISTE_IDENTITE_VISUELLE).
Mirrors apps/web/src/components/branding/assets/SymbolPlaceholder.tsx.
Mizu cyan (#0098B5) per CHARTE_GRAPHIQUE_TALAS §4.1.
-->
<path d="M7 4 Q 8.5 9, 7.5 14 T 8 20"
stroke="#0098B5" stroke-width="2.5" stroke-linecap="round" fill="none"/>
<path d="M11 5 Q 17 12, 12 19"
stroke="#0098B5" stroke-width="1.8" stroke-linecap="round" fill="none" opacity="0.85"/>
<circle cx="18.5" cy="6" r="1.0" fill="#0098B5"/>
</svg>

After

Width:  |  Height:  |  Size: 666 B

View file

@ -2,8 +2,8 @@
"name": "Veza Platform", "name": "Veza Platform",
"short_name": "Veza", "short_name": "Veza",
"description": "Plateforme de streaming, collaboration et distribution musicale moderne", "description": "Plateforme de streaming, collaboration et distribution musicale moderne",
"theme_color": "#1a1a1a", "theme_color": "#0098B5",
"background_color": "#ffffff", "background_color": "#0D0D0F",
"display": "standalone", "display": "standalone",
"orientation": "portrait", "orientation": "portrait",
"scope": "/", "scope": "/",

View file

@ -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<typeof Logo>;
export default meta;
type Story = StoryObj<typeof meta>;
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: () => (
<div className="flex flex-col gap-6 items-start">
{(['xs', 'sm', 'md', 'lg', 'xl'] as const).map((size) => (
<div key={size} className="flex items-center gap-4">
<span className="text-xs text-muted-foreground tracking-widest uppercase w-8">{size}</span>
<Logo brand="veza" variant="lockup" size={size} tagline="STREAMING" />
</div>
))}
</div>
),
};
export const AllVariants: Story = {
render: () => (
<div className="grid grid-cols-3 gap-8 items-start">
<div className="flex flex-col gap-2">
<span className="text-xs text-muted-foreground uppercase">Wordmark</span>
<Logo brand="veza" variant="wordmark" size="lg" />
</div>
<div className="flex flex-col gap-2">
<span className="text-xs text-muted-foreground uppercase">Symbol</span>
<Logo brand="talas" variant="symbol" size="lg" />
</div>
<div className="flex flex-col gap-2">
<span className="text-xs text-muted-foreground uppercase">Lockup</span>
<Logo brand="veza" variant="lockup" size="lg" tagline="STREAMING" />
</div>
</div>
),
};
export const TalasVsVeza: Story = {
render: () => (
<div className="grid grid-cols-2 gap-8 items-start">
<div className="flex flex-col gap-2">
<span className="text-xs text-muted-foreground uppercase">Talas (mother brand)</span>
<Logo brand="talas" variant="lockup" size="xl" />
</div>
<div className="flex flex-col gap-2">
<span className="text-xs text-muted-foreground uppercase">Veza (sub-brand)</span>
<Logo brand="veza" variant="lockup" size="xl" tagline="STREAMING" />
</div>
</div>
),
};

View file

@ -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<LogoSize, string> = {
xs: 'text-xs',
sm: 'text-sm',
md: 'text-base',
lg: 'text-xl',
xl: 'text-3xl',
};
const SIZE_TO_SYMBOL_PX: Record<LogoSize, number> = {
xs: 14,
sm: 18,
md: 24,
lg: 32,
xl: 48,
};
const SIZE_TO_TAGLINE_CLASS: Record<LogoSize, string> = {
xs: 'text-[8px]',
sm: 'text-[9px]',
md: 'text-[10px]',
lg: 'text-xs',
xl: 'text-sm',
};
const COLOR_TO_CLASS: Record<LogoColor, string> = {
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<LogoBrand, string> = {
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
* <Logo brand="veza" variant="lockup" size="md" tagline="STREAMING" />
* <Logo brand="talas" variant="symbol" size="lg" color="cyan" />
* <Logo variant="wordmark" size="xl" color="inverse" />
*/
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 = (
<span
className={cn(
'font-heading font-bold leading-none tracking-[0.12em] uppercase',
SIZE_TO_TEXT_CLASS[size],
colorClass,
)}
>
{label}
</span>
);
const symbol = (
<SymbolPlaceholder
width={SIZE_TO_SYMBOL_PX[size]}
height={SIZE_TO_SYMBOL_PX[size]}
className={cn('shrink-0', colorClass)}
/>
);
const taglineEl = tagline && (
<span
className={cn(
'font-heading font-light leading-none tracking-[0.2em] uppercase opacity-60',
SIZE_TO_TAGLINE_CLASS[size],
colorClass,
)}
>
{tagline}
</span>
);
if (variant === 'symbol') {
return (
<span role="img" aria-label={accessibleLabel} className={cn('inline-flex', className)}>
{symbol}
</span>
);
}
if (variant === 'wordmark') {
return (
<span role="img" aria-label={accessibleLabel} className={cn('inline-flex flex-col', className)}>
{wordmark}
{taglineEl}
</span>
);
}
// variant === 'lockup'
return (
<span
role="img"
aria-label={accessibleLabel}
className={cn(
'inline-flex items-center',
orientation === 'vertical' ? 'flex-col gap-1' : 'gap-2',
className,
)}
>
{symbol}
<span className="inline-flex flex-col">
{wordmark}
{taglineEl}
</span>
</span>
);
}
export default Logo;

View file

@ -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<SVGSVGElement>) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
{...props}
>
{/* Vertical brush stroke — ink */}
<path
d="M7 4 Q 8.5 9, 7.5 14 T 8 20"
stroke="currentColor"
strokeWidth="2.2"
strokeLinecap="round"
fill="none"
/>
{/* Curved arc — sound wave */}
<path
d="M11 5 Q 17 12, 12 19"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
fill="none"
opacity="0.85"
/>
{/* Ink dot — punctuation */}
<circle cx="18.5" cy="6" r="0.9" fill="currentColor" />
</svg>
);
}

View file

@ -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';

View file

@ -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<SVGSVGElement>) {
return (
<svg
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
{/* Single closed brush stroke approximating a play triangle */}
<path d="M7 5.5 L 7.5 18.5 L 18.2 12.3 Z" />
</svg>
);
}

View file

@ -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<SVGSVGElement>.
* 4. Add to the barrel below.
* 5. At call sites : `<SumiIcon sumi={PlayIcon} fallback={Play} />`.
*
* 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';