BikeControl (SwiftControl)
使用 Zwift Click、Zwift Play、Shimano Di2 或任何藍牙控制器,搭配 MyWhoosh、Rouvy、Zwift 等。iOS、Android、Windows、macOS 均可免費試用。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Coaching et parcours de Bodybuildeur naturel de Emmanuel Peccoux
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Site vitrine (vente indirecte, pousse au contact) reconstruit à partir de zéro le 2026-08-20, en 15 prompts découpés par phase technique. Le site est en production.
main (GitHub Pages)Le site n'est plus un tunnel de 3 pages : c'est un site complet d'une
centaine de pages, généré en HTML statique par tools/build.mjs, avec
Node seul et aucune dépendance npm. GitHub Pages sert toujours des
fichiers statiques commités.
node tools/build.mjs, ou SITE_DATE=AAAA-MM-JJ node tools/build.mjs pour fixer la date du sitemap. La commande réécrit toutes les pages, le sitemap.xml, puis le header et le footer de physique/ et business/.tools/content/ contient les rubriques de l'appli (features.mjs), le centre d'aide (help.mjs), la FAQ (faq.mjs) et les actus (news.mjs). L'accueil est dans tools/home.mjs.tools/config.mjs regroupe le domaine (BASE, la seule chose à changer le jour d'un CNAME), les liens vers l'appli, la navigation et le pied de page.tools/data/guides.json est un export de lead_magnets publiés, fait depuis le dépôt de l'appli : npx supabase db query --linked -o json "select slug, title, hook, category from lead_magnets where published = true order by category, title". Le relancer puis régénérer le site quand de nouveaux guides sortent.assets/images/app/ contient de vraies captures de l'appli, prises sur les comptes de démo avec des données réalistes (voir scripts/demo-accounts.ts côté appli). assets/images/logos/ contient les logos des outils réellement branchés à l'appli (Simple Icons, CC0), affichés en monochrome.assets/css/site.css porte la structure (navigation, pied de page, pages de contenu), assets/css/experience.css le visuel (téléphones, hero, visite produit, bento, formulaire). assets/js/site.js gère les apparitions, la parallaxe, la visite, le formulaire de lead et les filtres.https://ep-coaching.vercel.app/api/public/lead. Le lead entre dans le CRM exactement comme depuis /ressources : table leads, setter, mail de livraison, newsletter. Le site transmet l'origine réelle du visiteur (Instagram, LinkedIn...).Cette section suffit à reprendre le projet sans avoir suivi les 15 prompts qui l'ont construit. Le détail complet de chaque décision, bug trouvé et choix technique vit plus bas dans ce fichier (section "Journal de bord", classée par prompt, du plus récent au plus ancien) : à consulter seulement si cette section ne répond pas à la question.
Un seul élément ne dépend pas de moi (jamais fabriqué, conformément à la règle ferme du projet) :
Le logo et la photo portrait, fournis et choisis par Santamaria le 2026-09-10, sont réglés (voir "Déposer le logo" et "Photo portrait" plus bas).
Tout le reste (texte, design, animations, formulaires, Calendly, déploiement) est fini et vérifié en production.
Une seule valeur à changer, dans index.html, cherche
VIDEO_ID_A_REMPLACER :
<div class="vsl-placeholder" id="vsl-placeholder" data-youtube-id="VIDEO_ID_A_REMPLACER" ...>
Remplace VIDEO_ID_A_REMPLACER par l'identifiant de la vidéo YouTube
(la partie après v= dans son URL, ou après youtu.be/). Rien d'autre
à toucher : dès que cette valeur n'est plus la sentinelle, la vraie
miniature YouTube apparaît automatiquement à la place du dégradé, le
cadre devient cliquable et activable au clavier, et le clic charge la
vidéo (jamais avant, pour ne pas ralentir le chargement de la page).
Voir initVslFacade() dans assets/js/main.js si besoin de comprendre
le mécanisme en détail.
Fait le 2026-09-10 : Santamaria a fourni le vrai logo (PNG fond
transparent). Recadré sur son contenu visible (496×394px, sous les
640×200px recommandés mais c'est la résolution fournie) et posé dans
assets/images/logo.png, remplace le texte EP COACHING dans les 3
headers et les 3 footers (.footer-brand) des 3 pages. Le
<span class="diamond logo-mark"> qui imitait le losange en CSS sur la
homepage a été retiré (le logo réel a déjà son propre losange entre
« EP » et « COACHING », il aurait fait doublon).
Favicon régénéré à partir du logo (juste le mark « EP », sans
« COACHING » — illisible à 16-32px), posé sur fond --noir-profond
(#0D0000, la couleur de fond du site) plutôt que le rouge plein de
l'ancien placeholder : favicon.ico (16/32/48/64px), apple-touch-icon.png
(180×180px), et favicon.svg (le PNG encodé en base64 dans un wrapper
SVG minimal — la source fournie est une image, pas un vecteur, donc pas
de vraie vectorisation possible ; le lien type="image/svg+xml" reste
fonctionnel).
Fait le 2026-09-10 : Santamaria a fourni lui-même deux photos et choisi
explicitement de les utiliser (condition posée depuis le prompt 12/15
vu qu'il est mineur — le choix devait venir de lui, pas d'une sélection
unilatérale parmi les photos personnelles déjà présentes dans
assets/images/). Deux fichiers, un par page (plus de fichier unique
portrait.jpg partagé) :
assets/images/portrait_physique.jpg → physique/index.html, photo
physique (public coaching musculation).assets/images/portrait_business.jpg → business/index.html, photo
lifestyle plus posée (public coachs/business).Les deux sont recadrées 4:5 (bandes noires du cadrage vidéo d'origine
rognées sur la première). Résolution source sous les 1000×1250px
recommandés pour un rendu retina parfait — à remplacer si Santamaria
fournit une version plus définie plus tard. Le traitement visuel (noir &
blanc qui repasse en couleur au survol, vignettage rouge) est déjà en
place dans base.css (.portrait img), rien à ajouter côté style.
Aucun système de gabarit : le texte vit directement dans le HTML de
chaque page (index.html, physique/index.html, business/index.html),
en clair, pas de CMS ni de fichier de contenu séparé. Pour changer un
texte, cherche-le directement dans le fichier de la page concernée et
remplace-le. Le bloc bio (nom, biographie, 3 compétences) est dupliqué à
l'identique dans physique/index.html et business/index.html : un
changement doit être répercuté dans les deux fichiers.
Avant de modifier un texte, vérifie qu'il respecte les règles fermes ci-dessous.
@santamariasanchez_ uniquement, jamais
@emmanuelpeccoux.#E01E1E (rouge
principal), #890404 (rouge foncé), #B00202 (rouge moyen),
#270101 (rouge très foncé), #0D0000 (noir profond), #F5EDED
(blanc cassé), #FDC4C4 (rose clair, usage rare, utilisé pour
certains textes rouges sur fond sombre où le rouge principal manque
de contraste, voir base.css). Jamais #080808 ni #DC0A14
(interdits explicitement). Jamais de ton froid (bleu, vert, violet)..logo dans base.css).HTML/CSS/JS vanilla, zéro npm, zéro build, zéro framework. Librairies
via CDN uniquement : GSAP 3.12.5 + ScrollTrigger, Lenis 1.1.14 (smooth
scroll). Le repo ep-coaching-formulaires (formulaires + Calendly) est
un projet Next.js séparé, avec sa propre stack, voir son README.
/
├── index.html → Homepage courte (VSL + bifurcation vers les 2 parcours)
├── physique/index.html → Parcours "progresser en musculation"
├── business/index.html → Parcours "scaler son business de coach"
├── 404.html → Page d'erreur (lien retour en URL absolue, voir commentaire dans le fichier)
├── robots.txt / sitemap.xml
├── favicon.svg / favicon.ico / apple-touch-icon.png → générés depuis le vrai logo (mark "EP" seul, sur fond --noir-profond)
└── assets/
├── css/
│ ├── base.css → tokens (couleurs/espacements/typo), reset, tous les composants partagés (boutons, cards, motif diamant)
│ ├── home.css → hero, bifurcation, façade VSL (spécifique homepage)
│ ├── physique.css → vide (prêt pour un accent visuel propre à cette page si besoin un jour)
│ └── business.css → CTA de secours uniquement (le reste vient de base.css)
├── js/
│ ├── lenis-init.js → initialise le smooth scroll, expose window.lenis / window.prefersReducedMotion
│ └── main.js → tout le reste : animations, façade VSL, scroll d'ancre, découpe de texte
└── images/
├── og-image.png → image de partage (Open Graph), générée avec l'identité visuelle du site
├── logo.png → vrai logo EP Coaching (fourni par Santamaria, 2026-09-10), header + footer des 3 pages + favicons
├── portrait_physique.jpg → portrait de /physique/ (fourni par Santamaria, 2026-09-10)
├── portrait_business.jpg → portrait de /business/ (fourni par Santamaria, 2026-09-10)
└── (25 fichiers .jpg) → photothèque de l'ancienne version du site, aucun <img>
ne les utilise actuellement, réservée à un usage futur
GitHub Pages, déploiement automatique sur push vers main (aucune
action manuelle à faire). Servi sous
https://santamariasanchez.github.io/EPCoaching/, HTTPS actif (géré par
GitHub Pages, rien à configurer). Aucun domaine personnalisé configuré
(aucun fichier CNAME dans le repo) : si un domaine est acheté un jour,
y déposer un fichier CNAME contenant le nom de domaine et mettre à
jour les URLs absolues du repo (og:url, sitemap.xml, favicon, liens
dans les commentaires qui le mentionnent).
Pas d'accès aux settings GitHub Pages du repo depuis cet environnement
(gh CLI absent au moment de la construction, pas de connecteur
GitHub) : si un jour le déploiement semble ne plus se faire, le premier
réflexe est de vérifier Settings → Pages → Source sur GitHub
directement.
Ce qui suit documente, prompt par prompt, chaque décision, chaque bug trouvé et corrigé, chaque vérification effectuée pendant la construction du site. Utile pour comprendre pourquoi une chose est faite d'une certaine façon, pas nécessaire pour la maintenance courante (voir le "Guide de passation" plus haut pour ça).
ep-coaching-formulaires, reconstruit de zéro), voir son propre README pour le détailAudit de cohérence entre le contenu publié et le positionnement réel documenté dans Notion (🎯 Stratégie & Business, Pitch, Stratégie Contenu Instagram, synthèse webinaire Matis Clouet). Quatre écarts factuels corrigés, aucun changement de design ni de structure.
Ajouté au passage, sans toucher à la structure : la douleur réelle de l'avatar client (la surinformation, les avis contradictoires) était absente des trois pages alors que c'est le cœur du positionnement. Elle est maintenant nommée dans l'intro de la section "La méthode" de /physique/, et le pendant côté coach (savoir quoi changer en premier plutôt qu'une liste de cinquante choses) dans celle de /business/.
Résolu le 2026-09-08 : l'URL du site contenait le nom légal
(emmanuelpeccoux.github.io/EP-Coaching/) alors que l'identité publique
unique doit être Santamaria Sanchéz. Le compte GitHub a été renommé en
SantamariaSanchez et le repo en EPCoaching, l'URL est désormais
santamariasanchez.github.io/EPCoaching/ (mise à jour dans og:url,
sitemap.xml, robots.txt et 404.html). Un domaine personnalisé
(CNAME) resterait une amélioration possible mais n'est plus nécessaire
pour retirer le nom légal de l'URL.
Dernier prompt : aucun développement, uniquement vérification, correction et déploiement. Six vraies violations/défauts trouvés et corrigés, pas de simple relecture qui ne trouve rien.
Tiret cadratin, audit complet, décision changée par rapport aux prompts précédents. Les prompts précédents toléraient le caractère dans les commentaires de code (jamais lu par un visiteur). Ce prompt demandait explicitement de chercher "dans tous les fichiers, y compris les commentaires" : décision prise de le retirer partout, y compris des commentaires et de ce README, plutôt que de continuer à s'appuyer sur une distinction "visible/pas visible" que ce site statique (sans build, sans minification) ne garantit pas vraiment, le code source est servi tel quel à qui regarde la source de la page. 131 occurrences trouvées et remplacées (par une virgule, dans la quasi-totalité des cas) sur les deux repos (ep-site et ep-coaching-formulaires). Un aller-retour raté au passage : une première tentative de remplacement automatique a laissé des résidus en fin de ligne et corrompu 2 lignes qui citaient le caractère lui-même entre parenthèses pour documenter la règle, repéré et corrigé avant de committer.
3 écarts de contraste WCAG AA trouvés par calcul, pas par supposition. Script Python reproduisant la formule de luminance relative WCAG, appliqué à chaque paire texte/fond du site :
.eyebrow (rouge principal sur noir profond, petit texte gras) :
4.3:1, sous le seuil de 4.5:1..bloc-cta de la bifurcation homepage (rouge principal sur
rouge-très-foncé) : 4.01:1, sous le seuil..btn-subtext sous les boutons CTA (blanc cassé à 45% d'opacité) :
4.0:1, sous le seuil.Corrigé sans sortir de la palette des 7 couleurs officielles : les 3
premiers passent à --rose-clair (déjà dans la palette, prévue pour un
"usage rare", jamais vraiment utilisée jusque là, 12.7 à 13.6:1 selon le
fond), le dernier passe de 45% à 55% d'opacité de blanc cassé (5.55:1).
Une dizaine d'autres combinaisons texte/fond du site vérifiées dans la
foulée, toutes déjà largement conformes (5.5:1 à 17.9:1).
Bug de compatibilité Safari trouvé et corrigé : backdrop-filter
(flou du header compacté au scroll) n'avait pas son équivalent
-webkit-backdrop-filter, nécessaire sur Safari/iOS pour que le flou
s'applique réellement (sans le préfixe, le fond semi-transparent
s'affiche mais sans flou, dégradation silencieuse, pas d'erreur visible).
Corrigé. Note pour la suite : color-mix() est utilisé de façon
extensive dans tout base.css/home.css/business.css et nécessite
Safari 16.2+ (décembre 2022), un très large majorité du trafic iPhone en
2026 devrait être largement au-delà, mais c'est une dépendance non
retravaillée dans cette passe (l'aurait été si un vrai souci de rendu
avait été signalé, mais aucun moyen de le vérifier en pratique sans
navigateur réel disponible ici).
og:image manquant sur les 3 pages, trouvé en vérifiant "le rendu du
partage de lien" comme demandé. Sans lui, un lien partagé depuis
Instagram (canal principal du site) n'affiche aucune vignette. Créée une
image de partage (1200×630px, format standard Open Graph) avec
l'identité visuelle réelle du site : dégradé rouge-très-foncé vers noir
profond (même esprit que le hero), diamant signature en filigrane, "EP
COACHING" en vraie police Montserrat 900 (récupérée depuis le dépôt
officiel Google Fonts pour un rendu fidèle), tagline en rose clair.
Générée avec Pillow, aucune photo ni visage, juste l'identité de marque
déjà établie. Sauvegardée dans assets/images/og-image.png (33 Ko),
référencée via og:image/og:image:width/og:image:height/
twitter:card sur les 3 pages.
Vérifications qui n'ont rien trouvé à corriger (donc pas de section
dédiée, listées ici pour que la vérification elle-même soit tracée) :
aucun prix, aucun "Emmanuel" visible, aucun mauvais handle Instagram,
aucune couleur hors palette, aucune trace de Bebas Neue, aucun
témoignage/preuve sociale inventée, hiérarchie de titres correcte (un
seul H1 par page partout), :focus-visible présent globalement, aucun
outline:none sans remplacement, tous les liens sortants et internes
des 3 pages vérifiés un par un par requête HTTP réelle (aucun 404,
aucun placeholder restant), sitemap.xml/robots.txt/favicons vérifiés
en ligne, prefers-reduced-motion revérifié avec jsdom sur les 3 pages
(zéro élément bloqué à opacity:0, la vérification la plus importante
de cette section d'après le prompt), aucune largeur fixe en px
problématique pour le responsive.
Parcours de bout en bout : ce qui a été vérifié pour de vrai, et ce
qui relève du raisonnement plutôt que de l'observation directe, faute
de navigateur réel dans cet environnement (limite déjà documentée
partout dans ce fichier). Vérifié réellement par requêtes HTTP : tous
les liens, le déploiement, le contenu servi. Raisonné à partir de
l'architecture du code (pas observé dans un vrai navigateur) : un
rechargement en cours de parcours ne casse rien (pages statiques sans
état partagé entre elles, aucun localStorage/sessionStorage nulle
part dans le repo, vérifié par grep), un retour arrière navigateur ne
casse rien (mêmes raisons), l'ouverture directe d'une page profonde sans
passer par la homepage fonctionne à l'identique (aucune dépendance
inter-pages), le redimensionnement pendant le scroll est géré nativement
par ScrollTrigger (documenté dans son propre changelog, déjà noté dans
ce README au prompt 9/15). Le formulaire (repo séparé) a été testé
jusqu'à l'insertion réelle en base de données (voir son README), mais
pas jusqu'à une vraie réservation Calendly : ça créerait un rendez-vous
réel dans l'agenda de Santamaria, à nettoyer manuellement et risquant de
semer la confusion, jugé disproportionné pour un test.
Le CTA principal de /business/ (#TODO-prequalification-business
depuis le prompt 2) pointe maintenant vers
https://ep-coaching-formulaires.vercel.app/business, le formulaire créé
au prompt 13/15. Le CTA de /physique/ était déjà correct (route
/prequalification inchangée par le prompt 13/15), simplement revérifié.
Audit complet des liens sortants des 3 pages (section 2.3 du
prompt) : tous corrects, aucun lien mort, aucun placeholder restant
(vérifié par grep). Règle "conversion reste dans l'onglet, consultation
s'ouvre dans un nouvel onglet" déjà respectée avant ce prompt et
reconfirmée : formulaires et fallback business (inscription app) sans
target, légal et Instagram avec target="_blank".
Ce qui reste en dehors de ce repo : le câblage réel vers Calendly
(redirection après soumission, pré-remplissage, distinction
physique/business) vit dans ep-coaching-formulaires, pas ici, voir son
README pour le détail complet (option retenue, bug Calendly trouvé et
corrigé, etc.).
Mis à jour en fin de prompt 14/15 : le déploiement Vercel de
ep-coaching-formulaires, initialement bloqué (connecteur MCP non
fonctionnel), a été résolu dans la même session via la CLI Vercel en
local. Les deux URLs câblées ici (/prequalification et /business)
sont vérifiées en production, réponse 200 avec le nouveau contenu, le
funnel complet (homepage → choix du chemin → CTA → formulaire →
Calendly) est fonctionnel de bout en bout.
Logo : recherché dans le repo entier ET son historique git sur
toutes les branches (git log --all --diff-filter=A --name-only), introuvable, jamais commité nulle part sous ce projet. Règle du prompt
respectée à la lettre : pas fabriqué en CSS/SVG. Le placeholder texte
reste en place dans le header des 3 pages, avec un commentaire détaillé
dans index.html (recopié en résumé dans physique//business/)
indiquant le chemin exact attendu : assets/images/logo.png, PNG à
fond transparent, au moins 640×200px source pour rester net en retina
affiché à ~40px de haut. Une fois déposé, il suffit de suivre le
commentaire pour remplacer <a class="logo"> par un vrai <img> (header
des 3 pages + footer + favicon, voir prompt 3).
Suite, 2026-09-10 : Santamaria a fourni le vrai logo. Intégré partout (header, footer, favicons), voir "Déposer le logo" plus haut pour le détail.
VSL : façade complète implémentée (initVslFacade() dans main.js).
Rien n'est chargé au chargement de la page : l'attribut
data-youtube-id="VIDEO_ID_A_REMPLACER" sur #vsl-placeholder
(index.html) est l'unique valeur à changer quand la vidéo unlisted
sera prête. Tant qu'elle vaut ce placeholder, le bloc reste exactement
dans son état non interactif d'avant ce prompt (pas de role="button",
pas de faux affordance). Dès qu'un vrai ID y est mis : la vraie miniature
YouTube (img.youtube.com/vi/{id}/maxresdefault.jpg) remplace le dégradé
sombre avec un voile pour garantir le contraste du bouton play, le bloc
devient activable à la souris ET au clavier (Entrée/Espace), et le clic
injecte une iframe youtube-nocookie.com (rel=0, modestbranding=1,
autoplay=1 déclenché par le clic lui-même, jamais au chargement de la
page). Testé avec jsdom dans les 3 états (sentinelle non remplacée, ID
réel avec clic souris, ID réel avec activation clavier) : les trois se
comportent exactement comme prévu.
Photo portrait : les 25 photos de assets/images/ passées en revue
une par une (pas juste le nom de fichier), aucune n'est un portrait
posé/professionnel (selfies miroir, photos de progression, tenues de
sport), pas le traitement adapté à l'image publique en tête de page.
Sujet mineur : le choix de LA photo à publier revient délibérément à
Santamaria/son entourage, pas à une sélection unilatérale de ma part
parmi des photos personnelles existantes. Placeholder conservé. Fichier
attendu : assets/images/portrait.jpg (+ .webp en complément si
fourni), ratio 4:5, au moins 1000×1250px source. Bandes noires à rogner
et tout logo/watermark tiers à retirer avant intégration, comme demandé.
Suite, 2026-09-10 : Santamaria a fourni deux photos et choisi lui-même de les utiliser, exactement la condition posée ci-dessus. Placeholder retiré des deux pages, voir "Photo portrait" plus haut pour le détail (fichiers, recadrage, page par photo).
Icônes : audit de cohérence fait sur les 18 SVG des 2 pages (9 par
page), un seul jeu d'attributs partout
(fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"),
aucun emoji, aucune flèche bricolée à la main, vérifié par grep sur les
plages Unicode emoji (rien trouvé dans le HTML visible). Rien à changer,
déjà conforme depuis les prompts 5/6.
Un vrai bug trouvé en préparant la compression : 22 des 25 fichiers
de assets/images/ sont en réalité des JPEG (marqueurs jfif +
profil ICC "Google Inc. 2016" détectés par Pillow), simplement mal
étiquetés avec une extension .png. Une première tentative de
compression PNG "sans perte" les a fait grossir de 5 à 8× (2,03 Mo →
10,81 Mo) : PNG ne peut que réencoder en perte de temps ce qui est déjà
du JPEG décodé, jamais le compresser correctement. Repéré immédiatement
au résultat anormal, annulé avec git checkout avant tout commit, puis
corrigé à la racine : les 22 fichiers JPEG mal étiquetés sont renommés en
.jpg (copie identique à l'octet près, aucune perte, git confirme le
renommage). Les 3 vrais PNG restants (debut_2022a, debut_2022b,
proof_photo, contenu photographique) sont convertis en JPEG qualité 88.
Poids total de assets/images/ : 2,03 Mo → 1,49 Mo (28 % de gain),
mais aucun de ces fichiers n'est référencé dans le HTML à ce jour
(confirmé par grep) : ils ne font donc pas partie du poids de page
ci-dessous, loading="lazy" et les attributs width/height n'ont
nulle part où s'appliquer tant qu'aucun <img> ne les utilise.
Fichiers propres au site (HTML + CSS + JS, avant compression gzip/brotli du serveur) :
| Page | HTML | CSS | JS | Total 1ʳᵉ partie |
|---|---|---|---|---|
/ (home) | 8,9 Ko | 27,6 Ko (base+home) | 29,9 Ko | ~66 Ko |
/physique/ | 18,5 Ko | 19,4 Ko (base+physique) | 29,9 Ko | ~68 Ko |
/business/ | 19,3 Ko | 20,4 Ko (base+business) | 29,9 Ko | ~69 Ko |
Plus, partagé et mis en cache par le navigateur dès la 1ʳᵉ page visitée (pas re-téléchargé en changeant de page) :
curl qui les demande toutes), de l'ordre de 15 à 40 Ko en usage
réel, pas mesurable précisément sans un vrai navigateur ici.Aucune image de assets/images/ n'est chargée par une page actuellement
(aucun <img> dans le HTML), donc rien à ajouter aux totaux ci-dessus.
Même méthode qu'au prompt 10 : copy reproduit tel quel, sans reformulation.
Bio : identique au caractère près à /physique/, vérifié avec un
diff sur le bloc <section class="bio"> des deux fichiers (demande
explicite du prompt), aucune différence. Toujours dupliquée en HTML (site
statique, pas de templates), mais aucune règle de style spécifique n'a été
ajoutée : tout vient de base.css, business.css ne définit rien pour
cette section, exactement comme prévu depuis le prompt 5/15.
Coquille corrigée : "jonglar" du document source remplacé par "jongler" (pilier 6), comme demandé explicitement par le prompt.
CTA de secours : le copy distingue une phrase ("Tu préfères tester
par toi-même ?...") d'un lien ("Créer mon compte"), alors que le
placeholder n'avait qu'une seule phrase entièrement cliquable. Restructuré
en .fallback-intro (texte simple) + .btn-cta-fallback (le vrai lien,
seul élément cliquable désormais) : plus honnête pour un lecteur d'écran
et au survol souris que de rendre toute une question cliquable.
initCtaFinalReveal() dans main.js anime maintenant les deux comme un
seul petit groupe, avec la même garantie qu'avant (aucun overlap négatif :
ne démarre qu'une fois le CTA principal entièrement arrivé), revérifié en
inspectant l'ordre réel des appels à la timeline mockée (jsdom), pas
seulement en relisant le code.
Aucune mention de prix ni de durée d'essai : ni sur cette page ni ailleurs, conformément à la règle explicite de ce prompt (l'offre coach a un tarif et une période d'essai qui ne doivent apparaître nulle part sur le site).
Vérification des longueurs : les paragraphes de piliers vont de 109 à
140 caractères ; la ligne 1 de la grille (piliers 1-3) a un écart plus
marqué (109-140, contre un écart de 3 caractères seulement sur
/physique/) mais align-items: stretch (comportement Grid par défaut,
déjà en place) aligne les cards d'une même ligne en hauteur quel que soit
l'écart, aucun changement nécessaire. Le titre du pilier 1 ("Tous les
outils clients, à ta main", 34 caractères) est à la même longueur que les
titres les plus longs déjà vérifiés sur /physique/ : même conclusion,
retour à la ligne normal sur mobile étroit, aucun débordement. Le H2 de la
section piliers business (48 caractères) s'est avéré quasiment identique
en longueur à celui de physique (47 caractères) et pas "nettement plus
long" comme le prompt le supposait : mesuré plutôt que supposé, les deux
tiennent sans problème dans le max-width: 42rem du .section-header.
diff, voir plus haut)., ) : recherché dans tout le repo (HTML, CSS, JS, MD),
aucune occurrence en dehors des commentaires de code (documentation
interne aux fichiers, jamais lue par un visiteur).Copy final intégré sur la homepage et /physique/, reproduit tel quel
(aucune reformulation, comme demandé). /business/ suit au prompt 11/15,
la bio (bloc commun aux deux pages) n'a donc été mise à jour que sur
/physique/ pour l'instant.
Mots-clés accentués (<span class="accent">, rouge plein, un par
titre maximum) : plus vite (H1 hero), ton chemin (H2 bifurcation),
fait pour toi, 1-to-1, 6 piliers, accomplir, commencer sur
/physique/. Exception délibérée : le H1 de la bio (Santamaria Sanchéz) n'a pas d'accent [de mise en avant <span class="accent">], un prénom/nom n'a pas de "mot clé" à mettre
en avant sans que ça ait l'air arbitraire. Vérifié avec splitText() sur
le vrai HTML (jsdom) : le span survit intact à la découpe en mots, y
compris sur un accent de 3 mots ("fait pour toi") au milieu d'un H2 de 7
mots au total.
Bio physique/business : la structure du placeholder (1 phrase-crochet
en H1 + 1 paragraphe + 3 phrases courtes) ne correspondait plus au copy
validé (nom en H1 + 3 paragraphes + 3 intitulés de compétences). Le H1
reste un H1 (une seule règle jamais changée : un seul H1 par page, situé
dans la bio pour physique/business) mais son contenu textuel est
maintenant le nom. initSectionEntrance() dans main.js ciblait un seul
paragraphe (querySelector, singulier) : corrigé en querySelectorAll
pour que les 3 paragraphes de bio animent ensemble, sinon les paragraphes
2 et 3 seraient restés visibles d'emblée pendant que le reste de la
colonne anime encore.
Hero homepage : ajout du sous-titre et de la légende sous la VSL (pas
dans le placeholder d'origine, qui n'avait qu'un H1). Intégrés dans la
timeline d'entrée existante (initHomeEntrance) en les regroupant avec
leurs voisins immédiats (H1+sous-titre, VSL+légende) plutôt qu'en
ajoutant deux temps supplémentaires, pour rester dans le budget de 1,5s
déjà documenté (~1,5s au lieu de ~1,4s, écart minime).
Bifurcation homepage : le copy donnait un "bouton" par bloc en plus
du titre et de la ligne descriptive, mais chaque bloc est déjà un unique
<a> cliquable sur toute sa surface. Un <a> imbriqué dans un <a> est
invalide en HTML ; le "bouton" est donc un label visuel (.bloc-cta,
flèche qui se décale légèrement au survol), jamais un second lien, pour
ne pas casser l'accessibilité clavier (un seul stop de tabulation par
bloc, comme avant).
Ce qui n'était pas couvert par le copy (eyebrows, "Choisis ton chemin", le lien "Voir l'autre accompagnement" du header) : conservé tel quel, TODO retiré. Ce sont des micro-copies déjà sobres et cohérentes avec le reste, pas des placeholders à valider séparément.
Meta tags : titres et descriptions réécrits, dérivés du copy validé plutôt qu'inventés (ex. la description homepage reprend le sous-titre mot pour mot, déjà écrit pour ça). Toujours aucun prix, aucun tiret em.
Attributs alt : rien à faire dans ce prompt. Aucune balise <img>
n'existe encore dans le HTML (les photos ne sont pas intégrées), donc
aucun alt à renseigner pour l'instant, cf. section Accessibilité.
Vérification des longueurs (pas de vrai navigateur ici, analyse
statique comme pour les prompts précédents) : les 6 paragraphes de
piliers vont de 114 à 163 caractères, groupés en deux lignes de grille de
longueurs très proches (160/160/163 puis 127/114/129), .pilier-grid
utilise align-items: stretch par défaut (comportement Grid natif, rien
à ajouter), donc les cards d'une même ligne s'alignent en hauteur même
sans ce hasard de longueurs. Le H1 homepage (57 caractères) reste sous
max-width: 20ch avec text-wrap: balance, prévu pour wrapper sur 3
lignes par design. Les titres de cards les plus longs (34 caractères,
piliers "Suivi et progression..."/"Nutrition et programme...") wrapperont
sur 2 lignes à 375px, un <h3> en bloc sans white-space: nowrap nulle
part dans le projet (vérifié par grep) : jamais de débordement
horizontal, seulement un retour à la ligne normal.
Décision explicite de ne pas ajouter Three.js, avec des chiffres à l'appui plutôt qu'une impression :
curl) : Three.js 0.152.2 seul = 633 338 octets,
soit environ 5x le poids cumulé de GSAP + ScrollTrigger + Lenis
(72 214 + 43 380 + 12 790 = 128 384 octets). Un budget disproportionné
pour un effet que rien dans le brief ne rend indispensable.<script [email protected]> retiré des 3 pages
(sed -i '/[email protected]/d'), confirmé par grep : plus aucune trace dans
le repo, la chaîne GSAP → ScrollTrigger → Lenis reste intacte partout.Scroll vers les ancres (initAnchorScroll) : les liens href="#..."
passent par lenis.scrollTo() au lieu du saut natif abrupt, cohérent avec
le smooth scroll du reste du site. Reste un vrai <a> avec href réel
(navigable au clavier, fonctionne sans JS), seul le comportement de scroll
est intercepté, jamais l'accessibilité du lien.
Robustesse ScrollTrigger (initScrollTriggerRefresh) : un
ScrollTrigger.refresh() après document.fonts.ready (les polices
Google Fonts chargées en swap peuvent changer la hauteur du texte après
le calcul initial des triggers) et un autre sur pageshow avec
persisted: true (retour arrière/avant depuis le cache bfcache, où le JS
ne se réexécute pas mais les positions de scroll doivent rester justes).
Vrai bug de test trouvé et corrigé avant ce commit, pas dans le code
mais dans le test lui-même : la première vérification jsdom de ces deux
comportements rapportait un échec (refreshCalls: 0), alors que le code
était correct, le test retournait un instantané du compteur au moment
du return, pas une référence vivante mise à jour par les closures
asynchrones ultérieures. Un test isolé corrigé confirme que les deux
déclencheurs appellent bien ScrollTrigger.refresh() comme prévu. Retenu
ici comme leçon de méthode plutôt que caché : ça aurait pu passer pour un
"bug" alors que c'était le harnais de test qui mentait.
Audit "règle de retrait" : repassage sur chaque effet du fichier en se demandant s'il sert vraiment la lecture. Aucun effet individuel retiré, chacun répond à une demande explicite (prompt 8) ou reste volontairement discret (parallaxe portrait, compactage du header). Le retrait réel de cette passe est Three.js dans son ensemble, plus radical qu'un simple réglage d'intensité.
Repasse mobile 375px : toutes les grilles/flex du site passent en
colonne unique par défaut et ne basculent en plusieurs colonnes qu'au-delà
de 768px/900px ; aucune largeur fixe ne dépasse quelques rem (icônes,
diamants) ; le seul débordement volontaire (diamant fantôme du hero,
min(75vw, 760px) tourné à 45°) est doublement contenu, overflow:hidden
sur .hero-vsl et sur le body. Analyse statique (pas de navigateur réel
disponible ici), cohérente avec la vérification jsdom qui confirme que les
51 à 53 éléments mis à opacity:0 par page reviennent bien à opacity:1.
Titres H2 : découpés en mots via splitText() (spans stylisés
préservés), stagger court (35ms), déclenché à 85% du viewport. Un seul
mécanisme pour tous les H2 du site, y compris celui du CTA final (qui
reçoit juste une durée/easing plus marqués pour "plus de présence",
sans jamais dupliquer le système). Le H2 de bifurcation homepage passe
par la séquence d'entrée au chargement plutôt que par le scroll.
Cards en cascade : stagger réduit à 80ms (dans la fourchette 60-100ms demandée), durée de groupe raccourcie à 0.6s pour que la grille de 6 piliers reste sous 1s au total (5 × 0.08 + 0.6 = 1.0s pile). Stagger dans l'ordre du DOM = ordre de lecture naturel, rien à recalculer.
Bio/portrait : timeline unique en 3 temps (portrait, puis texte avec
léger décalage, puis les 3 compétences avec leur propre stagger) plutôt
qu'un seul fade-up comme au prompt 7. Parallaxe très subtile sur le
portrait (30px sur toute la traversée de la section, scrub), sur un
élément intérieur séparé de .portrait pour ne jamais entrer en conflit
avec sa propre animation d'entrée.
CTA final : séquence dédiée (pas le système générique), durée et
easing plus marqués (power3.out, 1s). Sur /business/, le CTA de secours
est ajouté à la timeline après le groupe principal, sans overlap :
garantit qu'il ne peut jamais apparaître avant le CTA principal. Pas de
pulsation du glow au repos, jugé "cheap" pour un site qui évite
justement les patterns génériques (le prompt autorisait explicitement ce
choix).
Bifurcation homepage : au hover desktop (hover:hover et
pointer:fine uniquement), le bloc survolé se met en avant et l'autre
s'estompe. Aucun effet dépendant du hover sur tactile.
Header au scroll : position: sticky ajouté (prérequis manquant pour
que l'effet ait un sens), padding réduit et fond qui se densifie passé
80px de scroll.
Vélocité de scroll : pas d'effet de skew ajouté dans cette passe, le
prompt autorise explicitement à ne pas le faire si le résultat n'est pas
vérifiable, et un skew dynamique ne peut pas se juger sans un vrai
navigateur pour voir un scroll rapide en conditions réelles.
window.lenisVelocity reste disponible si l'effet est retenté plus tard.
Vrai bug trouvé et corrigé avant ce commit, pas en relecture mais en
testant réellement la logique (jsdom, sans navigateur disponible ici) :
le H2 du CTA final était ciblé deux fois, une fois par le système
générique de titres (ses mots), une fois par initCtaFinalReveal (le
bloc entier). Deux ScrollTrigger distincts sur le même élément. Corrigé
en retirant le H2 de initCtaFinalReveal et en lui donnant seulement une
intensité différente dans le système générique. Vérifié après coup avec
une simulation complète de main.js (gsap/ScrollTrigger mockés) contre
les 3 pages réelles : 14 ScrollTrigger sur physique/business, 1 sur la
home, et surtout 51 à 53 éléments mis à opacity:0 ont bien tous une
contrepartie qui les ramène à opacity:1, aucun élément qui resterait
invisible pour de bon.
Lenis (lenis-init.js) : durée 1.0s (plus réactif que le défaut 1.2s),
syncTouch: false pour garder le momentum tactile natif iOS. Désactivé
entièrement (lenis.stop()) si prefers-reduced-motion. Vélocité toujours
lue dans gsap.ticker, jamais dans l'event scroll (piège déjà documenté
au prompt 1, toujours respecté).
Reveal au scroll (main.js) : [data-reveal] (élément isolé) et
[data-reveal-group] (anime les enfants directs avec stagger), posé au
prompt 7, affiné au prompt 8 (voir plus haut : les H2 en sont exclus, le
CTA final est passé à son propre système). Fade + translateY(28px),
déclenché à 83% du viewport, once: true. La homepage n'en a pas : hero
et bifurcation passent par la séquence d'entrée dédiée à la place.
Entrée de page : homepage = timeline orchestrée (logo → H1 → cadre VSL
→ bifurcation, ~1.4s, sous la limite de 1,5s demandée). /physique/ et
/business/ = timeline légère sur la bio (voir plus haut), sans
séquence longue.
Découpe de texte (splitText(), exposée sur window) : construite et
testée au prompt 7 (jsdom, pas juste relu), appliquée pour de vrai aux
titres H2 au prompt 8. Contre un titre avec un mot stylisé imbriqué
(<span class="accent">) : le span survit intact, seul son contenu est
découpé en unités animables, c'était le piège explicitement signalé.
Résilience CDN : si GSAP ou ScrollTrigger ne charge pas (réseau, ad
blocker), traité exactement comme prefers-reduced-motion, aucune
section ne dépend de ces libs pour redevenir visible. Repris de l'exigence
déjà documentée sur l'ancienne version de ce site ("tout dégrade
proprement si les CDN ne chargent pas").
Tokens dans base.css : échelle d'espacement (--space-xs à --space-3xl,
base 8px), échelle typographique en clamp() (h1/h2/h3, avec un H1 réduit
dans .split-content pour ne pas réutiliser l'échelle hero dans une
colonne deux fois moins large), boutons et cards avec hover. Motif
signature : le diamant rouge du logo historique, repris à 3 échelles, petit (puce/séparateur/logo), et en grand filigrane (.hero-diamond-ghost)
derrière le hero homepage. /physique/ et /business/ partagent
strictement le même système (.split, .cards, .pilier-grid,
.diamond-list, icônes SVG) défini une seule fois dans base.css, physique.css/business.css ne contiennent plus aucune règle de mise en
page, prêts pour un accent propre à chaque page si besoin un jour.
Icônes SVG en trait rouge (jamais d'emoji) : génériques (cible, message, cycle) pour les 3 points de méthode communs, et propres au contexte pour les 6 piliers, haltère/formation/graphique côté physique, boîte à outils/verrou/dashboard côté business. Toutes vérifiées visuellement avant commit (rendu PIL en local, faute de vrai navigateur dans cet environnement), 2 icônes retravaillées au prompt 5 (formes plus sûres que des arcs SVG non testés), aucun souci sur les 5 icônes du prompt 6.
Quality floor posé au prompt 6/15 : :focus-visible explicite (anneau
rouge) sur tout le site, prefers-reduced-motion respecté (transitions
neutralisées en CSS, Lenis désactivé et scroll natif repris en JS).
Bug de cohérence attrapé et corrigé au prompt 6/15 : business.css
n'avait jamais été nettoyé comme physique.css au prompt 5, un ancien
.criteres .cards en grille rentrait en conflit de spécificité avec la
version flex-column de base.css, /business/ affichait ses 3 cards de
critères en grille au lieu d'empilées. Les deux fichiers de page sont
maintenant symétriques et vides de règles de mise en page.
Un seul <h1> par page (bio pour physique/business, hero pour la home),
<h2> sur chaque section, pas de saut de niveau. Pas encore de balise
<img> dans le HTML (les photos dans assets/images/ ne sont pas
encore intégrées) : rien à mettre en alt pour l'instant, prévoir un
texte descriptif réel à l'intégration de chaque image en phase design/contenu.
Demande directe : le site avait l'air "fade" comparé à l'application (qui a déjà son propre langage visuel très abouti, glow/grain/reflets/ ombres en couches), champ libre donné sur les couleurs, ombres, animations et police pour combler cet écart.
Contrainte respectée : la palette officielle de 7 couleurs (base.css,
tout en haut) n'a pas bougé, ni les polices (Montserrat 900 titres,
Playfair Display Italic uniquement pour le logo) — l'écart n'était pas
une question de couleurs ou de police, mais d'atmosphère et de matière.
Repris tel quel depuis app/globals.css de l'application (mêmes valeurs,
pas une approximation) :
body::before) : les 5 dégradés radiaux
superposés qui donnent son identité "noir profond + brume rouge" à
l'app, en position: fixed pour rester stable au scroll. z-index: -1
plutôt que 0 (comme dans l'app) : ce site a du texte hors carte
(h1, p de section) qui n'est pas position: relative comme l'est
quasi tout dans l'app, un z-index 0 l'aurait peint par-dessus la brume
au lieu de dessous.body::after) : la texture SVG animée qui
empêche le noir de paraître plat à l'écran, au-dessus de tout,
identique à l'app..card, .bifurcation .bloc,
.vsl-placeholder, .pilier-card--emphasis) : backdrop-filter,
fond semi-transparent au lieu d'un aplat, reflet en trait fin en tête
de carte, ombres en couches (highlight interne + glow rouge + ombre
noire profonde), au lieu d'un simple fond plein + bordure + un seul
box-shadow au hover..btn-cta-primary) : reliefs internes (haut clair,
bas sombre) qui donnent une vraie épaisseur au lieu d'un aplat plat,
glow déjà visible au repos (pas seulement au hover)..ep-card-hero dans l'app), glow interne ajouté..ep-logo-glow dans
l'app), jamais un contour qui l'alourdirait.Deux fichiers touchés (base.css, partagé par les 3 pages + 404, et
home.css pour le hero/bifurcation propres à la homepage), aucun
changement structurel HTML, aucune nouvelle dépendance. Toutes les
valeurs de flou/ombre restent dans les mêmes ordres de grandeur que
l'app (14 à 20px de flou, jamais plus, même raisonnement que le
commentaire --blur-card/--blur-nav de app/globals.css sur le
compositing WebKit).
Retour explicite sur le round 1 ci-dessus : "on est à 10%", il fallait continuer sérieusement jusqu'à un style "masterclass", en autonomie. Le round 1 avait porté le langage visuel de l'app (glow/grain/verre dépoli) sur le site, mais de façon uniforme : une seule recette de card réutilisée partout, cinq sections de contenu qui partagent toutes exactement le même fond. Correct dans la direction, insuffisant dans la variété et l'audace, exactement le reproche formulé. Ce round ajoute des moments distincts par section plutôt que de refaire "plus fort" la même recette, en respectant scrupuleusement les mêmes contraintes verrouillées (7 couleurs, polices, jamais de tiret cadratin, jamais de prix).
.bio, .criteres, .accompagnement,
.piliers, .accomplissements, toutes dans base.css) : avant ce
round, ces cinq sections reposaient uniquement sur le fond global de
body::before, zéro identité propre. Chacune reçoit maintenant sa
propre brume radiale en plus (jamais à la place) de ce fond global,
positionnée et dosée différemment selon le rôle de la section (ex.
brume côté portrait sur .bio, halo large en tête de grille sur
.piliers), à une opacité assez faible pour rester une nuance. Même
raisonnement z-index: -1 que body::before : ces sections contiennent
aussi du texte simple non positionné, un z-index positif peindrait la
brume par-dessus au lieu de dessous..accompagnement .cards) : les 3 cards
"on fait le point / on construit ton plan / on ajuste en continu"
sont une vraie séquence, pas une liste, elles ne l'affichaient pas.
Chiffre fantôme (contour seul via counter() CSS, aucune modification
du HTML) qui déborde du coin haut-droit de chaque card, rogné par
l'overflow:hidden déjà en place, comme si l'étape "sortait" du cadre..portrait::before) : 4 équerres en coin façon
viseur/cadre technique, 8 dégradés linéaires composés (jamais d'image),
au-dessus de la photo et du voile existant. Combiné à un reveal en
clip-path au scroll (initSectionEntrance, main.js) plutôt qu'un
simple fondu : LE portrait du site (élément unique, pas répété)
mérite un traitement qu'aucun autre élément de la page ne partage.initHeroDiamondParallax,
main.js) : le seul élément entièrement statique d'une page par
ailleurs animée. Déplacement lerpé (amorti, jamais collé 1:1 à la
souris), plafonné à 10px par axe, réservé aux pointeurs fins avec vrai
hover (hover:hover et pointer:fine, même garde-fou que le hover de
.bifurcation .bloc en CSS) : jamais sur tactile, jamais "une
attraction" qui capterait l'oeil en continu..cta-final .form-slot::before) :
un dégradé conique qui tourne lentement (16s, linéaire) derrière le
bouton, plutôt qu'une pulsation d'échelle déjà écartée pour ce même
bouton (voir le commentaire d'initCtaFinalReveal, jugée "cheap").
Une rotation lente se lit comme un projecteur qui balaie, pas comme un
clignotement.Fichiers touchés : base.css (l'essentiel), main.js (portrait +
parallaxe hero), physique/index.html et business/index.html (ajout
mécanique de spans décoratifs aria-hidden, aucun texte nouveau à
traduire/vérifier ailleurs que "◆ Pilier prioritaire", répété tel quel
sur les 4 cards concernées). Toujours aucune nouvelle dépendance : le
chiffre fantôme utilise counter() CSS natif, le halo tournant un
@keyframes CSS natif, la parallaxe un requestAnimationFrame simple.
prefers-reduced-motion (règle globale déjà en place, base.css) gèle
le halo tournant sur une position figée et n'affecte pas le clip-path du
portrait au-delà de sa durée réduite à 0.01ms par cette même règle
globale (l'élément finit toujours visible, jamais bloqué à mi-reveal).
ep-coaching-formulaires), funnel complet du clic homepage jusqu'à la réservation, bout en bout, vérifié en production.criteres .cards .card.pilier-card--emphasis) : réutilise
.eyebrow (même composant que les kickers de section) comme étiquette
"◆ Pilier prioritaire" en tête des cards déjà mises en avant par bordure,
au lieu de ne compter que sur la couleur de bordure pour signaler la
priorité..diamond-list--lg) : les 3
<strong> montent au poids d'un vrai sous-titre (clamp(1.125rem, 1.6vw, 1.375rem)), une ligne fine sépare chaque item du suivant. La
seule section de contenu qui n'est pas construite en cards se lit
maintenant comme une liste de fin d'argumentaire plutôt qu'une liste
ordinaire agrandie.