Communitygithub.com

anhlamtruong/living-ui-swarm

Créez une animation de construction de page par un "essaim d'agents" pour React / Next.js. Une équipe de curseurs colorés (planificateur, codeurs, designers, mise en page, testeur, relecteur, déployeur) assemble chaque section au fur et à mesure qu'elle défile dans la vue.

Qu'est-ce que living-ui-swarm ?

Cette compétence fournit un moteur d'animation complet pour créer des pages web qui semblent être construites en direct par une équipe d'agents IA. Elle utilise un canevas superposé fixe pour dessiner des curseurs colorés qui réalisent des tâches comme encadrer des cartes, taper des titres, changer de polices, faire glisser des images, dupliquer des éléments de liste, choisir des couleurs sur une roue, disposer des grilles, puis réviser et approuver chaque section. Le moteur fonctionne avec React / Next.js, GSAP pour les animations, et Lenis pour le défilement fluide. Les sections sont marquées avec des attributs de données dans le HTML, et l'animation se déclenche lors du défilement, avec une mémoire du statut de construction par onglet (sessionStorage) pour ne pas rejouer l'animation au rechargement. La compétence est idéale pour les portfolios, les pages de lancement, les études de cas ou toute page où l'expérience de construction par des agents est le message central. Elle est conçue pour être performante (pas de décalage de mise en page, chargement différé du code d'animation, respect des préférences de réduction de mouvement) et offre des options de personnalisation étendues via des variables CSS et des profils de performances par appareil.

Compatible avec~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/anhlamtruong/living-ui-skills/tree/HEAD/skills/living-ui-swarm

Demander à votre IA préférée

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

Aperçu

Depuis le README du skill

living-ui-swarm preview

Documentation

Animation de construction par essaim d'agents

Un canevas superposé fixe dessine une petite équipe de curseurs qui "construisent" chaque section d'une page lorsqu'elle défile dans la vue. Le contenu est réel, du HTML rendu côté serveur pendant tout le processus. Le moteur cache les éléments pendant seulement quelques secondes et joue une courte chorégraphie pour chacun.

Exemple en direct : https://www.lamanhtruong.com. Sa source est épinglée à portfolio-anh@a5d1651. assets/swarm/ contient le même moteur, nettoyé pour être réutilisé.

Quand l'utiliser, et quand ne pas l'utiliser

Utilisez-le lorsque la page elle-même est l'argument de vente : un portfolio, une page de lancement ou d'histoire de produit, une étude de cas, une page "à propos de notre IA". Les gens visitent une fois, défilent de haut en bas, et la construction leur dit quelque chose ("cette personne livre avec des agents").

Évitez-le pour tout ce que les gens utilisent tous les jours : tableaux de bord, documentation, paramètres, checkout, lecture longue. La construction coûte quelques secondes par section, et un utilisateur qui revient n'a aucune raison de la regarder deux fois. C'est pourquoi le moteur se souvient d'une construction terminée par onglet (sessionStorage) et ne la rejoue jamais au rechargement.

Ce qu'il coûte :

  • Environ 5 000 lignes de TypeScript. La couche de construction est chargée avec next/dynamic après l'hydratation, donc la première peinture ne l'attend jamais ; les éléments de défilement fluide et de parallaxe (Lenis, GSAP, ScrollTrigger) se chargent avec la page. Une nouvelle application Next.js 15 avec les quatre compétences living-ui représentait environ 67 kB de JS de route (170 kB au premier chargement).
  • Un petit score de décalage de mise en page lorsque le journal en direct se remplit (environ 0,03 sur une page de test, bien en dessous de la ligne "bon" de 0,1) : les nouvelles lignes de journal se déplacent à l'intérieur de leur boîte de hauteur fixe. Le reste de la page ne bouge jamais.
  • Un canevas fixe supplémentaire et une boucle gsap.ticker. La boucle s'arrête lorsque rien ne bouge.
  • GSAP 3.13+ (gratuit, y compris ScrollTrigger), @gsap/react, Lenis.

Comment ça fonctionne en un paragraphe

Un petit script intégré dans <head> définit html[data-build="on"] avant la première peinture, et le CSS cache alors chaque élément non construit [data-build-item] avec opacity: 0. L'opacité plutôt que l'affichage ou la visibilité signifie aucun décalage de mise en page et rien n'est retiré de l'arborescence d'accessibilité. Après l'hydratation, le moteur scanne la page en un graphe de tâches par [data-build-section], arme un ScrollTrigger pour chaque section, et lorsqu'une entre dans la fenêtre d'affichage, exécute plan → construction → révision :

  • Plan : un planificateur dessine des guides et distribue les tâches.
  • Construction : les travailleurs réclament les tâches par distance, rôle et chemin critique, et chaque tâche joue une timeline GSAP.
  • Révision : un relecteur délimite la section, coche une liste de vérification et l'estampille.

Les éléments et sections terminés reçoivent [data-built]. Si le moteur ne démarre jamais, le script d'en-tête supprime le drapeau après 3 s pour que rien ne reste caché.

Architecture, flux de données et comment l'étendre : references/architecture.md.

Installation (Next.js App Router)

  1. Dépendances. Testé avec Next.js 15.1 + React 18.3 (l'exemple travaillé) et Next.js 15.5 + React 19.1 + Tailwind 4 (un nouveau create-next-app), TypeScript 5.8–5.9, [email protected]–3.15, @gsap/[email protected], [email protected] :

    npm i gsap@^3.13 @gsap/react@^2.1 lenis@^1.3
    
  2. Copiez le moteur. Copiez assets/swarm/ dans src/components/swarm/. Les importations à l'intérieur sont relatives, donc n'importe quel dossier fonctionne.

  3. Ajoutez le script de drapeau dans le <head> de la mise en page racine. Il définit un attribut sur <html> avant que React ne s'hydrate, donc ajoutez suppressHydrationWarning à <html> :

    // app/layout.tsx
    import { BuildFlagScript } from "@/components/swarm/build-flag-script";
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en" suppressHydrationWarning>
          <head>
            <BuildFlagScript />
          </head>
          <body>{children}</body>
        </html>
      );
    }
    
  4. Montez le moteur une fois, sur les pages qui l'utilisent, après le contenu :

    // app/page.tsx (un composant serveur ; reste statique)
    import "@/components/swarm/swarm.css";
    import { SwarmRoot } from "@/components/swarm/swarm-root";
    
    export default function Page() {
      return (
        <>
          <main>{/* sections marquées avec data-build-* */}</main>
          <SwarmRoot />
        </>
      );
    }
    

    SwarmRoot monte également le défilement fluide Lenis piloté par gsap.ticker. Si l'application utilise déjà Lenis, supprimez <SmoothScroll /> de swarm-root.tsx. build-layer.tsx lit l'instance via useLenis(), qui nécessite une instance root ReactLenis quelque part sur la page (<ReactLenis root />, comme smooth-scroll.tsx la monte). Ce n'est pas obligatoirement un parent.

  5. Gardez la route statique. Le moteur ne décore que le HTML qui existe déjà. N'appelez pas cookies(), headers() ou auth() dans l'arbre de mise en page de la page juste pour lire une session. Récupérez cela côté client à la place, et la page peut être pré-rendue et servie depuis le CDN. Sur les pages qui ne montent pas SwarmRoot, le script de drapeau est inoffensif : aucun élément ne porte data-build-item, et le mécanisme de sécurité efface le drapeau.

  6. Personnalisez-le. Définissez des variables CSS (--swarm-font-mono, --swarm-accent, --swarm-text, --swarm-muted, --swarm-dim, --swarm-line, --swarm-neutral-text|fill|stroke), puis modifiez COLORS et ROLE_COLOR dans palette.ts. Le canevas ne peut pas lire les variables CSS à moindre coût sur chaque image, donc sa palette vit dans TS.

Baliser la page

Tout est piloté par des attributs de données sur le balisage rendu côté serveur. La référence complète, avec les valeurs par défaut et le comportement sur téléphone, se trouve dans references/dom-contract.md. Des recettes copier-coller pour un héros, une grille de cartes, une liste, une image ou une vidéo, une bande de métriques et un final sont dans references/recipes.md.

AttributSurSignification
data-build-section="name"wrapper de sectionUne unité de construction avec son propre plan, équipe et révision
data-build-start="load"sectionConstruire juste après le démarrage au lieu du défilement, avec un budget héros plus grand
data-build-item + `data-build="frametypereveal
data-build-listconteneur de listeSon premier élément est le maître (frame) ; les frères dup sont copiés à partir de lui
data-build-gridconteneur ou sectionUn agent de mise en page dessine la grille et encadre chaque cellule en premier
data-build-font / -weight / -scaleéléments typeBrouillon mono puis échange de police ; balayage de poids ; commencer petit, puis redimensionner et déplacer
`data-build-color="textfillstroke"`
data-build-tuneframeOuvre l'inspecteur de rayon et de remplissage
`data-build-from="leftrighttop
data-build-keep / ="mobile"tout élémentJamais caché (téléphones uniquement avec mobile), toujours annoté
data-build-label, -order, -roletout élémentTexte d'étiquette, indication d'ordre, rôle qui le prend
data-swarm-spawn="role"<g> SVG dans le hérosNœuds de maillage d'où l'équipe sort au démarrage (≥1024 px)
data-swarm-logconteneurLes lignes de secours rendues côté serveur sont remplacées par un journal en direct
data-build-finale + data-deploy-target + data-deploy-textdernière section, son CTALe déployeur "livre" la page après la dernière révision
?build=off / ?build=forceURLDésactiver pour cette vue / rejouer même si cet onglet a déjà regardé

Le moteur définit : html[data-build="on"] pendant l'activité, [data-built] sur les éléments et sections terminés, [data-lit] sur les nœuds de maillage pendant le démarrage, et [data-deployed] sur la cible du final.

Règles pour que cela fonctionne :

  • Taille de section. Environ une fenêtre d'affichage d'éléments par section. Divisez les longues pages : une section par tâche dans une chronologie, une par carte dans une grande grille, une section distincte pour un en-tête de section. Les petites sections se terminent dans leur budget et semblent délibérées ; les énormes ralentissent à la vitesse de rattrapage.
  • Imbrication. Un élément imbriqué dans un autre élément attend son parent. Marquez la carte comme frame et son titre, son texte et ses puces à l'intérieur.
  • Type. Utilisez type uniquement pour du texte court (moins de 120 caractères environ). Le texte plus long revient automatiquement à reveal. La frappe lit les glyphes une fois, donc le texte géré par React qui change après l'hydratation (compteurs, nombres de vues) doit être un frame, pas type.
  • LCP. Gardez le titre principal visible sur les téléphones avec data-build-keep="mobile". Sur les téléphones, le titre du héros est le Largest Contentful Paint, et le cacher coûte vos Core Web Vitals. Le bureau reçoit toujours la construction complète avec échange de police.
  • Journal en direct. Le conteneur data-swarm-log doit être rendu côté serveur avec de vraies lignes de secours et une hauteur fixe (.swarm-log). Le moteur les échange pour des lignes en direct et les remet en cas de Skip ou démontage.

Héros minimal :

<section data-build-section="hero" data-build-start="load">
  <p data-build-item data-build="type">votre ligne d'accroche</p>
  <h1
    data-build-item data-build="type" data-build-font data-build-weight
    data-build-keep="mobile" data-build-order="-1" data-build-role="coder"
  >
    Votre Nom <span className="serif">(Accent)</span>
  </h1>
  <div data-build-item data-build="image" data-build-from="right" className="avatar">
    <img src="/me.jpg" alt="Portrait" />
  </div>
  {/* next/image fonctionne aussi (il rend un <img> interne que le moteur lit). */}
  <div data-build-list className="buttons">
    <a data-build-item data-build="frame" href="#">GitHub</a>
    <a data-build-item data-build="dup" href="#">LinkedIn</a>
    <a data-build-item data-build="dup" data-build-color="fill" href="#">CV</a>
  </div>
</section>

Règles de fluidité

Elles sont issues de l'observation de constructions réelles. Chacune a résolu un problème visible.

  • Ne jamais forcer la fin à une échéance. Une version antérieure accélérait toutes les timelines en cours à 5× et plaçait les éléments non réclamés à chaque échéance de section. Les gens voyaient un à-coup à la fin de chaque section. Les budgets sont désormais des objectifs de planification.
    • estimate() ajuste les durées des tâches pour que le plan tienne.
    • Lorsqu'une section prend du retard, pace() augmente progressivement un facteur de rattrapage, jusqu'à 1,7× sur 0,7 s.
    • Chaque interpolation dure au moins 120 ms même en rattrapage complet, et les étapes de frappe restent au moins 16 ms. Rien n'apparaît brusquement.
    • Si vous avez besoin de plus de vitesse, rendez les curseurs plus rapides (speed dans palette.ts). Ne coupez pas les animations.
  • Animer uniquement l'opacité, le clip-path, le filtre et la transformation. Pas d'écriture de mise en page. Les brouillons d'échange de police sont des clones dans une couche hors flux, et la tâche d'échelle supprime sa transformation en ligne à la fin.
  • Une seule horloge. La simulation et le dessin du canevas s'exécutent dans un seul callback gsap.ticker. Les changements d'état React se produisent uniquement au début ou à la fin de la construction, jamais par image. Le ticker s'arrête lorsqu'aucun agent n'est à l'écran.
  • Mesurer à l'avance. Les sections jusqu'à environ deux fenêtres d'affichage sous la ligne de flottaison sont mesurées dans des tranches requestIdleCallback. Le démarrage d'une section ne provoque jamais une grosse rafale de lecture de mise en page sur l'image de défilement.
  • Respecter le lecteur.
    • Le fait de mettre le focus sur quoi que ce soit à l'intérieur d'une section non construite termine cette section immédiatement, afin que les utilisateurs de clavier n'attendent jamais.
    • Un redimensionnement en cours de construction termine les sections en cours, car leurs rectangles mesurés sont obsolètes.
    • Un onglet masqué met tout en pause.
    • Le HUD propose toujours Skip, Replay et Inspect.
  • Les téléphones reçoivent moins. Chaque profil a son propre budget, équipe et détails (detectProfile() dans palette.ts).
    • Téléphones : 8 agents, environ 2,2 s par section, un échange de police par page, un choix de couleur par section, un balayage de duplication par liste, pas de panneaux d'inspection, pas de tâche d'échelle, pas de déployeur.
    • Tablettes : 11 agents.
    • Bureau : 15 agents, environ 3,9 s par section, 5,25 s pour le héros.

Boutons pour "plus rapide", "plus calme" et "plus d'agents" : references/tuning.md.

Mouvement réduit

Le moteur joue pour chaque visiteur, y compris ceux dont le système d'exploitation demande un mouvement réduit. Le propriétaire du site a décidé que la construction est le point central de la page, et le HUD propose toujours Skip, c'est donc un choix délibéré, pas une valeur par défaut à copier aveuglément. Pour respecter le paramètre à la place, apportez ces trois modifications :

// build-flag-script.tsx : à l'intérieur de CODE, avant d.setAttribute('data-build','on')
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;

// motion.ts
export function offByUrl(): boolean {
  return OFF_RE.test(window.location.search) ||
    window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}

// smooth-scroll.tsx
respectReducedMotion: true,

Testez les deux façons : certains navigateurs (et certaines configurations d'automatisation) rapportent reduce même lorsque personne ne l'a choisi, ce qui est une des raisons pour lesquelles la valeur par défaut ici est "activé".

Travailler avec d'autres effets

  • Verrouillage sur [data-built]. Le moteur lit les rectangles des éléments et bascule l'opacité et le clip-path pendant la construction. L'inclinaison, le scintillement, le brouillage, la parallaxe sur le contenu, ou toute autre chose qui transforme un élément corrompra ces mesures s'il s'exécute tôt. Démarrez ces effets uniquement lorsque l'élément et sa section portent [data-built], ou lorsque html[data-build] n'est pas "on". Un assistant prêt à l'emploi :

    export function isBuilt(el: Element): boolean {
      if (document.documentElement.getAttribute("data-build") !== "on") return true;
      const item = el.closest("[data-build-item]");
      if (item && !item.hasAttribute("data-built")) return false;
      const section = el.closest("[data-build-section]");
      return !section || section.hasAttribute("data-built");
    }
    
  • Vidéos. Un élément image qui encapsule un <video poster> est glissé en utilisant son cadre d'affiche comme vignette. Donnez au wrapper un data-build-label comme clip.mp4, car il n'y a pas de <img> pour lire un nom de fichier.

  • Parallaxe décorative est acceptable partout où elle ne déplace que de la décoration. Ne la mettez jamais sur quelque chose que le moteur mesure.

Faites-le vôtre

Le moteur ici est une implémentation originale, et la même norme s'applique à tout ce que vous construisez à partir de celui-ci. Si un site vous a inspiré, notez ce qui rend ce site reconnaissable et restez loin de ces choses exactement. Emprunter ...

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Skills associés