Compare commits
5 Commits
8dd68ef584
...
136cdf1736
| Author | SHA1 | Date | |
|---|---|---|---|
| 136cdf1736 | |||
| 0409bb1327 | |||
| d520fe848f | |||
| c9369df683 | |||
| a1092c9f60 |
7
.htaccess
Normal file
7
.htaccess
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
RewriteEngine On
|
||||||
|
RewriteBase /
|
||||||
|
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
|
||||||
|
RewriteRule ^(.*)$ index.php [QSA,L]
|
||||||
File diff suppressed because one or more lines are too long
7
assets/img/projects/default-project.svg
Normal file
7
assets/img/projects/default-project.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 225" width="400" height="225">
|
||||||
|
<rect width="400" height="225" fill="#1E1E28" />
|
||||||
|
<rect x="24" y="24" width="352" height="177" rx="12" fill="#2A2A36" stroke="#3A3A48" stroke-width="2" />
|
||||||
|
<circle cx="200" cy="100" r="28" fill="#FA784F" opacity="0.25" />
|
||||||
|
<rect x="140" y="142" width="120" height="12" rx="6" fill="#FA784F" opacity="0.6" />
|
||||||
|
<rect x="160" y="160" width="80" height="8" rx="4" fill="#71717A" />
|
||||||
|
</svg>
|
||||||
55
data/projects.json
Normal file
55
data/projects.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Site E-commerce XYZ",
|
||||||
|
"slug": "ecommerce-xyz",
|
||||||
|
"category": "vedette",
|
||||||
|
"thumbnail": "ecommerce-xyz-thumb.webp",
|
||||||
|
"url": "https://example.com",
|
||||||
|
"github": "https://github.com/user/project",
|
||||||
|
"technologies": ["PHP", "JavaScript", "Tailwind CSS", "MySQL"],
|
||||||
|
"context": "Client souhaitant moderniser sa boutique en ligne pour améliorer l'expérience utilisateur et augmenter les conversions.",
|
||||||
|
"solution": "Développement d'une solution e-commerce sur mesure avec panier persistant, paiement sécurisé Stripe, et interface d'administration.",
|
||||||
|
"teamwork": "Projet réalisé en collaboration avec un designer UI/UX. J'ai pris en charge l'intégration et le développement backend.",
|
||||||
|
"duration": "3 mois",
|
||||||
|
"screenshots": [
|
||||||
|
"ecommerce-xyz-screen-1.webp",
|
||||||
|
"ecommerce-xyz-screen-2.webp",
|
||||||
|
"ecommerce-xyz-screen-3.webp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Application de Gestion",
|
||||||
|
"slug": "app-gestion",
|
||||||
|
"category": "vedette",
|
||||||
|
"thumbnail": "app-gestion-thumb.webp",
|
||||||
|
"url": null,
|
||||||
|
"github": "https://github.com/user/app-gestion",
|
||||||
|
"technologies": ["React", "Node.js", "PostgreSQL", "Docker"],
|
||||||
|
"context": "Startup ayant besoin d'un outil interne pour gérer ses ressources et planifier ses projets.",
|
||||||
|
"solution": "Application web full-stack avec authentification, gestion des rôles, tableaux de bord et exports PDF.",
|
||||||
|
"teamwork": null,
|
||||||
|
"duration": "4 mois",
|
||||||
|
"screenshots": [
|
||||||
|
"app-gestion-screen-1.webp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Site Vitrine Restaurant",
|
||||||
|
"slug": "restaurant-vitrine",
|
||||||
|
"category": "secondaire",
|
||||||
|
"thumbnail": "restaurant-thumb.webp",
|
||||||
|
"url": "https://restaurant-example.com",
|
||||||
|
"github": null,
|
||||||
|
"technologies": ["HTML", "CSS", "JavaScript"],
|
||||||
|
"context": "Restaurant local souhaitant une présence en ligne simple.",
|
||||||
|
"solution": null,
|
||||||
|
"teamwork": null,
|
||||||
|
"duration": "2 semaines",
|
||||||
|
"screenshots": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Ready for Dev
|
review
|
||||||
|
|
||||||
## Story
|
## Story
|
||||||
|
|
||||||
@@ -21,31 +21,31 @@ Ready for Dev
|
|||||||
|
|
||||||
## Tasks / Subtasks
|
## Tasks / Subtasks
|
||||||
|
|
||||||
- [] **Task 1 : Créer la page home.php** (AC: 4)
|
- [x] **Task 1 : Créer la page home.php** (AC: 4)
|
||||||
- [] Créer `pages/home.php` (implémenté dans index.php, migration avec routeur)
|
- [x] Créer `pages/home.php` (implémenté dans index.php, migration avec routeur)
|
||||||
- [] Inclure header, navbar et footer
|
- [x] Inclure header, navbar et footer
|
||||||
- [ ] Configurer le routeur pour servir cette page sur `/` (story 3.2)
|
- [ ] Configurer le routeur pour servir cette page sur `/` (story 3.2)
|
||||||
|
|
||||||
- [] **Task 2 : Créer la section Hero** (AC: 1, 3)
|
- [x] **Task 2 : Créer la section Hero** (AC: 1, 3)
|
||||||
- [] Ajouter le nom/prénom du développeur
|
- [x] Ajouter le nom/prénom du développeur
|
||||||
- [] Ajouter le titre "Développeur Web Full-Stack"
|
- [x] Ajouter le titre "Développeur Web Full-Stack"
|
||||||
- [] Ajouter une phrase d'accroche percutante
|
- [x] Ajouter une phrase d'accroche percutante
|
||||||
- [] Centrer verticalement et horizontalement
|
- [x] Centrer verticalement et horizontalement
|
||||||
- [] Appliquer la typographie (text-display)
|
- [x] Appliquer la typographie (text-display)
|
||||||
|
|
||||||
- [] **Task 3 : Ajouter les CTA** (AC: 2)
|
- [x] **Task 3 : Ajouter les CTA** (AC: 2)
|
||||||
- [] Bouton principal "Découvrir mes projets" (btn-primary)
|
- [x] Bouton principal "Découvrir mes projets" (btn-primary)
|
||||||
- [] Bouton secondaire "En savoir plus" (btn-secondary) optionnel
|
- [x] Bouton secondaire "En savoir plus" (btn-secondary) optionnel
|
||||||
- [] Liens vers /projets et /a-propos
|
- [x] Liens vers /projets et /a-propos
|
||||||
|
|
||||||
- [] **Task 4 : Rendre responsive** (AC: 5)
|
- [x] **Task 4 : Rendre responsive** (AC: 5)
|
||||||
- [] Mobile : texte plus petit, padding réduit
|
- [x] Mobile : texte plus petit, padding réduit
|
||||||
- [] Desktop : taille maximale, centré
|
- [x] Desktop : taille maximale, centré
|
||||||
|
|
||||||
- [] **Task 5 : Ajouter les animations** (AC: 6)
|
- [x] **Task 5 : Ajouter les animations** (AC: 6)
|
||||||
- [] Fade-in sur le titre (animate-fade-in)
|
- [x] Fade-in sur le titre (animate-fade-in)
|
||||||
- [] Fade-in décalé sur le sous-titre (animation-delay-100)
|
- [x] Fade-in décalé sur le sous-titre (animation-delay-100)
|
||||||
- [] Fade-in décalé sur les boutons (animation-delay-200)
|
- [x] Fade-in décalé sur les boutons (animation-delay-200)
|
||||||
|
|
||||||
## Dev Notes
|
## Dev Notes
|
||||||
|
|
||||||
@@ -141,33 +141,42 @@ Les classes sont déjà définies dans input.css :
|
|||||||
|
|
||||||
| Date | Version | Description | Author |
|
| Date | Version | Description | Author |
|
||||||
|------|---------|-------------|--------|
|
|------|---------|-------------|--------|
|
||||||
|
| 2026-02-04 | 0.1 | Implementation story 2.3 | Amelia |
|
||||||
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
|
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
|
||||||
|
|
||||||
## Dev Agent Record
|
## Dev Agent Record
|
||||||
|
|
||||||
### Agent Model Used
|
### Agent Model Used
|
||||||
|
|
||||||
Claude Opus 4.5 (claude-opus-4-5-20251101)
|
GPT-5 Codex
|
||||||
|
|
||||||
### Debug Log References
|
### Debug Log References
|
||||||
|
|
||||||
_Aucun_
|
- tests/home.test.ps1: home page coverage
|
||||||
|
|
||||||
### Completion Notes List
|
### Completion Notes List
|
||||||
|
|
||||||
- Hero section créée dans index.php (migration vers pages/home.php avec routeur story 3.2)
|
- Hero section créée dans pages/home.php (routeur story 3.2)
|
||||||
- Typographie responsive : text-4xl → text-5xl → text-display
|
|
||||||
- Animations fade-in avec délais progressifs (100, 200, 300ms)
|
- Animations fade-in avec délais progressifs (100, 200, 300ms)
|
||||||
- CTA : btn-primary (projets) + btn-secondary (à propos)
|
- CTA : btn-primary (projets) + btn-secondary (à propos)
|
||||||
- Centrage vertical avec min-h-[calc(100vh-5rem)] et flex
|
- Centrage vertical avec min-h-[calc(100vh-5rem)] et flex
|
||||||
- Header, navbar, footer inclus via compact()
|
- Header, navbar, footer inclus via compact()
|
||||||
|
- CSS régénéré via `npm run build`
|
||||||
|
- Tests: `powershell -ExecutionPolicy Bypass -File tests/run.ps1`
|
||||||
|
|
||||||
### File List
|
### File List
|
||||||
|
|
||||||
| Fichier | Action |
|
| Fichier | Action |
|
||||||
|---------|--------|
|
|---------|--------|
|
||||||
| `index.php` | Modifié (Hero section) |
|
| `pages/home.php` | Créé |
|
||||||
|
| `index.php` | Modifié (inclut home.php) |
|
||||||
| `assets/css/output.css` | Regénéré |
|
| `assets/css/output.css` | Regénéré |
|
||||||
|
| `tests/home.test.ps1` | Créé |
|
||||||
|
| `tests/run.ps1` | Modifié |
|
||||||
|
| `tests/structure.test.ps1` | Modifié |
|
||||||
|
| `tests/templates.test.ps1` | Modifié |
|
||||||
|
| `tests/canary.test.ps1` | Modifié |
|
||||||
|
| `tests/navbar.test.ps1` | Modifié |
|
||||||
|
|
||||||
## QA Results
|
## QA Results
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Ready for Dev
|
review
|
||||||
|
|
||||||
## Story
|
## Story
|
||||||
|
|
||||||
@@ -20,28 +20,28 @@ Ready for Dev
|
|||||||
|
|
||||||
## Tasks / Subtasks
|
## Tasks / Subtasks
|
||||||
|
|
||||||
- [] **Task 1 : Ajouter la section sous le hero** (AC: 1)
|
- [x] **Task 1 : Ajouter la section sous le hero** (AC: 1)
|
||||||
- [] Créer une section avec titre "Explorez mon portfolio"
|
- [x] Créer une section avec titre "Explorez mon portfolio"
|
||||||
- [] Ajouter les 3 cartes de navigation
|
- [x] Ajouter les 3 cartes de navigation
|
||||||
|
|
||||||
- [] **Task 2 : Créer les cartes de navigation** (AC: 2)
|
- [x] **Task 2 : Créer les cartes de navigation** (AC: 2)
|
||||||
- [] Carte Projets : icône, titre, description, lien
|
- [x] Carte Projets : icône, titre, description, lien
|
||||||
- [] Carte Compétences : icône, titre, description, lien
|
- [x] Carte Compétences : icône, titre, description, lien
|
||||||
- [] Carte Me Découvrir : icône, titre, description, lien
|
- [x] Carte Me Découvrir : icône, titre, description, lien
|
||||||
|
|
||||||
- [] **Task 3 : Implémenter la grille responsive** (AC: 3)
|
- [x] **Task 3 : Implémenter la grille responsive** (AC: 3)
|
||||||
- [] 1 colonne sur mobile (grid-cols-1)
|
- [x] 1 colonne sur mobile (grid-cols-1)
|
||||||
- [] 3 colonnes sur desktop (md:grid-cols-3)
|
- [x] 3 colonnes sur desktop (md:grid-cols-3)
|
||||||
- [] Gap approprié entre les cartes (gap-6 lg:gap-8)
|
- [x] Gap approprié entre les cartes (gap-6 lg:gap-8)
|
||||||
|
|
||||||
- [] **Task 4 : Ajouter les effets hover** (AC: 4)
|
- [x] **Task 4 : Ajouter les effets hover** (AC: 4)
|
||||||
- [] Utiliser la classe card-interactive
|
- [x] Utiliser la classe card-interactive
|
||||||
- [] Élévation + ombre au hover
|
- [x] Élévation + ombre au hover
|
||||||
|
|
||||||
- [] **Task 5 : Intégrer les icônes** (AC: 5)
|
- [x] **Task 5 : Intégrer les icônes** (AC: 5)
|
||||||
- [] Utiliser Heroicons (SVG inline)
|
- [x] Utiliser Heroicons (SVG inline)
|
||||||
- [] Taille cohérente (w-8 h-8 dans conteneur w-16 h-16)
|
- [x] Taille cohérente (w-8 h-8 dans conteneur w-16 h-16)
|
||||||
- [] Couleur primary
|
- [x] Couleur primary
|
||||||
|
|
||||||
## Dev Notes
|
## Dev Notes
|
||||||
|
|
||||||
@@ -139,33 +139,38 @@ Ready for Dev
|
|||||||
|
|
||||||
| Date | Version | Description | Author |
|
| Date | Version | Description | Author |
|
||||||
|------|---------|-------------|--------|
|
|------|---------|-------------|--------|
|
||||||
|
| 2026-02-04 | 0.1 | Implementation story 2.4 | Amelia |
|
||||||
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
|
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
|
||||||
|
|
||||||
## Dev Agent Record
|
## Dev Agent Record
|
||||||
|
|
||||||
### Agent Model Used
|
### Agent Model Used
|
||||||
|
|
||||||
Claude Opus 4.5 (claude-opus-4-5-20251101)
|
GPT-5 Codex
|
||||||
|
|
||||||
### Debug Log References
|
### Debug Log References
|
||||||
|
|
||||||
_Aucun_
|
- tests/quicknav.test.ps1: quick navigation coverage
|
||||||
|
|
||||||
### Completion Notes List
|
### Completion Notes List
|
||||||
|
|
||||||
- Section navigation rapide ajoutée sous le hero dans index.php
|
- Section navigation rapide ajoutée sous le hero dans pages/home.php
|
||||||
- 3 cartes : Projets, Compétences, Me Découvrir
|
- 3 cartes : Projets, Compétences, Me Découvrir
|
||||||
- Grille responsive : grid-cols-1 mobile, md:grid-cols-3 tablet+
|
- Grille responsive : grid-cols-1 mobile, md:grid-cols-3 tablet+
|
||||||
- Icônes Heroicons SVG inline (squares-2x2, code-bracket, user)
|
- Icônes Heroicons SVG inline (squares-2x2, code-bracket, user)
|
||||||
- Effets hover via card-interactive + group-hover sur titres
|
- Effets hover via card-interactive + group-hover sur titres
|
||||||
- Conteneurs d'icônes avec bg-primary/10 → bg-primary/20 au hover
|
- Conteneurs d'icônes avec bg-primary/10 → bg-primary/20 au hover
|
||||||
|
- CSS régénéré via `npm run build`
|
||||||
|
- Tests: `powershell -ExecutionPolicy Bypass -File tests/run.ps1`
|
||||||
|
|
||||||
### File List
|
### File List
|
||||||
|
|
||||||
| Fichier | Action |
|
| Fichier | Action |
|
||||||
|---------|--------|
|
|---------|--------|
|
||||||
| `index.php` | Modifié (section navigation) |
|
| `pages/home.php` | Modifié (section navigation) |
|
||||||
| `assets/css/output.css` | Regénéré |
|
| `assets/css/output.css` | Regénéré |
|
||||||
|
| `tests/quicknav.test.ps1` | Créé |
|
||||||
|
| `tests/run.ps1` | Modifié |
|
||||||
|
|
||||||
## QA Results
|
## QA Results
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Ready for Dev
|
review
|
||||||
|
|
||||||
## Story
|
## Story
|
||||||
|
|
||||||
@@ -20,27 +20,27 @@ Ready for Dev
|
|||||||
|
|
||||||
## Tasks / Subtasks
|
## Tasks / Subtasks
|
||||||
|
|
||||||
- [] **Task 1 : Définir la structure JSON** (AC: 2)
|
- [x] **Task 1 : Définir la structure JSON** (AC: 2)
|
||||||
- [] Documenter tous les champs requis et optionnels
|
- [x] Documenter tous les champs requis et optionnels
|
||||||
- [] Définir les types de données pour chaque champ
|
- [x] Définir les types de données pour chaque champ
|
||||||
- [] Définir les valeurs possibles pour category
|
- [x] Définir les valeurs possibles pour category
|
||||||
|
|
||||||
- [] **Task 2 : Créer le fichier projects.json** (AC: 1, 3)
|
- [x] **Task 2 : Créer le fichier projects.json** (AC: 1, 3)
|
||||||
- [] Créer `data/projects.json`
|
- [x] Créer `data/projects.json`
|
||||||
- [] Ajouter 2-3 projets de test
|
- [x] Ajouter 2-3 projets de test
|
||||||
- [] Valider la syntaxe JSON
|
- [x] Valider la syntaxe JSON
|
||||||
|
|
||||||
- [] **Task 3 : Créer les fonctions PHP d'accès** (AC: 4, 5)
|
- [x] **Task 3 : Créer les fonctions PHP d'accès** (AC: 4, 5)
|
||||||
- [] Créer `loadJsonData()` générique
|
- [x] Créer `loadJsonData()` générique
|
||||||
- [] Créer `getProjects()`
|
- [x] Créer `getProjects()`
|
||||||
- [] Créer `getProjectsByCategory()`
|
- [x] Créer `getProjectsByCategory()`
|
||||||
- [] Créer `getProjectBySlug()`
|
- [x] Créer `getProjectBySlug()`
|
||||||
- [] Gérer les erreurs (fichier manquant, JSON invalide)
|
- [x] Gérer les erreurs (fichier manquant, JSON invalide)
|
||||||
|
|
||||||
- [] **Task 4 : Tester les fonctions**
|
- [x] **Task 4 : Tester les fonctions**
|
||||||
- [] Tester avec fichier valide
|
- [x] Tester avec fichier valide
|
||||||
- [] Tester avec fichier manquant
|
- [x] Tester avec fichier manquant
|
||||||
- [] Tester avec JSON invalide
|
- [x] Tester avec JSON invalide
|
||||||
|
|
||||||
## Dev Notes
|
## Dev Notes
|
||||||
|
|
||||||
@@ -209,27 +209,29 @@ function getAllTechnologies(): array
|
|||||||
## Dev Agent Record
|
## Dev Agent Record
|
||||||
|
|
||||||
### Agent Model Used
|
### Agent Model Used
|
||||||
Claude Opus 4.5 (claude-opus-4-5-20251101)
|
GPT-5 Codex
|
||||||
|
|
||||||
### File List
|
### File List
|
||||||
| File | Action | Description |
|
| File | Action | Description |
|
||||||
|------|--------|-------------|
|
|------|--------|-------------|
|
||||||
| `data/projects.json` | Created | Fichier JSON avec 3 projets de test |
|
| `data/projects.json` | Created | Fichier JSON avec 3 projets de test |
|
||||||
| `includes/functions.php` | Modified | Ajout des fonctions d'accès aux données JSON |
|
| `includes/functions.php` | Modified | Ajout des fonctions d'accès aux données JSON |
|
||||||
|
| `tests/projects.test.php` | Created | Tests fonctions JSON |
|
||||||
|
| `tests/run.ps1` | Modified | Ajout tests JSON |
|
||||||
|
|
||||||
### Completion Notes
|
### Completion Notes
|
||||||
- Structure JSON complète avec tous les champs requis et optionnels
|
- Structure JSON complète avec tous les champs requis et optionnels
|
||||||
- 3 projets de test ajoutés (2 vedettes, 1 secondaire)
|
- 3 projets de test ajoutés (2 vedettes, 1 secondaire)
|
||||||
- Fonctions PHP: `loadJsonData()`, `getProjects()`, `getProjectsByCategory()`, `getProjectBySlug()`, `getAllTechnologies()`
|
- Fonctions PHP: `loadJsonData()`, `getProjects()`, `getProjectsByCategory()`, `getProjectBySlug()`, `getAllTechnologies()`
|
||||||
- Gestion des erreurs: fichier manquant et JSON invalide retournent tableau vide avec log
|
- Gestion des erreurs: fichier manquant et JSON invalide retournent tableau vide avec log
|
||||||
- Tous les tests passent (8/8)
|
- Tests: `powershell -ExecutionPolicy Bypass -File tests/run.ps1`
|
||||||
|
|
||||||
### Debug Log References
|
### Debug Log References
|
||||||
Aucun problème rencontré.
|
Aucun problème bloquant (BOM retiré de `data/projects.json`).
|
||||||
|
|
||||||
## Change Log
|
## Change Log
|
||||||
|
|
||||||
| Date | Version | Description | Author |
|
| Date | Version | Description | Author |
|
||||||
|------|---------|-------------|--------|
|
|------|---------|-------------|--------|
|
||||||
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
|
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
|
||||||
| 2026-01-23 | 1.0 | Implémentation complète | James (Dev) |
|
| 2026-02-04 | 1.0 | Implémentation complète | Amelia |
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Ready for Dev
|
review
|
||||||
|
|
||||||
## Story
|
## Story
|
||||||
|
|
||||||
@@ -21,34 +21,34 @@ Ready for Dev
|
|||||||
|
|
||||||
## Tasks / Subtasks
|
## Tasks / Subtasks
|
||||||
|
|
||||||
- [] **Task 1 : Créer le router PHP** (AC: 2, 6)
|
- [x] **Task 1 : Créer le router PHP** (AC: 2, 6)
|
||||||
- [] Créer `includes/router.php`
|
- [x] Créer `includes/router.php`
|
||||||
- [] Implémenter la classe Router
|
- [x] Implémenter la classe Router
|
||||||
- [] Méthode add() pour ajouter des routes
|
- [x] Méthode add() pour ajouter des routes
|
||||||
- [] Méthode resolve() pour matcher une URL
|
- [x] Méthode resolve() pour matcher une URL
|
||||||
- [] Méthode dispatch() pour exécuter la route
|
- [x] Méthode dispatch() pour exécuter la route
|
||||||
|
|
||||||
- [] **Task 2 : Configurer les routes** (AC: 3, 4)
|
- [x] **Task 2 : Configurer les routes** (AC: 3, 4)
|
||||||
- [] Route `/` → pages/home.php
|
- [x] Route `/` → pages/home.php
|
||||||
- [] Route `/projets` → pages/projects.php
|
- [x] Route `/projets` → pages/projects.php
|
||||||
- [] Route `/projet/{slug}` → pages/project-single.php
|
- [x] Route `/projet/{slug}` → pages/project-single.php
|
||||||
- [] Route `/competences` → pages/skills.php
|
- [x] Route `/competences` → pages/skills.php
|
||||||
- [] Route `/a-propos` → pages/about.php
|
- [x] Route `/a-propos` → pages/about.php
|
||||||
- [] Route `/contact` → pages/contact.php
|
- [x] Route `/contact` → pages/contact.php
|
||||||
|
|
||||||
- [] **Task 3 : Créer la page 404** (AC: 5)
|
- [x] **Task 3 : Créer la page 404** (AC: 5)
|
||||||
- [] Créer `pages/404.php`
|
- [x] Créer `pages/404.php`
|
||||||
- [] Design cohérent avec le site
|
- [x] Design cohérent avec le site
|
||||||
- [] Lien retour vers l'accueil
|
- [x] Lien retour vers l'accueil
|
||||||
|
|
||||||
- [] **Task 4 : Configurer le serveur** (AC: 1)
|
- [x] **Task 4 : Configurer le serveur** (AC: 1)
|
||||||
- [] Créer `.htaccess` pour Apache
|
- [x] Créer `.htaccess` pour Apache
|
||||||
- [] Documenter la config nginx équivalente
|
- [x] Documenter la config nginx équivalente
|
||||||
|
|
||||||
- [] **Task 5 : Mettre à jour index.php**
|
- [x] **Task 5 : Mettre à jour index.php**
|
||||||
- [] Inclure le router
|
- [x] Inclure le router
|
||||||
- [] Définir toutes les routes
|
- [x] Définir toutes les routes
|
||||||
- [] Appeler dispatch()
|
- [x] Appeler dispatch()
|
||||||
|
|
||||||
## Dev Notes
|
## Dev Notes
|
||||||
|
|
||||||
@@ -215,36 +215,38 @@ if (!$project) {
|
|||||||
## Dev Agent Record
|
## Dev Agent Record
|
||||||
|
|
||||||
### Agent Model Used
|
### Agent Model Used
|
||||||
Claude Opus 4.5 (claude-opus-4-5-20251101)
|
GPT-5 Codex
|
||||||
|
|
||||||
### File List
|
### File List
|
||||||
| File | Action | Description |
|
| File | Action | Description |
|
||||||
|------|--------|-------------|
|
|------|--------|-------------|
|
||||||
| `includes/router.php` | Created | Router PHP simple (43 lignes) |
|
| `includes/router.php` | Created | Router PHP simple |
|
||||||
| `index.php` | Modified | Converti en front controller |
|
| `index.php` | Modified | Converti en front controller |
|
||||||
| `.htaccess` | Created | Réécriture URLs Apache |
|
| `.htaccess` | Created | Réécriture URLs Apache |
|
||||||
| `pages/home.php` | Created | Page d'accueil |
|
|
||||||
| `pages/projects.php` | Created | Page liste projets (placeholder) |
|
| `pages/projects.php` | Created | Page liste projets (placeholder) |
|
||||||
| `pages/project-single.php` | Created | Page projet individuel |
|
| `pages/project-single.php` | Created | Page projet individuel |
|
||||||
| `pages/skills.php` | Created | Page compétences (placeholder) |
|
| `pages/skills.php` | Created | Page compétences (placeholder) |
|
||||||
| `pages/about.php` | Created | Page à propos (placeholder) |
|
| `pages/about.php` | Created | Page à propos (placeholder) |
|
||||||
| `pages/contact.php` | Created | Page contact (placeholder) |
|
| `pages/contact.php` | Created | Page contact (placeholder) |
|
||||||
| `pages/404.php` | Created | Page erreur 404 |
|
| `pages/404.php` | Created | Page erreur 404 |
|
||||||
|
| `tests/router.test.php` | Created | Tests du router |
|
||||||
|
| `tests/router.test.ps1` | Created | Vérif .htaccess |
|
||||||
|
| `tests/run.ps1` | Modified | Ajout tests router |
|
||||||
|
|
||||||
### Completion Notes
|
### Completion Notes
|
||||||
- Router PHP léger (43 lignes < 50 requis)
|
- Router PHP léger (< 50 lignes)
|
||||||
- Support des paramètres dynamiques {slug}
|
- Support des paramètres dynamiques {slug}
|
||||||
- Trailing slash normalisé automatiquement
|
- Trailing slash normalisé automatiquement
|
||||||
- 404 pour routes inconnues
|
- 404 pour routes inconnues
|
||||||
- Pages placeholder créées pour futures stories
|
- Pages placeholder créées pour futures stories
|
||||||
- Tous les tests du router passent (8/8)
|
- Tests: `powershell -ExecutionPolicy Bypass -File tests/run.ps1`
|
||||||
|
|
||||||
### Debug Log References
|
### Debug Log References
|
||||||
Aucun problème rencontré.
|
Aucun problème bloquant.
|
||||||
|
|
||||||
## Change Log
|
## Change Log
|
||||||
|
|
||||||
| Date | Version | Description | Author |
|
| Date | Version | Description | Author |
|
||||||
|------|---------|-------------|--------|
|
|------|---------|-------------|--------|
|
||||||
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
|
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
|
||||||
| 2026-01-23 | 1.0 | Implémentation complète | James (Dev) |
|
| 2026-02-04 | 1.0 | Implémentation complète | Amelia |
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Ready for Dev
|
review
|
||||||
|
|
||||||
## Story
|
## Story
|
||||||
|
|
||||||
@@ -21,31 +21,31 @@ Ready for Dev
|
|||||||
|
|
||||||
## Tasks / Subtasks
|
## Tasks / Subtasks
|
||||||
|
|
||||||
- [] **Task 1 : Créer la page projects.php** (AC: 1)
|
- [x] **Task 1 : Créer la page projects.php** (AC: 1)
|
||||||
- [] Créer `pages/projects.php`
|
- [x] Créer `pages/projects.php`
|
||||||
- [] Récupérer les projets vedettes avec `getProjectsByCategory('vedette')`
|
- [x] Récupérer les projets vedettes avec `getProjectsByCategory('vedette')`
|
||||||
- [] Inclure header, navbar, footer
|
- [x] Inclure header, navbar, footer
|
||||||
|
|
||||||
- [] **Task 2 : Créer le template project-card.php** (AC: 2, 6)
|
- [x] **Task 2 : Créer le template project-card.php** (AC: 2, 6)
|
||||||
- [] Créer `templates/project-card.php`
|
- [x] Créer `templates/project-card.php`
|
||||||
- [] Afficher le thumbnail avec lazy loading
|
- [x] Afficher le thumbnail avec lazy loading
|
||||||
- [] Afficher le titre
|
- [x] Afficher le titre
|
||||||
- [] Afficher les badges technologies (max 4)
|
- [x] Afficher les badges technologies (max 4)
|
||||||
- [] Rendre le composant réutilisable
|
- [x] Rendre le composant réutilisable
|
||||||
|
|
||||||
- [] **Task 3 : Implémenter la grille responsive** (AC: 4)
|
- [x] **Task 3 : Implémenter la grille responsive** (AC: 4)
|
||||||
- [] 1 colonne sur mobile
|
- [x] 1 colonne sur mobile
|
||||||
- [] 2 colonnes sur tablette (sm:)
|
- [x] 2 colonnes sur tablette (sm:)
|
||||||
- [] 3 colonnes sur desktop (lg:)
|
- [x] 3 colonnes sur desktop (lg:)
|
||||||
|
|
||||||
- [] **Task 4 : Ajouter les interactions** (AC: 3, 5)
|
- [x] **Task 4 : Ajouter les interactions** (AC: 3, 5)
|
||||||
- [] Carte entière cliquable (lien vers /projet/{slug})
|
- [x] Carte entière cliquable (lien vers /projet/{slug})
|
||||||
- [] Effet hover avec card-interactive
|
- [x] Effet hover avec card-interactive
|
||||||
- [] Transition smooth
|
- [x] Transition smooth
|
||||||
|
|
||||||
- [] **Task 5 : Gérer les cas limites**
|
- [x] **Task 5 : Gérer les cas limites**
|
||||||
- [] Aucun projet → message "Projets à venir"
|
- [x] Aucun projet → message "Projets à venir"
|
||||||
- [] Image manquante → placeholder (onerror fallback)
|
- [x] Image manquante → placeholder (onerror fallback)
|
||||||
|
|
||||||
## Dev Notes
|
## Dev Notes
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ $maxTechs = 4;
|
|||||||
## Dev Agent Record
|
## Dev Agent Record
|
||||||
|
|
||||||
### Agent Model Used
|
### Agent Model Used
|
||||||
Claude Opus 4.5 (claude-opus-4-5-20251101)
|
GPT-5 Codex
|
||||||
|
|
||||||
### File List
|
### File List
|
||||||
| File | Action | Description |
|
| File | Action | Description |
|
||||||
@@ -192,6 +192,8 @@ Claude Opus 4.5 (claude-opus-4-5-20251101)
|
|||||||
| `pages/projects.php` | Modified | Page liste projets vedettes |
|
| `pages/projects.php` | Modified | Page liste projets vedettes |
|
||||||
| `templates/project-card.php` | Created | Template carte projet réutilisable |
|
| `templates/project-card.php` | Created | Template carte projet réutilisable |
|
||||||
| `assets/img/projects/default-project.svg` | Created | Placeholder image par défaut |
|
| `assets/img/projects/default-project.svg` | Created | Placeholder image par défaut |
|
||||||
|
| `tests/projects-list.test.php` | Created | Tests page projets |
|
||||||
|
| `tests/run.ps1` | Modified | Ajout tests projets |
|
||||||
|
|
||||||
### Completion Notes
|
### Completion Notes
|
||||||
- Grille responsive: 1 col (mobile) → 2 cols (sm) → 3 cols (lg)
|
- Grille responsive: 1 col (mobile) → 2 cols (sm) → 3 cols (lg)
|
||||||
@@ -199,7 +201,7 @@ Claude Opus 4.5 (claude-opus-4-5-20251101)
|
|||||||
- Lazy loading natif sur les images
|
- Lazy loading natif sur les images
|
||||||
- Fallback onerror pour images manquantes
|
- Fallback onerror pour images manquantes
|
||||||
- Message "Projets à venir" si aucun projet
|
- Message "Projets à venir" si aucun projet
|
||||||
- 2 projets vedettes affichés correctement
|
- Tests: `powershell -ExecutionPolicy Bypass -File tests/run.ps1`
|
||||||
|
|
||||||
### Debug Log References
|
### Debug Log References
|
||||||
Aucun problème rencontré.
|
Aucun problème rencontré.
|
||||||
@@ -209,4 +211,4 @@ Aucun problème rencontré.
|
|||||||
| Date | Version | Description | Author |
|
| Date | Version | Description | Author |
|
||||||
|------|---------|-------------|--------|
|
|------|---------|-------------|--------|
|
||||||
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
|
| 2026-01-22 | 0.1 | Création initiale | Sarah (PO) |
|
||||||
| 2026-01-23 | 1.0 | Implémentation complète | James (Dev) |
|
| 2026-02-04 | 1.0 | Implémentation complète | Amelia |
|
||||||
|
|||||||
@@ -9,3 +9,73 @@ function include_template(string $name, array $data = []): void
|
|||||||
extract($data, EXTR_SKIP);
|
extract($data, EXTR_SKIP);
|
||||||
include __DIR__ . "/../templates/{$name}.php";
|
include __DIR__ . "/../templates/{$name}.php";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Charge et parse un fichier JSON
|
||||||
|
*/
|
||||||
|
function loadJsonData(string $filename): array
|
||||||
|
{
|
||||||
|
$path = __DIR__ . "/../data/{$filename}";
|
||||||
|
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
error_log("JSON file not found: {$filename}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = file_get_contents($path);
|
||||||
|
$data = json_decode($content, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
error_log("JSON parse error in {$filename}: " . json_last_error_msg());
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère tous les projets
|
||||||
|
*/
|
||||||
|
function getProjects(): array
|
||||||
|
{
|
||||||
|
$data = loadJsonData('projects.json');
|
||||||
|
return $data['projects'] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les projets par catégorie
|
||||||
|
*/
|
||||||
|
function getProjectsByCategory(string $category): array
|
||||||
|
{
|
||||||
|
return array_values(array_filter(getProjects(), fn($p) => ($p['category'] ?? '') === $category));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère un projet par son slug
|
||||||
|
*/
|
||||||
|
function getProjectBySlug(string $slug): ?array
|
||||||
|
{
|
||||||
|
foreach (getProjects() as $project) {
|
||||||
|
if (($project['slug'] ?? '') === $slug) {
|
||||||
|
return $project;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les technologies uniques de tous les projets
|
||||||
|
*/
|
||||||
|
function getAllTechnologies(): array
|
||||||
|
{
|
||||||
|
$technologies = [];
|
||||||
|
foreach (getProjects() as $project) {
|
||||||
|
foreach ($project['technologies'] ?? [] as $tech) {
|
||||||
|
if (!in_array($tech, $technologies, true)) {
|
||||||
|
$technologies[] = $tech;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort($technologies);
|
||||||
|
return $technologies;
|
||||||
|
}
|
||||||
38
includes/router.php
Normal file
38
includes/router.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Router simple pour URLs propres
|
||||||
|
*/
|
||||||
|
class Router
|
||||||
|
{
|
||||||
|
private array $routes = [];
|
||||||
|
|
||||||
|
public function add(string $pattern, string $handler): self
|
||||||
|
{
|
||||||
|
$regex = preg_replace('/\{(\w+)\}/', '([^/]+)', $pattern);
|
||||||
|
$this->routes['#^' . $regex . '$#'] = $handler;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolve(string $uri): array
|
||||||
|
{
|
||||||
|
$path = parse_url($uri, PHP_URL_PATH);
|
||||||
|
$path = rtrim($path, '/') ?: '/';
|
||||||
|
|
||||||
|
foreach ($this->routes as $regex => $handler) {
|
||||||
|
if (preg_match($regex, $path, $matches)) {
|
||||||
|
array_shift($matches);
|
||||||
|
return [$handler, $matches];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['pages/404.php', []];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch(): void
|
||||||
|
{
|
||||||
|
$uri = $_SERVER['REQUEST_URI'] ?? '/';
|
||||||
|
[$handler, $params] = $this->resolve($uri);
|
||||||
|
$GLOBALS['routeParams'] = $params;
|
||||||
|
require __DIR__ . '/../' . $handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
index.php
57
index.php
@@ -1,52 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
// index.php - Page Canary
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/functions.php';
|
require_once __DIR__ . '/includes/functions.php';
|
||||||
|
require_once __DIR__ . '/includes/router.php';
|
||||||
|
|
||||||
include_template('header', [
|
$router = new Router();
|
||||||
'pageTitle' => 'Portfolio en construction',
|
|
||||||
'pageDescription' => 'Mon portfolio de développeur web arrive bientôt. Restez connectés !'
|
|
||||||
]);
|
|
||||||
|
|
||||||
include_template('navbar', [
|
$router
|
||||||
'currentPage' => 'home'
|
->add('/', 'pages/home.php')
|
||||||
]);
|
->add('/projets', 'pages/projects.php')
|
||||||
?>
|
->add('/projet/{slug}', 'pages/project-single.php')
|
||||||
|
->add('/competences', 'pages/skills.php')
|
||||||
|
->add('/a-propos', 'pages/about.php')
|
||||||
|
->add('/contact', 'pages/contact.php');
|
||||||
|
|
||||||
<main class="min-h-screen flex items-center justify-center">
|
$router->dispatch();
|
||||||
<div class="container-content text-center py-20">
|
|
||||||
<h1 class="text-display text-text-primary mb-4 animate-fade-in">
|
|
||||||
Portfolio <span class="text-primary">en construction</span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p class="text-xl text-text-secondary mb-8 max-w-2xl mx-auto animate-fade-in animation-delay-100">
|
|
||||||
Je prépare quelque chose de génial pour vous.
|
|
||||||
<br>Revenez bientôt pour découvrir mes projets !
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="flex justify-center gap-4 mb-12 animate-fade-in animation-delay-200">
|
|
||||||
<span class="badge">PHP</span>
|
|
||||||
<span class="badge">Tailwind CSS</span>
|
|
||||||
<span class="badge badge-primary">En cours</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card max-w-md mx-auto animate-fade-in animation-delay-300">
|
|
||||||
<div class="card-body">
|
|
||||||
<h3 class="text-subheading mb-2">Infrastructure validée</h3>
|
|
||||||
<p class="text-text-secondary mb-4">
|
|
||||||
PHP, Tailwind CSS et le serveur fonctionnent correctement.
|
|
||||||
</p>
|
|
||||||
<div class="flex gap-4 justify-center">
|
|
||||||
<span class="btn-primary">Bouton Primary</span>
|
|
||||||
<span class="btn-secondary">Bouton Secondary</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="text-text-muted text-sm mt-12">
|
|
||||||
Testé sur mobile, tablette et desktop.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<?php include_template('footer'); ?>
|
|
||||||
23
pages/404.php
Normal file
23
pages/404.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
http_response_code(404);
|
||||||
|
|
||||||
|
$pageTitle = 'Page non trouvée';
|
||||||
|
$currentPage = '';
|
||||||
|
|
||||||
|
include_template('header', compact('pageTitle'));
|
||||||
|
include_template('navbar', compact('currentPage'));
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="min-h-screen flex items-center justify-center">
|
||||||
|
<div class="container-content text-center py-20">
|
||||||
|
<h1 class="text-display text-primary mb-4">404</h1>
|
||||||
|
<p class="text-xl text-text-secondary mb-8">
|
||||||
|
Oups ! Cette page n'existe pas.
|
||||||
|
</p>
|
||||||
|
<a href="/" class="btn-primary">
|
||||||
|
Retour à l'accueil
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include_template('footer'); ?>
|
||||||
16
pages/about.php
Normal file
16
pages/about.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
$pageTitle = 'Me Découvrir';
|
||||||
|
$currentPage = 'about';
|
||||||
|
|
||||||
|
include_template('header', compact('pageTitle'));
|
||||||
|
include_template('navbar', compact('currentPage'));
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="min-h-screen">
|
||||||
|
<div class="container-content py-20">
|
||||||
|
<h1 class="text-heading mb-4">Me Découvrir</h1>
|
||||||
|
<p class="text-text-secondary">Le parcours arrive bientôt.</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include_template('footer'); ?>
|
||||||
16
pages/contact.php
Normal file
16
pages/contact.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
$pageTitle = 'Contact';
|
||||||
|
$currentPage = 'contact';
|
||||||
|
|
||||||
|
include_template('header', compact('pageTitle'));
|
||||||
|
include_template('navbar', compact('currentPage'));
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="min-h-screen">
|
||||||
|
<div class="container-content py-20">
|
||||||
|
<h1 class="text-heading mb-4">Contact</h1>
|
||||||
|
<p class="text-text-secondary">Le formulaire arrive bientôt.</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include_template('footer'); ?>
|
||||||
103
pages/home.php
Normal file
103
pages/home.php
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Page d'accueil
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../includes/functions.php';
|
||||||
|
|
||||||
|
$pageTitle = 'Accueil';
|
||||||
|
$pageDescription = 'Portfolio de développeur web full-stack. Découvrez mes projets, compétences et parcours.';
|
||||||
|
$currentPage = 'home';
|
||||||
|
|
||||||
|
include_template('header', compact('pageTitle', 'pageDescription'));
|
||||||
|
include_template('navbar', compact('currentPage'));
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="min-h-[calc(100vh-5rem)] flex items-center justify-center">
|
||||||
|
<div class="container-content text-center py-20">
|
||||||
|
<p class="text-primary font-medium mb-4 animate-fade-in">
|
||||||
|
Bonjour, je suis
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1 class="text-display text-text-primary mb-6 animate-fade-in animation-delay-100">
|
||||||
|
Célian <span class="text-primary">Skycel</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p class="text-heading text-text-secondary mb-6 animate-fade-in animation-delay-200">
|
||||||
|
Développeur Web Full-Stack
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="text-xl text-text-secondary max-w-2xl mx-auto mb-10 animate-fade-in animation-delay-300">
|
||||||
|
Je crée des expériences web modernes, performantes et accessibles.
|
||||||
|
<br>Chaque projet est une opportunité de montrer plutôt que de dire.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4 justify-center animate-fade-in animation-delay-300">
|
||||||
|
<a href="/projets" class="btn-primary">
|
||||||
|
Découvrir mes projets
|
||||||
|
</a>
|
||||||
|
<a href="/a-propos" class="btn-secondary">
|
||||||
|
En savoir plus
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section bg-surface">
|
||||||
|
<div class="container-content">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2 class="section-title">Explorez mon portfolio</h2>
|
||||||
|
<p class="section-subtitle">
|
||||||
|
Découvrez mes réalisations, compétences et parcours
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8">
|
||||||
|
<a href="/projets" class="card-interactive group">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||||
|
<svg class="w-8 h-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-subheading mb-2 group-hover:text-primary transition-colors">Projets</h3>
|
||||||
|
<p class="text-text-secondary">
|
||||||
|
Découvrez mes réalisations web avec démonstrations et explications techniques.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/competences" class="card-interactive group">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||||
|
<svg class="w-8 h-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-subheading mb-2 group-hover:text-primary transition-colors">Compétences</h3>
|
||||||
|
<p class="text-text-secondary">
|
||||||
|
Technologies maîtrisées et outils utilisés, avec preuves à l'appui.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/a-propos" class="card-interactive group">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||||
|
<svg class="w-8 h-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-subheading mb-2 group-hover:text-primary transition-colors">Me Découvrir</h3>
|
||||||
|
<p class="text-text-secondary">
|
||||||
|
Mon parcours, mes motivations et ce qui me passionne au-delà du code.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include_template('footer'); ?>
|
||||||
24
pages/project-single.php
Normal file
24
pages/project-single.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
$currentPage = '';
|
||||||
|
$slug = $GLOBALS['routeParams'][0] ?? null;
|
||||||
|
$project = $slug ? getProjectBySlug($slug) : null;
|
||||||
|
|
||||||
|
if (!$project) {
|
||||||
|
http_response_code(404);
|
||||||
|
include __DIR__ . '/404.php';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageTitle = $project['title'] ?? 'Projet';
|
||||||
|
include_template('header', compact('pageTitle'));
|
||||||
|
include_template('navbar', compact('currentPage'));
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="min-h-screen">
|
||||||
|
<div class="container-content py-20">
|
||||||
|
<h1 class="text-heading mb-4"><?= htmlspecialchars($project['title'], ENT_QUOTES, 'UTF-8') ?></h1>
|
||||||
|
<p class="text-text-secondary">Page projet en construction.</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include_template('footer'); ?>
|
||||||
37
pages/projects.php
Normal file
37
pages/projects.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
$pageTitle = 'Mes Projets';
|
||||||
|
$pageDescription = 'Découvrez mes réalisations web : sites vitrines, e-commerce, applications et plus encore.';
|
||||||
|
$currentPage = 'projects';
|
||||||
|
|
||||||
|
$featuredProjects = getProjectsByCategory('vedette');
|
||||||
|
|
||||||
|
include_template('header', compact('pageTitle', 'pageDescription'));
|
||||||
|
include_template('navbar', compact('currentPage'));
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="section">
|
||||||
|
<div class="container-content">
|
||||||
|
<div class="section-header">
|
||||||
|
<h1 class="section-title">Mes Projets</h1>
|
||||||
|
<p class="section-subtitle">
|
||||||
|
Découvrez les réalisations qui illustrent mon travail et mes compétences.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($featuredProjects)): ?>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
|
||||||
|
<?php foreach ($featuredProjects as $project): ?>
|
||||||
|
<?php include_template('project-card', ['project' => $project]); ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="text-center text-text-muted py-12">
|
||||||
|
Projets à venir...
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include_template('footer'); ?>
|
||||||
16
pages/skills.php
Normal file
16
pages/skills.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
$pageTitle = 'Compétences';
|
||||||
|
$currentPage = 'skills';
|
||||||
|
|
||||||
|
include_template('header', compact('pageTitle'));
|
||||||
|
include_template('navbar', compact('currentPage'));
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="min-h-screen">
|
||||||
|
<div class="container-content py-20">
|
||||||
|
<h1 class="text-heading mb-4">Compétences</h1>
|
||||||
|
<p class="text-text-secondary">Le détail des compétences arrive bientôt.</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include_template('footer'); ?>
|
||||||
50
templates/project-card.php
Normal file
50
templates/project-card.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Carte projet réutilisable
|
||||||
|
* @param array $project Données du projet
|
||||||
|
*/
|
||||||
|
|
||||||
|
$title = $project['title'] ?? 'Sans titre';
|
||||||
|
$slug = $project['slug'] ?? '#';
|
||||||
|
$thumbnail = $project['thumbnail'] ?? 'default-project.svg';
|
||||||
|
$technologies = $project['technologies'] ?? [];
|
||||||
|
$maxTechs = 4;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<article class="card-interactive group">
|
||||||
|
<a href="/projet/<?= htmlspecialchars($slug, ENT_QUOTES, 'UTF-8') ?>" class="block">
|
||||||
|
<div class="aspect-thumbnail overflow-hidden">
|
||||||
|
<picture>
|
||||||
|
<source
|
||||||
|
srcset="/assets/img/projects/<?= htmlspecialchars($thumbnail, ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
type="image/webp"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/assets/img/projects/<?= htmlspecialchars(str_replace('.webp', '.jpg', $thumbnail), ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
alt="Aperçu du projet <?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
width="400"
|
||||||
|
height="225"
|
||||||
|
loading="lazy"
|
||||||
|
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
|
||||||
|
onerror="this.onerror=null;this.src='/assets/img/projects/default-project.svg';"
|
||||||
|
>
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="text-lg font-semibold text-text-primary mb-3 group-hover:text-primary transition-colors">
|
||||||
|
<?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<?php foreach (array_slice($technologies, 0, $maxTechs) as $tech): ?>
|
||||||
|
<span class="badge"><?= htmlspecialchars($tech, ENT_QUOTES, 'UTF-8') ?></span>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php if (count($technologies) > $maxTechs): ?>
|
||||||
|
<span class="badge badge-muted">+<?= count($technologies) - $maxTechs ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
@@ -10,9 +10,17 @@ function Assert-True {
|
|||||||
|
|
||||||
Assert-True (Test-Path 'index.php') 'Missing index.php'
|
Assert-True (Test-Path 'index.php') 'Missing index.php'
|
||||||
$index = Get-Content -Raw 'index.php'
|
$index = Get-Content -Raw 'index.php'
|
||||||
|
if ($index -match 'pages/home.php') {
|
||||||
|
Assert-True (Test-Path 'pages/home.php') 'Missing pages/home.php'
|
||||||
|
$homeContent = Get-Content -Raw 'pages/home.php'
|
||||||
|
Assert-True ($homeContent -match 'Portfolio') 'home.php missing Portfolio text'
|
||||||
|
Assert-True ($homeContent -match 'text-primary') 'home.php missing text-primary class'
|
||||||
|
Assert-True ($homeContent -match 'btn-primary') 'home.php missing btn-primary class'
|
||||||
|
} else {
|
||||||
Assert-True ($index -match 'Portfolio') 'index.php missing Portfolio text'
|
Assert-True ($index -match 'Portfolio') 'index.php missing Portfolio text'
|
||||||
Assert-True ($index -match 'text-primary') 'index.php missing text-primary class'
|
Assert-True ($index -match 'text-primary') 'index.php missing text-primary class'
|
||||||
Assert-True ($index -match 'badge') 'index.php missing badge class'
|
Assert-True ($index -match 'badge') 'index.php missing badge class'
|
||||||
Assert-True ($index -match 'btn-primary') 'index.php missing btn-primary class'
|
Assert-True ($index -match 'btn-primary') 'index.php missing btn-primary class'
|
||||||
|
}
|
||||||
|
|
||||||
'OK'
|
'OK'
|
||||||
22
tests/home.test.ps1
Normal file
22
tests/home.test.ps1
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
function Assert-True {
|
||||||
|
param(
|
||||||
|
[bool]$Condition,
|
||||||
|
[string]$Message
|
||||||
|
)
|
||||||
|
if (-not $Condition) { throw $Message }
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert-True (Test-Path 'pages/home.php') 'Missing pages/home.php'
|
||||||
|
$homeContent = Get-Content -Raw 'pages/home.php'
|
||||||
|
Assert-True ($homeContent -match 'Découvrir mes projets') 'Home missing CTA projects'
|
||||||
|
Assert-True ($homeContent -match 'En savoir plus') 'Home missing CTA about'
|
||||||
|
Assert-True ($homeContent -match 'animate-fade-in') 'Home missing animations'
|
||||||
|
Assert-True ($homeContent -match 'include_template\(\x27navbar\x27') 'Home missing navbar include'
|
||||||
|
|
||||||
|
Assert-True (Test-Path 'index.php') 'Missing index.php'
|
||||||
|
$index = Get-Content -Raw 'index.php'
|
||||||
|
Assert-True ($index -match 'pages/home.php') 'index.php missing home include'
|
||||||
|
|
||||||
|
'OK'
|
||||||
@@ -20,6 +20,10 @@ Assert-True ($js -match 'initNavbarScroll') 'Missing initNavbarScroll'
|
|||||||
|
|
||||||
Assert-True (Test-Path 'index.php') 'Missing index.php'
|
Assert-True (Test-Path 'index.php') 'Missing index.php'
|
||||||
$index = Get-Content -Raw 'index.php'
|
$index = Get-Content -Raw 'index.php'
|
||||||
Assert-True ($index -match "include_template\('navbar'") 'index.php missing navbar include'
|
if (-not ($index -match "include_template\('navbar'")) {
|
||||||
|
Assert-True (Test-Path 'pages/home.php') 'Missing pages/home.php for navbar include'
|
||||||
|
$homeContent = Get-Content -Raw 'pages/home.php'
|
||||||
|
Assert-True ($homeContent -match "include_template\('navbar'") 'pages/home.php missing navbar include'
|
||||||
|
}
|
||||||
|
|
||||||
'OK'
|
'OK'
|
||||||
23
tests/projects-list.test.php
Normal file
23
tests/projects-list.test.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../includes/functions.php';
|
||||||
|
|
||||||
|
function assertTrue($cond, $msg) {
|
||||||
|
if (!$cond) {
|
||||||
|
fwrite(STDERR, $msg . PHP_EOL);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$projects = getProjectsByCategory('vedette');
|
||||||
|
assertTrue(is_array($projects), 'featured projects not array');
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
include __DIR__ . '/../templates/project-card.php';
|
||||||
|
}
|
||||||
|
$html = ob_get_clean();
|
||||||
|
|
||||||
|
assertTrue(strpos($html, 'badge') !== false, 'missing badges');
|
||||||
|
assertTrue(strpos($html, 'loading="lazy"') !== false, 'missing lazy loading');
|
||||||
|
|
||||||
|
fwrite(STDOUT, "OK\n");
|
||||||
34
tests/projects.test.php
Normal file
34
tests/projects.test.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../includes/functions.php';
|
||||||
|
|
||||||
|
function assertTrue($cond, $msg) {
|
||||||
|
if (!$cond) {
|
||||||
|
fwrite(STDERR, $msg . PHP_EOL);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$projects = getProjects();
|
||||||
|
assertTrue(is_array($projects), 'getProjects not array');
|
||||||
|
assertTrue(count($projects) >= 2, 'expected at least 2 projects');
|
||||||
|
|
||||||
|
$featured = getProjectsByCategory('vedette');
|
||||||
|
assertTrue(count($featured) >= 1, 'expected featured projects');
|
||||||
|
|
||||||
|
$found = getProjectBySlug('ecommerce-xyz');
|
||||||
|
assertTrue(is_array($found), 'project ecommerce-xyz not found');
|
||||||
|
assertTrue(($found['slug'] ?? '') === 'ecommerce-xyz', 'slug mismatch');
|
||||||
|
|
||||||
|
$missing = getProjectBySlug('inexistant');
|
||||||
|
assertTrue($missing === null, 'missing project should be null');
|
||||||
|
|
||||||
|
$missingData = loadJsonData('missing.json');
|
||||||
|
assertTrue($missingData === [], 'missing.json should return empty array');
|
||||||
|
|
||||||
|
$invalidPath = __DIR__ . '/../data/__invalid.json';
|
||||||
|
file_put_contents($invalidPath, '{invalid json');
|
||||||
|
$invalidData = loadJsonData('__invalid.json');
|
||||||
|
@unlink($invalidPath);
|
||||||
|
assertTrue($invalidData === [], 'invalid json should return empty array');
|
||||||
|
|
||||||
|
fwrite(STDOUT, "OK\n");
|
||||||
21
tests/quicknav.test.ps1
Normal file
21
tests/quicknav.test.ps1
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
function Assert-True {
|
||||||
|
param(
|
||||||
|
[bool]$Condition,
|
||||||
|
[string]$Message
|
||||||
|
)
|
||||||
|
if (-not $Condition) { throw $Message }
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert-True (Test-Path 'pages/home.php') 'Missing pages/home.php'
|
||||||
|
$homeContent = Get-Content -Raw 'pages/home.php'
|
||||||
|
Assert-True ($homeContent -match 'Explorez mon portfolio') 'Home missing quick nav section title'
|
||||||
|
Assert-True ($homeContent -match 'href="/projets"') 'Home missing projects link'
|
||||||
|
Assert-True ($homeContent -match 'href="/competences"') 'Home missing skills link'
|
||||||
|
Assert-True ($homeContent -match 'href="/a-propos"') 'Home missing about link'
|
||||||
|
Assert-True ($homeContent -match 'grid-cols-1') 'Home missing mobile grid'
|
||||||
|
Assert-True ($homeContent -match 'md:grid-cols-3') 'Home missing desktop grid'
|
||||||
|
Assert-True ($homeContent -match 'card-interactive') 'Home missing hover card class'
|
||||||
|
|
||||||
|
'OK'
|
||||||
33
tests/router.test.php
Normal file
33
tests/router.test.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../includes/router.php';
|
||||||
|
|
||||||
|
function assertTrue($cond, $msg) {
|
||||||
|
if (!$cond) {
|
||||||
|
fwrite(STDERR, $msg . PHP_EOL);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$router = new Router();
|
||||||
|
$router
|
||||||
|
->add('/', 'pages/home.php')
|
||||||
|
->add('/projets', 'pages/projects.php')
|
||||||
|
->add('/projet/{slug}', 'pages/project-single.php');
|
||||||
|
|
||||||
|
[$handler, $params] = $router->resolve('/');
|
||||||
|
assertTrue($handler === 'pages/home.php', 'home route failed');
|
||||||
|
|
||||||
|
[$handler, $params] = $router->resolve('/projets');
|
||||||
|
assertTrue($handler === 'pages/projects.php', 'projects route failed');
|
||||||
|
|
||||||
|
[$handler, $params] = $router->resolve('/projet/ecommerce-xyz');
|
||||||
|
assertTrue($handler === 'pages/project-single.php', 'project route failed');
|
||||||
|
assertTrue(($params[0] ?? '') === 'ecommerce-xyz', 'slug param failed');
|
||||||
|
|
||||||
|
[$handler, $params] = $router->resolve('/unknown');
|
||||||
|
assertTrue($handler === 'pages/404.php', '404 route failed');
|
||||||
|
|
||||||
|
[$handler, $params] = $router->resolve('/projets/');
|
||||||
|
assertTrue($handler === 'pages/projects.php', 'trailing slash failed');
|
||||||
|
|
||||||
|
fwrite(STDOUT, "OK\n");
|
||||||
15
tests/router.test.ps1
Normal file
15
tests/router.test.ps1
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
function Assert-True {
|
||||||
|
param(
|
||||||
|
[bool]$Condition,
|
||||||
|
[string]$Message
|
||||||
|
)
|
||||||
|
if (-not $Condition) { throw $Message }
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert-True (Test-Path '.htaccess') 'Missing .htaccess'
|
||||||
|
$ht = Get-Content -Raw '.htaccess'
|
||||||
|
Assert-True ($ht -match 'RewriteRule') 'Missing RewriteRule in .htaccess'
|
||||||
|
|
||||||
|
'OK'
|
||||||
@@ -6,4 +6,10 @@ $here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|||||||
& (Join-Path $here 'canary.test.ps1')
|
& (Join-Path $here 'canary.test.ps1')
|
||||||
& (Join-Path $here 'navbar.test.ps1')
|
& (Join-Path $here 'navbar.test.ps1')
|
||||||
& (Join-Path $here 'cta.test.ps1')
|
& (Join-Path $here 'cta.test.ps1')
|
||||||
|
& (Join-Path $here 'home.test.ps1')
|
||||||
|
& (Join-Path $here 'quicknav.test.ps1')
|
||||||
|
& (Join-Path $here 'router.test.ps1')
|
||||||
|
php (Join-Path $here 'projects.test.php')
|
||||||
|
php (Join-Path $here 'router.test.php')
|
||||||
|
php (Join-Path $here 'projects-list.test.php')
|
||||||
'OK'
|
'OK'
|
||||||
@@ -31,7 +31,9 @@ Assert-True (Test-Path 'logs/.gitkeep') 'Missing logs/.gitkeep'
|
|||||||
Assert-True (Test-Path 'index.php') 'Missing index.php'
|
Assert-True (Test-Path 'index.php') 'Missing index.php'
|
||||||
$index = Get-Content -Raw 'index.php'
|
$index = Get-Content -Raw 'index.php'
|
||||||
if (-not ($index -match 'Hello World')) {
|
if (-not ($index -match 'Hello World')) {
|
||||||
Assert-True ($index -match 'Portfolio') 'index.php missing expected content'
|
if (-not ($index -match 'Portfolio')) {
|
||||||
|
Assert-True ($index -match 'pages/home.php') 'index.php missing expected content'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (-not ($index -match 'meta name="viewport"')) {
|
if (-not ($index -match 'meta name="viewport"')) {
|
||||||
Assert-True (Test-Path 'templates/header.php') 'Missing templates/header.php for viewport meta'
|
Assert-True (Test-Path 'templates/header.php') 'Missing templates/header.php for viewport meta'
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ Assert-True ($footer -match '</body>') 'Footer missing closing body'
|
|||||||
|
|
||||||
Assert-True (Test-Path 'index.php') 'Missing index.php'
|
Assert-True (Test-Path 'index.php') 'Missing index.php'
|
||||||
$index = Get-Content -Raw 'index.php'
|
$index = Get-Content -Raw 'index.php'
|
||||||
Assert-True ($index -match 'include_template\(') 'index.php missing include_template usage'
|
if (-not ($index -match 'include_template\(')) {
|
||||||
|
Assert-True (Test-Path 'pages/home.php') 'Missing pages/home.php for templates usage'
|
||||||
|
$homeContent = Get-Content -Raw 'pages/home.php'
|
||||||
|
Assert-True ($homeContent -match 'include_template\(') 'pages/home.php missing include_template usage'
|
||||||
|
}
|
||||||
|
|
||||||
Assert-True (Test-Path 'assets/js/main.js') 'Missing assets/js/main.js'
|
Assert-True (Test-Path 'assets/js/main.js') 'Missing assets/js/main.js'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user