Vibe Coding: pourquoi il n’y a pas de flow sans formalisation

Vibe coding : la méthode pour passer du chaos créatif à des apps fonctionnelles. La formalisation n'étouffe pas le flow, elle le libère.
L’équipe Memoways a développé ces derniers mois plusieurs applications au travers d’outils dits de vibe coding. En quelques heures, nous avons pu “générer” des prototypes fonctionnels, des applications web utiles ou des sites web.
Plus besoin de passer par des outils no-code (oups, on creuse notre propre tombe…), plus besoin de payer des fortunes et d’attendre des mois avant d’avoir un résultat. Il “suffit” de dire en language naturel ce dont nous avons besoin, et de manière conversationnelle on construit son application, itération par itération. Sans savoir coder !
À travers les exemples illustrés dans ce billet, nous désirons partager avec vous ce que nous avons appris pour vous donner envie d’essayer cette nouvelle pratique créative et très productive qui donne littéralement des ailes aux personnes qui, comme nous, ne savent pas coder.
💡 C'est quoi le Vibe Coding ?
Le Vibe Coding, c'est créer des applications web en conversant avec une intelligence artificielle, sans avoir besoin de connaître la programmation. Vous décrivez ce que vous voulez en langage naturel, et l'IA génère le code pour vous en temps réel. Ce qui prenait des semaines à un développeur peut maintenant se créer en quelques heures - à condition de bien préparer son projet et de comprendre les principes de base de l'architecture numérique. C'est un peu comme avoir un développeur expert à qui vous donnez des instructions claires, sauf que c'est une machine qui code à votre place.
🎯 L’essentiel en 4 points
Homepage Audiogami : expérimentation multi-outils pour une landing page
L’équipe Memoways travaille actuellement sur Audiogami, une plateforme voice-to-data qui transforme la parole en données structurées. Pour communiquer sur le projet et préparer la validation de marché, une landing page est indispensable.
Voici plus de détails sur ce projet:
memoways.comMais plutôt que d’utiliser l’un de nos outils no-code de création de site web, l’équipe décide de tester une approche différente: développer la même landing page simultanément sur 4 plateformes de vibe coding différentes pour comparer les résultats.
Objectif : Valider quelle plateforme de vibe coding offre le meilleur rapport qualité/effort pour une landing page professionnelle, sans vendor lock-in (pouvoir posséder le code produit).
Le résultat obtenu en moins de 10 heures:
audiogami.comComment ça s’est passé ?
Les 4 plateformes testées en parallèle :
La méthode de travail :
Création d’un wireframe, d’un cahier de charges détaillé puis des allers-retours entre Claude (l’IA d’Anthropic) et l’IA de Notion pour structurer le narratif, les messages clés, ainsi que l’architecture du contenu.
Cette phase est la plus longue: 6 heures pour avoir un PRD (Product Requirement Document - la formalisation du cahier de charges) solide. Ce document au format markdown définit précisément ce que doit accomplir la landing page, pour qui, et quels sont les critères de succès.
Le prompt initial copié dans chaque outil : une phrase simple avec le PRD attaché.
Résultat : Des versions fonctionnelles obtenues très rapidement sur les 4 plateformes. En 3 à 5 minutes, 80% du travail a été effectué, avec un résultat correct au premier regard.
Mais comme souvent, les 20% restants demandent le plus d’efforts, même si ne sont pas des efforts très compliqués: il suffit de converser avec l’outil pour petit à petit arriver au résultat voulu.
Résultats et choix
Les 4 outils ont produit des landing pages très similaires en termes de qualité. Pour un projet techniquement simple comme une landing page (essentiellement de la structuration de contenu et du design), tous les outils se valent en première approche.
Mais Replit se démarque pour la finalisation :
L’enseignement clé : Pour une landing page, le choix de l’outil importe moins que la qualité du contexte fourni. C’est le PRD et la structuration en amont qui déterminent le temps total passé sur le projet ainsi que la robustesse du code généré.
Voici une capture d’écran de l’interface de l’application Replit: à gauche le chat pour interagir avec l’agent, au centre le résultat (ou les autres fonctionnalités comme la synchro avec GitHub etc); à droite l’arborescence de l’application générée. La publication sur un nom de domaine custom est très simple - ensuite il faut juste cliquer sur “Republish” pour mettre à jour le site à partir de Replit.

Dilemme Plastique / Edugami : la préparation est essentielle pour une app complexe
Dilemme Plastique est un un projet d’expérimentation pédagogique sous forme conversationnelle développée en partenariat avec Bord Cadre Films. L’objectif : sensibiliser sur la pollution du plastique à travers une expérience interactive avec un avatar conversationnel.
Voici plus d’informations sur le projet:
memoways.comComment ça s’est passé ?
L’équipe décide de développer un prototype avec Replit. Contrairement à la landing page Audiogami (test multi-outils) qui est simple, ici le choix est délibéré parce qu’il y a une architecture technique complexe avec l’intégration de plusieurs services additionnels:
Voici le schéma de l’architecture de ce projet:

Phase 1 : Préparation (1 à 2 jours)
L’équipe investit dans la préparation en amont : élaboration d’un PRD détaillé sur base d’un schéma architecture technique, structuration des fonctionnalités principales (conversation, avatar, monde virtuel), wireframe. Génération d’un PRD très détaillé.
Phase 2 : Développement (une demi-journée)
Avec ce contexte solide, le développement est fulgurant : génération d’une application complète en moins d’une heure, améliorations en temps réel, itérations fluides grâce au contexte clair.
Phase 3: Améliorations des fonctionnalités et optimisations techniques (1 journée)
Cette phase est encore en cours, avec l’enjeu principal de réduire la latence de la conversation. C’est ici que se montre notre limite de non développeur: comment savoir où se situe précisément le problème, pour guider l’IA sur les bonne pistes…
Notre solution: faire auditer le code et le problème par diverses IA et revenir dans Replit avec un nouveau plan d’optimisation. Ce processus est encore en cours, avec l’ajout des nouveaux modèles (Gemini 3 et Opus 4.5) dans notre boucle d’optimisation.
Sur ce chemin, nous découvrons un vertige supplémentaire: comme on peut “tout” demander, il n’y a pas de contraintes à la créativité - il faut donc bien cadrer ce que l’on décide d’implémenter pour éviter de partir dans tous les sens…
Voici une capture d’écran de l’interface de cette application web:

Ce qui a fonctionné
L’investissement en préparation paie drastiquement : 1-2 jours de structuration + une demi-journée de vibe coding = prototype fonctionnel.
Sans cette préparation, le vibe coding aurait produit… quelque chose. Mais pas nécessairement une expérience pédagogique cohérente avec des objectifs d’apprentissage clairs, couplés au stack technique existant.
État actuel : Le prototype est utilisable avec quelques limitations à corriger. Mais il est suffisamment avancé pour tester les hypothèses principales du projet avec des élèves.
Replit se confirme comme l’outil intermédiaire idéal : assez simple pour être productif rapidement, assez avancé pour gérer des projets qui produisent du code solide.
Application de gestion de factures: développer son propre outil d’admin
Pourquoi cette application ?
Un gestionnaire a besoin de tracer et maîtriser les frais payés par carte de crédit. Le problème : le logiciel de comptabilité (Bexio dans notre cas) existant ne permet pas de trier automatiquement par type de dépense, catégoriser par fournisseur, ni analyser par catégorie de factures.
Objectif : Développer une application qui gère automatiquement les factures PDF pour donner une visibilité complète sur les dépenses carte de crédit.
Pourquoi Claude Code ?
Ce projet nécessite un niveau de contrôle et de précision supérieur aux outils simples, avec un développement en local sur sa propre machine. Claude Code (l’outil proposé par Anthropic) offre un environnement de développement professionnel, le contrôle total sur le code généré, la capacité à gérer des fichiers PDF en local avec extraction de données et catégorisation automatisée.
Comment ça s’est passé ?
Approche très itérative dès le début : Contrairement aux projets précédents, ici pas de longue phase de préparation. L’approche est différente - nous avons vibe codé directement avec Claude Code, de manière très itérative.
Résultat : Ça s’est très bien passé. Le développement itératif a permis d’ajuster en continu les fonctionnalités de tri, catégorisation et analyse.
Cependant, pour rendre l’application pérenne et exploitable via le web, il faudra transformer assez radicalement le code existant - en mettant Opus 4.5 ou Gemini 3 sur l’affaire, cela devrait pouvoir se faire assez rapidement.
Comme capture d’écran, je partage l’outil de travail que j’utilise pour vibe coder avec Claude Code: Conare

Les résultats et enseignements
Learning : Claude Code permet un développement professionnel avec contrôle total, mais nécessite un niveau de maîtrise technique supérieur. C’est l’outil pour produire des applications qui doivent être maintenues et évolutives.
Démonstrateurs Audiogami: prototyper pour démontrer
Dans le cadre du développement d’Audiogami, l’équipe a besoin de montrer rapidement les mécaniques d’utilisation. Pas encore avec des données réelles, pas encore avec une interactivité complète - juste démontrer le concept.
Objectif : Valider l’approche et le workflow avec des parties prenantes avant d’investir dans le développement complet.
Comment ça s’est passé ?
Deux démonstrateurs ont été développés :
L’approche est différente de la landing page ou du prototype Dilemme Plastique : ici, l’objectif est la rapidité pour montrer, pas la robustesse pour produire.
Après 30 minutes d’itération via Claude (Sonnet 4.5) et ChatGPT 5, un PRD a pu être exporté.
Un des PRD qui a été utilisé pour générer ces prototypes
"Human in the Loop" - Voice-to-Database UX Testing
Version: 1.0
Date: 16 novembre 2025
Objectif: Prototypes cliquables testant la correction en temps réel pendant la dictée vocale
Plateformes cibles: Bolt, Lovable, Replit, Cursor
📋 Executive Summary
Créer 3 prototypes interactifs sans backend pour tester différentes approches UX de validation "human in the loop". Chaque prototype simule le flux complet : transcription progressive (effet typewriter 45 sec) → extraction d'entités → édition en temps réel → validation finale.
Livrables:
🎯 Hypothèses UX à valider
Hypothèse #1: Édition temps réel vs post-validation
Question: Les utilisateurs préfèrent-ils corriger les champs pendant la "dictée" simulée ou uniquement sur l'écran de validation final?
Tests:
Hypothèse #2: Affichage progressif
Question: Le remplissage dynamique des champs rassure-t-il ou distrait-il l'utilisateur?
Mesures:
Hypothèse #3: Format de validation
Question: Quel format d'écran de validation est le plus intuitif?
Options:
📊 Scénarios et données
SCÉNARIO A - Simple (Tâche design client)
Contexte: Designer chez Memoways créant une tâche de révision mockup.
Transcription vocale simulée (45 secondes):
Bon alors euh... il faut que je crée une tâche pour, attends... pour
réviser le mockup de la page d'accueil du client, tu sais le projet
euh... comment il s'appelle déjà... ah oui "Transformation Boulangerie Moderne",
c'est ça. Donc il faut que Florian regarde ça, parce que lui il s'occupe
du design système, et euh... on avait dit quoi déjà pour la deadline...
ah oui demain soir, le 17 novembre. Bon alors oui c'est prioritaire,
priorité haute parce que le client attend la présentation vendredi.
Et puis euh... ah oui il faut aussi ajouter le tag "Design" et "Client-Premium"
pour qu'on retrouve facilement dans la base. Voilà je pense que c'est bon.
Timeline d'extraction (pour animation progressive):
{
"extractions": [
{"time_sec": 8.5, "field": "title", "value": "Réviser mockup page d'accueil", "confidence": 0.88},
{"time_sec": 12.0, "field": "project_name", "value": "Transformation Boulangerie Moderne", "confidence": 0.95},
{"time_sec": 18.5, "field": "assignee", "value": "Florian", "confidence": 0.92},
{"time_sec": 24.0, "field": "due_date", "value": "demain soir", "needs_correction": true},
{"time_sec": 25.0, "field": "due_date", "value": "2025-11-17", "corrected": true},
{"time_sec": 30.0, "field": "priority", "value": "high", "confidence": 0.90},
{"time_sec": 38.0, "field": "tags", "value": ["Design"], "confidence": 0.95},
{"time_sec": 40.5, "field": "tags", "value": ["Design", "Client-Premium"], "confidence": 0.85}
]
}
Entités finales (pour écran de validation):
{
"task": {
"id": "task_new_001",
"title": "Réviser mockup page d'accueil",
"description": "Révision du mockup pour le projet Transformation Boulangerie Moderne. Présentation client prévue vendredi.",
"project_id": "proj_transformation_boulangerie",
"project_name": "Transformation Boulangerie Moderne",
"status": "todo",
"priority": "high",
"assignee_id": "user_florian_dupont",
"assignee_name": "Florian Dupont",
"due_date": "2025-11-17",
"tags": ["Design", "Client-Premium"],
"created_at": "2025-11-16T14:32:00Z"
}
}
Points de friction à observer:
SCÉNARIO B - Moyen avec ambiguïtés (Intégration API)
Contexte: Chef de projet planifiant une intégration no-code complexe.
Transcription vocale simulée (45 secondes):
OK alors la prochaine étape pour le Little Helper IA du client, c'est euh...
l'intégration avec Make.com pour automatiser les réponses emails.
Donc ça c'est pour le projet... attends je vérifie... "Automatisation Support ClientCo".
Nicolas et Antoine vont bosser dessus ensemble, parce que Nicolas fait
la partie Make et Antoine gère l'API du Little Helper. Bon alors euh...
pour la date, on a dit mi-décembre, donc disons le 15 décembre, mais euh...
attends non plutôt le 18 parce qu'il y a le sprint review le 16.
Ah et puis c'est bloqué par la tâche "Configuration serveur" qui doit être
terminée avant. Et en priorité... mouais priorité moyenne pour l'instant
parce qu'on a d'autres trucs urgents. Voilà c'est tout je crois.
Timeline d'extraction:
{
"extractions": [
{"time_sec": 6.0, "field": "title", "value": "Intégration Make.com", "confidence": 0.75},
{"time_sec": 10.5, "field": "title", "value": "Intégration Make.com pour automatisation emails", "confidence": 0.92},
{"time_sec": 15.0, "field": "project_name", "value": "Automatisation Support ClientCo", "confidence": 0.90},
{"time_sec": 19.0, "field": "assignee", "value": "Nicolas", "confidence": 0.88},
{"time_sec": 20.5, "field": "assignee", "value": ["Nicolas", "Antoine"], "multi_assign": true},
{"time_sec": 28.0, "field": "due_date", "value": "2025-12-15", "tentative": true},
{"time_sec": 31.0, "field": "due_date", "value": "2025-12-18", "corrected": true, "reason": "Sprint review le 16"},
{"time_sec": 36.0, "field": "blocked_by", "value": "Configuration serveur", "confidence": 0.85},
{"time_sec": 36.5, "field": "status", "value": "blocked", "auto_inferred": true},
{"time_sec": 41.0, "field": "priority", "value": "medium", "confidence": 0.80}
]
}
Entités finales:
{
"task": {
"id": "task_new_002",
"title": "Intégration Make.com pour automatisation emails",
"description": "Intégration Make.com avec API Little Helper IA pour réponses emails automatisées. Nicolas gère Make, Antoine l'API. Bloqué par configuration serveur (sprint review le 16/12).",
"project_id": "proj_automatisation_clientco",
"project_name": "Automatisation Support ClientCo",
"status": "blocked",
"priority": "medium",
"assignee_id": ["user_nicolas_martin", "user_antoine_rousseau"],
"assignee_names": ["Nicolas Martin", "Antoine Rousseau"],
"due_date": "2025-12-18",
"tags": ["No-code", "Automation", "API", "Make.com"],
"blocked_by": {
"task_id": "task_config_serveur_prod",
"task_title": "Configuration serveur production"
},
"created_at": "2025-11-16T14:35:00Z"
}
}
Ambiguïtés à gérer:
SCÉNARIO C - Complexe (Coordination multipartie)
Contexte: CEO créant une tâche stratégique avec sous-tâches.
Transcription vocale simulée (45 secondes):
Alors je dois créer une grosse tâche pour la préparation du pitch investisseurs
pour Audiogami. C'est un projet clé donc très haute priorité, urgente même.
Il faut que Timo prépare la démo technique, que moi je finalise les slides
de présentation, et euh... on doit aussi intégrer les retours de l'étude
de marché qu'Antoine a faite. Donc en gros trois sous-tâches: démo tech,
slides exec, et intégration données marché. Pour la date... on pitch le
3 décembre donc il faut que tout soit prêt le 1er décembre au plus tard,
disons même le 30 novembre pour avoir de la marge. Ah et puis il faut
taguer ça "Fundraising", "Stratégie" et "Investor-Ready". Bon et euh...
je mets un commentaire aussi: "Prévoir session de répétition avec Timo
le 29 novembre matin". Voilà c'est complet je pense.
Timeline d'extraction:
{
"extractions": [
{"time_sec": 5.0, "field": "title", "value": "Préparation pitch investisseurs", "confidence": 0.85},
{"time_sec": 7.5, "field": "project_name", "value": "Audiogami", "confidence": 0.98},
{"time_sec": 11.0, "field": "priority", "value": "urgent", "confidence": 0.95},
{"time_sec": 14.0, "field": "assignee", "value": "Timo", "subtask_detected": true},
{"time_sec": 16.5, "field": "assignee", "value": ["Ulrich", "Timo"], "multi_assign": true},
{"time_sec": 18.0, "field": "subtasks", "value": [{"title": "Démo technique", "assignee": "Timo"}]},
{"time_sec": 20.0, "field": "subtasks", "value": [
{"title": "Démo technique", "assignee": "Timo"},
{"title": "Slides présentation executive", "assignee": "Ulrich"}
]},
{"time_sec": 23.0, "field": "assignee", "value": ["Ulrich", "Timo", "Antoine"]},
{"time_sec": 24.0, "field": "subtasks", "value": [
{"title": "Démo technique Audiogami", "assignee": "Timo"},
{"title": "Slides présentation executive", "assignee": "Ulrich"},
{"title": "Intégration données étude marché", "assignee": "Antoine"}
]},
{"time_sec": 31.0, "field": "due_date", "value": "2025-12-01", "tentative": true},
{"time_sec": 34.0, "field": "due_date", "value": "2025-11-30", "corrected": true, "reason": "Marge sécurité"},
{"time_sec": 38.0, "field": "tags", "value": ["Fundraising"], "confidence": 0.95},
{"time_sec": 39.5, "field": "tags", "value": ["Fundraising", "Stratégie", "Investor-Ready"]},
{"time_sec": 43.0, "field": "comment", "value": "Prévoir session de répétition avec Timo le 29 novembre matin"}
]
}
Entités finales:
{
"task": {
"id": "task_new_003",
"title": "Préparation pitch investisseurs Audiogami",
"description": "Préparation complète du pitch investisseurs pour Audiogami. Pitch prévu le 3 décembre. Trois axes: démo technique (Timo), slides exécutives (Ulrich), intégration données marché (Antoine). Session répétition le 29 novembre matin.",
"project_id": "proj_fundraising_audiogami",
"project_name": "Fundraising Audiogami",
"status": "todo",
"priority": "urgent",
"assignee_id": ["user_ulrich_fischer", "user_timo_schuler", "user_antoine_rousseau"],
"assignee_names": ["Ulrich Fischer", "Timo Schuler", "Antoine Rousseau"],
"due_date": "2025-11-30",
"tags": ["Fundraising", "Stratégie", "Investor-Ready"],
"subtasks": [
{
"id": "subtask_001",
"title": "Démo technique Audiogami",
"assignee_id": "user_timo_schuler",
"assignee_name": "Timo Schuler",
"status": "todo"
},
{
"id": "subtask_002",
"title": "Slides présentation executive",
"assignee_id": "user_ulrich_fischer",
"assignee_name": "Ulrich Fischer",
"status": "todo"
},
{
"id": "subtask_003",
"title": "Intégration données étude de marché",
"assignee_id": "user_antoine_rousseau",
"assignee_name": "Antoine Rousseau",
"status": "todo"
}
],
"comments": [
{
"id": "comment_001",
"author_id": "user_ulrich_fischer",
"author_name": "Ulrich Fischer",
"body": "Prévoir session de répétition avec Timo le 29 novembre matin",
"created_at": "2025-11-16T14:38:00Z"
}
],
"created_at": "2025-11-16T14:38:00Z"
}
}
Complexités maximales testées:
🎨 Identité visuelle Audiogami
Palette de couleurs
/* === COULEURS PRIMAIRES - Origami Paper === */
:root {
/* Rose signature Audiogami */
--primary-pink: #FF69B4;
--primary-soft: #FFB6D9;
--primary-pale: #FFE4F1;
/* Papier origami */
--secondary-cream: #FFF8F3;
--secondary-gray: #E8E4E1;
--accent-gold: #D4AF37;
/* États système */
--success-green: #4CAF50;
--warning-orange: #FF9800;
--error-red: #F44336;
--info-blue: #2196F3;
/* Texte */
--text-dark: #2C2C2C;
--text-medium: #666666;
--text-light: #999999;
--text-white: #FFFFFF;
/* Overlays */
--shadow-soft: rgba(0, 0, 0, 0.08);
--shadow-medium: rgba(0, 0, 0, 0.15);
--overlay-dark: rgba(44, 44, 44, 0.6);
}
Typographie
/* Import fonts */
@import url('<https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap>');
/* Hierarchy */
:root {
/* Headers */
--font-heading: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
--weight-heading: 600;
/* Body */
--font-body: 'Inter', 'SF Pro Text', -apple-system, BlinkMacSystemFont, sans-serif;
--weight-body: 400;
--weight-medium: 500;
/* Mono (code/data) */
--font-mono: 'JetBrains Mono', 'SF Mono', 'Courier New', monospace;
/* Sizes */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
}
/* Typography classes */
h1, .heading-1 {
font-family: var(--font-heading);
font-size: var(--text-3xl);
font-weight: var(--weight-heading);
color: var(--text-dark);
}
h2, .heading-2 {
font-family: var(--font-heading);
font-size: var(--text-2xl);
font-weight: var(--weight-heading);
color: var(--text-dark);
}
body, .body-text {
font-family: var(--font-body);
font-size: var(--text-base);
font-weight: var(--weight-body);
color: var(--text-dark);
line-height: 1.6;
}
Animations signature Origami
/* === ORIGAMI FOLD/UNFOLD ANIMATIONS === */
/* Pliage d'ouverture (reveal) */
@keyframes origami-unfold {
0% {
transform: scaleY(0) rotateX(90deg);
transform-origin: top;
opacity: 0;
}
50% {
transform: scaleY(0.5) rotateX(45deg);
opacity: 0.5;
}
100% {
transform: scaleY(1) rotateX(0deg);
opacity: 1;
}
}
/* Pliage de fermeture (hide) */
@keyframes origami-fold {
0% {
transform: scaleY(1) rotateX(0deg);
opacity: 1;
}
50% {
transform: scaleY(0.5) rotateX(45deg);
opacity: 0.5;
}
100% {
transform: scaleY(0) rotateX(90deg);
transform-origin: top;
opacity: 0;
}
}
/* Pliage latéral (slide reveal) */
@keyframes origami-slide-unfold {
0% {
transform: translateX(-100%) rotateY(-90deg);
opacity: 0;
}
100% {
transform: translateX(0) rotateY(0deg);
opacity: 1;
}
}
/* Effet papier froissé au hover */
.paper-effect {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:
1px 1px 2px var(--shadow-soft),
2px 2px 4px var(--shadow-soft);
}
.paper-effect:hover {
box-shadow:
2px 2px 4px var(--shadow-soft),
4px 4px 8px rgba(0,0,0,0.06),
inset 1px 1px 2px rgba(255,255,255,0.4);
transform: translateY(-2px) scale(1.01);
}
/* Pulse pour nouveaux champs */
@keyframes field-pulse {
0%, 100% {
background-color: var(--secondary-cream);
border-color: var(--primary-pale);
}
50% {
background-color: var(--primary-pale);
border-color: var(--primary-soft);
}
}
/* Classes utilitaires */
.animate-unfold {
animation: origami-unfold 0.6s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
.animate-fold {
animation: origami-fold 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
.animate-pulse {
animation: field-pulse 1.5s ease-in-out 2;
}
Composants de base
/* Buttons */
.btn {
font-family: var(--font-body);
font-weight: var(--weight-medium);
padding: 0.75rem 1.5rem;
border-radius: 8px;
border: none;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-pink), var(--primary-soft));
color: var(--text-white);
box-shadow: 0 2px 8px rgba(255, 105, 180, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 105, 180, 0.4);
}
.btn-secondary {
background: var(--secondary-cream);
color: var(--text-dark);
border: 1px solid var(--secondary-gray);
}
/* Input fields */
.input-field {
font-family: var(--font-body);
font-size: var(--text-base);
padding: 0.75rem 1rem;
border: 2px solid var(--secondary-gray);
border-radius: 8px;
background: white;
transition: all 0.2s ease;
}
.input-field:focus {
outline: none;
border-color: var(--primary-pink);
box-shadow: 0 0 0 3px var(--primary-pale);
}
/* Tags/badges */
.tag {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.75rem;
background: var(--primary-pale);
color: var(--primary-pink);
border-radius: 16px;
font-size: var(--text-sm);
font-weight: var(--weight-medium);
}
/* Cards */
.card {
background: white;
border-radius: 12px;
padding: 1.5rem;
box-shadow:
0 1px 3px var(--shadow-soft),
0 2px 6px var(--shadow-soft);
transition: all 0.3s ease;
}
.card:hover {
box-shadow:
0 4px 8px var(--shadow-soft),
0 6px 12px var(--shadow-medium);
}
📱 Layouts détaillés
Mobile Layout (écran vertical < 768px)
<!-- Structure HTML mobile -->
<div class="mobile-container">
<!-- Header sticky -->
<header class="mobile-header">
<button class="btn-back">◀</button>
<h1 class="logo">Audiogami</h1>
<button class="btn-menu">≡</button>
</header>
<!-- Recording status -->
<div class="recording-status">
<div class="status-indicator">
<span class="pulse-dot"></span>
🎤 ENREGISTREMENT
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: 60%"></div>
<span class="time">0:32 / 0:45</span>
</div>
</div>
<!-- Transcription panel (collapsible) -->
<div class="transcript-panel">
<div class="transcript-header" onclick="toggleTranscript()">
<h3>Transcription</h3>
<button>▼ AFFICHER MOINS</button>
</div>
<div class="transcript-content" id="transcriptText">
<!-- Texte en typewriter ici -->
<p class="typing-text"></p>
</div>
</div>
<!-- Fields section (scroll vertical) -->
<div class="fields-section">
<!-- Titre -->
<div class="field-group">
<label>📋 Titre de la tâche</label>
<input
type="text"
class="input-field"
id="field-title"
placeholder="Titre détecté apparaîtra ici..."
/>
</div>
<!-- Assigné à -->
<div class="field-group">
<label>👤 Assigné à</label>
<select class="input-field" id="field-assignee">
<option value="">Sélectionner...</option>
<option value="florian">Florian Dupont</option>
<option value="nicolas">Nicolas Martin</option>
<option value="antoine">Antoine Rousseau</option>
<option value="timo">Timo Schuler</option>
<option value="ulrich">Ulrich Fischer</option>
</select>
</div>
<!-- Date limite -->
<div class="field-group">
<label>📅 Date limite</label>
<input
type="date"
class="input-field"
id="field-due-date"
/>
</div>
<!-- Priorité -->
<div class="field-group">
<label>⚡ Priorité</label>
<div class="priority-toggle">
<button class="priority-btn" data-priority="low">
○ Faible
</button>
<button class="priority-btn" data-priority="medium">
○ Moyenne
</button>
<button class="priority-btn active" data-priority="high">
● Haute
</button>
<button class="priority-btn" data-priority="urgent">
○ Urgente
</button>
</div>
</div>
<!-- Tags -->
<div class="field-group">
<label>🏷️ Tags</label>
<div class="tags-container" id="tagsContainer">
<!-- Tags ajoutés dynamiquement -->
</div>
<input
type="text"
class="input-field"
id="field-add-tag"
placeholder="Ajouter un tag..."
/>
</div>
</div>
<!-- Actions sticky bottom -->
<div class="mobile-actions">
<button class="btn btn-secondary btn-pause">
⏸ PAUSE
</button>
<button class="btn btn-primary btn-validate">
✓ VALIDER
</button>
</div>
</div>
/* Mobile styles */
.mobile-container {
min-height: 100vh;
display: flex;
flex-direction: column;
background: var(--secondary-cream);
}
.mobile-header {
position: sticky;
top: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
background: white;
border-bottom: 1px solid var(--secondary-gray);
}
.recording-status {
padding: 1rem;
background: linear-gradient(135deg, var(--primary-pale), white);
}
.status-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: var(--weight-medium);
color: var(--primary-pink);
margin-bottom: 0.5rem;
}
.pulse-dot {
width: 8px;
height: 8px;
background: var(--primary-pink);
border-radius: 50%;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.2); }
}
.progress-bar {
position: relative;
height: 6px;
background: var(--secondary-gray);
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary-pink), var(--primary-soft));
transition: width 0.3s ease;
}
.transcript-panel {
background: white;
margin: 1rem;
border-radius: 12px;
padding: 1rem;
max-height: 200px;
overflow-y: auto;
box-shadow: 0 2px 8px var(--shadow-soft);
}
.typing-text {
font-size: var(--text-sm);
color: var(--text-medium);
line-height: 1.6;
}
.fields-section {
flex: 1;
padding: 1rem;
overflow-y: auto;
}
.field-group {
margin-bottom: 1.5rem;
}
.field-group label {
display: block;
font-size: var(--text-sm);
font-weight: var(--weight-medium);
color: var(--text-dark);
margin-bottom: 0.5rem;
}
.priority-toggle {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
}
.priority-btn {
padding: 0.75rem;
border: 2px solid var(--secondary-gray);
background: white;
border-radius: 8px;
font-size: var(--text-sm);
cursor: pointer;
transition: all 0.2s ease;
}
.priority-btn.active {
border-color: var(--primary-pink);
background: var(--primary-pale);
color: var(--primary-pink);
}
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.mobile-actions {
position: sticky;
bottom: 0;
display: flex;
gap: 0.75rem;
padding: 1rem;
background: white;
border-top: 1px solid var(--secondary-gray);
box-shadow: 0 -2px 8px var(--shadow-soft);
}
.mobile-actions .btn {
flex: 1;
}
Desktop Layout (écran paysage > 768px)
<!-- Structure HTML desktop -->
<div class="desktop-container">
<!-- Header global -->
<header class="desktop-header">
<div class="logo-section">
<img src="logo-audiogami.svg" alt="Audiogami" />
<h1>Audiogami</h1>
</div>
<nav class="main-nav">
<a href="#" class="nav-link active">Widget</a>
<a href="#" class="nav-link">Base de données</a>
<a href="#" class="nav-link">Paramètres</a>
</nav>
</header>
<!-- Main grid: 3 colonnes -->
<main class="desktop-main">
<!-- COLONNE 1: Transcription -->
<section class="column column-transcript">
<div class="column-header">
<h2>Transcription</h2>
<div class="recording-indicator">
<span class="pulse-dot"></span>
<span class="time">0:32</span>
</div>
</div>
<div class="progress-bar-desktop">
<div class="progress-fill" style="width: 60%"></div>
</div>
<div class="transcript-text" id="transcriptDesktop">
<!-- Effet typewriter ici -->
<p class="typing-text"></p>
<!-- Mots détectés surlignés -->
<mark class="detected-entity" data-field="title">
réviser le mockup
</mark>
<mark class="detected-entity" data-field="assignee">
Florian
</mark>
<mark class="detected-entity" data-field="date">
demain soir
</mark>
</div>
<div class="transcript-actions">
<button class="btn btn-secondary">
⏸ Pause
</button>
<button class="btn btn-secondary">
⏹ Stop
</button>
</div>
</section>
<!-- COLONNE 2: Extraction & Champs -->
<section class="column column-fields">
<div class="column-header">
<h2>Extraction en cours</h2>
<div class="confidence-badge">
<span class="confidence-icon">✓</span>
92% confiance
</div>
</div>
<div class="extraction-summary">
<div class="summary-item">
<span class="icon">📋</span>
<span class="label">Tâche créée</span>
</div>
</div>
<!-- Formulaire dynamique -->
<form class="extraction-form" id="extractionForm">
<!-- Chaque champ apparaît avec animation origami-unfold -->
<div class="form-field animate-unfold" data-field="title">
<label>Titre</label>
<input
type="text"
class="input-field"
value="Réviser mockup page d'accueil"
/>
<span class="field-status">✓ Détecté</span>
</div>
<div class="form-field animate-unfold" data-field="assignee">
<label>Assigné à</label>
<select class="input-field">
<option value="florian" selected>Florian Dupont</option>
<option value="nicolas">Nicolas Martin</option>
<option value="antoine">Antoine Rousseau</option>
</select>
<span class="field-status">✓ Détecté</span>
</div>
<div class="form-field animate-unfold animate-pulse" data-field="due-date">
<label>Date limite</label>
<input
type="date"
class="input-field"
value="2025-11-17"
/>
<span class="field-status correction">
🔄 Corrigé: "demain soir" → 17/11/2025
</span>
</div>
<div class="form-field animate-unfold" data-field="priority">
<label>Priorité</label>
<div class="priority-grid">
<button type="button" class="priority-option">Faible</button>
<button type="button" class="priority-option">Moyenne</button>
<button type="button" class="priority-option active">Haute</button>
<button type="button" class="priority-option">Urgente</button>
</div>
</div>
<div class="form-field animate-unfold" data-field="tags">
<label>Tags</label>
<div class="tags-input">
<span class="tag">Design ×</span>
<span class="tag">Client-Premium ×</span>
<input type="text" placeholder="Ajouter..." />
</div>
</div>
</form>
<div class="form-actions">
<button class="btn btn-primary btn-validate-desktop">
✓ Valider et enregistrer
</button>
</div>
</section>
<!-- COLONNE 3: Base de données -->
<section class="column column-database">
<div class="column-header">
<h2>Base de données</h2>
<input
type="search"
class="search-input"
placeholder="🔍 Filtrer..."
/>
</div>
<!-- Liste des tâches existantes -->
<div class="tasks-list">
<!-- Nouvelle tâche (preview) -->
<div class="task-card task-new">
<div class="task-checkbox">
<input type="checkbox" id="task-new" />
</div>
<div class="task-content">
<h3 class="task-title">
Réviser mockup page d'accueil
</h3>
<div class="task-meta">
<span class="assignee">@Florian</span>
<span class="due-date">17/11</span>
<span class="priority high">🔥 Haute</span>
</div>
<div class="task-tags">
<span class="tag">Design</span>
<span class="tag">Client-Premium</span>
</div>
</div>
</div>
<!-- Tâches existantes -->
<div class="task-card">
<div class="task-checkbox">
<input type="checkbox" id="task-1" />
</div>
<div class="task-content">
<h3 class="task-title">
Intégration Make.com automatisation emails
</h3>
<div class="task-meta">
<span class="assignee">@Nicolas @Antoine</span>
<span class="due-date">18/12</span>
<span class="priority medium">⚡ Moyenne</span>
<span class="status blocked">🚫 Bloqué</span>
</div>
</div>
</div>
<div class="task-card">
<div class="task-checkbox">
<input type="checkbox" id="task-2" />
</div>
<div class="task-content">
<h3 class="task-title">
Préparation pitch investisseurs Audiogami
</h3>
<div class="task-meta">
<span class="assignee">@Ulrich @Timo</span>
<span class="due-date">30/11</span>
<span class="priority urgent">🔥🔥 Urgente</span>
</div>
</div>
</div>
</div>
</section>
</main>
<!-- Footer: 3 dernières entrées (pleine largeur) -->
<footer class="desktop-footer">
<h3>Dernières entrées</h3>
<div class="recent-tasks-grid">
<div class="recent-task">
<div class="recent-icon">✓</div>
<div class="recent-content">
<h4>Démo client AVA</h4>
<p>@Timo • Terminé • 15/11</p>
</div>
</div>
<div class="recent-task">
<div class="recent-icon">⏳</div>
<div class="recent-content">
<h4>Configuration serveur production</h4>
<p>@Nicolas • En cours • 18/11</p>
</div>
</div>
<div class="recent-task">
<div class="recent-icon">📝</div>
<div class="recent-content">
<h4>Révision UX landing page</h4>
<p>@Antoine • À faire • 20/11</p>
</div>
</div>
</div>
</footer>
</div>
/* Desktop styles */
.desktop-container {
min-height: 100vh;
display: flex;
flex-direction: column;
background: var(--secondary-cream);
}
.desktop-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem 2rem;
background: white;
border-bottom: 2px solid var(--secondary-gray);
}
.logo-section {
display: flex;
align-items: center;
gap: 1rem;
}
.logo-section img {
height: 40px;
}
.main-nav {
display: flex;
gap: 2rem;
}
.nav-link {
color: var(--text-medium);
text-decoration: none;
font-weight: var(--weight-medium);
padding: 0.5rem 1rem;
border-radius: 8px;
transition: all 0.2s ease;
}
.nav-link.active {
background: var(--primary-pale);
color: var(--primary-pink);
}
.desktop-main {
flex: 1;
display: grid;
grid-template-columns: 1fr 1.2fr 1fr;
gap: 1.5rem;
padding: 1.5rem;
overflow: hidden;
}
.column {
background: white;
border-radius: 12px;
padding: 1.5rem;
display: flex;
flex-direction: column;
box-shadow: 0 2px 8px var(--shadow-soft);
overflow-y: auto;
}
.column-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 2px solid var(--secondary-gray);
}
.column-header h2 {
font-size: var(--text-xl);
margin: 0;
}
.recording-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--primary-pink);
font-weight: var(--weight-medium);
}
.progress-bar-desktop {
height: 4px;
background: var(--secondary-gray);
border-radius: 2px;
overflow: hidden;
margin-bottom: 1.5rem;
}
.transcript-text {
flex: 1;
padding: 1rem;
background: var(--secondary-cream);
border-radius: 8px;
font-size: var(--text-sm);
line-height: 1.8;
overflow-y: auto;
}
.detected-entity {
background: var(--primary-pale);
color: var(--primary-pink);
padding: 0.125rem 0.25rem;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
}
.detected-entity:hover {
background: var(--primary-soft);
}
.transcript-actions {
display: flex;
gap: 0.75rem;
margin-top: 1rem;
}
.extraction-summary {
background: linear-gradient(135deg, var(--primary-pale), white);
padding: 1rem;
border-radius: 8px;
margin-bottom: 1.5rem;
}
.summary-item {
display: flex;
align-items: center;
gap: 0.75rem;
font-weight: var(--weight-medium);
}
.extraction-form {
flex: 1;
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.form-field {
opacity: 0;
transform: scaleY(0);
transform-origin: top;
}
.form-field.animate-unfold {
animation: origami-unfold 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
.form-field label {
display: block;
font-size: var(--text-sm);
font-weight: var(--weight-medium);
color: var(--text-dark);
margin-bottom: 0.5rem;
}
.field-status {
display: inline-block;
margin-top: 0.25rem;
font-size: var(--text-xs);
color: var(--success-green);
font-weight: var(--weight-medium);
}
.field-status.correction {
color: var(--warning-orange);
}
.priority-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.5rem;
}
.priority-option {
padding: 0.5rem;
border: 2px solid var(--secondary-gray);
background: white;
border-radius: 6px;
font-size: var(--text-sm);
cursor: pointer;
transition: all 0.2s ease;
}
.priority-option.active {
border-color: var(--primary-pink);
background: var(--primary-pale);
color: var(--primary-pink);
font-weight: var(--weight-medium);
}
.tags-input {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding: 0.75rem;
border: 2px solid var(--secondary-gray);
border-radius: 8px;
background: white;
}
.tags-input input {
border: none;
outline: none;
flex: 1;
min-width: 100px;
font-size: var(--text-sm);
}
.form-actions {
margin-top: 1rem;
padding-top: 1rem;
border-top: 2px solid var(--secondary-gray);
}
.btn-validate-desktop {
width: 100%;
justify-content: center;
}
.tasks-list {
flex: 1;
display: flex;
flex-direction: column;
gap: 1rem;
}
.task-card {
display: flex;
gap: 1rem;
padding: 1rem;
background: var(--secondary-cream);
border-radius: 8px;
border: 2px solid transparent;
transition: all 0.2s ease;
cursor: pointer;
}
.task-card:hover {
border-color: var(--primary-soft);
background: white;
box-shadow: 0 2px 8px var(--shadow-soft);
}
.task-card.task-new {
background: linear-gradient(135deg, var(--primary-pale), white);
border-color: var(--primary-pink);
animation: origami-unfold 0.6s ease;
}
.task-checkbox input {
width: 20px;
height: 20px;
cursor: pointer;
}
.task-content {
flex: 1;
}
.task-title {
font-size: var(--text-base);
font-weight: var(--weight-medium);
color: var(--text-dark);
margin: 0 0 0.5rem 0;
}
.task-meta {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
font-size: var(--text-xs);
color: var(--text-medium);
margin-bottom: 0.5rem;
}
.task-meta .assignee {
color: var(--info-blue);
font-weight: var(--weight-medium);
}
.task-meta .priority {
font-weight: var(--weight-medium);
}
.task-meta .priority.high {
color: var(--warning-orange);
}
.task-meta .priority.urgent {
color: var(--error-red);
}
.task-meta .status.blocked {
color: var(--error-red);
}
.task-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.desktop-footer {
background: white;
padding: 1.5rem 2rem;
border-top: 2px solid var(--secondary-gray);
}
.desktop-footer h3 {
font-size: var(--text-lg);
margin: 0 0 1rem 0;
}
.recent-tasks-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.recent-task {
display: flex;
gap: 1rem;
padding: 1rem;
background: var(--secondary-cream);
border-radius: 8px;
}
.recent-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--primary-pale);
border-radius: 8px;
font-size: var(--text-lg);
}
.recent-content h4 {
font-size: var(--text-sm);
margin: 0 0 0.25rem 0;
}
.recent-content p {
font-size: var(--text-xs);
color: var(--text-medium);
margin: 0;
}
/* Responsive breakpoint */
@media (max-width: 768px) {
.desktop-container {
display: none;
}
}
@media (min-width: 769px) {
.mobile-container {
display: none;
}
}
⚙️ Logique JavaScript (pseudo-code)
// === CONFIGURATION ===
const SCENARIOS = {
A: '/data/scenario-a.json',
B: '/data/scenario-b.json',
C: '/data/scenario-c.json'
};
const PLAYBACK_SPEED = 1.5; // Accéléré pour tests
let currentScenario = null;
let simulationTimer = null;
// === INITIALISATION ===
async function initPrototype() {
// Charger le scénario A par défaut
await loadScenario('A');
// Configurer les event listeners
setupEventListeners();
// Démarrer la simulation automatiquement après 2 sec
setTimeout(() => startSimulation(), 2000);
}
// === CHARGEMENT SCÉNARIO ===
async function loadScenario(scenarioKey) {
const response = await fetch(SCENARIOS[scenarioKey]);
currentScenario = await response.json();
console.log('Scénario chargé:', currentScenario.scenario);
}
// === SIMULATION PRINCIPALE ===
function startSimulation() {
if (!currentScenario) return;
let currentTime = 0;
const chunks = currentScenario.transcript_chunks;
const extractions = currentScenario.extraction_timeline;
// 1. Démarrer l'effet typewriter
animateTranscript(chunks, currentTime);
// 2. Programmer les extractions
extractions.forEach(extraction => {
const delay = (extraction.time_sec * 1000) / PLAYBACK_SPEED;
setTimeout(() => {
updateField(extraction);
}, delay);
});
// 3. Afficher écran validation après 45 sec
const totalDuration = 45000 / PLAYBACK_SPEED;
setTimeout(() => {
showValidationScreen();
}, totalDuration);
// 4. Mettre à jour la barre de progression
updateProgressBar(totalDuration);
}
// === ANIMATION TYPEWRITER ===
function animateTranscript(chunks, startTime = 0) {
const transcriptEl = document.querySelector('.typing-text');
let currentChunkIndex = 0;
let currentCharIndex = 0;
function typeNextChar() {
if (currentChunkIndex >= chunks.length) {
// Transcription terminée
return;
}
const chunk = chunks[currentChunkIndex];
const text = chunk.text;
if (currentCharIndex < text.length) {
// Ajouter le caractère suivant
const char = text[currentCharIndex];
transcriptEl.textContent += char;
currentCharIndex++;
// Délai entre caractères (50ms / speed)
setTimeout(typeNextChar, 50 / PLAYBACK_SPEED);
} else {
// Passer au chunk suivant
transcriptEl.textContent += ' ';
currentChunkIndex++;
currentCharIndex = 0;
// Délai entre chunks (200ms / speed)
setTimeout(typeNextChar, 200 / PLAYBACK_SPEED);
}
}
typeNextChar();
}
// === MISE À JOUR DES CHAMPS ===
function updateField(extraction) {
const field = extraction.field;
const value = extraction.value;
// Sélectionner l'élément DOM
const fieldEl = document.querySelector(`[data-field="${field}"]`);
if (!fieldEl) return;
// Afficher le champ avec animation origami
if (fieldEl.style.display === 'none') {
fieldEl.style.display = 'block';
fieldEl.classList.add('animate-unfold');
}
// Remplir la valeur
const inputEl = fieldEl.querySelector('input, select, textarea');
if (inputEl) {
inputEl.value = Array.isArray(value) ? value[0] : value;
// Si correction, ajouter effet pulse
if (extraction.corrected) {
fieldEl.classList.add('animate-pulse');
showCorrectionBadge(fieldEl, extraction);
}
}
// Cas spéciaux
if (field === 'tags' && Array.isArray(value)) {
updateTags(value);
}
if (field === 'assignee' && Array.isArray(value)) {
handleMultiAssign(value);
}
if (field === 'subtasks') {
createSubtasks(value);
}
}
// === GESTION TAGS ===
function updateTags(tags) {
const container = document.getElementById('tagsContainer');
container.innerHTML = '';
tags.forEach(tag => {
const tagEl = document.createElement('span');
tagEl.className = 'tag';
tagEl.textContent = tag;
// Bouton suppression
const removeBtn = document.createElement('button');
removeBtn.textContent = '×';
removeBtn.onclick = () => tagEl.remove();
tagEl.appendChild(removeBtn);
container.appendChild(tagEl);
// Animation d'apparition
tagEl.style.animation = 'origami-unfold 0.3s ease';
});
}
// === CORRECTION BADGE ===
function showCorrectionBadge(fieldEl, extraction) {
const badge = document.createElement('div');
badge.className = 'correction-badge';
badge.innerHTML = `
<span class="correction-icon">🔄</span>
<span class="correction-text">
Corrigé: "${extraction.raw_value || 'valeur originale'}"
→ ${extraction.value}
</span>
`;
fieldEl.appendChild(badge);
}
// === ÉCRAN DE VALIDATION ===
function showValidationScreen() {
const main = document.querySelector('.desktop-main, .mobile-container');
// Créer overlay de validation
const overlay = document.createElement('div');
overlay.className = 'validation-overlay';
overlay.innerHTML = generateValidationHTML();
// Animation d'apparition
overlay.style.animation = 'origami-unfold 0.6s ease';
main.appendChild(overlay);
// Remplir avec les données finales
populateValidationForm(currentScenario.final_entities);
}
function generateValidationHTML() {
return `
<div class="validation-modal">
<div class="validation-header">
<h2>✓ Validation de la tâche</h2>
<button class="btn-close" onclick="closeValidation()">×</button>
</div>
<div class="validation-content">
<form id="validationForm">
<!-- Champs pré-remplis -->
<!-- Identiques au formulaire principal -->
</form>
</div>
<div class="validation-actions">
<button class="btn btn-secondary" onclick="closeValidation()">
Annuler
</button>
<button class="btn btn-primary" onclick="submitTask()">
✓ Enregistrer dans la base
</button>
</div>
</div>
`;
}
// === SOUMISSION FINALE ===
function submitTask() {
// Récupérer les données du formulaire
const formData = new FormData(document.getElementById('validationForm'));
const taskData = Object.fromEntries(formData);
console.log('Tâche validée:', taskData);
// Animation de succès
showSuccessAnimation();
// Ajouter à la liste des tâches
addToDatabase(taskData);
// Fermer la modal après 1.5 sec
setTimeout(() => {
closeValidation();
resetSimulation();
}, 1500);
}
function showSuccessAnimation() {
const modal = document.querySelector('.validation-modal');
// Overlay de succès
const success = document.createElement('div');
success.className = 'success-overlay';
success.innerHTML = `
<div class="success-icon">✓</div>
<h3>Tâche enregistrée !</h3>
`;
modal.appendChild(success);
}
// === BARRE DE PROGRESSION ===
function updateProgressBar(totalDuration) {
const progressBar = document.querySelector('.progress-fill');
const timeDisplay = document.querySelector('.time');
let elapsed = 0;
const interval = 100; // Update tous les 100ms
const timer = setInterval(() => {
elapsed += interval;
const percentage = (elapsed / totalDuration) * 100;
progressBar.style.width = `${Math.min(percentage, 100)}%`;
const seconds = Math.floor(elapsed / 1000);
const totalSec = Math.floor(totalDuration / 1000);
timeDisplay.textContent = `${seconds}s / ${totalSec}s`;
if (elapsed >= totalDuration) {
clearInterval(timer);
}
}, interval);
}
// === ÉDITION INLINE ===
function enableInlineEdit(fieldEl) {
const input = fieldEl.querySelector('input, select, textarea');
if (!input) return;
// Focus et sélection
input.focus();
if (input.select) input.select();
// Highlight du champ
fieldEl.classList.add('editing');
// Event listener pour sauvegarder
input.addEventListener('blur', () => {
fieldEl.classList.remove('editing');
console.log('Champ mis à jour:', input.name, input.value);
});
}
// === EVENT LISTENERS ===
function setupEventListeners() {
// Boutons de scénario
document.querySelectorAll('.scenario-btn').forEach(btn => {
btn.addEventListener('click', async (e) => {
const scenario = e.target.dataset.scenario;
await loadScenario(scenario);
resetSimulation();
startSimulation();
});
});
// Bouton pause
document.querySelector('.btn-pause')?.addEventListener('click', () => {
pauseSimulation();
});
// Édition des champs
document.querySelectorAll('.form-field').forEach(field => {
field.addEventListener('click', () => {
enableInlineEdit(field);
});
});
}
// === RESET ===
function resetSimulation() {
// Nettoyer la transcription
document.querySelectorAll('.typing-text').forEach(el => {
el.textContent = '';
});
// Réinitialiser les champs
document.querySelectorAll('.form-field').forEach(field => {
field.style.display = 'none';
field.classList.remove('animate-unfold', 'animate-pulse');
});
// Réinitialiser la barre de progression
document.querySelectorAll('.progress-fill').forEach(bar => {
bar.style.width = '0%';
});
}
// === DÉMARRAGE ===
document.addEventListener('DOMContentLoaded', initPrototype);
📦 Structure des fichiers de données
scenario-a.json
{
"scenario": "A",
"name": "Simple - Tâche design client",
"description": "Designer créant une tâche de révision mockup",
"transcript_text": "Bon alors euh... il faut que je crée une tâche pour, attends... pour réviser le mockup de la page d'accueil du client, tu sais le projet euh... comment il s'appelle déjà... ah oui \\"Transformation Boulangerie Moderne\\", c'est ça. Donc il faut que Florian regarde ça, parce que lui il s'occupe du design système, et euh... on avait dit quoi déjà pour la deadline... ah oui demain soir, le 17 novembre. Bon alors oui c'est prioritaire, priorité haute parce que le client attend la présentation vendredi. Et puis euh... ah oui il faut aussi ajouter le tag \\"Design\\" et \\"Client-Premium\\" pour qu'on retrouve facilement dans la base. Voilà je pense que c'est bon.",
"transcript_chunks": [
{"time_sec": 0, "text": "Bon alors euh..."},
{"time_sec": 1.5, "text": "il faut que je crée une tâche pour,"},
{"time_sec": 3.0, "text": "attends..."},
{"time_sec": 4.0, "text": "pour réviser le mockup"},
{"time_sec": 6.5, "text": "de la page d'accueil du client,"},
{"time_sec": 9.0, "text": "tu sais le projet euh..."},
{"time_sec": 10.5, "text": "comment il s'appelle déjà..."},
{"time_sec": 12.0, "text": "ah oui \\"Transformation Boulangerie Moderne\\","},
{"time_sec": 15.0, "text": "c'est ça."},
{"time_sec": 16.0, "text": "Donc il faut que Florian regarde ça,"},
{"time_sec": 18.5, "text": "parce que lui il s'occupe du design système,"},
{"time_sec": 21.0, "text": "et euh..."},
{"time_sec": 22.0, "text": "on avait dit quoi déjà pour la deadline..."},
{"time_sec": 24.5, "text": "ah oui demain soir,"},
{"time_sec": 26.0, "text": "le 17 novembre."},
{"time_sec": 28.0, "text": "Bon alors oui c'est prioritaire,"},
{"time_sec": 30.5, "text": "priorité haute"},
{"time_sec": 32.0, "text": "parce que le client attend la présentation vendredi."},
{"time_sec": 35.0, "text": "Et puis euh..."},
{"time_sec": 36.0, "text": "ah oui il faut aussi ajouter le tag \\"Design\\""},
{"time_sec": 39.0, "text": "et \\"Client-Premium\\""},
{"time_sec": 41.0, "text": "pour qu'on retrouve facilement dans la base."},
{"time_sec": 43.5, "text": "Voilà je pense que c'est bon."}
],
"extraction_timeline": [
{
"time_sec": 8.5,
"field": "title",
"value": "Réviser mockup page d'accueil",
"confidence": 0.88,
"source_text": "réviser le mockup de la page d'accueil"
},
{
"time_sec": 12.0,
"field": "project_name",
"value": "Transformation Boulangerie Moderne",
"confidence": 0.95,
"source_text": "Transformation Boulangerie Moderne"
},
{
"time_sec": 18.5,
"field": "assignee",
"value": "Florian",
"confidence": 0.92,
"source_text": "Florian regarde ça"
},
{
"time_sec": 24.0,
"field": "due_date",
"value": "demain soir",
"confidence": 0.70,
"needs_correction": true,
"source_text": "demain soir"
},
{
"time_sec": 25.0,
"field": "due_date",
"value": "2025-11-17",
"confidence": 0.95,
"corrected": true,
"correction_reason": "Interprétation de 'demain soir' basée sur date actuelle + mention explicite '17 novembre'",
"source_text": "le 17 novembre"
},
{
"time_sec": 30.0,
"field": "priority",
"value": "high",
"confidence": 0.90,
"source_text": "priorité haute"
},
{
"time_sec": 38.0,
"field": "tags",
"value": ["Design"],
"confidence": 0.95,
"source_text": "tag Design"
},
{
"time_sec": 40.5,
"field": "tags",
"value": ["Design", "Client-Premium"],
"confidence": 0.85,
"source_text": "et Client-Premium"
}
],
"final_entities": {
"task": {
"id": "task_new_001",
"title": "Réviser mockup page d'accueil",
"description": "Révision du mockup pour le projet Transformation Boulangerie Moderne. Présentation client prévue vendredi.",
"project_id": "proj_transformation_boulangerie",
"project_name": "Transformation Boulangerie Moderne",
"status": "todo",
"priority": "high",
"assignee_id": "user_florian_dupont",
"assignee_name": "Florian Dupont",
"due_date": "2025-11-17",
"tags": ["Design", "Client-Premium"],
"created_at": "2025-11-16T14:32:00Z",
"created_by": "user_current_speaker"
}
},
"existing_tasks": [
{
"id": "task_001",
"title": "Démo client AVA",
"assignee_name": "Timo Schuler",
"status": "done",
"due_date": "2025-11-15",
"priority": "high"
},
{
"id": "task_002",
"title": "Configuration serveur production",
"assignee_name": "Nicolas Martin",
"status": "in_progress",
"due_date": "2025-11-18",
"priority": "medium"
},
{
"id": "task_003",
"title": "Révision UX landing page",
"assignee_name": "Antoine Rousseau",
"status": "todo",
"due_date": "2025-11-20",
"priority": "medium"
}
]
}
🎬 Instructions d'implémentation
Étape 1: Setup initial (5 min)
/data/Étape 2: Layout HTML (15 min)
index.html avec structure mobile ET desktopdata-field="title")Étape 3: Styles CSS (20 min)
Étape 4: JavaScript (30 min)
Étape 5: Tests & polish (10 min)
🧪 Tests utilisateurs
Protocole de test
Questionnaire feedback
### Prototype Audiogami - Feedback utilisateur
**Scénario testé**: [ ] A [ ] B [ ] C [ ] Tous
**Questions (échelle 1-5)**:
1. La transcription en temps réel était-elle claire?
☐ 1 - Pas du tout ☐ 2 ☐ 3 ☐ 4 ☐ 5 - Très clair
2. L'apparition progressive des champs était-elle rassurante?
☐ 1 - Perturbante ☐ 2 ☐ 3 ☐ 4 ☐ 5 - Rassurante
3. Avez-vous compris quand et comment corriger les champs?
☐ 1 - Pas compris ☐ 2 ☐ 3 ☐ 4 ☐ 5 - Évident
4. L'écran de validation finale était-il utile?
☐ 1 - Inutile ☐ 2 ☐ 3 ☐ 4 ☐ 5 - Essentiel
5. Recommanderiez-vous ce système à un collègue?
☐ 1 - Non ☐ 2 ☐ 3 ☐ 4 ☐ 5 - Absolument
**Questions ouvertes**:
6. Qu'avez-vous le plus apprécié?
[ ]
7. Qu'avez-vous trouvé frustrant ou confus?
[ ]
8. Utiliseriez-vous cet outil pour [votre usage] ?
[ ]
**Suggestions d'amélioration**:
[ ]
[ ]
📝 Checklist finale
Avant de livrer le prototype
Documentation à fournir
🚀 Prochaines étapes (post-prototype)
Bonne chance pour le prototypage! 🎨✨
Ce PRD est prêt à être copié-collé dans Bolt, Lovable, Replit ou Cursor pour générer le prototype fonctionnel.
Dans Bolt et Lovable, après 5-8 minutes, le prototype était quasiment prêt à l’emploi; encore une petite heure pour soigner quelques détails et les liens étaient partageables !
Voici l’un des prototypes développé avec Bolt, en mode embed:
Et un autre prototype, réalisé avec Lovable:
Les résultats et enseignements
Learning : Les outils simples (Lovable et Bolt) sont parfaits pour des démonstrateurs rapides. Pas besoin de Cursor ou Claude Code quand l’objectif est de montrer un concept, pas de livrer en production.
Le défi du vibe coding: l’IA sans contexte fait ce qu’elle veut
Quand on discute avec l’IA dans un flux conversationnel, c’est comme WhatsApp - fluide, rapide, naturel. Mais contrairement à un humain, l’IA ne connaît pas votre contexte métier, vos objectifs stratégiques, vos contraintes techniques, ni ce qu’est un “succès” pour votre projet.
Résultat : Elle fait de son mieux avec les bribes d’information que vous lui donnez. Mais “de son mieux” n’est pas “exactement ce dont vous avez besoin”.
La solution : Formaliser avant de générer
Documenter le contexte signifie :
- Définir les objectifs: Où veut-on aller ? Pourquoi ?
- Clarifier les attendus: Qu’est-ce qu’on doit obtenir concrètement ?
- Établir les critères de succès: Comment sait-on que c’est réussi ? Où veut-on arriver ?
Plus le contexte est précis, meilleur sera le résultat. C’est précisément le rôle du Product Requirement Document (PRD).
Et bien sûr: toujours se connecter avec Github, pour avoir accès au code indépendamment de l’outil utilisé (pour éviter le vendor lock-in et pour donner accès au code généré à un développeur, si besoin).
La hiérarchie des outils
Outils simples : Lovable, Bolt, EasySite
Quand les utiliser ?
Débuter en vibe coding, tester rapidement une idée, créer des démonstrateurs, ou pour des projets techniquement simples (landing pages, prototypes basiques).
Forces : - Résultats immédiats - Interface intuitive - Pas besoin de connaissances techniques
Limites : - Simplicité technique - Moins de contrôle sur le résultat final
Outil intermédiaire : Replit
Quand l’utiliser ?
Projets nécessitant plus de professionnalisme, besoin de travail collaboratif (Git), prototypes évolutifs, gestion de sécurité, accessibilité et conformité.
Forces : - Équilibre entre simplicité et contrôle - Environnement structuré - Possibilité de finaliser proprement
Caractéristique clé :
Agent autonome qui génère le résultat complet (approche “boîte noire” relative).
Outils professionnels : Cursor, Claude Code
Quand les utiliser ?
Développement bas niveau, applications en production, besoin de contrôle total sur le code, projets nécessitant maintenance et évolution.
Forces : - Maîtrise complète - Intégration Git native, également développement en local - Environnement de développement professionnel
Conseil de progression
Ne pas brûler les étapes :
- Commencer avec Lovable/Bolt/EasySite pour comprendre le vibe coding
- Passer par Replit pour structurer des projets plus sérieux
- Utiliser Cursor/Claude Code quand on a envie de mieux comprendre et d’interagir à plus bas niveau avec le code
Voici des concepts qui peuvent aider au démarrage avec Claude Code:
every.toConclusion
Le vibe coding révolutionne la manière de développer : landing pages en heures, prototypes en demi-journée. Mais cette puissance vient avec une responsabilité - celle de fournir un contexte précis à l’IA.
Comme le résume Ulrich : “Pas de vibe sans formalisation.” Le flow conversationnel et la rigueur documentaire ne s’opposent pas - ils vont de pair. La formalisation permet au vibe coding de véritablement décoller.
Pour aller plus loin :
Cette session fait partie de notre série Open Research sur les nouveaux paradigmes de travail avec l’IA.
Vous expérimentez avec Notion 3.0 ? Partagez vos découvertes et tensions avec nous au sein de la communauté Memoways sur Discord - nous apprenons collectivement.
Billet généré à partir d’une transcription de session Discord Open Research, corrigé et structuré par l’équipe Memoways.
Ressources
Bonus : Accès gratuit aux outils
- Lenny’s Product Pass - Obtenez une année gratuite sur de nombreux outils de vibe coding mentionnés
Outils de vibe coding mentionnés
- Lovable - Plateforme simple pour débuter
- Bolt - Génération rapide d’applications
- Replit - Environnement intermédiaire avec Git
- EasySite - Création de sites simplifiée
- Cursor - IDE professionnel avec IA
- Claude Code - Outil Anthropic pour développeurs
Comparatif d’applications
substack.com