# Story 1.2: Configuration de Tailwind CSS avec CLI ## Status Ready for Dev ## Story **As a** développeur, **I want** configurer Tailwind CSS avec le CLI et le build optimisé, **so that** je bénéficie d'un CSS performant avec purge automatique. ## Acceptance Criteria 1. Node.js et npm sont utilisés uniquement pour le build (pas en production) 2. `tailwind.config.js` est créé avec les chemins des fichiers PHP à scanner 3. Le fichier `assets/css/input.css` contient les directives Tailwind (@tailwind base, components, utilities) 4. La commande `npm run build` génère `assets/css/output.css` minifié 5. Une commande `npm run dev` permet le développement en temps réel (watch) 6. Le fichier CSS généré fait moins de 50kb après purge 7. La palette de couleurs personnalisée est configurée (thème sombre) ## Tasks / Subtasks - [] **Task 1 : Initialiser npm et installer les dépendances** (AC: 1) - [] Créer `package.json` avec `npm init -y` - [] Installer Tailwind CSS : `npm install -D tailwindcss postcss autoprefixer` - [] Vérifier que `node_modules/` est dans `.gitignore` - [] **Task 2 : Créer la configuration Tailwind** (AC: 2, 7) - [] Créer `tailwind.config.js` avec les chemins PHP à scanner - [] Configurer la palette de couleurs personnalisée (primary, background, surface, text, etc.) - [] Configurer les polices (Inter, JetBrains Mono) - [] Configurer les tailles de texte personnalisées - [] Configurer les ombres personnalisées - [] **Task 3 : Créer le fichier CSS source** (AC: 3) - [] Créer `assets/css/input.css` - [] Ajouter les directives `@tailwind base`, `@tailwind components`, `@tailwind utilities` - [] Ajouter les styles de base dans `@layer base` - [] Ajouter les composants réutilisables dans `@layer components` (btn, card, input, badge, etc.) - [] Ajouter les animations dans `@layer utilities` - [] **Task 4 : Configurer PostCSS** (AC: 1) - [] Créer `postcss.config.js` avec tailwindcss et autoprefixer - [] **Task 5 : Configurer les scripts npm** (AC: 4, 5) - [] Ajouter le script `build` dans package.json - [] Ajouter le script `dev` (watch) dans package.json - [] Tester les deux scripts - [] **Task 6 : Valider la taille du CSS** (AC: 6) - [] Exécuter `npm run build` - [] Vérifier que `output.css` < 50kb (6,4 Ko) - [] Vérifier que le purge fonctionne correctement ## Dev Notes ### Dépendances npm (devDependencies uniquement) ```json { "devDependencies": { "tailwindcss": "^3.4.0", "postcss": "^8.4.0", "autoprefixer": "^10.4.0" } } ``` ### Configuration tailwind.config.js Complète ```javascript /** @type {import('tailwindcss').Config} */ module.exports = { content: [ './*.php', './pages/**/*.php', './templates/**/*.php', './assets/js/**/*.js' ], theme: { extend: { colors: { primary: { DEFAULT: '#FA784F', light: '#FB9570', dark: '#E5623A', }, background: '#17171F', surface: { DEFAULT: '#1E1E28', light: '#2A2A36', }, border: '#3A3A48', text: { primary: '#F5F5F7', secondary: '#A1A1AA', muted: '#71717A', }, success: '#34D399', warning: '#FBBF24', error: '#F87171', info: '#60A5FA', }, fontFamily: { sans: ['Inter', 'system-ui', 'sans-serif'], mono: ['JetBrains Mono', 'monospace'], }, fontSize: { 'display': ['2.5rem', { lineHeight: '1.2', fontWeight: '700' }], 'heading': ['2rem', { lineHeight: '1.3', fontWeight: '600' }], 'subheading': ['1.5rem', { lineHeight: '1.4', fontWeight: '600' }], 'body': ['1rem', { lineHeight: '1.6', fontWeight: '400' }], 'small': ['0.875rem', { lineHeight: '1.5', fontWeight: '400' }], }, maxWidth: { 'content': '1280px', }, boxShadow: { 'card': '0 4px 20px rgba(0, 0, 0, 0.25)', 'card-hover': '0 10px 40px rgba(0, 0, 0, 0.3)', 'input-focus': '0 0 0 3px rgba(250, 120, 79, 0.2)', }, }, }, plugins: [], } ``` ### Configuration postcss.config.js ```javascript module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, } } ``` ### Contenu assets/css/input.css ```css @tailwind base; @tailwind components; @tailwind utilities; @layer base { html { @apply scroll-smooth; } body { @apply bg-background text-text-primary font-sans antialiased; } h1 { @apply text-display text-text-primary; } h2 { @apply text-heading text-text-primary; } h3 { @apply text-subheading text-text-primary; } p { @apply text-body text-text-secondary; } a { @apply text-primary hover:text-primary-light transition-colors duration-150; } :focus-visible { @apply outline-none ring-2 ring-primary ring-offset-2 ring-offset-background; } ::selection { @apply bg-primary/30 text-text-primary; } } @layer components { /* Container */ .container-content { @apply max-w-content mx-auto px-4 sm:px-6 lg:px-8; } /* Boutons */ .btn { @apply inline-flex items-center justify-center gap-2 px-6 py-3 font-medium rounded-lg transition-all duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-background disabled:opacity-50 disabled:cursor-not-allowed; } .btn-primary { @apply btn bg-primary text-background hover:bg-primary-light active:bg-primary-dark focus:ring-primary; } .btn-secondary { @apply btn border-2 border-primary text-primary bg-transparent hover:bg-primary hover:text-background focus:ring-primary; } .btn-ghost { @apply btn text-primary bg-transparent hover:text-primary-light hover:bg-surface-light focus:ring-primary; } /* Badges */ .badge { @apply inline-flex items-center px-2.5 py-1 text-xs font-medium rounded bg-surface-light text-text-secondary; } .badge-primary { @apply bg-primary/20 text-primary; } .badge-muted { @apply bg-border text-text-muted; } /* Cartes */ .card { @apply bg-surface rounded-lg overflow-hidden border border-border/50 transition-all duration-200; } .card-interactive { @apply card cursor-pointer hover:-translate-y-1 hover:shadow-card-hover hover:border-border; } .card-body { @apply p-4 sm:p-6; } /* Inputs */ .input { @apply w-full px-4 py-3 bg-surface border border-border rounded-lg text-text-primary placeholder-text-muted transition-all duration-150 focus:outline-none focus:border-primary focus:shadow-input-focus; } .input-error { @apply border-error focus:border-error focus:shadow-[0_0_0_3px_rgba(248,113,113,0.2)]; } .textarea { @apply input min-h-[150px] resize-y; } .label { @apply block text-sm font-medium text-text-secondary mb-2; } .label-required::after { content: '*'; @apply text-error ml-1; } .error-message { @apply text-sm text-error mt-1.5 flex items-center gap-1; } /* Sections */ .section { @apply py-16 sm:py-24; } .section-header { @apply text-center mb-12; } .section-title { @apply text-heading mb-4; } .section-subtitle { @apply text-body text-text-secondary max-w-2xl mx-auto; } /* Témoignage */ .testimonial { @apply bg-surface-light rounded-lg p-6 border-l-4 border-primary; } /* Breadcrumb */ .breadcrumb { @apply flex items-center gap-2 text-sm text-text-muted; } .breadcrumb-link { @apply text-text-secondary hover:text-primary transition-colors; } .breadcrumb-current { @apply text-text-primary; } } @layer utilities { .animate-fade-in { animation: fadeIn 0.6s ease-out forwards; } .animate-fade-in-up { animation: fadeInUp 0.6s ease-out forwards; } .animation-delay-100 { animation-delay: 100ms; } .animation-delay-200 { animation-delay: 200ms; } .animation-delay-300 { animation-delay: 300ms; } .aspect-thumbnail { aspect-ratio: 16 / 9; } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } /* Accessibilité - Réduction de mouvement */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } } ``` ### Scripts package.json ```json { "scripts": { "dev": "tailwindcss -i ./assets/css/input.css -o ./assets/css/output.css --watch", "build": "tailwindcss -i ./assets/css/input.css -o ./assets/css/output.css --minify" } } ``` ### Palette de Couleurs (Thème Sombre) | Couleur | Hex | Usage | |---------|-----|-------| | Primary | `#FA784F` | CTA, liens, accents | | Primary Light | `#FB9570` | Hover | | Primary Dark | `#E5623A` | Active/pressed | | Background | `#17171F` | Fond de page | | Surface | `#1E1E28` | Cartes, navbar | | Surface Light | `#2A2A36` | Hover cartes | | Border | `#3A3A48` | Bordures | | Text Primary | `#F5F5F7` | Titres | | Text Secondary | `#A1A1AA` | Texte secondaire | | Text Muted | `#71717A` | Placeholders | ## Testing ### Validation Manuelle - [ ] `npm install` s'exécute sans erreur - [ ] `npm run build` génère `output.css` - [ ] `npm run dev` lance le watch mode - [ ] Le fichier `output.css` < 50kb - [ ] Les classes Tailwind fonctionnent dans le navigateur ### Commandes de Test ```bash # Installer les dépendances npm install # Build production npm run build # Vérifier la taille du CSS ls -lh assets/css/output.css # Lancer le watch pour le dev npm run dev ``` ### Test Visuel Créer un fichier HTML temporaire pour tester les classes : - `.btn-primary` → bouton orange - `.bg-background` → fond sombre #17171F - `.text-primary` → texte orange - `.card` → carte avec fond surface ## Change Log | Date | Version | Description | Author | |------|---------|-------------|--------| | 2026-01-22 | 0.1 | Création initiale de la story | Sarah (PO) | ## Dev Agent Record ### Agent Model Used Claude Opus 4.5 (claude-opus-4-5-20251101) ### Debug Log References _À compléter par le dev agent_ ### Completion Notes List - Package.json mis à jour (v2.0.0) avec scripts dev/build Tailwind - Dépendances installées : tailwindcss ^3.4.0, postcss ^8.4.0, autoprefixer ^10.4.0 - tailwind.config.js créé avec palette couleurs sombre, polices Inter/JetBrains Mono - input.css complet avec @layer base, components (btn, card, input, badge, etc.), utilities (animations) - postcss.config.js configuré - Build validé : output.css = 6,4 Ko (< 50 Ko requis) - Warning normal : pas encore de fichiers PHP utilisant les classes ### File List | Fichier | Action | |---------|--------| | `package.json` | Modifié | | `package-lock.json` | Modifié | | `tailwind.config.js` | Créé | | `postcss.config.js` | Créé | | `assets/css/input.css` | Créé | | `assets/css/output.css` | Généré | ## QA Results _À compléter par le QA agent_