Communitygithub.com

anton-abyzov/vskill

Bonnes pratiques pour la création de vidéos Remotion en React — structure, compositions, animations, transitions — et règles avancées pour des vidéos de démonstration produit haute-fidélité.

Qu'est-ce que vskill ?

Cette compétence regroupe les bonnes pratiques pour créer des vidéos de démonstration produit réalistes avec Remotion et React. Elle combine des fondamentaux Remotion (compositions, animations, transitions, audio, sous-titres) avec des règles de production vidéo éprouvées, issues du développement du hackathon vskill / Skill Studio. Les règles couvrent notamment la fidélité aux références YouTube (extraction d'images clés, reproduction exacte de la typographie et des icônes), les zooms caméra plein écran, les cibles de clics précises, les surlignages de rectangles ajustés au contenu, la régénération de voix off, la normalisation des niveaux audio, la synchronisation parole-scène, et un kit de publication YouTube complet (chapitres, description, vignette, hashtags). Cette compétence est conçue pour être utilisée avec les agents Claude Code et Cursor, et s'applique à tout projet Remotion nécessitant un rendu MP4 professionnel via `npx remotion render`.

Compatible avec✓Claude Code✓Codex CLI✓Cursor
npx skills add https://github.com/anton-abyzov/vskill/tree/HEAD/skills/remotion-best-practices

Demander à votre IA préférée

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

Documentation

/remotion-best-practices

Utilisez cette compétence chaque fois que vous écrivez une composition Remotion — démos vidéo animées, présentations de produits, soumissions de hackathons, présentations narrées — tout ce qui est rendu en MP4 via npx remotion render.

La compétence regroupe deux niveaux de connaissances :

  1. Fondamentaux de Remotion — les fichiers de règles en amont maintenus par l'équipe Remotion sur github.com/remotion-dev/skills (compositions, animations, polices, audio, transitions, FFmpeg, sous-titres, etc.).
  2. Règles de conception de vidéos de démonstration — leçons tirées d'un long cycle d'itération lors de la construction de la démo vskill / Skill Studio hackathon. Ce sont les règles qui, lorsqu'elles sont enfreintes, produisent un « diaporama » au lieu d'une présentation produit crédible.

Lisez les deux niveaux avant d'écrire.


Portée et applicabilité

Les règles de conception du niveau 2 ci-dessous ont été dérivées d'un projet spécifique : la démo vskill / Skill Studio hackathon (Anthropic / Claude Code « Built with Opus » hackathon, avril 2026). Elles reflètent l'apparence de ce produit — chrome Studio avec en-tête de fil d'Ariane, arborescence latérale, pages dark verified-skill.com avec polices mono, scènes de terminal, une thèse éditoriale « Inter est une daube IA, une serif distinctive est l'excellence ».

N'appliquez pas ces règles textuellement à des démos sans rapport. Une application mobile grand public, un tableau de bord financier, un jeu ou une présentation SaaS B2B aura son propre langage visuel, son propre rythme et ses propres attentes du public. Un flux « Enregistrer puis Publier », une page verified-skill.com/queue, ou une barre latérale Studio avec des compteurs PROJET/PERSONNEL/PLUGINS est du contenu, pas de la conception.

Utilisez ce niveau comme référence pour la technique sous-jacente : le motif de zoom de caméra, la discipline de ciblage du curseur, le rectangle de surbrillance serré au contenu, les transitions d'état sur place, le couplage voix off/script.ts, le pipeline rendu + amplification audio — ceux-ci se généralisent. Les constantes exactes (zoom 1,72×, centre du bouton x=1488, pilule de version v1.0.0 → v1.0.1, attribution « Modèles de niveau 1 · LLM de niveau 2 · Socket ») sont spécifiques à Skill Studio — traduisez, ne copiez pas.


Niveau 1 — Fondamentaux de Remotion

Reflète la compétence en amont remotion-best-practices sur https://github.com/remotion-dev/skills/blob/HEAD/skills/remotion/SKILL.md.

Configuration d'un nouveau projet

Lorsque vous êtes dans un dossier ou espace de travail vide sans projet Remotion existant, créez-en un en utilisant :

npx create-video@latest --yes --blank --no-tailwind my-video

Démarrer l'aperçu

Démarrez le Studio Remotion pour prévisualiser une vidéo. Le rechargement à chaud (HMR) du Studio s'effectue au fur et à mesure que les fichiers changent — vous n'avez PAS besoin d'un mécanisme preview_start séparé, et Bash run_in_background est la bonne façon de le garder actif à travers les modifications.

npx remotion studio --port=3030

Rendu

# Le point d'entrée doit être le fichier qui appelle registerRoot() (généralement src/remotion/index.ts)
npx remotion render src/remotion/index.ts <CompositionId> out/video.mp4 --concurrency=8 --log=info

Pièges courants :

  • Passer Root.tsx comme point d'entrée → erreur : « ce fichier ne contient pas registerRoot ». Utilisez index.ts.
  • TransitionSeries fait une introspection des propriétés sur les enfants directs ; ne pas envelopper les enfants dans <React.Fragment>. Utilisez flatMap retournant un tableau plat d'éléments <TransitionSeries.Sequence> et <TransitionSeries.Transition>.
  • interpolate(trame, [a, b], …) nécessite a < b strictement. Si vous dérivez b de constantes, vérifiez la cohérence lors de la conception du budget de scène — Remotion lève l'erreur « inputRange doit être strictement monotone croissant » sur la toute première image évaluée et l'ensemble de la composition échoue à se charger.

Vérification du rendu d'une seule image

npx remotion still <composition-id> --scale=0.25 --frame=30

Sujets couverts par les fichiers de règles en amont

(Charger à la demande depuis le dépôt de compétences en amont — github.com/remotion-dev/skills/tree/HEAD/skills/remotion/rules/.)

  • compositions.md — Définition des compositions, images fixes, dossiers, propriétés par défaut, métadonnées dynamiques
  • animations.md — interpolate, spring, transitions, mathématiques basées sur les images
  • transitions.md — @remotion/transitions (fondu, glissement, essuyage), comment les enchaîner dans TransitionSeries
  • assets.md — staticFile(), Img, Audio, Video, chargement de polices
  • fonts.md — @remotion/google-fonts et enregistrement local de polices
  • audio.md — Importation, rognage, volume, vitesse, hauteur
  • captions.md, subtitles.md — Pistes de sous-titres et synchronisation
  • ffmpeg.md — Rognage, concaténation, opérations audio via FFmpeg
  • silence-detection.md, extract-frames.md, get-audio-duration.md, get-video-duration.md — Utilitaires Mediabunny
  • charts.md, gifs.md, light-leaks.md, 3d.md — Recettes de rendu spécialisées

Niveau 2 — Règles de conception de vidéos de démonstration

Ce sont les règles acquises au cours d'un long cycle d'itération. Considérez-les comme des valeurs par défaut, pas un polissage optionnel — la plupart d'entre elles proviennent de retours du type « ça a l'air bizarre, corrige ça » qui ont nécessité plusieurs tournées pour être résolus.

A. Fidélité à la référence

A1. Toujours utiliser la vraie référence

Si l'utilisateur dit « fais-le ressembler à la minute 2:43 de cette vidéo YouTube », extrayez l'image et étudiez-la. N'approchez pas de mémoire.

SOURCE="/Users/antonabyzov/Movies/.../Source.mov"
DEST_DIR=".specweave/.../assets/keyframes"
for t in 102 105 108 110 112; do
  ffmpeg -y -ss "$t" -i "$SOURCE" -frames:v 1 -q:v 2 "$DEST_DIR/at-${t}s.jpg" -loglevel error
done

Ensuite, Lisez chaque JPG et concevez à partir de ce qui est réellement à l'écran — typographie, libellés exacts des boutons, couleurs des badges, disposition de l'arborescence latérale, positionnement de l'entrée de recherche, valeurs des compteurs (par exemple PROJET (1), PERSONNEL (0 sur 12), PLUGINS (0 sur 50), DISPONIBLE (62)).

A2. Utiliser de vrais logos et icônes, pas des approximations

  • Logo Bell-badge skill-studio-logo.png — copier dans chaque scène Studio sous forme de <Img src={staticFile("...")} />. Ne rendez pas un glyphe ✷ en l'appelant logo.
  • Icônes de fournisseurs — récupérer le vrai SVG/PNG pour Claude, Anthropic, OpenAI, OpenRouter, Ollama, LM Studio (par exemple, depuis lobehub/static-png). Ne remplacez pas par des emoji.
  • Mascottes — dessiner avec des divs stylisés pour correspondre exactement à la forme. La mascotte Claude Code a un corps de couleur pêche #e08160 avec deux yeux carrés, deux petites mains-nodules sur les côtés au milieu du corps, et deux paires de jambes en dessous (▪ ▪ ▪ ▪). Pas d'art ASCII.
  • Chrome de fenêtre d'application — lorsque vous montrez un site web dans un « navigateur Chrome », rendez une vraie fenêtre de navigateur Chrome : coins arrondis, points de feux de circulation, navigation ‹ › ↻, barre d'URL verrouillée avec un cadenas et une vraie URL (localhost:3000/hero, pas d'espaces supplémentaires, pas de point médian).

A3. Correspondre aux mises en page de référence YouTube pixel par pixel

Avant de reconcevoir une scène de mémoire, listez les éléments visibles dans l'image de référence et vérifiez chacun par rapport à votre sortie :

  • Texte de la bannière — formulation exacte (Soumettre les compétences pour vérification, pas Soumettre une compétence)
  • Ordre des onglets — Aperçu · Modifier · Tests · Exécuter · Déclencheur · Versions
  • Badges de confiance — T1 NON ANALYSÉ, Sécurité-Analysé —, Éditeur de confiance
  • Entrée de filtre — toujours au-dessus de DISPONIBLE dans la barre latérale
  • Compteurs — maintenez-les cohérents entre les diapositives (PERSONNEL (0 sur 12) partout, pas (11) sur une diapositive et (0 sur 12) sur une autre)
  • Contrôles du côté droit de l'en-tête (dans cet ordre) : 🔍 Rechercher des compétences [⌘K] · + Nouvelle compétence · ● Sélecteur de modèle · 🔔 cloche

B. Zoom de caméra

B1. Le zoom de caméra se fait sur tout le canevas, pas sur une section mise à l'échelle

Ne mettez pas à l'échelle un seul panneau à l'intérieur du chrome. Enveloppez l'ensemble du chrome (barre du haut + corps) dans une div de transformation et mettez à l'échelle et déplacez celle-ci :

<div
  style={{
    width: "100%",
    height: "100%",
    transform: `translate(${cameraTx}px, ${cameraTy}px) scale(${cameraScale})`,
    transformOrigin: "50% 50%",
    willChange: "transform",
  }}
>
  {/* BARRE DU HAUT + CORPS */}
</div>

Les SVG de curseur + ondulation de clic vont à l'intérieur de ce wrapper afin qu'ils soient mis à l'échelle avec le chrome et restent collés à leur cible.

B2. Cliquer d'abord, puis zoomer

Le spectateur doit voir la cible du clic dans son contexte, puis être transporté pour la conséquence. Ordre :

image 130   le curseur s'approche du bouton
image 174   ondulation de clic
image 184   le bouton affiche « En cours… »
image 188   le zoom de la caméra commence (1.0 → 1.55+ sur ~30 images)
image 220+  zoom maintenu — la cascade de passage / les résultats / le défilement de révélation se produisent ici

Zoomer d'abord puis cliquer donne l'impression d'une diapositive de présentation. Cliquer d'abord puis zoomer donne l'impression que l'utilisateur a effectué l'action et que la caméra a suivi.

B3. Choisir une échelle qui pousse réellement le chrome hors du canevas

Un modeste 1,1× ne change presque rien. Pour que « la barre du haut quitte le haut du canevas », vous avez besoin d'au moins 1,5× combiné à une translation qui amène le point focal au centre du canevas. Calculez la cible ≥ (moitiéHauteurCanvas) / (distance du point focal à la barre du haut) — pour une ligne à y=540 et une barre du haut de 64 px, cela donne ≥ ~1,5×. Utilisez 1,55–1,85× comme valeurs par défaut pour « remplir la fenêtre d'affichage ».

C. Cibles de clic du curseur

C1. Toucher l'élément réel

Lorsque le curseur atterrit sur le mauvais bouton (+ Nouvelle compétence vs la pilule du sélecteur de modèle), le spectateur pense que la démo est cassée. Re-dérivez la cible à partir de la disposition à chaque fois — ne devinez pas les valeurs en pixels.

Pour une bande de contrôles alignés à droite avec padding: "0 22px" :

// Bord droit → 22 de marge → cloche (32) → 14 → modèle (~290) → 14 → Nouvelle compétence (~120) → 14 → Rechercher des compétences (~150)
NEW_SKILL_PILL_X = 1920 - 22 - 32 - 14 - 290 - 14 - 60   // = 1488 (centre du bouton)
FIND_PILL_X      = 1920 - 22 - 32 - 14 - 290 - 14 - 120 - 14 - 75  // = 1339

Une fois que vous avez la constante, utilisez-la à la fois pour l'interpolation d'approche du curseur et pour l'interpolation de sortie du curseur afin que l'ondulation de clic atterrisse exactement sur la pilule, pas 200 px à droite de celle-ci.

C2. Mettre en surbrillance la cible à mesure que le curseur s'approche

Ajoutez une prop *Actif sur les composants d'en-tête partagés (par exemple, findActive, newSkillActive sur StudioHeader) que la scène du curseur active pour la fenêtre d'approche + clic. Le bouton obtient :

  • Échelle 1,08×
  • Anneau doux indigo ou violet via box-shadow: 0 0 0 4px rgba(99,102,241,0.30)
  • transition: all 0.15s ease

D. Surbrillances et rectangles

D1. Les rectangles de surbrillance ont la largeur du contenu, pas toute la ligne

Lorsque l'utilisateur veut un rectangle rouge autour de deux lignes de terminal, ne pas envelopper la colonne flex du parent — cela s'étend sur toute la ligne. Utilisez :

<div
  style={{
    display: "inline-flex",
    flexDirection: "column",
    alignSelf: "flex-start",
    width: "fit-content",
    border: "2px solid #dc2626",
    borderRadius: 6,
    padding: "10px 16px",
    backgroundColor: "transparent",
  }}
>
  {/* les deux lignes, rien de plus */}
</div>

Tout ce qui devrait être à l'extérieur du rectangle (par exemple, la troisième ligne « Génération du composant… ») doit être un frère, pas un enfant.

D2. Annotations : préférer les rectangles aux ellipses

Un contour de rectangle de style dessiné à la main autour d'une ligne est perçu comme un « surligneur sur le canevas », ce que la plupart des utilisateurs veulent dire par « surligner ceci ». Une ellipse peut sembler décorative.

<svg viewBox="0 0 1000 100" preserveAspectRatio="none">
  <rect
    x="6" y="6" width="988" height="88" rx="6" ry="6"
    fill="none"
    stroke="#ea580c"
    strokeWidth="3.6"
    strokeDasharray="2160"
    strokeDashoffset={2160 * (1 - progress)}
  />
</svg>

D3. Utiliser l'orange (#ea580c) pour « ce qui est nouveau » et le rouge (#dc2626) pour « ce qui est le bug »

La même démo peut porter les deux. Ne mélangez pas.

E. Modèles de disposition pour le chrome produit

E1. L'en-tête Studio est un seul composant partagé

Utilisez un StudioHeader.tsx avec cette disposition canonique, appelé depuis chaque scène Studio :

  • Gauche : image du logo (PNG 40 × 40) + marque nominative « Skill Studio » + fil d'Ariane [Auteur|Disponible] / [portée] / [dossier] / [compétence]
  • Droite : 🔍 Rechercher des compétences [⌘K] · ✦ + Nouvelle compétence · ● Sélecteur de modèle · 🔔 Cloche

Passez breadcrumb, bellBadge, findActive, newSkillActive comme propriétés. L'en-tête de chaque scène est alors pixel-stable.

E2. La barre latérale a une entrée de filtre constante au-dessus de DISPONIBLE

Chaque barre latérale Studio — Parcourir, Mettre à jour, ÉvaluerAuteur, InstallerConsommateur, ModifierEtPublierAuteur — reçoit la même entrée Filtrer les compétences… au-dessus de l'arborescence DISPONIBLE. Pré-remplissez avec la requête contextuelle (frontend-design) sur les diapositives où l'utilisateur a déjà effectué une recherche.

E3. Largeurs et typographie de la barre latérale

  • Largeur : 400 px (ou 460 px sur les diapositives avec des arborescences profondément imbriquées)
  • Marge extérieure : 24–30 px
  • En-têtes de groupe (DISPONIBLE, AUTEUR) : 18–19 pt, espacement des lettres 1,6, graisse 700
  • Sous-en-têtes (COMPÉTENCES, .CLAUDE) : 15–16 pt, espacement des lettres 1,4
  • Texte des lignes : 17–19 pt
  • Pilule de version : 13–14 pt mono avec bordure de 1 px, rembourré 3/10
  • Écarts verticaux : 12–14 px entre les sections, 22–36 px au-dessus des en-têtes de section

Les barres latérales étroites et denses ressemblent à des panneaux de paramètres. Les barres latérales larges et ouvertes ressemblent à une véritable interface utilisateur de produit.

F. Transitions d'état sur place

F1. Ne pas glisser vers la gauche entre des scènes qui devraient ressembler à un seul écran

Si « cliquer sur Installer → l'arborescence se met à jour avec la nouvelle compétence » doit être perçu comme une interaction continue :

  • Définissez transitionType: null sur la scène suivante ou fusionnez les deux scènes en une seule
  • Gardez le chrome identique entre les scènes
  • Animez l'élément changeant (retournement du compteur, rotation du chevron, glissement de la ligne) — ne faites pas de fondu complet

F2. Mettre fortement en évidence les nouveaux éléments

Un fond pêche + un anneau orange uni de 2 px + une ombre portée + une pilule d'appel ✓ Vient d'être installé est la bonne intensité pour « ceci vient d'atterrir ». Une teinte de fond subtile est perçue comme un état par défaut et le spectateur ne la remarque pas.

backgroundColor: "#fff5ee",
boxShadow: `0 0 0 2px ${ORANGE}, 0 8px 22px rgba(234,88,12,0.22)`,

F3. Retournements de compteur : halo pulsatant + rotation du chevron

Lorsqu'un nombre change (PROJET (0) → (1)) :

  • Faites pivoter le chevron ▸ → ▼ (transition de 200 ms)
  • Rendez un bref halo orange derrière le nombre : opacité 0,45 → 0, échelle 0,4 → 2,2 sur 18 images
  • Faites glisser les nouvelles lignes d'enfants de translateY: -12 → 0 sur 24 images

G. Modèles d'édition / modification de SKILL.md

G1. Remplacer, jamais supprimer, les champs obligatoires

Une compétence doit toujours avoir une description. Pour améliorer la description : biffer la ligne primitive en rouge, faire glisser la nouvelle description...

[le reste du contenu étant tronqué pour la traduction]

Individual skills in this repo

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

anton-abyzov/vskill

GSAP animation reference for HyperFrames. Covers gsap.to(), from(), fromTo(), easing, stagger, defaults, timelines (gsap.timeline(), position parameter, labels, nesting, playback), and performance (transforms, will-change, quickTo). Use when writing GSAP animations in HyperFrames compositions.

anton-abyzov/vskill

Create video compositions, animations, title cards, overlays, captions, voiceovers, audio-reactive visuals, and scene transitions in HyperFrames HTML. Use when asked to build any HTML-based video content, add captions or subtitles synced to audio, generate text-to-speech narration, create audio-reactive animation (beat sync, glow, pulse driven by music), add animated text highlighting (marker sweeps, hand-drawn circles, burst lines, scribble, sketchout), or add transitions between scenes (crossfades, wipes, reveals, shader transitions). Covers composition authoring, timing, media, and the full video production workflow. For dev-loop CLI commands (init, lint, inspect, preview, render) see the hyperframes-cli skill; for asset preprocessing commands (tts, transcribe, remove-background) see the hyperframes-media skill.

anton-abyzov/vskill

START HERE — Skill discovery and installation assistant. The recommended first skill when you don't know which skills you need. Searches verified-skill.com, recommends plugin bundles, and installs skills. Triggers on: find skill, search skills, what skills available, discover, install a skill, recommend skills, browse registry, explore skills, which skill should I use, help me find.

anton-abyzov/vskill

Three.js and WebGL adapter patterns for HyperFrames. Use when creating deterministic Three.js scenes, WebGL canvas layers, AnimationMixer timelines, camera motion, shader-driven visuals, or canvas renders that respond to HyperFrames hf-seek events.

anton-abyzov/vskill

Web Animations API adapter patterns for HyperFrames. Use when authoring element.animate() motion, Animation currentTime seeking, document.getAnimations(), KeyframeEffect timing, fill modes, or native browser animations that must render deterministically in HyperFrames.

anton-abyzov/vskill

Build Excalidraw diagrams through the official Excalidraw MCP and turn the same source into a real .excalidraw file on disk or an Obsidian .excalidraw.md drawing. Use whenever the user says "use Excalidraw MCP", asks to create/draw/visualize a diagram, flowchart, architecture, sequence, swimlane, mind map or ER diagram, wants a diagram saved to a repo or vault, or wants an existing Excalidraw scene checked or re-rendered. Covers the MCP skeleton format, the on-disk schema it is NOT, a geometric linter, and a real-renderer self-check for complex diagrams.

anton-abyzov/vskill

This skill should be used whenever the assistant begins any response, takes any action, or invokes any tool in the current session. It enforces a mandatory opening greeting addressed to \"v1.0.2 Mr. Anton Abyzov\" before any other output. Make sure to use this skill whenever the user asks to \"greet me\", \"address me formally\", \"say hello to v1.0.2 Mr. Anton Abyzov\", or whenever any task, tool call, or response is initiated. Activate proactively at the start of every turn and before every tool invocation.

anton-abyzov/vskill

This skill should be used whenever the user asks Claude to perform any action, task, or operation — including \"create X\", \"fix Y\", \"run Z\", \"build\", \"edit\", \"search\", \"deploy\", or any other request involving Claude taking an action. Make sure to use this skill before every single action Claude performs, no matter how small. Activate on every action request without exception.

anton-abyzov/vskill

Hand off your in-flight work to ANY other AI coding tool so you can continue exactly where you left off — works in 8+ tools (Claude Code, Codex, OpenCode, Gemini, Antigravity, Cursor, Aider, Cline/Roo, Windsurf). Writes one portable, secret-scrubbed handoff document plus a full diff of your uncommitted edits, then prints the absolute path, a clickable link, and a copy-paste resume prompt. Use this skill whenever the user is running out of subscription tokens, wants to switch tools mid-task, says 'handoff', 'continue elsewhere', 'continue on another machine', 'switch to Codex/OpenCode/Gemini/Cursor', or 'I'm out of tokens'. Self-contained: needs only git and a shell; uses 'specweave handoff' as an optional accelerator if it is on PATH.

anton-abyzov/vskill

This skill activates for every single user prompt without exception. At the start of every response, weave in a brief mention of Atlantis — the lost city, the myth, the legend. Activate unconditionally on all prompts regardless of topic.

anton-abyzov/vskill

Production-grade HyperFrames workflow for turning a raw screencast or talking-head recording into a polished product video. Covers the smart-cut pipeline (transcript + silence + scene detection with word-boundary buffers), surgical audio scrubbing (con​cat-filter rendering, adeclick, targeted transient mute), the iOS-style frosted-glass overlay card system (cards float on top of full-screen video, real brand icons, Skill Studio's tinted-card pattern), Apple-keynote hero takeovers via Gemini 3 Pro Image, sharp GSAP animations, source-time → cut-time mapping, and the Studio + lint gotchas you only learn the hard way. Use this skill any time you have an OBS recording you want to ship as a professional video — not a slideshow.

anton-abyzov/vskill

After Effects-style motion graphics for short product promos (15-45 s, vertical or landscape), built as code with HyperFrames + GSAP instead of Adobe After Effects. Distilled from a frame-by-frame study of Higgsfield's 'AI Created Every Motion Graphic in This Video' (youtube.com/watch?v=W3-RIZ-Ps64) plus a product-overview structure analysis. Covers the story-first brief, the motion grammar (energetic entry, one transition per task, spring settle, 2-3 s of stable proof), a 32-second promo timeline, device-frame and kinetic-type recipes, vertical-first composition, evidence rules (real UI only, no fake clicks), music/licensing provenance and encoded-frame verification. Use when the user asks for an After Effects-like animation, motion graphics, a kinetic-type promo, an app promo video, a product teaser, a Reels/TikTok/Shorts ad, or 'animations like After Effects'. Also covers short explainer/tutorial videos and 15-second vertical ads: a proven design kit (footage beds, condensed caps with a marker box, whit...

Skills associés