~7 min de lecture
J'ai refondu la landing EasyAngularKit avec impeccable, voici ce que ça donne
J'ai un site Angular en prod, easyangularkit.com. Tech solide (Angular 21 SSR + Nx + Tailwind v4), mais je sentais que le design avait un problème : il sentait le template SaaS AI-généré moyen. Inter + Space Grotesk + JetBrains Mono partout, glassmorphism décoratif, hero centré icon-title-CTA, grilles 3 colonnes répétées. Pas catastrophique, mais pas non plus distinctif. Or je vends une formation premium — ma landing doit être à la hauteur.
J'ai testé une skill open-source qui s'appelle impeccable. Voici le retour brut, avec les chiffres, les commits et les pièges.
L'outil : impeccable
impeccable.style (GitHub), par Paul Bakaus. C'est une skill compatible Claude Code, Cursor, Codex CLI, Gemini CLI. Une skill = un ensemble cohérent d'instructions injectées dans le contexte de l'agent.
Concrètement, impeccable apporte :
- 23 sous-commandes design :
audit,typeset,colorize,quieter,layout,harden,polish, etc. - Une liste codifiée d'anti-patterns : typographies "reflex-reject" (Inter, DM Sans, Cormorant…), glassmorphism par défaut, side-stripe borders, gradient text, em dashes, hero-metric template…
- Un protocole strict : avant toute commande, l'agent doit charger
PRODUCT.md(stratégie marque) etDESIGN.md(système visuel). SansPRODUCT.md, l'audit refuse de tourner.
Installation
npx skills add pbakaus/impeccable --yes
Crée .agents/skills/impeccable/ avec :
SKILL.md: entrée principale, règles globales, routing des commandesreference/*.md: 23 fichiers de spec, un par sous-commandescripts/*.mjs: utilitaires (load-context, pin/unpin)
Le protocole imposé
Chaque sous-commande passe par des "gates" :
- Context gate —
node .agents/skills/impeccable/scripts/load-context.mjschargePRODUCT.md+DESIGN.md - Product gate —
PRODUCT.mddoit exister, sinon$impeccable teachest invoqué (interview courte) - Command gate — la spec de la commande appelée doit être chargée
Avant le premier $impeccable audit, j'ai dû créer PRODUCT.md via une interview de 4 questions (registre brand vs product, 3 mots de personnalité, références visuelles, niveau d'a11y visé). Mes réponses : brand / "expert, direct, premium" / Linear-Vercel-Stripe / WCAG 2.1 AA. Ces réponses conditionnent tout l'audit qui suit — elles servent de filtre opinionnated pour distinguer un anti-pattern vrai d'un anti-pattern de circonstance.
Le déroulé chronologique
Phase 0 — Setup
npx skills add pbakaus/impeccable --yes
Skill installée + PRODUCT.md créé après interview.
Phase 1 — Audit initial
$impeccable audit
L'audit ne modifie rien. Score : 10/20 (D+ — Acceptable). Il a sorti une liste de findings :
- P0 Typographie reflex-reject (Inter / Space Grotesk / JetBrains Mono — les trois explicitement bannies)
- P0 Glassmorphism prolifique (28
backdrop-filtercumulés) - P0
transition: widthsur la barre de progression de lecture (layout thrash à chaque scroll) - P0 Em dash dans la copy
- P1
outline: none/:focus-visibleincomplets - P1 Hex hardcodés contournant les tokens CSS
- P1 Hero centré template
- P1 Grilles 3-cols répétées
- P1 Contraste insuffisant
light-bluesur fond clair - P1
overflow-x: hiddenà 3 niveaux
Phase 2 — Application des recommandations
Une commande, un commit. Dans l'ordre P0 → P1 → P2 :
| # | Commande | Effet |
|---|---|---|
| 1 | $impeccable optimize |
Barre de progression width → transform: scaleX. Blurs plafonnés à 12-16 px. |
| 2 | $impeccable clarify |
Em dash + typos congrats.page.ts. |
| 3 | $impeccable harden |
:focus-visible global, prefers-reduced-motion global, hex tokenisés. |
| 4 | $impeccable adapt |
overflow-x: hidden redondants retirés, touch targets 44 px + aria sur tag-filters. |
| 5 | $impeccable quieter |
Glassmorphism réduit à 1 surface (la navbar). 28 → 2 occurrences. |
| 6 | $impeccable colorize |
Token --color-link-blue (#3a6fa5, AA sur blanc), --color-white teinté, jaune décoratif retiré. |
| 7 | $impeccable typeset |
Inter / Space Grotesk / JetBrains Mono → Cabinet Grotesk + Switzer + Geist Mono. ~6 Mo de fonts orphelines supprimées. |
| 8 | $impeccable layout |
why-angular : grille 3-cols → liste-spécimen numérotée asymétrique. |
| 9 | $impeccable shape + bolder |
Hero centré → asymétrique 2 colonnes (texte gauche, vidéo droite). |
| 10 | $impeccable document |
Génération de DESIGN.md (format Stitch, 6 sections normatives). |
| 11 | $impeccable polish |
Tokens CSS obsolètes supprimés, CLAUDE.md aligné. |
Phase 3 — Bugs découverts en preview Vercel
L'audit ne testait pas le rendu réel. Premiers déploiements ont révélé :
| Bug | Cause |
|---|---|
| Carré clippé en haut à gauche sur mobile | background-attachment: fixed sur body — bug iOS Safari |
| Fond redevenu blanc | html::before ne propageait pas au canvas — gradient remis sur html direct |
| Hero suivi directement du footer | @defer (on idle) ne firait pas (CDN fonts maintenaient la page non-idle) → migration vers withIncrementalHydration() + hydrate on viewport / hydrate on timer(2s) |
| Photo Gaëtan en broken-image icon | <picture><source> + NgOptimizedImage créait un conflit d'hydratation. Final : <img> natif simple |
| Pastilles dark-blue sur fond glass-card dark-blue | Disques navy invisibles. Remplacés par icônes outline jaune flush |
| Testimonials coupé / lent | iframeResizer injecté tardivement. Final : <script async> dans index.html + min-height: 480px + preconnect |
| Code inline qui déborde des titres blog | Trois itérations pour trouver le bon équilibre : pastille sombre conservée + line-height: 1.3 heading + chip compressé |
| Lead atout sur 3 lignes mobile | Stack vertical mobile / grid 2-col desktop |
Phase 4 — Re-audit
$impeccable audit
Score : 16/20 (+6, "Good"). 4 P1 restants identifiés.
Phase 5 — Application des P1 restants
| # | Commande | Effet |
|---|---|---|
| 1 | $impeccable harden |
Bug class="..." + [class]="..." sur même élément (le statique écrasé), focus:outline-none retiré |
| 2 | $impeccable optimize |
Hero iframe loading="eager", floating-emojis.ts supprimé (code mort, 15 nœuds DOM par page) |
| 3 | $impeccable colorize |
Yellow density sur wall-of-love : 4 → 2 backgrounds |
| 4 | $impeccable layout |
border-l-4 border-primary-yellow (side-stripe banni) → bordure 1px hairline + jaune au hover |
| 5 | $impeccable polish |
9 tokens CSS morts retirés |
Phase 6 — Audit final
Score : 19/20 ("Excellent — minor polish"). Le seul P2 restant est un bundle JS à 595 kB (warning band, pas blocker).
Les chiffres
| Métrique | Avant | Après | Δ |
|---|---|---|---|
Score $impeccable audit |
10/20 | 19/20 | +9 |
| Familles typographiques reflex-reject | 3/3 | 0/3 | -3 |
| Surfaces glassmorphism | 28 | 1 | -27 |
bg-primary-yellow décoratifs (≥ 32 px hors CTA) |
13 | 5 | -8 |
| Tokens CSS morts | 18 | 0 | -18 |
| Self-hosted font files | ~6 Mo | 0 | -6 Mo |
| Side-stripe borders | 1 | 0 | -1 |
| Em dash dans le code | 1 | 0 | -1 |
| Hardcoded hex colors dans composants | ~20 | 0 | -20 |
outline: none non remplacé |
2 | 0 | -2 |
| Code net (.ts + .css) | — | -503 lignes | — |
Le top 3 de ce qui change vraiment
1. La typographie
Avant : Inter (corps) + Space Grotesk (titres) + JetBrains Mono (code) — exactement la signature de 80 % des landings AI-générées.
Après : Cabinet Grotesk + Switzer (Fontshare, gratuit) + Geist Mono (Google Fonts). Hors reflex-reject list, plus distinctif.
<!-- index.html -->
<link rel="preconnect" href="https://api.fontshare.com" crossorigin>
<link rel="preconnect" href="https://cdn.fontshare.com" crossorigin>
<link rel="stylesheet" href="https://api.fontshare.com/v2/css?f[]=cabinet-grotesk@500,600,700,800&f[]=switzer@300,400,500,600,700&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;600&display=swap">
2. Le hero
Avant : centré icon-title-subtitle-CTA, video en glass-premium sous le tout. Template.
Après : grille asymétrique 1.05fr / 1fr — texte à gauche (eyebrow formation angular · v18 → v21, titre Cabinet Grotesk 8vw, sous-titre, CTA), vidéo YouTube à droite. Stack vertical sur mobile.
3. L'incremental hydration
Découverte forcée par un bug : @defer (on idle) ne firait jamais sur certaines sessions iOS Safari à cause des nombreux scripts tiers (analytics, Stripe, Hotjar, GTM, testimonial.to…). Migration vers :
// app.config.ts
provideClientHydration(withEventReplay(), withIncrementalHydration())
// landing.page.ts
@defer (hydrate on viewport; hydrate on timer(2s)) {
<app-why-angular />
<!-- ... -->
}
Toutes les sections sont SSR-rendered (visibles dès le premier paint), l'hydratation JS arrive ensuite quand le déclencheur fire.
Les apprentissages
Sans
PRODUCT.mdetDESIGN.md, l'agent fait du générique. Les deux fichiers obligent à fixer les anti-références ("ce que je ne veux PAS être") et les principes — c'est ça qui transforme l'audit générique en audit opinionnated.Le protocole de gates oblige à ralentir. Avant chaque commande, le contexte doit être chargé. Ça force à séquencer les passes plutôt que tout faire en parallèle.
L'audit n'est pas un test visuel. Il scanne le code — il ne voit pas iOS Safari, le bundle réel sur device, les hydration mismatches. La phase 3 (debug post-deploy) est inévitable et ne remet pas en cause la qualité du diagnostic initial.
Le score 19/20 n'est pas une fin. C'est un signal que les anti-patterns codifiés sont éliminés. Les vrais tests d'usage (Lighthouse, A/B, conversion) restent à faire indépendamment.
Beaucoup de "fixes" sont des reverts. La phase 3 a vu plusieurs commits qui détricotent des choix de la phase 2 (pastilles inline-code, photo wrapper, defer trigger). Itération > planification parfaite.
Reproductibilité
Si tu veux refaire ce travail sur un autre projet :
# 1. Installer la skill
npx skills add pbakaus/impeccable --yes
# 2. Charger le contexte (dans Claude Code)
node .agents/skills/impeccable/scripts/load-context.mjs
# 3. Si pas de PRODUCT.md
# $impeccable teach
# 4. Audit
# $impeccable audit
# 5. Appliquer chaque recommandation, un commit par commande
# $impeccable typeset / quieter / colorize / layout / harden / optimize / clarify / adapt / polish
# 6. Document quand le système est stable
# $impeccable document
# 7. Re-audit pour valider
# $impeccable audit
Verdict
impeccable n'est pas une baguette magique. C'est un filtre opinionnated qui transforme l'agent en designer-junior sérieux : il ne va pas inventer de génie créatif, mais il va éliminer les 5 ou 6 tells qui font qu'une landing ressemble à 1000 autres. Pour un projet comme EAK où "le design est un argument de preuve", c'était exactement ce qu'il fallait.
Score 10/20 → 19/20 en 28 commits, étalés sur quelques sessions. Si tu vends quelque chose en ligne et que ta landing date de plus d'un an sans refonte, tu vas trouver des choses.
🎁 Et si tu veux voir le résultat en vrai : easyangularkit.com