` for page sections — consistent max-width and padding
+9. [ ] Parent wrappers (CardBody, etc.) set inherited styles — children `` elements don't need className for color/fontSize
+
+### Validation
+
+1. [ ] TypeScript validates `Text` variant prop — only allows defined variants
+2. [ ] TypeScript validates `IconBox` size/variant/color props — only allows defined values
+3. [ ] Panda CSS generates recipes on build — styled-system/recipes exports new recipes
+
+### UI/UX
+
+1. [ ] PartnerCard hover states work identically — logo desaturates, badges highlight, links animate
+2. [ ] Typography renders identically to current state — no visual regression
+3. [ ] Icon boxes render identically to current state — same sizes and colours
+
+## Implementation decisions
+
+### Core principles
+
+1. **Séparation style/contenu à 2 niveaux:**
+ - **Composants partagés** → `atoms/` (Text, IconBox, FormField)
+ - **Local style wrappers** → même fichier, en haut, non exportés
+2. **Héritage CSS natif** — Déclarer `color`, `fontSize`, `lineHeight` sur le parent, les enfants héritent automatiquement (pas de child selectors `'& h3': {...}`)
+3. **Text component pour override** — Utiliser `` uniquement quand un enfant doit différer du parent
+4. **Compound components** — Slots flexibles (Header, Body, Footer) via pattern Object.assign
+
+### Local style wrappers pattern
+
+**Deux approches selon le use case:**
+
+#### A) `css()` — styles simples sans variants
+
+```tsx
+function SectionContainer({ children }: { children: React.ReactNode }) {
+ return (
+
+ );
+}
+```
+
+#### B) `styled()` — styles avec variants (PRÉFÉRÉ)
+
+Quand le wrapper a besoin de variants (props qui changent les styles), utiliser `styled()` plutôt que `css.raw()` :
+
+```tsx
+import { styled } from 'styled-system/jsx';
+
+// ===== STYLE WRAPPER avec variants (haut du fichier, non exporté) =====
+
+const CardWrapper = styled('div', {
+ base: {
+ bg: 'white',
+ p: '10',
+ rounded: 'xl',
+ shadow: 'xl',
+ transition: 'all',
+ _hover: { transform: 'translateY(-4px)' },
+ '&:hover .icon-box': { bg: 'var(--hover-color)' },
+ },
+ variants: {
+ animate: {
+ true: { animation: 'fade-in-up', opacity: 0 },
+ false: { opacity: 1 },
+ },
+ size: {
+ sm: { p: '6' },
+ md: { p: '8' },
+ lg: { p: '10' },
+ },
+ },
+ defaultVariants: {
+ animate: false,
+ size: 'md',
+ },
+});
+
+// ===== COMPOSANT PRINCIPAL (contenu seulement) =====
+
+export function PartnerCard({ animate, children }) {
+ return (
+
+ {children}
+
+ );
+}
+```
+
+**Pourquoi `styled()` > `css.raw()` pour les variants:**
+- Props typées automatiquement par Panda CSS
+- Pattern identique aux recipes globales (base + variants + defaultVariants)
+- Plus déclaratif et lisible
+- Pas de composition manuelle avec `css()`
+
+**Avantages des local wrappers (vs recipes globales):**
+- Le composant principal ne contient que la structure/contenu
+- Les styles sont isolés et lisibles en haut du fichier
+- Pas de pollution de `panda.config.ts` avec des styles à usage unique
+- Recipes globales réservées aux patterns réutilisables (Text, Badge, Button...)
+
+### Technical choices
+
+- **Panda CSS recipes pour styles réutilisables** — Text, IconBox, Badge dans panda.config.ts
+- **Local `styled()` wrappers pour usage unique** — PartnerCardWrapper dans le fichier composant
+- **Compound components via Object.assign** — follows existing FlexPair/HeroSplit pattern
+- **Polymorphic `as` prop for Text** — allows h1/h2/h3/p/span/label semantic tags
+- **Recipe variants over props** — `variant="display-xl"` cleaner than `size="6xl" weight="black"`
+
+### Quand utiliser recipe vs styled()
+
+| Critère | Recipe (panda.config.ts) | styled() (local) |
+|---------|--------------------------|------------------|
+| Réutilisation | Multi-fichiers/pages | Fichier unique |
+| Exemples | Text, Badge, Button | PartnerCardWrapper |
+| Où | panda.config.ts | Haut du fichier composant |
+| Export | Via styled-system/recipes | Non exporté |
+
+### CSS inheritance example
+
+```tsx
+// Parent définit les defaults (color, fontSize héritent)
+
+ Override title
+ Paragraph inherits parent styles
+ Another paragraph inherits too
+
+```
+
+**Properties that inherit:** `color`, `font-size`, `font-family`, `font-weight`, `line-height`, `letter-spacing`
+**Properties that don't inherit:** `margin`, `padding`, `border`, `background`
+
+## Definition of done
+
+1. [ ] 2 recipes added to panda.config.ts (text, iconBox) — réutilisables cross-project
+2. [ ] All 3 atom components created (text.tsx, icon-box.tsx, form-field.tsx)
+3. [ ] PartnerCard refactored to compound component with local `styled()` wrapper
+4. [ ] All 5 partner section files updated to use new components
+5. [ ] `pnpm run build` passes
+6. [ ] `pnpm exec tsc --noEmit` passes
+7. [ ] Visual regression test — /partners page renders identically
+
+## Out of scope
+
+- Refactoring other pages to use new components (future work)
+- Creating additional recipes (e.g., card, tag)
+- Adding animation tokens to design system
+- Unit tests for new components
+
+## Further notes
+
+This is Phase 2 of the Partners refactoring POC. Phase 1 extracted data and split into section components. Phase 2 focuses on style abstraction and composition patterns.
+
+Recipes to create (global, réutilisables):
+- `text` — 6 variants (display-xl, display-lg, display-md, subtitle, body, label) + 6 colours
+- `iconBox` — 3 sizes (sm, md, lg) + 3 variants (solid, outline, ghost) + 5 colours
+
+Local styled() wrappers (usage unique):
+- `PartnerCardWrapper` — base hover states + animate variant (dans partner-card.tsx)
diff --git a/stories.json b/stories.json
new file mode 100644
index 0000000..82bb806
--- /dev/null
+++ b/stories.json
@@ -0,0 +1,306 @@
+{
+ "project": {
+ "name": "Partners Style Components & Composition",
+ "branch": "refactor/partners-style-components"
+ },
+ "source_prd": "specs/partners-style-components.md",
+ "created_at": "2026-01-27T10:00:00Z",
+ "rules": [
+ "Work on ONE feature at a time",
+ "Read ## Codebase Patterns section FIRST each iteration",
+ "Only modify 'passes', 'blocked', 'blocked_reason', 'completed_at' fields",
+ "Never edit or remove feature definitions",
+ "Test end-to-end before marking passes: true",
+ "Commit after each completed feature",
+ "Add reusable patterns to TOP of progress.txt",
+ "Append feature log to BOTTOM of progress.txt",
+ "Update AGENTS.md if you learn module-specific patterns"
+ ],
+ "features": [
+ {
+ "id": 1,
+ "category": "functional",
+ "priority": "high",
+ "description": "Add text recipe to panda.config.ts with typography variants",
+ "steps": [
+ "Open panda.config.ts",
+ "Add text recipe with variants: display-xl, display-lg, display-md, subtitle, body, label",
+ "Add color variants: dark, muted, white, sky, mint, yellow, coral",
+ "Run pnpm run codegen to generate styled-system/recipes",
+ "Verify text recipe is exported from styled-system/recipes"
+ ],
+ "passes": true,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": "2026-01-28T10:00:00Z"
+ },
+ {
+ "id": 2,
+ "category": "functional",
+ "priority": "high",
+ "description": "Create Text atom component with polymorphic as prop",
+ "steps": [
+ "Create components/atoms/text.tsx",
+ "Import text recipe from styled-system/recipes",
+ "Implement TextProps with as, variant, color, children, className",
+ "Support tags: h1, h2, h3, h4, p, span, label",
+ "Export named function Text",
+ "Verify TypeScript compiles without errors"
+ ],
+ "passes": true,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": "2026-01-28T10:05:00Z"
+ },
+ {
+ "id": 3,
+ "category": "functional",
+ "priority": "high",
+ "description": "Add iconBox recipe to panda.config.ts",
+ "steps": [
+ "Open panda.config.ts",
+ "Add iconBox recipe with base: flex, center, rounded",
+ "Add size variants: sm (8x8), md (10x10), lg (16x16)",
+ "Add variant options: solid, outline, ghost",
+ "Add color variants: default, sky, mint, yellow, coral",
+ "Run pnpm run codegen",
+ "Verify iconBox recipe is exported"
+ ],
+ "passes": true,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": "2026-01-28T10:10:00Z"
+ },
+ {
+ "id": 4,
+ "category": "functional",
+ "priority": "high",
+ "description": "Create IconBox atom component",
+ "steps": [
+ "Create components/atoms/icon-box.tsx",
+ "Import iconBox recipe from styled-system/recipes",
+ "Implement IconBoxProps with size, variant, color, children, className",
+ "Export named function IconBox",
+ "Verify TypeScript compiles without errors"
+ ],
+ "passes": true,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": "2026-01-28T10:15:00Z"
+ },
+ {
+ "id": 5,
+ "category": "functional",
+ "priority": "high",
+ "description": "Add partnerCard recipe with hover states",
+ "steps": [
+ "Open panda.config.ts",
+ "Add partnerCard recipe with base card styles",
+ "Add hover states for: card-logo, card-badge, card-tag, card-separator, card-link",
+ "Add animate variant (true/false) for fade-in animation",
+ "Run pnpm run codegen",
+ "Verify partnerCard recipe is exported"
+ ],
+ "passes": false,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": null
+ },
+ {
+ "id": 6,
+ "category": "functional",
+ "priority": "high",
+ "description": "Refactor PartnerCard as compound component",
+ "steps": [
+ "Open components/sections/partners/partner-card.tsx",
+ "Create CardContext for shared state",
+ "Extract CardRoot using partnerCard recipe",
+ "Extract CardHeader, CardLogo, CardBody, CardFooter subcomponents",
+ "Export compound component via Object.assign pattern",
+ "Update ecosystem-section.tsx to use new compound API",
+ "Verify /partners page renders correctly",
+ "Verify hover states work: logo, badges, tags, links"
+ ],
+ "passes": false,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": null
+ },
+ {
+ "id": 7,
+ "category": "functional",
+ "priority": "medium",
+ "description": "Create FormField atom component",
+ "steps": [
+ "Create components/atoms/form-field.tsx",
+ "Import Text component for label",
+ "Implement FormFieldProps with label, required, children",
+ "Use vstack pattern for layout",
+ "Export named function FormField",
+ "Verify TypeScript compiles"
+ ],
+ "passes": false,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": null
+ },
+ {
+ "id": 8,
+ "category": "functional",
+ "priority": "medium",
+ "description": "Update hero-section.tsx to use Text and Container",
+ "steps": [
+ "Open components/sections/partners/hero-section.tsx",
+ "Replace inline h1 styles with ",
+ "Replace inline p styles with ",
+ "Wrap content with Section and Container organisms",
+ "Remove duplicated css({}) calls",
+ "Verify /partners page renders identically"
+ ],
+ "passes": false,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": null
+ },
+ {
+ "id": 9,
+ "category": "functional",
+ "priority": "medium",
+ "description": "Update philosophy-section.tsx with local style wrappers pattern",
+ "steps": [
+ "Open components/sections/partners/philosophy-section.tsx",
+ "Extract SectionContainer, DotPattern, CardContainer, CardIconBox as local style wrappers at top of file",
+ "Keep PhilosophyCard and PhilosophySection as content components below",
+ "Use for headings",
+ "Main components should contain only structure/content, no inline css({}) for style concerns",
+ "Verify /partners page renders identically"
+ ],
+ "passes": false,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": null
+ },
+ {
+ "id": 10,
+ "category": "functional",
+ "priority": "medium",
+ "description": "Update partner-form.tsx with local style wrappers and FormField",
+ "steps": [
+ "Open components/sections/partners/partner-form.tsx",
+ "Extract FormContainer, GradientBar, SubmitButton as local style wrappers at top of file",
+ "Use atom for label + input pairs",
+ "Use for headings",
+ "Use atom for benefit icons",
+ "Main components should contain only structure/content",
+ "Verify /partners form renders identically"
+ ],
+ "passes": false,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": null
+ },
+ {
+ "id": 11,
+ "category": "functional",
+ "priority": "medium",
+ "description": "Update cta-section.tsx to use Text and Section",
+ "steps": [
+ "Open components/sections/partners/cta-section.tsx",
+ "Replace inline h2 styles with ",
+ "Replace inline p styles with ",
+ "Wrap with Section organism",
+ "Remove duplicated css({}) calls",
+ "Verify /partners CTA section renders identically"
+ ],
+ "passes": false,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": null
+ },
+ {
+ "id": 12,
+ "category": "functional",
+ "priority": "medium",
+ "description": "Update ecosystem-section.tsx to use Container",
+ "steps": [
+ "Open components/sections/partners/ecosystem-section.tsx",
+ "Wrap content with Container organism for consistent max-width and padding",
+ "Remove duplicated maxW/mx/px css({}) calls",
+ "Verify /partners ecosystem section renders identically"
+ ],
+ "passes": false,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": null
+ },
+ {
+ "id": 13,
+ "category": "functional",
+ "priority": "medium",
+ "description": "Extend SectionHeader to support children JSX",
+ "steps": [
+ "Open components/organisms/SectionHeader.tsx",
+ "Add children prop as alternative to title string",
+ "Support JSX content like ",
+ "Keep backward compatibility with title prop",
+ "Verify TypeScript compiles"
+ ],
+ "passes": false,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": null
+ },
+ {
+ "id": 14,
+ "category": "validation",
+ "priority": "high",
+ "description": "TypeScript validates all new component props",
+ "steps": [
+ "Run pnpm exec tsc --noEmit",
+ "Verify no type errors in new components",
+ "Verify recipe types are correctly inferred",
+ "Verify compound component types work"
+ ],
+ "passes": false,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": null
+ },
+ {
+ "id": 15,
+ "category": "validation",
+ "priority": "high",
+ "description": "Build passes with all changes",
+ "steps": [
+ "Run pnpm run build",
+ "Verify no build errors",
+ "Verify styled-system generates correctly",
+ "Verify bundle size is reasonable"
+ ],
+ "passes": false,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": null
+ },
+ {
+ "id": 16,
+ "category": "ui",
+ "priority": "high",
+ "description": "Visual regression test - Partners page identical",
+ "steps": [
+ "Run pnpm run dev",
+ "Navigate to /partners",
+ "Verify hero section renders correctly",
+ "Verify philosophy section 3 cards render correctly",
+ "Verify ecosystem section grid and filters work",
+ "Verify partner cards hover states work",
+ "Verify partner form section renders correctly",
+ "Verify CTA section renders correctly"
+ ],
+ "passes": false,
+ "blocked": false,
+ "blocked_reason": null,
+ "completed_at": null
+ }
+ ]
+}