Communitygithub.com

mvpdsigner/faceless-motion-reel

Créez un reel de motion design sans visage (typographie cinétique, canvas blanc, une couleur d'accent, icônes animées, badges, compteurs, transitions whip) sous forme de code avec Remotion et exportez-le en MP4. Idéal pour tout reel, court-métrage, TikTok, Instagram ou LinkedIn vidéo, vidéo sans visage, motion graphics, texte cinétique ou demande d'explication animée, ou lorsqu'un script, un conseil ou une citation doit devenir une courte vidéo verticale.

Qu'est-ce que faceless-motion-reel ?

Cette compétence vous permet de transformer un script court en une vidéo verticale de 15 à 30 secondes, animée par du code React avec Remotion. Le style est celui du 'minimal visuel, impact maximal' : un canvas blanc, du texte noir, une seule couleur d'accent et un petit objet animé par idée. Pas besoin de séquences réelles, de visage ou de voix-off : chaque frame est exacte et facile à réviser. La compétence inclut un kit de composants (typographie cinétique, transitions avec flou de mouvement, objets SVG animés comme des badges ou des compteurs) et définit des règles de design strictes pour garantir un résultat proche des reels de motion design les plus efficaces. Elle est optimisée pour les plateformes TikTok, Instagram, LinkedIn et tout format court. Le processus commence par la création d'un beat sheet (tableau de planification), suivi de la configuration du projet Remotion avec les polices et dépendances nécessaires, puis l'implémentation des scènes à l'aide des composants fournis. Le tout est rendu en MP4 via la CLI Remotion.

Compatible avec~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/mvpdsigner/claude-skills/tree/HEAD/skills/faceless-motion-reel

Demander à votre IA préférée

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

Documentation

Reel de motion design sans visage

Transformez un script court en une vidéo verticale de 15 à 30 secondes dans le style "minimal visuel, impact maximal" : un canvas blanc, un texte noir, une couleur d'accent et un petit objet animé par idée. Pas de séquences réelles, pas de visage, pas de voix-off nécessaire. La vidéo est écrite en code React avec Remotion, donc chaque frame est exacte et facile à réviser.

Le look (ce qui rend ce style efficace)

Ces règles proviennent de l'étude frame par frame d'un reel de référence. Elles sont plus importantes que n'importe quel effet seul, donc conservez-les même lorsque vous inventez de nouvelles scènes.

  • Une idée par scène. Chaque scène montre une courte ligne (2 à 6 mots) plus un objet qui porte le sens. Un spectateur doit la comprendre sans le son.
  • Trois couleurs seulement. Fond blanc, encre presque noir, un accent. Le gris est utilisé uniquement pour le texte qui n'est "pas encore placé". Un second accent donne l'impression d'un modèle.
  • Deux voix typographiques. Un sans-serif serré (Inter, graisse 500, crénage autour de -0.04em) pour la plupart des mots, et un serif italique dans la couleur d'accent pour la phrase importante. Utilisez le serif au maximum une fois par scène.
  • Le texte arrive, il n'apparaît pas simplement. Les nouveaux mots arrivent en gris et légèrement floutés, puis se stabilisent en noir. L'œil suit le mot le plus récent.
  • Rien ne reste jamais immobile. Chaque scène conserve un zoom lent, et les anneaux d'arrière-plan dérivent constamment. Une image figée donne l'impression d'un diaporama.
  • Les transitions sont des mouvements de caméra. Les scènes poussent vers le haut ou sur les côtés avec un flou de mouvement, ou plongent vers le blanc. Pas de fondus, de rotations ou d'explosions de zoom.
  • Petite police, beaucoup d'espace. Les titres mesurent environ 84 à 100 px sur un cadre de 1080 de large. L'espace vide est le style.
  • L'objet est l'idée. Un anneau de compte à rebours pour le temps, des badges étiquetés pour une liste de problèmes, des pilules empilées pour une liste de traits, une boucle tracée autour de la ligne de récompense. Lorsque le script contient une image forte (une horloge, un graphique, un curseur), dessinez cet objet en SVG au lieu de réutiliser une pièce du kit.

Flux de travail

1. Obtenir le brief

Vous avez besoin : du message ou du script, de la couleur d'accent, du logo ou du nom pour la dernière scène, et de savoir s'il y a un fichier musical. Si l'utilisateur a donné un sujet mais pas de script, rédigez le script vous-même et affichez-le dans le beat sheet. Ne stagnez pas sur les questions. Valeurs par défaut raisonnables : environ 20 secondes, 1080x1920, 30 fps, accent #1FB81A, pas d'audio.

2. Écrire d'abord le beat sheet

Avant tout code, écrivez le plan sous forme d'un petit tableau et montrez-le à l'utilisateur dans le même message où vous commencez la construction. C'est là que la vidéo se gagne ou se perd, car des beats faibles ne peuvent pas être corrigés avec de l'animation.

#Ligne à l'écranPhrase d'accentObjetEntrée / SortieImages

Directives :

  • Le beat 1 est l'accroche : 2 à 5 mots, énoncés comme une tension ou une affirmation audacieuse.
  • 6 à 9 beats pour 15 à 30 secondes. La plupart des beats durent 60 à 110 images (2 à 3,5 secondes). Accordez plus de temps aux beats de liste (badges, pilules) : environ 20 images par élément plus 30 pour lire.
  • Construisez vers une ligne de récompense vers la fin et entourez-la d'une boucle (Circled).
  • Le dernier beat est le logo ou le pseudo, 45 à 60 images.
  • Variez l'objet d'un beat à l'autre. Deux beats de texte seul à la suite, c'est acceptable. Deux beats de badges à la suite, non.
  • Variez les transitions : principalement des poussées up, une poussée latérale, un fade avant la récompense.

3. Configurer le projet

Exécutez ceci une fois dans un dossier vide. Les polices sont copiées dans le projet pour que le rendu ne dépende jamais du réseau.

npm init -y
npm i remotion @remotion/cli @remotion/fonts react react-dom lucide-react @fontsource/inter @fontsource/instrument-serif
npm i -D typescript @types/react
mkdir -p src public/fonts out
cp node_modules/@fontsource/inter/files/inter-latin-400-normal.woff2 node_modules/@fontsource/inter/files/inter-latin-500-normal.woff2 node_modules/@fontsource/instrument-serif/files/instrument-serif-latin-400-italic.woff2 public/fonts/

tsconfig.json :

{"compilerOptions":{"target":"ES2022","module":"ESNext","moduleResolution":"Bundler","jsx":"react-jsx","strict":true,"skipLibCheck":true,"noEmit":true,"esModuleInterop":true},"include":["src"]}

src/index.ts :

import {registerRoot} from 'remotion';
import {Root} from './Root';
registerRoot(Root);

src/Root.tsx :

import React from 'react';
import {Composition} from 'remotion';
import {totalFrames} from './kit';
import {Video, beats} from './Video';

export const Root: React.FC = () => (
  <Composition id="Reel" component={Video} width={1080} height={1920} fps={30} durationInFrames={totalFrames(beats)} />
);

4. Ajouter le kit de motion

Créez src/kit.tsx avec exactement ce code. Il a été rendu et vérifié, donc copiez-le tel quel et modifiez uniquement la ligne de thème. Construisez de nouvelles pièces à côté dans le même esprit.

import React from 'react';
import {
  AbsoluteFill, Easing, Sequence, interpolate, interpolateColors, random, spring,
  staticFile, useCurrentFrame, useVideoConfig,
} from 'remotion';
import {loadFont} from '@remotion/fonts';

// Les polices sont des fichiers locaux (copiés depuis @fontsource lors de la configuration), donc les rendus n'ont jamais besoin du réseau.
const font = (family: string, file: string, weight: string, style = 'normal') =>
  loadFont({family, url: staticFile(`fonts/${file}.woff2`), weight, style});
font('Inter', 'inter-latin-400-normal', '400');
font('Inter', 'inter-latin-500-normal', '500');
font('Instrument Serif', 'instrument-serif-latin-400-italic', '400', 'italic');
const sans = 'Inter, sans-serif';
const serif = '"Instrument Serif", serif';

// ---- Thème : un arrière-plan, une encre, un gris, UN accent. Échangez l'accent par marque (hexa à 6 chiffres). ----
export const T = {bg: '#FFFFFF', ink: '#0B0B0C', muted: '#B4B6BB', accent: '#1FB81A', sans, serif};

const CL = {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'} as const;
const c01 = (n: number) => Math.min(1, Math.max(0, n));
const whip = Easing.bezier(0.76, 0, 0.18, 1); // lent, rapide, lent : le "coup de fouet" de la caméra

/** Ressort de 0 à 1 qui commence à `delay`. Lisse par défaut ; passez une config pour un rebond. */
export const useEnter = (delay = 0, config: object = {damping: 200}) => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
  return spring({frame: frame - delay, fps, config});
};

// ---- Timeline : les scènes se chevauchent de OVERLAP images pour que la sortie et l'entrée aient lieu ensemble ----
export const OVERLAP = 12;
export type Beat = {dur: number; el: React.ReactNode};
export const beatStarts = (beats: Beat[], overlap = OVERLAP) =>
  beats.map((_, i) => beats.slice(0, i).reduce((s, x) => s + x.dur - overlap, 0));
export const totalFrames = (beats: Beat[], overlap = OVERLAP) =>
  beats.reduce((s, b) => s + b.dur, 0) - overlap * (beats.length - 1);

export const Timeline: React.FC<{beats: Beat[]; overlap?: number}> = ({beats, overlap = OVERLAP}) => {
  const starts = beatStarts(beats, overlap);
  return (
    <AbsoluteFill style={{background: T.bg}}>
      {beats.map((b, i) => (
        <Sequence key={i} from={starts[i]} durationInFrames={b.dur}>
          {b.el}
        </Sequence>
      ))}
    </AbsoluteFill>
  );
};

// ---- Scène : encapsule une idée. Gère la poussée/fondu entrant et sortant, le flou de mouvement, le zoom lent. ----
type Dir = 'up' | 'down' | 'left' | 'right' | 'fade';
const VEC: Record<Dir, [number, number]> = {up: [0, -1], down: [0, 1], left: [-1, 0], right: [1, 0], fade: [0, 0]};

/** `enter="up"` = le contenu se déplace vers le haut pour se mettre en place. Pour une poussée, donnez à la scène A exit="up" et à la scène B enter="up". */
export const Scene: React.FC<{
  enter?: Dir; exit?: Dir; t?: number; zoom?: number; children: React.ReactNode;
}> = ({enter = 'fade', exit = 'fade', t = OVERLAP, zoom = 0.05, children}) => {
  const frame = useCurrentFrame();
  const {width, height, durationInFrames: dur} = useVideoConfig(); // = longueur de cette Sequence
  const id = React.useId().replace(/:/g, '');
  const pos = (f: number) => {
    const a = 1 - whip(c01(f / t));
    const b = whip(c01((f - (dur - t)) / t));
    const [ex, ey] = VEC[enter];
    const [lx, ly] = VEC[exit];
    return [(-ex * a + lx * b) * width, (-ey * a + ly * b) * height];
  };
  const [x, y] = pos(frame);
  const [px, py] = pos(frame - 1);
  const bx = Math.abs(x - px) * 0.35; // le flou suit la vitesse, comme un vrai obturateur
  const by = Math.abs(y - py) * 0.35;
  const fIn = enter === 'fade' ? interpolate(frame, [t * 0.5, t], [0, 1], CL) : 1; // l'ancienne scène s'efface d'abord
  const fOut = exit === 'fade' ? interpolate(frame, [dur - t, dur - t * 0.5], [1, 0], CL) : 1;
  const soft = (1 - fIn * fOut) * 14;
  const filter = bx + by > 0.4 ? `url(#${id})` : soft > 0.2 ? `blur(${soft}px)` : undefined;
  return (
    <>
      <svg width="0" height="0" style={{position: 'absolute'}}>
        <filter id={id} x="-40%" y="-40%" width="180%" height="180%">
          <feGaussianBlur stdDeviation={`${bx} ${by}`} />
        </filter>
      </svg>
      <AbsoluteFill
        style={{
          transform: `translate(${x}px, ${y}px) scale(${1 + (zoom * frame) / dur})`,
          opacity: fIn * fOut, filter,
          fontFamily: T.sans, color: T.ink, alignItems: 'center', justifyContent: 'center',
        }}
      >
        {children}
      </AbsoluteFill>
    </>
  );
};

// ---- Texte ----
/** Révèle le texte mot par mot (ou lettre par lettre). Le nouveau texte arrive gris, puis se stabilise en encre.
 *  Encadrez la phrase clé par *étoiles* pour en faire l'accent. Utilisez \n pour un saut de ligne. */
export const Words: React.FC<{
  text: string; delay?: number; stagger?: number; by?: 'word' | 'char';
  size?: number; accent?: 'serif' | 'sans'; style?: React.CSSProperties;
}> = ({text, delay = 0, stagger, by = 'word', size = 92, accent = 'serif', style}) => {
  const frame = useCurrentFrame();
  const step = stagger ?? (by === 'word' ? 5 : 1.2);
  let i = 0;
  const unit = (s: string, hot: boolean, key: string) => {
    const local = frame - delay - i++ * step;
    const p = interpolate(local, [0, 6], [0, 1], CL);
    const final = hot ? T.accent : T.ink;
    return (
      <span
        key={key}
        style={{
          display: 'inline-block', whiteSpace: 'pre', opacity: p,
          color: interpolateColors(c01((local - 3) / 9), [0, 1], [T.muted, final]),
          transform: by === 'word' ? `translateY(${(1 - p) * 0.2}em)` : undefined,
          filter: p < 1 ? `blur(${(1 - p) * 7}px)` : undefined,
        }}
      >
        {s}
      </span>
    );
  };
  return (
    <div style={{fontSize: size, fontWeight: 500, letterSpacing: '-0.04em', lineHeight: 1.08, textAlign: 'center', ...style}}>
      {text.split('\n').map((line, li) => (
        <div key={li}>
          {line.split('*').map((seg, si) => {
            const hot = si % 2 === 1;
            const hotStyle: React.CSSProperties = hot && accent === 'serif'
              ? {fontFamily: T.serif, fontStyle: 'italic', fontWeight: 400, fontSize: '1.14em', letterSpacing: '-0.02em'}
              : {};
            return (
              <span key={si} style={hotStyle}>
                {seg.split(/(\s+)/).filter(Boolean).map((w, wi) =>
                  /^\s+$/.test(w) ? <span key={wi} style={{whiteSpace: 'pre'}}>{w}</span>
                  : by === 'word' ? unit(w, hot, `${wi}`)
                  : <span key={wi} style={{display: 'inline-block', whiteSpace: 'nowrap'}}>{[...w].map((ch, ci) => unit(ch, hot, `${wi}-${ci}`))}</span>
                )}
              </span>
            );
          })}
        </div>
      ))}
    </div>
  );
};

/** Le coup d'ouverture : commence énorme et flouté, se stabilise en environ 8 images. */
export const PunchIn: React.FC<{delay?: number; from?: number; children: React.ReactNode}> = ({delay = 0, from = 3, children}) => {
  const s = useEnter(delay, {damping: 200, stiffness: 220});
  return (
    <div style={{transform: `scale(${interpolate(s, [0, 1], [from, 1])})`, opacity: c01(s * 5), filter: s < 0.98 ? `blur(${(1 - s) * 34}px)` : undefined}}>
      {children}
    </div>
  );
};

/** Petite ligne de soutien sous le titre. */
export const Caption: React.FC<{children: React.ReactNode; delay?: number; width?: number}> = ({children, delay = 0, width = 620}) => {
  const s = useEnter(delay);
  return (
    <div style={{fontSize: 31, lineHeight: 1.28, letterSpacing: '-0.02em', color: '#2B2C2F', textAlign: 'center', width, marginTop: 44, opacity: s, filter: s < 0.98 ? `blur(${(1 - s) * 6}px)` : undefined}}>
      {children}
    </div>
  );
};

// ---- Atmosphère ----
/** Lumière d'accent douce derrière le mot clé. Placez-la avec x / y comme valeurs CSS. */
export const Glow: React.FC<{x?: string; y?: string; size?: number; delay?: number}> = ({x = '50%', y = '50%', size = 900, delay = 0}) => {
  const s = useEnter(delay);
  const frame = useCurrentFrame();
  return (
    <div style={{position: 'absolute', left: x, top: y, width: size, height: size, borderRadius: '50%', opacity: s,
      transform: `translate(-50%, -50%) scale(${0.6 + 0.4 * s + frame * 0.0012})`,
      background: `radial-gradient(closest-side, ${T.accent}3D, ${T.accent}00)`}} />
  );
};

/** Cercles dérivants près des bords pour que l'image ne soit jamais immobile. thin = contours, soft = anneaux floutés épais. */
export const Rings: React.FC<{seed?: string; variant?: 'thin' | 'soft'; count?: number}> = ({seed = 'a', variant = 'thin', count = 3}) => {
  const frame = useCurrentFrame();
  const {width, height} = useVideoConfig();
  return (
    <AbsoluteFill>
      {Array.from({length: count}).map((_, i) => {
        const r = (k: string) => random(`${seed}-${i}-${k}`);
        const d = 300 + r('d') * 420;
        const cx = (i % 2 ? 0.86 + r('x') * 0.2 : -0.06 + r('x') * 0.2) * width;
        const cy = (r('y') * 1.1 - 0.05) * height;
        const ang = r('a') * Math.PI * 2;
        const s = spring({frame: frame - i * 4, fps: 30, config: {damping: 200}});
        return (
          <div key={i} style={{position: 'absolute', width: d, height: d, borderRadius: '50%', opacity: s,
            left: cx - d / 2 + Math.cos(ang) * frame * 0.9, top: cy - d / 2 + Math.sin(ang) * frame * 0.9,
            border: variant === 'thin' ? `2px solid ${T.accent}99` : `${d * 0.2}px solid ${T.accent}1F`,
            filter: variant === 'thin' ? undefined : `blur(${d * 0.08}px)`,
          }} />
        );
      })}
    </AbsoluteFill>
  );
};

5. Écrire la vidéo

Créez src/Video.tsx. Exportez un tableau beats et un composant Video qui utilise les scènes du kit pour construire votre histoire. Le premier exemple ci-dessous est une vidéo de démonstration (elle fonctionne immédiatement) ; remplacez les beats par votre script.

import React from 'react';
import {
  Rings, Scene, Timeline, Words, Glow, T, PunchIn, Caption, Beat,
} from './kit';

// ---- Définir la musique
// Mettez votre fichier audio dans public/music.mp3 ou mettez à jour staticFile.
const MUSIC = null; // staticFile('music/audio.mp3') s'il est présent

// ---- Décrire chaque beat ----
export const beats: Beat[] = [
  {dur: 90, el: (
    <Scene enter="fade" exit="up">
      <Glow y="54%" size={960} />
      <div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 0, marginTop: -90}}>
        <PunchIn delay={4}>
          <Words text="Le constat" size={118} by="word" stagger={2} accent="sans" />
        </PunchIn>
        <div style={{height: 44}} />
        <Words text="*Presque tout ce qu'on vous a dit*" delay={20} size={84} stagger={4} />
        <div style={{height: 38}} />
        <Caption delay={44} width={520}>sur le motion design est faux</Caption>
      </div>
    </Scene>
  )},
  {dur: 80, el: (
    <Scene enter="up" exit="up">
      <Rings count={3} />
      <Words text="Le vrai secret" delay={6} size={106} by="word" stagger={4} />
      <div style={{height: 108}} />
      <Words text="*Moins d'effets*" delay={32} size={96} by="char" stagger={1.6} />
    </Scene>
  )},
  {dur: 120, el: (
    <Scene enter="up" exit="fade">
      <Rings variant="soft" count={2} />
      <div style={{display: 'flex', flexDirection: 'column', gap: 24, alignItems: 'center'}}>
        <Glow y="46%" size={900} />
        <Words text="3 couleurs" delay={4} size={108} />
        <Words text="1 objet par scène" delay={26} size={92} />
        <Words text="Aucune image réelle" delay={48} size={100} />
        <Words text="Un mouvement constant" delay={70} size={96} />
      </div>
    </Scene>
  )},
  {dur: 98, el: (
    <Scene enter="fade" exit="up">
      <Glow y="52%" size={850} />
      <div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12}}>
        <Words text="Codez votre reel" delay={2} size={108} by="word" stagger={4} />
        <div style={{height: 24}} />
        <Words text="Avec *Remotion*" delay={28} size={104} by="word" stagger={5} accent="serif" />
        <div style={{height: 24}} />
        <Words text="(Aucun logiciel vidéo requis)" delay={54} size={44} by="word" stagger={3} accent="sans" />
      </div>
    </Scene>
  )},
  {dur: 74, el: (
    <Scene enter="up" exit="right">
      <Rings seed="c" count={3} variant="thin" />
      <Words text="*Un accent, pas plus*" delay={8} size={102} />
    </Scene>
  )},
  {dur: 96, el: (
    <Scene enter="left" exit="fade">
      <Glow y="48%" size={800} />
      <Words text="Aujourd'hui" delay={4} size={108} />
      <div style={{height: 104}} />
      <Words text="*Un motion designer, c'est aussi un codeur*" delay={24} size={58} accent="serif" />
    </Scene>
  )},
  {dur: 76, el: (
    <Scene enter="fade" exit="up">
      <Rings seed="d" count={2} variant="soft" />
      <Words text="Prêt à essayer ?" delay={4} size={108} />
      <div style={{height: 80}} />
      <Words text="Le code vous attend" delay={28} size={98} />
    </Scene>
  )},
  {dur: 56, el: (
    <Scene enter="up" exit="fade">
      <Glow size={720} />
      <Words text="@mvpdsigner" delay={6} size={92} />
    </Scene>
  )},
];

// ---- Composant vidéo ----
export const Video: React.FC = () => (
  <Timeline beats={beats} />
);

6. Rendre

npx remotion render src/index.ts Reel out/reel.mp4

Flag utile : --frames si vous voulez un essai plus court, p. ex. --frames=0-60.

Si le rendu échoue pour cause de mémoire insuffisante

Les vidéos longues peuvent dépasser la mémoire par défaut de Node (512 Mo). Relancez avec :

NODE_OPTIONS="--max-old-space-size=2048" npx remotion render src/index.ts Reel out/reel.mp4

Quoi faire quand l'utilisateur a un script

  1. Analyser le script pour les moments clés qui deviendront des lignes à l'écran. Chaque beat = une idée en 2 à 6 mots.
  2. Écrire le beat sheet (le tableau) et le montrer avant ou pendant que vous codez.
  3. Choisir les objets du kit en fonction du sens de chaque beat. Pas de kit ? Dessinez avec SVG.
  4. Écrire le fichier Video.tsx avec des scènes du kit composant la séquence.
  5. Rendre en MP4.

(Le cas échéant, dites à l'utilisateur que vous allez construire le beat sheet et l'implémentation en une seule réponse.)

Quoi demander avant de commencer

  • La couleur d'accent. Valeur par défaut : #1FB81A.
  • Le script ou le sujet – si c'est un sujet, écrivez le script vous-même.
  • Le nom/handle de la scène finale. Valeur par défaut : @mvpdsigner.
  • Un fichier audio facultatif – s'il y en a un, dites à l'utilisateur de le placer dans public/music/ du projet. Valeur par défaut : pas de musique.

Règles de style (les non-négociables)

  • Pas de dégradés, pas d'images, pas de séquences réelles. Rien que du SVG, du texte et des bordures.
  • Pas de fondus, de rotations ou d'explosions de zoom. Les transitions sont des poussées, un seul fondu avant la récompense, ou un plongeon vers le blanc.
  • Pas de second accent. T.accent est la seule couleur non neutre.
  • Pas de mouvement élastique. Les ressorts sont toujours sur-amortis sauf indication explicite.
  • Si le script mentionne une chose que le kit ne couvre pas, construisez un petit SVG qui la représente – ne retournez pas tout au texte.
  • N'ajoutez jamais de délais à PunchIn – il doit arriver immédiatement ou avec un retard d'environ 4 images maximum.

Quand utiliser cette compétence

Utilisez-la pour n'importe quel type de reel, court-métrage, TikTok, Instagram ou LinkedIn vidéo, vidéo sans visage, motion graphics, texte cinétique ou demande d'explication animée. Utilisez-la lorsqu'un script, un conseil ou une citation doit devenir une courte vidéo verticale.

Individual skills in this repo

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

Skills associés