diff --git a/docs/design-system/01-tokens.md b/docs/design-system/01-tokens.md new file mode 100644 index 0000000..c266dab --- /dev/null +++ b/docs/design-system/01-tokens.md @@ -0,0 +1,251 @@ +# Design Tokens — Ocobo + +> Spécification des tokens pour migration vers **PandaCSS** + +--- + +## Colors + +### Core palette + +```ts +// panda.config.ts +export default defineConfig({ + theme: { + extend: { + tokens: { + colors: { + ocobo: { + dark: { value: '#212323' }, + yellow: { value: '#F1CF25' }, + mint: { value: '#9ADBBA' }, + sky: { value: '#99D1DF' }, + coral: { value: '#FE9C87' }, + } + } + } + } + } +}) +``` + +### Light variants (backgrounds) + +```ts +tokens: { + colors: { + ocobo: { + yellowLight: { value: '#FFFCEE' }, + mintLight: { value: '#EBFDF5' }, + skyLight: { value: '#F0F9FB' }, + coralLight: { value: '#FFF5F2' }, + gray: { value: '#F5F5F5' }, + } + } +} +``` + +### Semantic tokens + +```ts +semanticTokens: { + colors: { + // Backgrounds + bg: { + primary: { value: '{colors.white}' }, + secondary: { value: '{colors.ocobo.gray}' }, + inverse: { value: '{colors.ocobo.dark}' }, + accent: { value: '{colors.ocobo.yellow}' }, + }, + // Text + text: { + primary: { value: '{colors.ocobo.dark}' }, + secondary: { value: '#6B7280' }, // gray-500 + muted: { value: '#9CA3AF' }, // gray-400 + inverse: { value: '{colors.white}' }, + }, + // Borders + border: { + default: { value: '#E5E7EB' }, // gray-200 + subtle: { value: '#F3F4F6' }, // gray-100 + }, + } +} +``` + +--- + +## Typography + +### Fonts + +```ts +tokens: { + fonts: { + display: { value: 'Bermia, Inter, sans-serif' }, + body: { value: 'Bornia, Inter, sans-serif' }, + } +} +``` + +### Font sizes + +```ts +tokens: { + fontSizes: { + xs: { value: '0.625rem' }, // 10px (tags, labels) + sm: { value: '0.875rem' }, // 14px + base: { value: '1rem' }, // 16px + lg: { value: '1.125rem' }, // 18px + xl: { value: '1.25rem' }, // 20px + '2xl': { value: '1.5rem' }, // 24px + '3xl': { value: '1.875rem' }, // 30px + '4xl': { value: '2.25rem' }, // 36px + '5xl': { value: '3rem' }, // 48px + '6xl': { value: '3.75rem' }, // 60px + '7xl': { value: '4.5rem' }, // 72px + } +} +``` + +### Font weights + +```ts +tokens: { + fontWeights: { + normal: { value: '400' }, + medium: { value: '500' }, + semibold: { value: '600' }, + bold: { value: '700' }, + black: { value: '900' }, + } +} +``` + +### Letter spacing + +```ts +tokens: { + letterSpacings: { + tighter: { value: '-0.02em' }, + tight: { value: '-0.01em' }, + normal: { value: '0' }, + wide: { value: '0.1em' }, + wider: { value: '0.2em' }, + widest: { value: '0.3em' }, + ultrawide: { value: '0.4em' }, // pour labels uppercase + } +} +``` + +--- + +## Spacing + +```ts +tokens: { + spacing: { + // Standard Tailwind-like scale + '0': { value: '0' }, + '1': { value: '0.25rem' }, + '2': { value: '0.5rem' }, + '3': { value: '0.75rem' }, + '4': { value: '1rem' }, + '5': { value: '1.25rem' }, + '6': { value: '1.5rem' }, + '8': { value: '2rem' }, + '10': { value: '2.5rem' }, + '12': { value: '3rem' }, + '16': { value: '4rem' }, + '20': { value: '5rem' }, + '24': { value: '6rem' }, + '32': { value: '8rem' }, + '40': { value: '10rem' }, + // Section-specific + sectionY: { value: '6rem' }, // py-24 equivalent + heroTop: { value: '10rem' }, // pt-40 equivalent + } +} +``` + +--- + +## Radii + +```ts +tokens: { + radii: { + none: { value: '0' }, + sm: { value: '0.125rem' }, + base: { value: '0.25rem' }, + md: { value: '0.375rem' }, + lg: { value: '0.5rem' }, + xl: { value: '0.75rem' }, + '2xl': { value: '1rem' }, + '3xl': { value: '1.5rem' }, + full: { value: '9999px' }, + // Custom + card: { value: '2.5rem' }, // rounded-[2.5rem] + button: { value: '9999px' }, // rounded-full + } +} +``` + +--- + +## Shadows + +```ts +tokens: { + shadows: { + sm: { value: '0 1px 2px 0 rgb(0 0 0 / 0.05)' }, + base: { value: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)' }, + md: { value: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)' }, + lg: { value: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)' }, + xl: { value: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)' }, + '2xl': { value: '0 25px 50px -12px rgb(0 0 0 / 0.25)' }, + // Custom Ocobo + card: { value: '0 40px 100px -20px rgba(0,0,0,0.08)' }, + brutal: { value: '8px 8px 0px 0px rgba(33,35,35,1)' }, + brutalYellow: { value: '8px 8px 0px 0px #F1CF25' }, + brutalMint: { value: '8px 8px 0px 0px #9ADBBA' }, + brutalSky: { value: '8px 8px 0px 0px #99D1DF' }, + brutalCoral: { value: '8px 8px 0px 0px #FE9C87' }, + } +} +``` + +--- + +## Breakpoints + +```ts +theme: { + breakpoints: { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', + } +} +``` + +--- + +## Z-index + +```ts +tokens: { + zIndex: { + base: { value: '0' }, + docked: { value: '10' }, + dropdown: { value: '1000' }, + sticky: { value: '1100' }, + overlay: { value: '1200' }, + modal: { value: '1300' }, + popover: { value: '1400' }, + tooltip: { value: '1500' }, + } +} +``` diff --git a/docs/design-system/02-animations.md b/docs/design-system/02-animations.md new file mode 100644 index 0000000..a268e1e --- /dev/null +++ b/docs/design-system/02-animations.md @@ -0,0 +1,170 @@ +# Animations — Ocobo Design System + +> Spécification des keyframes et classes d'animation pour PandaCSS + +--- + +## Keyframes + +```ts +// panda.config.ts +export default defineConfig({ + theme: { + extend: { + keyframes: { + fadeInUp: { + '0%': { opacity: '0', transform: 'translateY(20px)' }, + '100%': { opacity: '1', transform: 'translateY(0)' }, + }, + floatGentle: { + '0%, 100%': { transform: 'translateY(0)' }, + '50%': { transform: 'translateY(-12px)' }, + }, + floatSlow: { + '0%, 100%': { transform: 'translateY(0) rotate(0deg)' }, + '50%': { transform: 'translateY(-15px) rotate(2deg)' }, + }, + bounceSubtle: { + '0%, 100%': { transform: 'translateY(0)' }, + '50%': { transform: 'translateY(8px)' }, + }, + marquee: { + '0%': { transform: 'translateX(0)' }, + '100%': { transform: 'translateX(-33.33%)' }, + }, + pulse: { + '0%, 100%': { opacity: '1' }, + '50%': { opacity: '0.5' }, + }, + slowFade: { + '0%, 100%': { opacity: '0.1' }, + '50%': { opacity: '0.85' }, + }, + radarSweep: { + '0%': { transform: 'rotate(0deg)' }, + '100%': { transform: 'rotate(360deg)' }, + }, + scaleIn: { + '0%': { opacity: '0', transform: 'scale(0.95)' }, + '100%': { opacity: '1', transform: 'scale(1)' }, + }, + }, + }, + }, +}) +``` + +--- + +## Animation recipes + +```ts +// animations.recipe.ts +import { defineRecipe } from '@pandacss/dev' + +export const animateRecipe = defineRecipe({ + className: 'animate', + base: {}, + variants: { + type: { + fadeInUp: { + animation: 'fadeInUp 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) forwards', + opacity: 0, + }, + floatGentle: { + animation: 'floatGentle 8s ease-in-out infinite', + }, + floatSlow: { + animation: 'floatSlow 12s ease-in-out infinite', + }, + bounceSubtle: { + animation: 'bounceSubtle 3s ease-in-out infinite', + }, + marquee: { + animation: 'marquee 90s linear infinite', + display: 'flex', + width: 'max-content', + }, + pulse: { + animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', + }, + scaleIn: { + animation: 'scaleIn 0.3s ease-out forwards', + }, + }, + }, +}) +``` + +--- + +## Usage patterns + +### Staggered animations (cards grid) + +```tsx +// Pattern pour animer une grille de cartes avec délai progressif +{items.map((item, idx) => ( + +))} +``` + +### Hover state animations + +```ts +// Pattern pour transitions au hover +const cardStyles = css({ + transition: 'all 0.3s ease', + _hover: { + transform: 'translateY(-8px)', + boxShadow: 'xl', + }, +}) +``` + +### Scroll-triggered animations + +```tsx +// Recommandation : utiliser Intersection Observer ou Framer Motion +// pour les animations déclenchées au scroll +import { useInView } from 'framer-motion' + +const ref = useRef(null) +const isInView = useInView(ref, { once: true }) + +
+``` + +--- + +## Performance guidelines + +1. **Préférer `transform` et `opacity`** — Ces propriétés sont optimisées GPU +2. **Utiliser `will-change` avec parcimonie** — Seulement pour les animations critiques +3. **Respecter `prefers-reduced-motion`** : + +```ts +// Dans panda.config.ts, ajouter media queries +const safeAnimation = css({ + animation: 'fadeInUp 0.6s ease', + '@media (prefers-reduced-motion: reduce)': { + animation: 'none', + opacity: 1, + transform: 'none', + }, +}) +``` diff --git a/docs/design-system/03-components-phase1.md b/docs/design-system/03-components-phase1.md new file mode 100644 index 0000000..abe1e40 --- /dev/null +++ b/docs/design-system/03-components-phase1.md @@ -0,0 +1,464 @@ +# Component Specs — Quick Wins (Phase 1) + +> Spécifications pour les composants à fort impact : `PageHero`, `SectionHeading`, `CTASection`, `ClientMarquee` + +--- + +## 1. PageHero + +### Description +Section hero standardisée présente sur toutes les pages principales. Structure deux colonnes sur desktop, stack sur mobile. + +### Props + +```ts +interface PageHeroProps { + /** Tag/label affiché en haut (ex: "SUCCESS STORIES") */ + tag?: string + /** Couleur du tag - utilise les couleurs thème */ + tagColor?: 'yellow' | 'mint' | 'sky' | 'coral' + /** Titre principal - supporte JSX pour les line breaks */ + title: React.ReactNode + /** Partie du titre en gris (optionnel) */ + titleMuted?: string + /** Paragraphe de description */ + description?: string + /** Texte du CTA principal */ + ctaText?: string + /** Lien du CTA */ + ctaHref?: string + /** Slot pour illustration custom à droite */ + illustration?: React.ReactNode + /** Inverse le layout (illustration à gauche) */ + reverse?: boolean +} +``` + +### PandaCSS Recipe + +```ts +// pageHero.recipe.ts +import { defineSlotRecipe } from '@pandacss/dev' + +export const pageHeroRecipe = defineSlotRecipe({ + className: 'pageHero', + slots: ['root', 'content', 'tag', 'title', 'titleMuted', 'description', 'cta', 'illustrationWrapper'], + base: { + root: { + pt: '40', // pt-40 = 10rem + pb: '24', + maxW: '7xl', + mx: 'auto', + px: { base: '4', sm: '6', lg: '8' }, + }, + content: { + display: 'flex', + flexDirection: { base: 'column', lg: 'row' }, + alignItems: 'center', + gap: { base: '16', lg: '24' }, + }, + tag: { + fontFamily: 'display', + fontWeight: 'black', + fontSize: 'xs', // 10px + textTransform: 'uppercase', + letterSpacing: 'widest', // 0.3em + px: '4', + py: '1.5', + mb: '10', + display: 'inline-block', + border: '1px solid', + }, + title: { + fontFamily: 'display', + fontSize: { base: '5xl', md: '7xl' }, + fontWeight: 'bold', + lineHeight: '0.95', + letterSpacing: 'tight', + mb: '10', + }, + titleMuted: { + color: 'gray.400', + }, + description: { + fontSize: 'xl', + color: 'gray.700', + lineHeight: 'relaxed', + fontWeight: 'medium', + maxW: 'xl', + mb: '12', + }, + illustrationWrapper: { + w: { base: 'full', lg: '1/2' }, + display: 'flex', + justifyContent: { base: 'center', lg: 'flex-end' }, + alignItems: 'center', + }, + }, + variants: { + tagColor: { + yellow: { + tag: { + bg: 'ocobo.yellowLight', + color: 'ocobo.dark', + borderColor: 'ocobo.yellow/20', + }, + }, + mint: { + tag: { + bg: 'ocobo.mintLight', + color: 'ocobo.dark', + borderColor: 'ocobo.mint/20', + }, + }, + sky: { + tag: { + bg: 'ocobo.skyLight', + color: 'ocobo.dark', + borderColor: 'ocobo.sky/20', + }, + }, + coral: { + tag: { + bg: 'ocobo.coralLight', + color: 'ocobo.dark', + borderColor: 'ocobo.coral/20', + }, + }, + }, + reverse: { + true: { + content: { + flexDirection: { lg: 'row-reverse' }, + }, + }, + }, + }, + defaultVariants: { + tagColor: 'yellow', + reverse: false, + }, +}) +``` + +### Usage example + +```tsx +Ils ont choisi
l'architecture.} + description="Découvrez comment nous accompagnons les plus belles scale-ups européennes." + ctaText="Prendre RDV" + ctaHref="/contact" + illustration={} +/> +``` + +--- + +## 2. SectionHeading + +### Description +Bloc de titre de section avec tag optionnel et sous-titre. Peut être centré ou aligné à gauche. + +### Props + +```ts +interface SectionHeadingProps { + /** Tag/label au-dessus du titre */ + tag?: string + tagColor?: 'yellow' | 'mint' | 'sky' | 'coral' + /** Titre principal */ + title: React.ReactNode + /** Sous-titre/description */ + subtitle?: string + /** Alignement */ + align?: 'left' | 'center' + /** Taille du titre */ + size?: 'md' | 'lg' | 'xl' +} +``` + +### PandaCSS Recipe + +```ts +export const sectionHeadingRecipe = defineSlotRecipe({ + className: 'sectionHeading', + slots: ['root', 'tag', 'title', 'subtitle'], + base: { + root: { + mb: { base: '12', md: '16' }, + }, + tag: { + fontFamily: 'display', + fontWeight: 'black', + fontSize: 'xs', + textTransform: 'uppercase', + letterSpacing: 'widest', + px: '4', + py: '1.5', + mb: '8', + display: 'inline-block', + borderRadius: 'full', + }, + title: { + fontFamily: 'display', + fontWeight: 'bold', + letterSpacing: 'tight', + mb: '6', + }, + subtitle: { + fontSize: 'xl', + fontWeight: 'medium', + color: 'gray.500', + lineHeight: 'relaxed', + }, + }, + variants: { + align: { + left: { + root: { textAlign: 'left' }, + subtitle: { maxW: 'xl' }, + }, + center: { + root: { textAlign: 'center', mx: 'auto' }, + subtitle: { maxW: '2xl', mx: 'auto' }, + }, + }, + size: { + md: { + title: { fontSize: { base: '3xl', md: '4xl' } }, + }, + lg: { + title: { fontSize: { base: '4xl', md: '5xl' } }, + }, + xl: { + title: { fontSize: { base: '4xl', md: '6xl' } }, + }, + }, + }, + defaultVariants: { + align: 'center', + size: 'lg', + }, +}) +``` + +--- + +## 3. CTASection + +### Description +Section call-to-action full-width en fin de page. 3 variantes de couleur principales. + +### Props + +```ts +interface CTASectionProps { + /** Titre principal */ + title: React.ReactNode + /** Sous-titre(s) */ + subtitle?: string | string[] + /** Texte du bouton */ + ctaText: string + /** Lien du bouton */ + ctaHref: string + /** Variante de couleur */ + variant?: 'yellow' | 'dark' | 'light' + /** Afficher des éléments décoratifs */ + showDecorations?: boolean +} +``` + +### PandaCSS Recipe + +```ts +export const ctaSectionRecipe = defineSlotRecipe({ + className: 'ctaSection', + slots: ['root', 'container', 'title', 'subtitle', 'button'], + base: { + root: { + py: '24', + textAlign: 'center', + position: 'relative', + overflow: 'hidden', + }, + container: { + maxW: '4xl', + mx: 'auto', + px: '4', + position: 'relative', + zIndex: 10, + }, + title: { + fontFamily: 'display', + fontSize: { base: '4xl', md: '5xl' }, + fontWeight: 'bold', + mb: '8', + letterSpacing: 'tighter', + }, + subtitle: { + fontSize: { base: 'lg', md: 'xl' }, + fontWeight: 'medium', + mb: '10', + }, + }, + variants: { + variant: { + yellow: { + root: { bg: 'ocobo.yellow' }, + title: { color: 'ocobo.dark' }, + subtitle: { color: 'ocobo.dark', opacity: 0.9 }, + }, + dark: { + root: { bg: 'ocobo.dark' }, + title: { color: 'white' }, + subtitle: { color: 'gray.300' }, + }, + light: { + root: { bg: 'ocobo.skyLight' }, + title: { color: 'ocobo.dark' }, + subtitle: { color: 'gray.600' }, + }, + }, + }, + defaultVariants: { + variant: 'yellow', + }, +}) +``` + +### Usage patterns + +```tsx +// Variant Yellow (le plus courant) +Prenez le contrôle
de votre croissance.} + subtitle={["30 minutes pour analyser votre machine revenue.", "Clarité garantie."]} + ctaText="Prendre RDV" + ctaHref="/contact" +/> + +// Variant Dark + +``` + +--- + +## 4. ClientMarquee + +### Description +Bandeau défilant avec les noms des clients. Animation infinie, edge fading. + +### Props + +```ts +interface ClientMarqueeProps { + /** Liste des noms de clients */ + clients: string[] + /** Variante de couleur du fond */ + variant?: 'dark' | 'light' + /** Vitesse (durée en secondes) */ + speed?: number +} +``` + +### PandaCSS Recipe + +```ts +export const clientMarqueeRecipe = defineSlotRecipe({ + className: 'clientMarquee', + slots: ['root', 'fadeLeft', 'fadeRight', 'track', 'item'], + base: { + root: { + position: 'relative', + w: 'full', + overflow: 'hidden', + py: '8', + }, + fadeLeft: { + position: 'absolute', + left: 0, + top: 0, + bottom: 0, + w: { base: '32', md: '64' }, + zIndex: 10, + pointerEvents: 'none', + }, + fadeRight: { + position: 'absolute', + right: 0, + top: 0, + bottom: 0, + w: { base: '32', md: '64' }, + zIndex: 10, + pointerEvents: 'none', + }, + track: { + display: 'flex', + animation: 'marquee 90s linear infinite', + whiteSpace: 'nowrap', + width: 'max-content', + }, + item: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + px: { base: '10', md: '14' }, + fontFamily: 'display', + fontWeight: 'black', + fontSize: { base: 'base', md: 'xl' }, + letterSpacing: 'wider', + textTransform: 'uppercase', + userSelect: 'none', + cursor: 'default', + transition: 'color 0.2s', + }, + }, + variants: { + variant: { + dark: { + root: { bg: 'ocobo.dark' }, + fadeLeft: { bgGradient: 'to-r', gradientFrom: 'ocobo.dark', gradientTo: 'transparent' }, + fadeRight: { bgGradient: 'to-l', gradientFrom: 'ocobo.dark', gradientTo: 'transparent' }, + item: { + color: 'white/30', + _hover: { color: 'ocobo.yellow' }, + }, + }, + light: { + root: { bg: 'white', borderY: '1px solid', borderColor: 'gray.100' }, + fadeLeft: { bgGradient: 'to-r', gradientFrom: 'white', gradientTo: 'transparent' }, + fadeRight: { bgGradient: 'to-l', gradientFrom: 'white', gradientTo: 'transparent' }, + item: { + color: 'gray.300', + _hover: { color: 'ocobo.dark' }, + }, + }, + }, + }, + defaultVariants: { + variant: 'dark', + }, +}) +``` + +### Implementation notes + +- Le track doit tripler les items pour un loop seamless +- Utiliser `will-change: transform` pour performance +- Respecter `prefers-reduced-motion` + +```tsx +// Usage +const clients = ["TheFork", "Qonto", "PayFit", "Spendesk", "Qobra", "Tomorro"] + + +``` diff --git a/docs/design-system/04-components-phase2.md b/docs/design-system/04-components-phase2.md new file mode 100644 index 0000000..1b1c43f --- /dev/null +++ b/docs/design-system/04-components-phase2.md @@ -0,0 +1,389 @@ +# Component Specs — Cards System (Phase 2) + +> Spécifications pour les composants cartes : `FeatureCard`, `StepCard`, `ValueCard` + +--- + +## 1. FeatureCard + +### Description +Carte avec icône, titre et description. Utilisée pour les grilles de features (ex: "Aligner, Simplifier, Fiabiliser, Piloter"). + +### Props + +```ts +interface FeatureCardProps { + /** Icône Lucide ou custom */ + icon: React.ReactNode + /** Titre de la feature */ + title: string + /** Description */ + description: string + /** Couleur thème */ + color: 'yellow' | 'mint' | 'sky' | 'coral' + /** Variante de layout */ + variant?: 'default' | 'compact' | 'centered' + /** Afficher un label au-dessus du titre */ + label?: string + /** Afficher une action au hover */ + showAction?: boolean +} +``` + +### PandaCSS Recipe + +```ts +export const featureCardRecipe = defineSlotRecipe({ + className: 'featureCard', + slots: ['root', 'iconWrapper', 'label', 'title', 'description', 'action'], + base: { + root: { + position: 'relative', + bg: 'white', + border: '1px solid', + borderColor: 'gray.100', + p: '8', + transition: 'all 0.3s ease', + _hover: { + shadow: 'xl', + transform: 'translateY(-8px)', + }, + }, + iconWrapper: { + mb: '6', + transition: 'all 0.3s ease', + }, + label: { + fontFamily: 'display', + fontWeight: 'black', + fontSize: 'xs', + textTransform: 'uppercase', + letterSpacing: 'ultrawide', + color: 'gray.400', + mb: '4', + }, + title: { + fontFamily: 'display', + fontSize: '2xl', + fontWeight: 'bold', + color: 'ocobo.dark', + mb: '3', + transition: 'color 0.3s', + }, + description: { + fontSize: 'sm', + color: 'gray.600', + lineHeight: 'relaxed', + }, + action: { + position: 'absolute', + bottom: '6', + opacity: 0, + transition: 'opacity 0.3s', + _groupHover: { + opacity: 0.2, + }, + }, + }, + variants: { + color: { + yellow: { + iconWrapper: { color: 'ocobo.yellow' }, + title: { _groupHover: { color: 'ocobo.yellow' } }, + root: { _hover: { borderColor: 'ocobo.yellow/30' } }, + }, + mint: { + iconWrapper: { color: 'ocobo.mint' }, + title: { _groupHover: { color: 'ocobo.mint' } }, + root: { _hover: { borderColor: 'ocobo.mint/30' } }, + }, + sky: { + iconWrapper: { color: 'ocobo.sky' }, + title: { _groupHover: { color: 'ocobo.sky' } }, + root: { _hover: { borderColor: 'ocobo.sky/30' } }, + }, + coral: { + iconWrapper: { color: 'ocobo.coral' }, + title: { _groupHover: { color: 'ocobo.coral' } }, + root: { _hover: { borderColor: 'ocobo.coral/30' } }, + }, + }, + variant: { + default: { + root: { borderRadius: 'xl' }, + }, + compact: { + root: { p: '6', borderRadius: 'lg' }, + title: { fontSize: 'xl' }, + }, + centered: { + root: { + textAlign: 'center', + aspectRatio: '1', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '2xl', + }, + iconWrapper: { mb: '8' }, + }, + }, + }, + defaultVariants: { + color: 'yellow', + variant: 'default', + }, +}) +``` + +### Usage patterns + +```tsx +// Grid de features +
+ } + title="Aligner" + description="les équipes" + color="coral" + variant="centered" + label="Organiser =" + /> + {/* ... autres cartes */} +
+``` + +--- + +## 2. StepCard + +### Description +Carte numérotée pour afficher des étapes de process ou de méthode. + +### Props + +```ts +interface StepCardProps { + /** Numéro de l'étape */ + step: number + /** Titre */ + title: string + /** Description (optionnel) */ + description?: string + /** Livrables (optionnel) */ + deliverables?: string[] + /** Couleur du numéro */ + color?: 'yellow' | 'mint' | 'sky' | 'coral' | 'dark' + /** Variante de style */ + variant?: 'default' | 'minimal' | 'brutal' +} +``` + +### PandaCSS Recipe + +```ts +export const stepCardRecipe = defineSlotRecipe({ + className: 'stepCard', + slots: ['root', 'stepNumber', 'content', 'title', 'description', 'deliverables', 'deliverableItem'], + base: { + root: { + bg: 'white', + border: '1px solid', + borderColor: 'gray.100', + transition: 'all 0.3s', + _hover: { + shadow: 'lg', + }, + }, + stepNumber: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontFamily: 'display', + fontWeight: 'bold', + mb: '6', + }, + title: { + fontFamily: 'display', + fontSize: 'xl', + fontWeight: 'bold', + color: 'ocobo.dark', + mb: '3', + }, + description: { + fontSize: 'sm', + color: 'gray.600', + }, + deliverables: { + pt: '4', + borderTop: '1px solid', + borderColor: 'gray.50', + }, + deliverableItem: { + fontSize: 'xs', + fontWeight: 'bold', + color: 'ocobo.dark', + }, + }, + variants: { + color: { + yellow: { + stepNumber: { bg: 'ocobo.yellow', color: 'ocobo.dark' }, + }, + mint: { + stepNumber: { bg: 'ocobo.mint', color: 'ocobo.dark' }, + }, + sky: { + stepNumber: { bg: 'ocobo.sky', color: 'ocobo.dark' }, + }, + coral: { + stepNumber: { bg: 'ocobo.coral', color: 'ocobo.dark' }, + }, + dark: { + stepNumber: { bg: 'ocobo.dark', color: 'white' }, + }, + }, + variant: { + default: { + root: { p: '8', borderRadius: 'none' }, + stepNumber: { w: '10', h: '10', fontSize: 'sm' }, + }, + minimal: { + root: { p: '6', borderRadius: 'lg' }, + stepNumber: { w: '8', h: '8', fontSize: 'xs' }, + }, + brutal: { + root: { + p: '8', + borderWidth: '2px', + borderColor: 'ocobo.dark', + shadow: 'brutal', + }, + stepNumber: { + w: '16', + h: '16', + fontSize: '2xl', + borderWidth: '1px', + borderColor: 'ocobo.dark', + }, + }, + }, + }, + defaultVariants: { + color: 'dark', + variant: 'default', + }, +}) +``` + +--- + +## 3. ValueCard + +### Description +Carte pour afficher une valeur/USP avec titre coloré et description. + +### Props + +```ts +interface ValueCardProps { + /** Titre de la valeur */ + title: string + /** Description principale */ + description: string + /** Citation/explication additionnelle (italique) */ + quote?: string + /** Couleur du titre */ + color?: 'yellow' | 'mint' | 'sky' | 'coral' | 'white' + /** Variante de fond */ + variant?: 'transparent' | 'card' +} +``` + +### PandaCSS Recipe + +```ts +export const valueCardRecipe = defineSlotRecipe({ + className: 'valueCard', + slots: ['root', 'title', 'description', 'quote'], + base: { + title: { + fontFamily: 'display', + fontSize: '2xl', + fontWeight: 'bold', + mb: '4', + }, + description: { + lineHeight: 'relaxed', + }, + quote: { + fontSize: 'sm', + fontStyle: 'italic', + mt: '2', + }, + }, + variants: { + color: { + yellow: { title: { color: 'ocobo.yellow' } }, + mint: { title: { color: 'ocobo.mint' } }, + sky: { title: { color: 'ocobo.sky' } }, + coral: { title: { color: 'ocobo.coral' } }, + white: { title: { color: 'white' } }, + }, + variant: { + transparent: { + root: {}, + description: { color: 'gray.300' }, + quote: { color: 'gray.500' }, + }, + card: { + root: { + bg: 'white', + p: '8', + borderRadius: 'xl', + border: '1px solid', + borderColor: 'gray.100', + }, + description: { color: 'gray.600' }, + quote: { color: 'gray.400' }, + }, + }, + }, + defaultVariants: { + color: 'yellow', + variant: 'transparent', + }, +}) +``` + +--- + +## Patterns d'utilisation ArkUI + +Pour les composants interactifs (accordéons, tabs, etc.), utiliser ArkUI comme base : + +```tsx +import { Accordion } from '@ark-ui/react' +import { accordionRecipe } from './recipes/accordion.recipe' +import { css } from '../styled-system/css' + +// Exemple : FAQ ou sections collapsibles + + + + Question 1 + + + Réponse 1 + + + +``` + +Les composants ArkUI à considérer pour le site : +- `Accordion` — FAQ, sections collapsibles +- `Tabs` — Navigation ressources (Blog, Podcast, Webinars) +- `Dialog` — Modales contact/newsletter +- `Tooltip` — Explications termes techniques +- `Menu` — Navigation mobile diff --git a/docs/design-system/05-components-phase3.md b/docs/design-system/05-components-phase3.md new file mode 100644 index 0000000..268b6a0 --- /dev/null +++ b/docs/design-system/05-components-phase3.md @@ -0,0 +1,513 @@ +# Component Specs — Specific Components (Phase 3) + +> Spécifications pour : `TeamMemberCard`, `StoryCard`, `ComparisonBlock`, `CheckList` + +--- + +## 1. TeamMemberCard + +### Description +Carte profil pour les membres de l'équipe. + +### Props + +```ts +interface TeamMemberCardProps { + /** URL de la photo */ + image: string + /** Nom complet */ + name: string + /** Titre/rôle */ + role: string + /** Bio courte */ + bio: string + /** URL LinkedIn */ + linkedinUrl?: string + /** Couleur de la bordure photo */ + color?: 'yellow' | 'mint' | 'sky' | 'coral' +} +``` + +### PandaCSS Recipe + +```ts +export const teamMemberCardRecipe = defineSlotRecipe({ + className: 'teamMemberCard', + slots: ['root', 'imageWrapper', 'image', 'name', 'role', 'bio', 'social'], + base: { + root: { + bg: 'white', + p: '8', + border: '1px solid', + borderColor: 'gray.100', + shadow: 'sm', + textAlign: 'center', + transition: 'all 0.3s', + _hover: { + shadow: 'lg', + }, + }, + imageWrapper: { + w: '32', + h: '32', + mx: 'auto', + mb: '6', + borderRadius: 'full', + overflow: 'hidden', + borderWidth: '4px', + }, + image: { + w: 'full', + h: 'full', + objectFit: 'cover', + }, + name: { + fontFamily: 'display', + fontSize: '2xl', + fontWeight: 'bold', + color: 'ocobo.dark', + mb: '1', + }, + role: { + fontSize: 'xs', + fontWeight: 'bold', + color: 'gray.400', + textTransform: 'uppercase', + letterSpacing: 'widest', + mb: '4', + }, + bio: { + fontSize: 'sm', + color: 'gray.600', + lineHeight: 'relaxed', + mb: '6', + }, + social: { + color: 'gray.400', + transition: 'color 0.2s', + _hover: { + color: 'ocobo.dark', + }, + }, + }, + variants: { + color: { + yellow: { imageWrapper: { borderColor: 'ocobo.yellow' } }, + mint: { imageWrapper: { borderColor: 'ocobo.mint' } }, + sky: { imageWrapper: { borderColor: 'ocobo.sky' } }, + coral: { imageWrapper: { borderColor: 'ocobo.coral' } }, + }, + }, + defaultVariants: { + color: 'yellow', + }, +}) +``` + +--- + +## 2. StoryCard + +### Description +Carte case study (success story) avec image, segment, headline, personne, ROI et stack. + +### Props + +```ts +interface StoryCardProps { + /** ID pour le lien */ + id: string + /** Nom de l'entreprise */ + company: string + /** URL de l'image */ + image: string + /** URL du logo */ + logo: string + /** Segment (Série A/B, Scale-up, Enterprise) */ + segment: string + /** Titre principal */ + headline: string + /** Nom du contact */ + contactName: string + /** Rôle du contact */ + contactRole: string + /** Métrique ROI */ + roi: string + /** URLs des logos d'outils */ + tools: string[] + /** Couleur d'accent */ + color?: 'yellow' | 'mint' | 'sky' | 'coral' +} +``` + +### PandaCSS Recipe + +```ts +export const storyCardRecipe = defineSlotRecipe({ + className: 'storyCard', + slots: [ + 'root', 'imageWrapper', 'image', 'logoBadge', + 'content', 'meta', 'segment', 'company', 'headline', + 'contact', 'contactName', 'contactRole', + 'roiBlock', 'roiLabel', 'roiValue', + 'toolsBlock', 'toolsLabel', 'toolsLogos' + ], + base: { + root: { + position: 'relative', + display: 'flex', + flexDirection: 'column', + bg: 'white', + border: '1px solid', + borderColor: 'gray.100', + borderRadius: 'card', // 2.5rem + p: '6', + transition: 'all 0.5s', + h: 'full', + _hover: { + shadow: 'card', + transform: 'translateY(-8px)', + }, + }, + imageWrapper: { + position: 'relative', + aspectRatio: '16/10', + overflow: 'hidden', + borderRadius: '2xl', + bg: 'gray.50', + mb: '8', + }, + image: { + w: 'full', + h: 'full', + objectFit: 'cover', + filter: 'grayscale(1)', + opacity: 0.8, + transition: 'all 0.7s', + _groupHover: { + filter: 'grayscale(0)', + opacity: 1, + transform: 'scale(1.05)', + }, + }, + logoBadge: { + position: 'absolute', + bottom: '4', + right: '4', + bg: 'ocobo.dark', + p: '3', + borderRadius: 'xl', + shadow: '2xl', + opacity: 0, + transform: 'translateY(8px)', + transition: 'all 0.5s', + _groupHover: { + opacity: 1, + transform: 'translateY(0)', + }, + }, + meta: { + display: 'flex', + alignItems: 'center', + gap: '2', + mb: '4', + }, + segment: { + fontSize: 'xs', + fontWeight: 'black', + textTransform: 'uppercase', + letterSpacing: 'wide', + color: 'gray.400', + }, + company: { + fontSize: 'xs', + fontWeight: 'black', + textTransform: 'uppercase', + letterSpacing: 'wide', + color: 'ocobo.dark', + }, + headline: { + fontFamily: 'display', + fontSize: '2xl', + fontWeight: 'bold', + color: 'ocobo.dark', + lineHeight: 'tight', + letterSpacing: 'tight', + mb: '2', + transition: 'color 0.2s', + _groupHover: { + color: 'black', + }, + }, + contactName: { + fontSize: 'xs', + fontWeight: 'black', + textTransform: 'uppercase', + letterSpacing: 'widest', + color: 'ocobo.dark', + opacity: 0.6, + }, + contactRole: { + fontSize: 'xs', + fontWeight: 'medium', + color: 'gray.400', + }, + roiBlock: { + bg: 'gray.50', + p: '5', + borderRadius: '2xl', + border: '1px solid', + borderColor: 'gray.100', + transition: 'all 0.3s', + _groupHover: { + bg: 'white', + }, + }, + roiLabel: { + fontSize: 'xs', + fontWeight: 'black', + textTransform: 'uppercase', + letterSpacing: 'widest', + color: 'gray.400', + mb: '1', + }, + roiValue: { + fontFamily: 'display', + fontSize: 'xl', + fontWeight: 'black', + color: 'ocobo.dark', + display: 'flex', + alignItems: 'center', + gap: '3', + }, + toolsBlock: { + px: '5', + py: '3', + borderTop: '1px solid', + borderColor: 'gray.100', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + }, + toolsLabel: { + fontSize: 'xs', + fontWeight: 'black', + textTransform: 'uppercase', + letterSpacing: 'widest', + color: 'gray.400', + }, + toolsLogos: { + display: 'flex', + alignItems: 'center', + gap: '3', + filter: 'grayscale(1)', + opacity: 0.4, + transition: 'all 0.3s', + _groupHover: { + opacity: 1, + filter: 'grayscale(0)', + }, + }, + }, +}) +``` + +--- + +## 3. ComparisonBlock + +### Description +Bloc comparatif "Ocobo vs Others" avec deux colonnes. + +### Props + +```ts +interface ComparisonBlockProps { + /** Titre colonne gauche (Ocobo) */ + leftTitle: string + /** Items colonne gauche (positifs) */ + leftItems: string[] + /** Titre colonne droite (Others) */ + rightTitle: string + /** Items colonne droite (négatifs) */ + rightItems: string[] +} +``` + +### PandaCSS Recipe + +```ts +export const comparisonBlockRecipe = defineSlotRecipe({ + className: 'comparisonBlock', + slots: ['root', 'grid', 'leftCard', 'rightCard', 'badge', 'title', 'list', 'listItem', 'icon'], + base: { + root: { + maxW: '5xl', + mx: 'auto', + }, + grid: { + display: 'grid', + gridTemplateColumns: { base: '1fr', md: '1fr 1fr' }, + gap: '8', + }, + leftCard: { + bg: 'white', + color: 'ocobo.dark', + p: '10', + position: 'relative', + }, + rightCard: { + bg: 'white/5', + border: '1px solid', + borderColor: 'white/10', + p: '10', + color: 'gray.300', + }, + badge: { + position: 'absolute', + top: 0, + left: 0, + bg: 'ocobo.yellow', + color: 'ocobo.dark', + px: '4', + py: '1', + fontWeight: 'bold', + fontSize: 'xs', + textTransform: 'uppercase', + letterSpacing: 'widest', + }, + title: { + fontFamily: 'display', + fontSize: '2xl', + fontWeight: 'bold', + mb: '8', + mt: '4', + }, + list: { + display: 'flex', + flexDirection: 'column', + gap: '4', + }, + listItem: { + display: 'flex', + alignItems: 'flex-start', + gap: '3', + }, + icon: { + flexShrink: 0, + mt: '1', + }, + }, +}) +``` + +--- + +## 4. CheckList + +### Description +Liste avec icônes check colorées. Composant utilitaire très réutilisé. + +### Props + +```ts +interface CheckListProps { + /** Items de la liste */ + items: string[] + /** Couleur des checks */ + color?: 'yellow' | 'mint' | 'sky' | 'coral' | 'dark' + /** Taille des icônes */ + size?: 'sm' | 'md' | 'lg' + /** Variante de style */ + variant?: 'default' | 'card' | 'inline' +} +``` + +### PandaCSS Recipe + +```ts +export const checkListRecipe = defineSlotRecipe({ + className: 'checkList', + slots: ['root', 'item', 'icon', 'text'], + base: { + root: { + display: 'flex', + flexDirection: 'column', + }, + item: { + display: 'flex', + alignItems: 'flex-start', + }, + icon: { + flexShrink: 0, + }, + text: { + fontWeight: 'medium', + }, + }, + variants: { + color: { + yellow: { icon: { color: 'ocobo.yellow' } }, + mint: { icon: { color: 'ocobo.mint' } }, + sky: { icon: { color: 'ocobo.sky' } }, + coral: { icon: { color: 'ocobo.coral' } }, + dark: { icon: { color: 'ocobo.dark' } }, + }, + size: { + sm: { + root: { gap: '2' }, + item: { gap: '2' }, + icon: { w: '4', h: '4', mt: '0.5' }, + text: { fontSize: 'sm' }, + }, + md: { + root: { gap: '3' }, + item: { gap: '3' }, + icon: { w: '5', h: '5', mt: '0.5' }, + text: { fontSize: 'base' }, + }, + lg: { + root: { gap: '4' }, + item: { gap: '4' }, + icon: { w: '6', h: '6', mt: '1' }, + text: { fontSize: 'lg' }, + }, + }, + variant: { + default: {}, + card: { + item: { + bg: 'gray.50', + p: '4', + borderRadius: 'lg', + }, + }, + inline: { + root: { + flexDirection: 'row', + flexWrap: 'wrap', + }, + }, + }, + }, + defaultVariants: { + color: 'mint', + size: 'md', + variant: 'default', + }, +}) +``` + +### Usage + +```tsx + +``` diff --git a/docs/design-system/06-button.md b/docs/design-system/06-button.md new file mode 100644 index 0000000..c68a2a1 --- /dev/null +++ b/docs/design-system/06-button.md @@ -0,0 +1,168 @@ +# Button Component + +> Composant Button existant à adapter pour PandaCSS + ArkUI + +--- + +## Analyse du composant actuel + +Le composant `Button.tsx` actuel est simple et fonctionnel. Il utilise 3 variants et une flèche optionnelle. + +### Props actuelles + +```ts +interface ButtonProps { + variant?: 'primary' | 'outline' | 'white' + showArrow?: boolean + children: React.ReactNode +} +``` + +--- + +## Migration PandaCSS + +```ts +// button.recipe.ts +import { defineRecipe } from '@pandacss/dev' + +export const buttonRecipe = defineRecipe({ + className: 'button', + base: { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + gap: '2', + borderRadius: 'full', + px: '6', + py: '3', + fontSize: 'sm', + fontWeight: 'semibold', + letterSpacing: 'wide', + transition: 'all 0.3s', + cursor: 'pointer', + // Group for arrow animation + '& svg': { + transition: 'transform 0.3s', + }, + _hover: { + '& svg': { + transform: 'translateX(4px)', + }, + }, + _disabled: { + opacity: 0.5, + cursor: 'not-allowed', + }, + }, + variants: { + variant: { + primary: { + bg: 'ocobo.dark', + color: 'white', + border: '1px solid transparent', + _hover: { + bg: 'gray.800', + }, + }, + outline: { + bg: 'transparent', + color: 'ocobo.dark', + border: '1px solid', + borderColor: 'ocobo.dark', + _hover: { + bg: 'ocobo.dark', + color: 'white', + }, + }, + white: { + bg: 'white', + color: 'ocobo.dark', + border: '1px solid transparent', + _hover: { + bg: 'gray.100', + }, + }, + // Nouveau variant pour CTA sur fond jaune + dark: { + bg: 'ocobo.dark', + color: 'white', + shadow: 'xl', + _hover: { + bg: 'black', + shadow: '2xl', + transform: 'translateY(-2px)', + }, + }, + }, + size: { + sm: { + px: '4', + py: '2', + fontSize: 'xs', + }, + md: { + px: '6', + py: '3', + fontSize: 'sm', + }, + lg: { + px: '10', + py: '4', + fontSize: 'base', + }, + xl: { + px: '12', + py: '5', + fontSize: 'lg', + shadow: 'xl', + }, + }, + }, + defaultVariants: { + variant: 'primary', + size: 'md', + }, +}) +``` + +--- + +## Intégration ArkUI (optionnel) + +Pour les boutons dans des contextes de formulaire ou d'accessibilité avancée : + +```tsx +import { Button } from '@ark-ui/react' +import { buttonRecipe } from './recipes/button.recipe' +import { ArrowRight } from 'lucide-react' + +interface OcoboButtonProps { + variant?: 'primary' | 'outline' | 'white' | 'dark' + size?: 'sm' | 'md' | 'lg' | 'xl' + showArrow?: boolean + children: React.ReactNode +} + +export const OcoboButton = ({ + variant = 'primary', + size = 'md', + showArrow = true, + children, + ...props +}: OcoboButtonProps) => ( + + {children} + {showArrow && } + +) +``` + +--- + +## Recommandations + +1. **Ajouter le variant `size`** — Le prototype override souvent padding/font-size inline +2. **Ajouter le variant `dark`** — Pour les CTAs sur fond coloré +3. **Support du `asChild` pattern** — Pour wrapper des `` react-router +4. **Loading state** — Ajouter un variant pour état de chargement diff --git a/docs/design-system/README.md b/docs/design-system/README.md new file mode 100644 index 0000000..a045cdd --- /dev/null +++ b/docs/design-system/README.md @@ -0,0 +1,88 @@ +# Ocobo Design System + +> Documentation pour la migration du prototype vers PandaCSS + ArkUI + +--- + +## Overview + +Ce design system documente les patterns UI extraits du prototype Ocobo (généré par Google AI Studio) pour faciliter la migration vers la stack de production **PandaCSS + ArkUI**. + +--- + +## Structure de la documentation + +| Fichier | Contenu | +|---------|---------| +| [01-tokens.md](./01-tokens.md) | Design tokens (couleurs, typo, spacing, shadows) | +| [02-animations.md](./02-animations.md) | Keyframes et recipes d'animation | +| [03-components-phase1.md](./03-components-phase1.md) | Quick wins : PageHero, SectionHeading, CTASection, ClientMarquee | +| [04-components-phase2.md](./04-components-phase2.md) | Cards : FeatureCard, StepCard, ValueCard | +| [05-components-phase3.md](./05-components-phase3.md) | Spécifiques : TeamMemberCard, StoryCard, ComparisonBlock, CheckList | +| [06-button.md](./06-button.md) | Migration du composant Button existant | + +--- + +## Stack cible + +| Layer | Technologie | Usage | +|-------|-------------|-------| +| **Styling** | PandaCSS | Tokens, recipes, patterns, CSS-in-JS type-safe | +| **Components** | ArkUI | Headless components accessibles (Dialog, Accordion, Tabs...) | +| **Icons** | Lucide React | Icônes (déjà utilisées dans le prototype) | +| **Routing** | React Router | Navigation (déjà en place) | + +--- + +## Priorité d'implémentation + +### Phase 1 — Quick wins (~60% du code dupliqué) +- `PageHero` +- `SectionHeading` +- `CTASection` +- `ClientMarquee` +- Centralisation des animations CSS + +### Phase 2 — Cards system (~25% du code) +- `FeatureCard` +- `StepCard` +- `ValueCard` + +### Phase 3 — Composants spécifiques (~15% du code) +- `TeamMemberCard` +- `StoryCard` +- `ComparisonBlock` +- `CheckList` + +--- + +## Notes de migration + +### De Tailwind CDN vers PandaCSS + +| Tailwind | PandaCSS | +|----------|----------| +| `className="px-4 py-2"` | `className={css({ px: '4', py: '2' })}` | +| `hover:bg-gray-100` | `_hover: { bg: 'gray.100' }` | +| `md:text-xl` | `fontSize: { base: 'lg', md: 'xl' }` | +| `group-hover:` | `_groupHover:` | + +### Patterns PandaCSS recommandés + +- **Tokens** pour les valeurs de design (couleurs, spacing) +- **Recipes** (`cva`) pour les composants simples +- **Slot Recipes** pour les composants complexes multi-parts +- **Patterns** (`container`, `stack`, `grid`) pour les layouts + +--- + +## Illustrations + +Les illustrations SVG restent inline dans chaque page car elles sont spécifiques : +- `DashboardIllustration` (Home) +- `ArchitecturalGrid` (Services) +- `PyramidSection` (Services) +- `DataIllustration` (About) +- `StoriesIllustration` (Stories) + +Elles peuvent être extraites dans `/components/illustrations/` si besoin de réutilisation.