Communitygithub.com

karero/website-design-system

Gère la couche **visuelle et de rendu** : graphismes mobiles, mode sombre et jetons de thème.

Qu'est-ce que website-design-system ?

Ce skill définit le système de design visuel d’un site web, couvrant le rendu mobile, le mode sombre, la réactivité typographique multilingue, les tableaux adaptatifs et l’obfuscation des e-mails. Il impose des règles strictes pour les images (WebP/AVIF, dimensions explicites, lazy‑loading), les contrastes d’accessibilité (WCAG AA dans les deux thèmes), et une approche « mobile first » avec des breakpoints à 360/768/1280 px. La gestion des traductions est intégrée via des largeurs flexibles et la césure automatique (`hyphens: auto`) pour éviter les débordements. Les tableaux suivent deux patrons (`stack` pour le texte long, `figures` pour les nombres courts) et sont testés automatiquement. Le skill garantit qu’aucune adresse e-mail ou `mailto:` n’est en clair dans le HTML grâce au composant `EmailLink.astro`. Il fonctionne avec des projets Astro, utilise des tokens CSS personnalisés pour le thème, et s’appuie sur des tests (`tables.spec.ts`, `images.spec.ts`, `website-qa`) pour valider la conformité.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/karero/croftweaver/tree/HEAD/skills/website-design-system

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Système de design du site web

Gère la couche visuelle et de rendu : graphismes mobiles, mode sombre et jetons de thème. Produit et maintient BRAND.md (modèle : le fichier templates/brand.md du skill new-website) et le bloc de jetons dans templates/astro/src/styles/global.css de ce même skill. Les règles pour les images sont ci‑dessous (WebP/AVIF via astro:assets d’Astro) ; le test images.spec.ts les applique. Pour générer les ressources images, utilisez le skill image.

Mode sombre (citoyen de première classe)

  • Des jetons, pas des couleurs en dur. Toute couleur passe par des propriétés CSS personnalisées ; le mode clair est la valeur par défaut de :root, les surcharges sombres sous :root[data-theme="dark"], ainsi qu’un bloc @media (prefers-color-scheme: dark) pour les visiteurs sans JavaScript. Le starter fournit exactement cette structure.
  • Script anti‑FOUC dans <head> définit data-theme depuis localStorage.theme ou la préférence système avant le premier affichage (déjà câblé dans Base.astro). Le bouton de bascule écrit localStorage.theme (le starter n’en a pas ; si vous en ajoutez un, mentionnez‑le dans la section 3 de la page de confidentialité).
  • Contraste dans les deux thèmes : chaque paire texte/fond passe WCAG AA (4,5:1 pour le corps, 3:1 pour les grands textes/éléments d’interface). Une couleur qui passe en clair échoue souvent en sombre — le test d’accessibilité parcourt la matrice complète clair+sombre, donc vérifiez les deux.

Graphismes mobiles et adaptatifs

  • Mobile first : conception à partir de 360 px ; cibles tactiles ≥ 44 px ; pas de défilement horizontal. Vérifier à 360 / 768 / 1280 (preview_resize).
  • Formats modernes : WebP (ou AVIF) plutôt que JPEG/PNG pour les photos.
  • Dimension par rapport à l’affichage, pas à la source. Ne livrez jamais une image de 2000 px dans un emplacement de 80 px ; générez des variantes de largeur et utilisez srcset/sizes (ou <picture>).
  • Pas de décalage de mise en page : définissez les attributs explicites width/height pour que le cadre soit réservé avant le chargement de l’image (protège CLS / LCP). Le composant <Image> d’Astro les définit automatiquement ; images.spec.ts exige ces attributs (un simple aspect-ratio en CSS ne satisfait pas le test).
  • Paresseux ou immédiat : loading="lazy" en dessous de la ligne de flottaison ; l’image LCP/héro est chargée immédiatement avec fetchpriority="high" (ne PAS la charger en différé).
  • Texte alternatif sur chaque image (alt="" uniquement si purement décorative).
  • Polices : auto‑hébergées en woff2 avec font-display: swap ; précharger uniquement les 1 à 2 utilisées au‑dessus de la ligne de flottaison. Évitez les décalages de mise en page dus aux échanges tardifs de polices.

Réactivité de la longueur du texte (contenu traduit)

  • Le texte traduit est plus long. L’allemand (et de nombreuses autres langues) est généralement 20 à 35 % plus long que l’anglais équivalent. Un bouton, un lien de navigation ou une carte dimensionné pour un texte de substitution anglais débordera silencieusement, s’enroulera de manière disgracieuse ou sera tronqué dès que le contenu traduit réel le remplacera.
  • Préférez les largeurs flexibles. Utilisez min-width, flex ou les tailles auto/fr de la grille pour tout élément contenant du texte traduit, pas une width fixe calquée sur la chaîne anglaise.
  • Débordement ≠ largeur : la flexibilité n’empêche pas un seul long mot composé (Datenschutzerklärung) de sortir d’une colonne étroite — il faut hyphens: auto (correct par langue grâce à l’attribut lang que Base.astro définit déjà) plus overflow-wrap: break-word en solution de repli ; le global.css du starter fournit les deux sur body. Les cellules de tableau désactivent la césure (voir Tableaux).
  • Ne vous fiez pas uniquement à white-space: nowrap + text-overflow: ellipsis pour les liens de navigation ou les étiquettes de boutons — tronquer un lien de navigation est généralement inacceptable en UX (le visiteur ne peut plus dire où il mène). Acceptable pour du contenu où la troncature est un compromis admis (par exemple une cellule de tableau), pas pour la navigation.
  • Vérifiez avec les étiquettes traduites réelles, pas les substituts anglais. Quand un site utilise astro-i18n-setup avec LanguageSwitcher.astro ou toute navigation bilingue/multilingue, testez la mise en page à 360/768/1280 (preview_resize) avec le contenu réel livré dans chaque langue — pas seulement le substitut anglais.

Tableaux

Un seul style pour chaque tableau ; une page n’a pas de CSS de mise en page propre à un tableau. Il réside dans src/styles/tables.css du starter (importé après global.css par Base.astro) et tests/tables.spec.ts le vérifie. Un site créé avant cette section copie tables.css, ajoute la ligne d’import dans sa mise en page et supprime d’abord le CSS de tableau propre à chaque page.

Deux types, distingués par ce que contiennent les cellules :

  • stack (phrases) : un tableau au‑dessus de 40 rem ; en dessous, chaque ligne s’empile, l’en‑tête de ligne comme titre et chaque cellule sous le nom de sa colonne. Quatre colonnes de prose ne peuvent pas partager 320 px.
  • figures (courts nombres, class="num" sur une cellule numérique) : reste un tableau sur un téléphone et doit tenir dans 320 px. S’il ne tient plus, passez‑le en stack ; une barre de défilement n’est pas la solution. stack figures ensemble donne l’aspect des chiffres avec la mise en page empilée.

Balises, pour chaque tableau :

<div class="table-wrap stack" role="region" aria-label="Ce que montre le tableau" tabindex="0">
  <table role="table">
    <thead role="rowgroup"><tr role="row">
      <th scope="col" role="columnheader">{cols[0]}</th><th scope="col" role="columnheader">{cols[1]}</th>
    </tr></thead>
    <tbody role="rowgroup">{rows.map((r) => (
      <tr role="row"><th scope="row" role="rowheader">{r.item}</th><td role="cell" data-label={cols[1]}>{r.text}</td></tr>
    ))}</tbody>
  </table>
</div>
  • Écrivez les noms des colonnes une fois, dans une liste, et utilisez‑les pour l’en‑tête et pour chaque data-label : le libellé d’une cellule empilée doit être égal à celui de son en‑tête de colonne, et le test compare les deux. Un tableau figures n’a besoin ni de rôles par cellule ni de data-label.
  • Un tableau empilé n’est plus display: table. Les rôles sont une solution de repli pour un navigateur qui abandonne le tableau lorsque la disposition change ; on ne vérifie pas qu’ils fonctionnent là. Dans Chromium, mesuré une fois sur un vrai tableau, l’arborescence d’accessibilité a conservé le tableau sans eux.
  • class="tap" sur un lien seul dans une cellule en fait une cible tactile de 44 px sur un téléphone. nowrap maintient une courte valeur (1, 3, 6) sur une ligne. long-head (avec stack) élargit la première colonne lorsque les en‑têtes de ligne sont des phrases.
  • Les mots ne sont pas coupés dans les cellules. Le hyphens: auto du corps comprime une première colonne étroite en fragments ("An-thropic"), donc le bloc y définit hyphens: manual.
  • Une page dont les tableaux proviennent de l’extérieur et ne peuvent pas porter ce balisage va dans TABLE_EXEMPT dans la spécification, avec la raison. PAGES reste intact : le plan du site et les vérifications llms.txt ont besoin de chaque route active qui s’y trouve.

Ce que vérifie tables.spec.ts : chaque tableau est dans une .table-wrap d’un des deux types avec sa région, son libellé et tabindex, scope="col" sur chaque cellule d’en‑tête et un <th scope="row"> comme première cellule de chaque ligne, avec <td> pour le reste ; un tableau empilé a ses rôles et un libellé égal à l’en‑tête sur chaque cellule ; à 360 et 320 px, aucune page ni aucun tableau ne défile latéralement ; à 641 px et plus, un tableau empilé redevient un tableau avec sa ligne d’en‑tête visible et aucun mot coupé (cela attrape des règles comme overflow-wrap: anywhere qui laissent une colonne comprimer un mot) ; aucune cellule n’a hyphens: auto. Il teste aussi le CSS sur un élément fixe ajouté à la page d’accueil, avec axe dans les deux thèmes, donc un site sans tableau prouve quand même le bloc. Chromium sans tête ne coupe pas les mots, donc la vérification des césures lit le style calculé, pas la mise en page. Safari et VoiceOver ne sont pas vérifiés.

Obfuscation des e-mails (cacher les adresses aux robots)

Ne mettez jamais un e-mail en clair ou mailto:votre@adresse dans le code HTML source — les robots de collecte les récupèrent pour le spam en quelques heures. Affichez chaque adresse via le composant EmailLink.astro (src/components/EmailLink.astro dans templates/astro/ du skill new-website, + son assistant lib/obfuscate.ts) : l’adresse est inversée + encodée en base64 à la construction et réassemblée dans le navigateur, de sorte que le HTML servi ne contient aucune adresse lisible. Les visiteurs sans JavaScript voient une indication utilisable "nom [at] domaine [dot] com".

<EmailLink address="[email protected]" subject="Demande concernant le projet" text="Envoyez‑nous un e-mail" />

Règle : zéro lien mailto: brut et zéro adresse littérale avec @ dans le HTML produit — website-qa le vérifie. Même principe pour tout numéro de téléphone que vous ne souhaitez pas voir collecté.

Jetons de marque

Refléter la palette de BRAND.md dans le bloc de jetons et les maintenir synchronisés. Définissez : marque (fixe), et par thème en‑tête/texte/atténué/accent/lien/trait/fond/fond‑doux/surface. La couleur d’accent et la couleur de lien doivent passer AA sur le fond dans les deux thèmes, c’est pourquoi chaque thème a les siennes. Le website-brand-kit optionnel effectue le miroir avec une vérification : son analyse lit les tableaux de BRAND.md, vérifie que chaque couleur de texte a un ratio de 4,5:1 sur chaque fond dans les deux thèmes, et écrit les valeurs dans les trois blocs de couleurs de global.css (clair, sombre, et le bloc sombre pour les visiteurs sans JavaScript). À la main, modifiez les trois blocs ensemble.

Fait signifie

BRAND.md rempli et cohérent avec le bloc de jetons ; les images sont en WebP, dimensionnées, chargées paresseusement/immédiatement correctement, avec texte alternatif ; le test d’accessibilité (clair+sombre) est vert ; la mise en page tient à 360/768/1280 sans décalage ; chaque tableau est stack ou figures et tables.spec.ts est vert. Les chiffres de performance (FCP/LCP) sont vérifiés dans website-qa. La mise en page tient aussi à 360/768/1280 avec le contenu réel (pas un substitut) dans chaque langue livrée, pas seulement la locale par défaut.

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Skills associés