From 22110d3dec139d85348f91d1e81b749139e30ca1 Mon Sep 17 00:00:00 2001 From: Aurelien Mhosaic Date: Sat, 3 Oct 2026 20:04:42 -0400 Subject: [PATCH 1/8] =?UTF-8?q?feat(s2-gc):=20preview=20deck=20for=20s?= =?UTF-8?q?=C3=A9ance=202=20GrafiComp=C3=A9tences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 39 slides from the content/ bricks, order and cuts per D116-D118. Adds a Prompt component (verbatim, copyable prompts); sober slate theme. Co-Authored-By: Claude Opus 5.5 --- index.html | 8 +- src/App.tsx | 2000 +++++++++++++++++++++---------------- src/components/Prompt.tsx | 88 ++ src/styles/tokens.css | 30 +- 4 files changed, 1231 insertions(+), 895 deletions(-) create mode 100644 src/components/Prompt.tsx diff --git a/index.html b/index.html index fb4074a..1118131 100644 --- a/index.html +++ b/index.html @@ -1,15 +1,15 @@ - + - Replace — your deck title + Séance 2 — Utiliser efficacement les modèles de langage - +
diff --git a/src/App.tsx b/src/App.tsx index f209b6d..fe6f61d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,976 +1,1230 @@ +import type { CSSProperties, ReactNode } from 'react'; import Deck from './deck/Deck'; import Slide from './deck/Slide'; import Build from './deck/Build'; import Reveal from './deck/Reveal'; -import Bento from './components/Bento'; -import Split from './components/Split'; -import CountUp from './components/CountUp'; -import TiltCard from './components/TiltCard'; -import Marquee from './components/Marquee'; -import VisualDashboard from './components/VisualDashboard'; -import StatGrid from './components/StatGrid'; -import Accordion from './components/Accordion'; -import Comparison from './components/Comparison'; -import Tabs from './components/Tabs'; -import Timeline from './components/Timeline'; -import CodeWindow from './components/CodeWindow'; -import BrowserFrame from './components/BrowserFrame'; -import SpotlightCard from './components/SpotlightCard'; -import { BarChart, LineChart, DonutChart } from './components/Charts'; -import Section from './components/Section'; -import Quote from './components/Quote'; -import Pricing from './components/Pricing'; -import Steps from './components/Steps'; -import Agenda from './components/Agenda'; -import Team from './components/Team'; import Cover from './components/Cover'; -import BigNumber from './components/BigNumber'; -import Contrast from './components/Contrast'; -import Chat from './components/Chat'; +import Steps from './components/Steps'; import Table from './components/Table'; -import Globe from './components/Globe'; - -/* ══════════════════════════════════════════════════════════════════════ - ⚠️ THROWAWAY DEMO showing every component. DELETE these slides and AUTHOR - THE USER'S DECK. Each child of is one slide. Add speaker notes with - notes="…" on any slide (shown in presenter mode — press P). - ══════════════════════════════════════════════════════════════════════ */ -const panel = (extra = 0.22): React.CSSProperties => ({ - position: 'absolute', - inset: 0, - background: `radial-gradient(120% 100% at 30% 20%, color-mix(in srgb, var(--primary) ${ - extra * 100 - }%, transparent), transparent 60%), var(--surface-2)`, -}); -const card: React.CSSProperties = { - padding: 22, +import Timeline from './components/Timeline'; +import Prompt from './components/Prompt'; + +/* Séance 2 — GrafiCompétences (L'Établi de l'IA). PREVIEW v1. + Sources: content/ bricks of séance 2, order and cuts per D116–D118. + Facilitator notes live in `notes` (presenter mode, P). */ + +const n = (...bullets: string[]) => bullets.map((b) => '• ' + b).join('\n'); + +const card: CSSProperties = { + padding: 'clamp(14px, 1.8vw, 22px)', borderRadius: 'var(--radius)', - background: 'var(--surface)', - border: '1px solid var(--hair)', + textAlign: 'left', +}; +const small: CSSProperties = { + fontSize: 'clamp(13px, 1.25vw, 17px)', + color: 'var(--fg-muted)', + lineHeight: 1.45, +}; +const h3: CSSProperties = { + fontSize: 'clamp(17px, 1.7vw, 23px)', + marginBottom: 8, }; +const gap = (g = 20): CSSProperties => ({ marginTop: g }); +const stack: CSSProperties = { display: 'grid', gap: 10, alignContent: 'start' }; -export default function App() { +function Head({ + k, + title, + sub, + center, +}: { + k?: string; + title: ReactNode; + sub?: ReactNode; + center?: boolean; +}) { + const c: CSSProperties = center + ? { textAlign: 'center', marginInline: 'auto' } + : {}; return ( - - {/* Cover */} - Bolt Slides} - subtitle="A responsive React deck engine. Delete this and build the real one." - foot="June 2026 · Component demo" - /> - - {/* Statement + click-build */} - + {k && ( +
+ {k} +
+ )} +

-

- Dashboards are everywhere.{' '} - Insight isn't. -

- -

- Bolt Slides turns raw events into answers — automatically. -

-
-
+ {title} + + {sub && ( +

+ {sub} +

+ )} + + ); +} - {/* Agenda */} - +function Card({ + title, + children, + style, +}: { + title?: ReactNode; + children?: ReactNode; + style?: CSSProperties; +}) { + return ( +
+ {title &&

{title}

} + {children &&
{children}
} +
+ ); +} - {/* Contrast — the problem */} - + +
+ > +
+

{setup}

+ + {before} + +
+ Il manquait : + {missing.map((m) => ( + + {m} + + ))} +
+ +

{takeaway}

+
+
+ + {after} + +
+ + ); +} - {/* Split feature */} - + {/* ── Ouverture ─────────────────────────────────────────── */} + - Everything, as it happens. - - } - body="Live metrics with sub-second latency — no pipelines to babysit." - media={ - <> -
-
- - - -
+ Utiliser efficacement{' '} + les modèles de langage } + subtitle="Lire un prompt, le construire, dialoguer avec le modèle — sur vos vraies tâches." + foot="En direct sur Zoom · 3 h 30" + notes={n( + 'Accueil 15 min : présence, LibreChat, tour de micro.', + 'Vérifier la veille que LibreChat répond (GPT-5.5 ; les modèles Claude répondaient 403 le 3 oct).' + )} /> - {/* Bento */} - , - body: 'events / min at peak.', - c: 5, - r: 2, - variant: 'glow', - }, - { - k: 'Uptime', - fig: , - c: 4, - }, - { k: 'Regions', fig: , c: 3, variant: 'accent' }, - { - k: 'Connectors', - title: '120+ native', - body: 'Snowflake, Kafka, dbt…', - c: 4, - }, - { k: 'Compliance', title: 'SOC 2 · HIPAA', c: 3 }, + - {/* Globe */} - - Everywhere your data lives. - - } - body="Ingest close to the source; answer from the nearest edge." - markers={[ - { - location: [37.77, -122.41], - size: 0.08, - label: 'sfo1', - value: '221k evt/s', - }, - { location: [40.71, -74.0], size: 0.08 }, - { - location: [51.5, -0.12], - size: 0.07, - label: 'lhr1', - value: '188k evt/s', - }, - { location: [52.52, 13.4], size: 0.05 }, - { - location: [1.35, 103.82], - size: 0.07, - label: 'sin1', - value: '96k evt/s', - }, - { location: [35.68, 139.69], size: 0.06 }, - { location: [-33.87, 151.2], size: 0.05 }, - { location: [-23.55, -46.63], size: 0.05 }, - ]} - arcs={[ - { from: [37.77, -122.41], to: [51.5, -0.12] }, - { from: [51.5, -0.12], to: [1.35, 103.82] }, - { from: [37.77, -122.41], to: [-23.55, -46.63] }, - ]} - stats={[ - { value: '48%', label: 'North America' }, - { value: '31%', label: 'EMEA' }, - { value: '21%', label: 'APAC + LATAM' }, - ]} - /> + + +
+ Le mauvais prompt, ensemble. + Comment il lit, les six cases, l'ordre, l'exemple. + Continuer la conversation, faire critiquer, la boucle. + Trois situations, les images IA floues. + Avant/après en sous-groupes, puis le Promptuaire. +
+

+ Gardez sous la main votre tâche réelle — celle de + l'inventaire de la séance 1. +

+
- {/* StatGrid — traction */} - , - label: 'ARR', - caption: 'up 3× year over year', - }, - { - value: , - label: 'Net retention', - caption: 'best in class', - }, - { - value: , - label: 'Enterprise logos', - caption: 'across six industries', - }, - ]} - /> + {/* ── Vivre le mauvais prompt (brique 2) ───────────────── */} + + +
+ Écris un courriel de suivi à un client. +
+ +

+ À main levée : l'enverriez-vous telle quelle, aujourd'hui, + à un vrai client ? +

+
+
- {/* BigNumber */} - } - caption="events answered in under a second." - foot="Production traffic, trailing 30 days" - /> + + +
+ + {`Objet : Suivi de notre échange - {/* Section divider */} -
- How it works. - - } - /> +Bonjour [Nom du client], - {/* Steps */} - +Je fais suite à notre échange concernant [projet]. N'hésitez pas à me faire part de vos questions. - {/* Chat */} - +Cordialement, +[Votre nom]`} + + + {`Bonjour, + +J'espère que vous allez bien ! Je souhaitais prendre de vos nouvelles et m'assurer que tout se passe bien de votre côté. Au plaisir de poursuivre notre belle collaboration.`} + + + {`Avec plaisir ! Pouvez-vous me préciser le client, l'objet du suivi et le ton souhaité ?`} + +
+ +

+ Aucune ne contient un seul fait vrai sur votre client. +

+
+
- {/* Comparison */} - -
- Why teams switch + + Qu'est-ce qui manquait ? + + } + center + /> +

+ Chacun, au micro, nomme une information que le modèle aurait + eu besoin de savoir. +

+

+ Je note tout, en vrac, sur mon écran. +

+ + + {/* ── Comment un LLM lit votre prompt (brique 3) ───────── */} + + + Il lit tout. Il ne devine rien. + + } + center + /> +
+ + + Votre message, les documents fournis, l'historique. Pas votre intention, + pas votre contexte d'entreprise, pas « ce qui va de soi ». + + + + + À partir du plus probable. Une réponse générique est presque toujours le + symptôme d'un prompt qui ne disait pas assez. + + +
+ +

+ Dire = obtenir. Avant de blâmer l'outil, relisez ce que vous lui avez + réellement donné. +

+
+
+ + {/* ── L'anatomie d'un prompt (brique 4) ─────────────────── */} + + +
+ {SIX.map(([c, q], i) => ( + + {q} + + ))} +
+
+ + + +
+
+ + Pas un sujet, une commande. + + + Aide-moi avec le devis du client Tremblay. + + + Rédige le courriel qui accompagne le devis du client Tremblay. +
-

- The honest comparison. -

- - -
- +
+ + La case qui transforme un texte passe-partout en votre texte. + + + Je suis chargée de projet dans un atelier d'impression d'une douzaine de + personnes. Le client est un organisme communautaire qui commande chez nous + pour la première fois, et le budget est serré. +
- +
+ + Le contexte se décrit, la source se colle. + + + Voici le devis en question : […]. Voici aussi un courriel du même type que + j'ai envoyé le mois dernier et qui a bien fonctionné : […]. + +
+
- {/* Tabs */} - -
- One platform + +
+
+ + La moitié « éviter » est presque toujours oubliée. + + + 150 mots maximum. Aucun terme technique de prépresse. N'annonce aucun délai + de livraison : ils ne sont pas confirmés. +
-

- Built for every team. -

- - - - Trace any request end-to-end, alert on anomalies, ship with - confidence. -

- ), - }, - { - label: 'Data', - content: ( -
- -
- ), - }, - { - label: 'Ops', - content: ( -

- One source of truth for uptime, cost, and capacity — no - spreadsheets. -

- ), - }, - ]} - /> -
+
+ + Un texte utilisable tel quel, pas un pavé à découper. + + + Un courriel : une ligne d'objet, deux courts paragraphes, une liste de trois + points pour les options, une phrase de clôture. + +
+
+ + Le modèle reçoit sa propre grille de correction. + + + C'est réussi si le client comprend ce qu'il paie sans avoir besoin de me + rappeler. + +
+
- {/* Split + code */} - - Drop-in simple. - - } - body="Add it to your app in three lines. No SDK to learn, no schema to define." - media={ - <> -
-
- + + +
+
+ + Écris une publication LinkedIn sur l'impression grand format. + +

+ Un texte lisse sur l'impression grand format en général. Rien qui vous + appartienne. +

+
+ +
+ + Écris une publication LinkedIn pour annoncer que notre atelier lance un + service d'impression grand format. + +

+ Enfin une annonce, mais interchangeable : n'importe quel atelier du Québec + pourrait la publier. +

- - } - /> +
+
+
- {/* Browser frame */} - -
- See it live + +
+
+ + Même demande. Contexte : atelier d'impression d'une douzaine de personnes, + région de Québec ; nos clients sont des chargés de marketing de PME qui + n'ont pas de designer à l'interne. Contraintes : 120 mots maximum, aucun + superlatif, ne promets aucun délai. + +

+ Le texte s'adresse enfin à quelqu'un ; les promesses vagues disparaissent. +

-

- Your data, one screen. -

- - -
- -
-
-
- Bolt Slides -
- {['Overview', 'Events', 'Funnels', 'Cohorts', 'Settings'].map( - (n, i) => ( -
- {n} -
- ) - )} -
-
-
-

- Overview -

- Last 30 days -
-
- {[ - ['Revenue', '$1.24M', '▲ 18.2%'], - ['Active users', '48,210', '▲ 9.4%'], - ['Churn', '1.9%', '▼ 0.6%'], - ].map(([l, v, d]) => ( -
-
- {l} -
-
- {v} -
-
- {d} -
-
- ))} -
-
- -
-
-
-
+ +
+ + Même demande, mêmes contraintes. Voici la fiche technique du service : + […]. Voici une de nos publications qui a bien fonctionné, pour le ton : + […]. Format : une accroche d'une ligne, trois puces (ce que ça permet, + pour qui, comment nous joindre), une question de clôture. C'est réussi si + un chargé de marketing comprend en dix secondes s'il est concerné. + +

+ Vos vraies capacités, votre ton, la bonne forme : il reste une relecture. +

+
+
+
+ +

+ Entre la version 1 et la version 4, le modèle n'a pas changé.{' '} + Vous, oui. +

+
+ + + {/* ── Ordre, clarté, contradictions (brique 5) ─────────── */} + + +
+
+ + Coller trois pages de brief, puis écrire à la fin « peux-tu m'aider avec + ça ? ». Le modèle reçoit surtout un document… et il le résume. + + + La consigne en tête, la matière encadrée au milieu, la contrainte qui + compte vraiment rappelée à la fin. +
- + + {`Rédige la réponse au brief ci-dessous, en 200 mots, pour un client sans culture graphique. + +— DÉBUT DU BRIEF — […] — FIN DU BRIEF — + +Rappel : 200 mots, aucun jargon.`} + +
- {/* Charts */} + + +
+
+ + Une ambiguïté n'est jamais signalée : elle est tranchée en silence. Une + phrase, une intention. + + + Fais un truc court pour le client. + + + Réécris ce texte de 250 mots en 90 mots pour le verso d'une carte postale + 5 × 7 po, sans retirer les coordonnées ni changer les faits. + +
+
+ + « Sois exhaustif mais reste bref » donne un compromis mou. Ne supprimez pas + la tension : dites laquelle l'emporte. + + + Priorité à la brièveté : 120 mots maximum. Si tu dois couper, coupe les + détails techniques avant les coordonnées. + +
+
+
+ + {/* ── Montrez-lui un exemple (brique 6) ─────────────────── */} + + +
+ + Écris un courriel de relance à un client. Ton professionnel mais chaleureux, + dynamique sans être insistant, concis mais complet. + + + {`Voici un courriel de relance que j'ai envoyé le mois dernier et qui a bien fonctionné : + +[collez le courriel au complet] + +Écris le prochain, dans le même ton et la même structure, pour le dossier suivant : […].`} + +
+
+ + Pour trier des demandes, montrez deux ou trois cas déjà classés. + + + On le colle, on ne le décrit pas. Un contre-exemple marche aussi. + +
+
+ + {/* ── Le prompt comme conversation (brique 7) ───────────── */} + + + On ne recommence pas : on continue. + + } + sub="La première réponse est un premier jet. La conversation contient déjà votre contexte." + center + /> +
+
+ Ajouter la case qui manquait. + + Reprends ta réponse. Le destinataire est le propriétaire de l'entreprise, + pas son équipe marketing. + +
+
+ Resserrer ce qui a débordé. + + Trop long et trop enthousiaste. Refais-le en 120 mots, sans superlatif, en + gardant les trois options. + +
+
+ Demander une variante. + + Donne-moi deux autres versions de l'accroche : une plus directe, une plus + formelle. + +
+
+
+ + + +
+ + Une conversation qui a servi à un devis traîne son vocabulaire. + + + Il tourne en rond ou se contredit : repartez avec un prompt propre, enrichi de + ce que vous avez appris. + +
+ +

+ Arrivé à la bonne sortie ? Le chemin parcouru est la version réutilisable de + votre prompt : une entrée pour votre Promptuaire. +

+
+
+ + {/* ── Pause ─────────────────────────────────────────────── */} + +
Pause
+

+ 10 min +

+

+ On reprend avec la critique. +

+
+ + {/* ── Faire critiquer sa propre sortie (brique 8) ───────── */} - -
- The numbers + +
+
+ + + Relis ta réponse. Qu'est-ce qui lui manque pour être utilisable telle quelle + par [le destinataire] ? Liste les manques, ne réécris rien pour l'instant. +
-

- Growth you can see. -

- - -
-
-
- Weekly active -
-
- -
-
-
-
- Revenue -
- -
-
- -
+
+ + + Donne-moi les trois faiblesses les plus sérieuses de ce texte, du point de + vue de [le destinataire]. Puis produis une version corrigée qui les règle. + +
+
+ + + Évalue ce texte sur 10 selon ces critères : [vos critères]. Justifie la note + critère par critère, puis écris la version qui obtiendrait 10. +
- +
+ + + + + Un deuxième avis, pas un audit + + } + sub="La critique sort de la même machine : utile pour un oubli ou une lourdeur, incapable de dire qu'un prix est faux. Les faits, les chiffres et les engagements restent à votre œil de métier." + center + /> + +
+ + « On pourrait ajouter un mot de transition. » + + + Prenez la version que vous aviez deux tours plus tôt. + +
+
+
+ + {/* ── La boucle, version courte (brique 15) ─────────────── */} + + + {/* ── Trois situations (brique 9, GC) ──────────────────── */} + + +
+ +50 %}> + de publicités produites par personne, dans des équipes marketing, grâce à + l'IA. + + 17 %}> + des tâches en arts, design et médias suscitent l'envie de les confier à l'IA + : les créatifs sont les moins pressés. + +
+

+ Source : Stanford AI Index 2026, p. 219 et 226. +

- {/* Data table */} + + + + + + + {/* ── Les images IA floues (brique 11, GC) ──────────────── */} - -
- Unit economics -
-

+
+
Le diagnostic en trente secondes
+

- Growth, by region. -

-
- + taille (cm) = pixels ÷ ppp × 2,54 +

+

+ 1024 px à 300 ppp → 8,7 cm (petite étiquette) · à 150 ppp, + grand format vu de loin → 17 cm +

+
+ + + + +
- + + +

+ Premier réflexe : « avez-vous gardé votre prompt ? » +

+
- {/* Timeline */} - -
- Where we're going + +
+ + Décris ce logo pour qu'un graphiste puisse le redessiner à l'identique : les + formes et leur disposition, chaque couleur avec son code hexadécimal, le style + (plat, dégradé, 3D, photo), le texte exact et sa place. Ne donne pas ton avis, + décris. + +
+ +
+

Relisez le texte qu'elle a lu

+

+ Lors de nos essais, « boulangerie de quartier » est devenu « boutonnerie de + quartier ». Le texte exact, c'est le client qui le confirme. +

-

- The roadmap. -

- -
+ + + + + +
+
+
    +
  • + Objectif : un logo imprimable, pas une jolie image. +
  • +
  • + Contexte : il servira sur [une étiquette, un autocollant + de 1,2 m, une carte]. +
  • +
  • + Contraintes : style plat, 2 à 4 couleurs franches (les + codes relevés), aucun dégradé, aucune ombre, bords nets, fond blanc uni. +
  • +
  • + Format : un fichier SVG, aux dimensions finales. +
  • +
  • + Critères : chaque forme se distingue nettement de sa + voisine. +
  • +
+
+
+ + Demandez-le « dans un bloc de code » : un bouton Copier apparaît. Collez-le + dans un fichier texte, enregistrez en .svg, ouvrez-le dans votre logiciel : + net à 5 cm comme à 5 m. + + + Bien sur un logo simple, fait de formes et d'aplats. Une image regénérée + reste une image : 768 px, soit 6,5 cm à 300 ppp. + +
+
+
+ + + +
+ + Un courriel de cinq lignes : ce qui n'allait pas, ce que vous proposez, la + question qui manque (« quelle police ? »). + + + VTracer et Potrace transforment une image en tracés vectoriels. + + + Upscayl et Real-ESRGAN reconstruisent les détails. Les brancher à votre outil + d'IA : séance 4. + +
+
+ + {/* ── Avant/après en sous-groupes (brique 12, reformatée D116–D118) ── */} + + +
- {/* Pricing */} - + +
+ {[1, 2, 3, 4].map((i) => ( +
+ Sous-groupe {i} +

Cas {i}

+

Tiré de votre inventaire S1 — à compléter.

+
+ ))} +
+ + + - {/* Spotlight principles */} - - -
- What we believe -
-

- Three principles. -

-
- -
- {[ - { - k: '01', - t: 'Fast by default', - d: 'Speed is a feature. Everything is realtime.', - }, - { - k: '02', - t: 'Yours to own', - d: 'Your data, your infra, no lock-in.', - }, - { - k: '03', - t: 'Honest pricing', - d: 'No per-seat tax. Scale without surprises.', - }, - ].map((p) => ( - -
- {p.k} -
-

- {p.t} -

-

- {p.d} -

-
- ))} -
-
+ + +
+ {SIX.map(([c, q]) => ( +
+
+ {c} +
+

{q}

+
+
+ ))} +
- {/* Accordion — FAQ */} + {/* ── Ancrer : le Promptuaire ───────────────────────────── */} - -
- Common questions -
-

- Frequently asked. -

-
- -
- -
-
-
- - {/* Team */} - + + Chaque prompt « après » entre au{' '} + Promptuaire + + } + center + /> +
+ Sous la tâche métier de l'inventaire. + + Noms de clients remplacés par des crochets, pour qu'un collègue le reprenne. + + Ce qu'il produit. +
+

+ Avant de quitter : trois questions, deux minutes, sur la plateforme. +

+ - {/* Logos */} - -
- Trusted by teams everywhere -
-
- +
+ {[1, 2, 3].map((i) => ( +
+

« … »

+

+ — prénom, cas {i} +

+
+ ))} +
- {/* Quote */} - + {/* ── Pont vers la séance 3 (briques 18, 19) ────────────── */} + + + Et si le modèle connaissait{' '} + vos documents ? + + } + sub="À chaque fois, il faut tout réexpliquer : vos gabarits, vos façons de faire. À la séance 3, on lui donne la matière." + center + /> + +

+ Une seule chose à faire d'ici là :{' '} + choisir les documents que vous nous envoyez. +

+
+
+ + + +
+
+ +

+ Dans le doute sur le niveau : on n'envoie pas, on pose la question. +

+ - {/* CTA */} - -

- Let's talk. -

-

- hello@bolt.new -

-
+ + Votre matière, pour la séance 3 + + } + center + /> +
+ + 1 ou 2 documents que vous touchez chaque semaine : gabarit de soumission, + procédure, guide de normes. PDF, Word, Excel — pas de PDF scanné ni de .indd. + + + Par courriel, à l'adresse du courriel de fin de séance.{' '} + Une semaine avant la séance 3. + + + On refait le tri à la réception et on réécrit les exercices sur votre + matière. Vos documents restent dans votre cohorte. + +
+

+ Merci — à la séance 3. +

); diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx new file mode 100644 index 0000000..51e820f --- /dev/null +++ b/src/components/Prompt.tsx @@ -0,0 +1,88 @@ +import { useState, type MouseEvent, type ReactNode } from 'react'; + +/* A prompt to copy, verbatim: mono text in a bordered box with a copy button. + `label` names it (e.g. "Version 1"); `muted` styles a "before" prompt. */ +export default function Prompt({ + children, + label = 'Prompt à copier', + muted, + size = 'md', +}: { + children: ReactNode; + label?: string; + muted?: boolean; + size?: 'sm' | 'md' | 'lg'; +}) { + const [copied, setCopied] = useState(false); + const fontSize = { + sm: 'clamp(11.5px, 1.05vw, 15px)', + md: 'clamp(13px, 1.3vw, 18px)', + lg: 'clamp(16px, 2vw, 28px)', + }[size]; + const copy = (e: MouseEvent) => { + const text = + e.currentTarget.parentElement?.parentElement?.querySelector('pre') + ?.innerText ?? ''; + navigator.clipboard?.writeText(text).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 1500); + }); + }; + return ( +
+
+ {label} + +
+
+        {children}
+      
+
+ ); +} diff --git a/src/styles/tokens.css b/src/styles/tokens.css index a46a563..fcb1e5b 100644 --- a/src/styles/tokens.css +++ b/src/styles/tokens.css @@ -2,35 +2,29 @@ THEME TOKENS — edit ONLY this :root block to re-theme the whole deck. The SAME token vocabulary as the `slides` (Slidev) and `webdeck` skills, so a brand looks identical across all of them. The values below are a - "dark product" default — REPLACE them to match the brief or the brand. + sober dark slate with one muted blue accent (Zoom screen-share readability). =================================================================== */ :root { /* Surfaces */ - --bg: #05070a; - --bg-grad-1: rgba(79, 229, 176, 0.12); - --bg-grad-2: rgba(41, 192, 240, 0.08); + --bg: #0f141b; + --bg-grad-1: rgba(120, 160, 230, 0.07); + --bg-grad-2: rgba(120, 160, 230, 0.04); --surface: rgba(255, 255, 255, 0.045); --surface-2: rgba(255, 255, 255, 0.07); /* Ink */ - --fg: #f4f7fa; - --fg-muted: #9aa4b2; - --fg-faint: #626d7d; + --fg: #f2f4f7; + --fg-muted: #b4bcc8; + --fg-faint: #7d8796; /* Hairlines */ --hair: rgba(255, 255, 255, 0.1); --hair-2: rgba(255, 255, 255, 0.06); /* Accent — ONE accent. May be a 2–4 stop gradient within ~80° of hue. */ - --primary: #4fe5b0; - --accent: linear-gradient( - 115deg, - #c8f56e 0%, - #5aeeb0 42%, - #34dcd8 74%, - #29c0f0 100% - ); - --accent-ink: #04140e; + --primary: #8db4f2; + --accent: linear-gradient(115deg, #a9c8f7 0%, #8db4f2 60%, #7aa2e8 100%); + --accent-ink: #0b1220; /* Radius */ --radius: 22px; @@ -39,7 +33,7 @@ /* Depth */ --shadow: 0 50px 120px -50px rgba(0, 0, 0, 0.9); - --glow: 0 30px 80px -40px rgba(79, 229, 176, 0.5); + --glow: 0 30px 80px -40px rgba(141, 180, 242, 0.35); /* Type */ --font-head: 'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Display', @@ -54,7 +48,7 @@ --ease-spring: cubic-bezier(0.16, 1, 0.3, 1); /* Atmosphere */ - --grain: 0.035; + --grain: 0.02; --vignette: rgba(0, 0, 0, 0.22); /* Responsive gutter — fluid side / vertical padding */ From 94b8c749f16034098e16fd886227f0fac2154902 Mon Sep 17 00:00:00 2001 From: Aurelien Mhosaic Date: Mon, 5 Oct 2026 18:24:59 -0400 Subject: [PATCH 2/8] feat(decks): one deck per folder, picked by the first path segment src/decks//index.tsx served at // (default s2-gc). The path, not a query, because the presenter tab keeps only ?presenter=1. Third-party media in public/media/ stay out of git. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 3 +- src/App.tsx | 1243 +------------------------------------ src/decks/s2-gc/index.tsx | 1231 ++++++++++++++++++++++++++++++++++++ src/vite-env.d.ts | 1 + 4 files changed, 1252 insertions(+), 1226 deletions(-) create mode 100644 src/decks/s2-gc/index.tsx create mode 100644 src/vite-env.d.ts diff --git a/.gitignore b/.gitignore index da431f1..6ffddfb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -node_modules/ +node_modules dist/ .vite/ .DS_Store .env +public/media/ diff --git a/src/App.tsx b/src/App.tsx index fe6f61d..55cbe4a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,1231 +1,24 @@ -import type { CSSProperties, ReactNode } from 'react'; -import Deck from './deck/Deck'; -import Slide from './deck/Slide'; -import Build from './deck/Build'; -import Reveal from './deck/Reveal'; -import Cover from './components/Cover'; -import Steps from './components/Steps'; -import Table from './components/Table'; -import Timeline from './components/Timeline'; -import Prompt from './components/Prompt'; +import { lazy, Suspense } from 'react'; +import type { ComponentType } from 'react'; -/* Séance 2 — GrafiCompétences (L'Établi de l'IA). PREVIEW v1. - Sources: content/ bricks of séance 2, order and cuts per D116–D118. - Facilitator notes live in `notes` (presenter mode, P). */ - -const n = (...bullets: string[]) => bullets.map((b) => '• ' + b).join('\n'); - -const card: CSSProperties = { - padding: 'clamp(14px, 1.8vw, 22px)', - borderRadius: 'var(--radius)', - textAlign: 'left', -}; -const small: CSSProperties = { - fontSize: 'clamp(13px, 1.25vw, 17px)', - color: 'var(--fg-muted)', - lineHeight: 1.45, -}; -const h3: CSSProperties = { - fontSize: 'clamp(17px, 1.7vw, 23px)', - marginBottom: 8, -}; -const gap = (g = 20): CSSProperties => ({ marginTop: g }); -const stack: CSSProperties = { display: 'grid', gap: 10, alignContent: 'start' }; - -function Head({ - k, - title, - sub, - center, -}: { - k?: string; - title: ReactNode; - sub?: ReactNode; - center?: boolean; -}) { - const c: CSSProperties = center - ? { textAlign: 'center', marginInline: 'auto' } - : {}; - return ( - - {k && ( -
- {k} -
- )} -

- {title} -

- {sub && ( -

- {sub} -

- )} -
- ); -} - -function Card({ - title, - children, - style, -}: { - title?: ReactNode; - children?: ReactNode; - style?: CSSProperties; -}) { - return ( -
- {title &&

{title}

} - {children &&
{children}
} -
- ); -} - -const SIX = [ - ['Objectif', "Qu'est-ce que je veux obtenir, concrètement ?"], - ['Contexte', 'Que doit-il savoir (mon rôle, la situation, le destinataire) ?'], - ['Sources', "Sur quoi doit-il s'appuyer (document, données, exemple) ?"], - ['Contraintes', 'Que doit-il respecter — et éviter ?'], - ['Format', 'Quelle forme pour la sortie (liste, tableau, courriel, longueur) ?'], - ['Critères de qualité', 'À quoi reconnaîtrai-je que c’est bon ?'], -]; - -function Situation({ - n: num, - title, - setup, - before, - missing, - after, - takeaway, -}: { - n: number; - title: string; - setup: string; - before: string; - missing: string[]; - after: string; - takeaway: string; - nav?: string; - notes?: string; -}) { - return ( - - -
-
-

{setup}

- - {before} - -
- Il manquait : - {missing.map((m) => ( - - {m} - - ))} -
- -

{takeaway}

-
-
- - {after} - -
-
- ); -} +/* One deck per folder in src/decks//index.tsx, picked by the first path + segment (/fb-s1/ → src/decks/fb-s1). The path, not a ?query, because the + presenter tab is opened as pathname + '?presenter=1' and keeps only that. */ +const decks = import.meta.glob<{ default: ComponentType }>('./decks/*/index.tsx'); +const id = window.location.pathname.split('/').filter(Boolean)[0] || 's2-gc'; +const load = decks[`./decks/${id}/index.tsx`]; +const Deck = load ? lazy(load) : null; export default function App() { + if (!Deck) + return ( +

+ Unknown deck « {id} ». Decks: {Object.keys(decks).map((k) => k.split('/')[2]).join(', ')} +

+ ); return ( - - {/* ── Ouverture ─────────────────────────────────────────── */} - - Utiliser efficacement{' '} - les modèles de langage - - } - subtitle="Lire un prompt, le construire, dialoguer avec le modèle — sur vos vraies tâches." - foot="En direct sur Zoom · 3 h 30" - notes={n( - 'Accueil 15 min : présence, LibreChat, tour de micro.', - 'Vérifier la veille que LibreChat répond (GPT-5.5 ; les modèles Claude répondaient 403 le 3 oct).' - )} - /> - - - - - -
- Le mauvais prompt, ensemble. - Comment il lit, les six cases, l'ordre, l'exemple. - Continuer la conversation, faire critiquer, la boucle. - Trois situations, les images IA floues. - Avant/après en sous-groupes, puis le Promptuaire. -
-

- Gardez sous la main votre tâche réelle — celle de - l'inventaire de la séance 1. -

-
- - {/* ── Vivre le mauvais prompt (brique 2) ───────────────── */} - - -
- Écris un courriel de suivi à un client. -
- -

- À main levée : l'enverriez-vous telle quelle, aujourd'hui, - à un vrai client ? -

-
-
- - - -
- - {`Objet : Suivi de notre échange - -Bonjour [Nom du client], - -Je fais suite à notre échange concernant [projet]. N'hésitez pas à me faire part de vos questions. - -Cordialement, -[Votre nom]`} - - - {`Bonjour, - -J'espère que vous allez bien ! Je souhaitais prendre de vos nouvelles et m'assurer que tout se passe bien de votre côté. Au plaisir de poursuivre notre belle collaboration.`} - - - {`Avec plaisir ! Pouvez-vous me préciser le client, l'objet du suivi et le ton souhaité ?`} - -
- -

- Aucune ne contient un seul fait vrai sur votre client. -

-
-
- - - - Qu'est-ce qui manquait ? - - } - center - /> -

- Chacun, au micro, nomme une information que le modèle aurait - eu besoin de savoir. -

-

- Je note tout, en vrac, sur mon écran. -

-
- - {/* ── Comment un LLM lit votre prompt (brique 3) ───────── */} - - - Il lit tout. Il ne devine rien. - - } - center - /> -
- - - Votre message, les documents fournis, l'historique. Pas votre intention, - pas votre contexte d'entreprise, pas « ce qui va de soi ». - - - - - À partir du plus probable. Une réponse générique est presque toujours le - symptôme d'un prompt qui ne disait pas assez. - - -
- -

- Dire = obtenir. Avant de blâmer l'outil, relisez ce que vous lui avez - réellement donné. -

-
-
- - {/* ── L'anatomie d'un prompt (brique 4) ─────────────────── */} - - -
- {SIX.map(([c, q], i) => ( - - {q} - - ))} -
-
- - - -
-
- - Pas un sujet, une commande. - - - Aide-moi avec le devis du client Tremblay. - - - Rédige le courriel qui accompagne le devis du client Tremblay. - -
-
- - La case qui transforme un texte passe-partout en votre texte. - - - Je suis chargée de projet dans un atelier d'impression d'une douzaine de - personnes. Le client est un organisme communautaire qui commande chez nous - pour la première fois, et le budget est serré. - -
-
- - Le contexte se décrit, la source se colle. - - - Voici le devis en question : […]. Voici aussi un courriel du même type que - j'ai envoyé le mois dernier et qui a bien fonctionné : […]. - -
-
-
- - - -
-
- - La moitié « éviter » est presque toujours oubliée. - - - 150 mots maximum. Aucun terme technique de prépresse. N'annonce aucun délai - de livraison : ils ne sont pas confirmés. - -
-
- - Un texte utilisable tel quel, pas un pavé à découper. - - - Un courriel : une ligne d'objet, deux courts paragraphes, une liste de trois - points pour les options, une phrase de clôture. - -
-
- - Le modèle reçoit sa propre grille de correction. - - - C'est réussi si le client comprend ce qu'il paie sans avoir besoin de me - rappeler. - -
-
-
- - - -
-
- - Écris une publication LinkedIn sur l'impression grand format. - -

- Un texte lisse sur l'impression grand format en général. Rien qui vous - appartienne. -

-
- -
- - Écris une publication LinkedIn pour annoncer que notre atelier lance un - service d'impression grand format. - -

- Enfin une annonce, mais interchangeable : n'importe quel atelier du Québec - pourrait la publier. -

-
-
-
-
- - - -
-
- - Même demande. Contexte : atelier d'impression d'une douzaine de personnes, - région de Québec ; nos clients sont des chargés de marketing de PME qui - n'ont pas de designer à l'interne. Contraintes : 120 mots maximum, aucun - superlatif, ne promets aucun délai. - -

- Le texte s'adresse enfin à quelqu'un ; les promesses vagues disparaissent. -

-
- -
- - Même demande, mêmes contraintes. Voici la fiche technique du service : - […]. Voici une de nos publications qui a bien fonctionné, pour le ton : - […]. Format : une accroche d'une ligne, trois puces (ce que ça permet, - pour qui, comment nous joindre), une question de clôture. C'est réussi si - un chargé de marketing comprend en dix secondes s'il est concerné. - -

- Vos vraies capacités, votre ton, la bonne forme : il reste une relecture. -

-
-
-
- -

- Entre la version 1 et la version 4, le modèle n'a pas changé.{' '} - Vous, oui. -

-
-
- - {/* ── Ordre, clarté, contradictions (brique 5) ─────────── */} - - -
-
- - Coller trois pages de brief, puis écrire à la fin « peux-tu m'aider avec - ça ? ». Le modèle reçoit surtout un document… et il le résume. - - - La consigne en tête, la matière encadrée au milieu, la contrainte qui - compte vraiment rappelée à la fin. - -
- - {`Rédige la réponse au brief ci-dessous, en 200 mots, pour un client sans culture graphique. - -— DÉBUT DU BRIEF — […] — FIN DU BRIEF — - -Rappel : 200 mots, aucun jargon.`} - -
-
- - - -
-
- - Une ambiguïté n'est jamais signalée : elle est tranchée en silence. Une - phrase, une intention. - - - Fais un truc court pour le client. - - - Réécris ce texte de 250 mots en 90 mots pour le verso d'une carte postale - 5 × 7 po, sans retirer les coordonnées ni changer les faits. - -
-
- - « Sois exhaustif mais reste bref » donne un compromis mou. Ne supprimez pas - la tension : dites laquelle l'emporte. - - - Priorité à la brièveté : 120 mots maximum. Si tu dois couper, coupe les - détails techniques avant les coordonnées. - -
-
-
- - {/* ── Montrez-lui un exemple (brique 6) ─────────────────── */} - - -
- - Écris un courriel de relance à un client. Ton professionnel mais chaleureux, - dynamique sans être insistant, concis mais complet. - - - {`Voici un courriel de relance que j'ai envoyé le mois dernier et qui a bien fonctionné : - -[collez le courriel au complet] - -Écris le prochain, dans le même ton et la même structure, pour le dossier suivant : […].`} - -
-
- - Pour trier des demandes, montrez deux ou trois cas déjà classés. - - - On le colle, on ne le décrit pas. Un contre-exemple marche aussi. - -
-
- - {/* ── Le prompt comme conversation (brique 7) ───────────── */} - - - On ne recommence pas : on continue. - - } - sub="La première réponse est un premier jet. La conversation contient déjà votre contexte." - center - /> -
-
- Ajouter la case qui manquait. - - Reprends ta réponse. Le destinataire est le propriétaire de l'entreprise, - pas son équipe marketing. - -
-
- Resserrer ce qui a débordé. - - Trop long et trop enthousiaste. Refais-le en 120 mots, sans superlatif, en - gardant les trois options. - -
-
- Demander une variante. - - Donne-moi deux autres versions de l'accroche : une plus directe, une plus - formelle. - -
-
-
- - - -
- - Une conversation qui a servi à un devis traîne son vocabulaire. - - - Il tourne en rond ou se contredit : repartez avec un prompt propre, enrichi de - ce que vous avez appris. - -
- -

- Arrivé à la bonne sortie ? Le chemin parcouru est la version réutilisable de - votre prompt : une entrée pour votre Promptuaire. -

-
-
- - {/* ── Pause ─────────────────────────────────────────────── */} - -
Pause
-

- 10 min -

-

- On reprend avec la critique. -

-
- - {/* ── Faire critiquer sa propre sortie (brique 8) ───────── */} - - -
-
- - - Relis ta réponse. Qu'est-ce qui lui manque pour être utilisable telle quelle - par [le destinataire] ? Liste les manques, ne réécris rien pour l'instant. - -
-
- - - Donne-moi les trois faiblesses les plus sérieuses de ce texte, du point de - vue de [le destinataire]. Puis produis une version corrigée qui les règle. - -
-
- - - Évalue ce texte sur 10 selon ces critères : [vos critères]. Justifie la note - critère par critère, puis écris la version qui obtiendrait 10. - -
-
-
- - - - Un deuxième avis, pas un audit - - } - sub="La critique sort de la même machine : utile pour un oubli ou une lourdeur, incapable de dire qu'un prix est faux. Les faits, les chiffres et les engagements restent à votre œil de métier." - center - /> - -
- - « On pourrait ajouter un mot de transition. » - - - Prenez la version que vous aviez deux tours plus tôt. - -
-
-
- - {/* ── La boucle, version courte (brique 15) ─────────────── */} - - - {/* ── Trois situations (brique 9, GC) ──────────────────── */} - - -
- +50 %}> - de publicités produites par personne, dans des équipes marketing, grâce à - l'IA. - - 17 %}> - des tâches en arts, design et médias suscitent l'envie de les confier à l'IA - : les créatifs sont les moins pressés. - -
-

- Source : Stanford AI Index 2026, p. 219 et 226. -

-
- - - - - - - - {/* ── Les images IA floues (brique 11, GC) ──────────────── */} - - -
-
Le diagnostic en trente secondes
-

- taille (cm) = pixels ÷ ppp × 2,54 -

-

- 1024 px à 300 ppp → 8,7 cm (petite étiquette) · à 150 ppp, - grand format vu de loin → 17 cm -

-
-
- - - -
-
- - -

- Premier réflexe : « avez-vous gardé votre prompt ? » -

-
- - - - -
- - Décris ce logo pour qu'un graphiste puisse le redessiner à l'identique : les - formes et leur disposition, chaque couleur avec son code hexadécimal, le style - (plat, dégradé, 3D, photo), le texte exact et sa place. Ne donne pas ton avis, - décris. - -
- -
-

Relisez le texte qu'elle a lu

-

- Lors de nos essais, « boulangerie de quartier » est devenu « boutonnerie de - quartier ». Le texte exact, c'est le client qui le confirme. -

-
-
-
- - - -
-
-
    -
  • - Objectif : un logo imprimable, pas une jolie image. -
  • -
  • - Contexte : il servira sur [une étiquette, un autocollant - de 1,2 m, une carte]. -
  • -
  • - Contraintes : style plat, 2 à 4 couleurs franches (les - codes relevés), aucun dégradé, aucune ombre, bords nets, fond blanc uni. -
  • -
  • - Format : un fichier SVG, aux dimensions finales. -
  • -
  • - Critères : chaque forme se distingue nettement de sa - voisine. -
  • -
-
-
- - Demandez-le « dans un bloc de code » : un bouton Copier apparaît. Collez-le - dans un fichier texte, enregistrez en .svg, ouvrez-le dans votre logiciel : - net à 5 cm comme à 5 m. - - - Bien sur un logo simple, fait de formes et d'aplats. Une image regénérée - reste une image : 768 px, soit 6,5 cm à 300 ppp. - -
-
-
- - - -
- - Un courriel de cinq lignes : ce qui n'allait pas, ce que vous proposez, la - question qui manque (« quelle police ? »). - - - VTracer et Potrace transforment une image en tracés vectoriels. - - - Upscayl et Real-ESRGAN reconstruisent les détails. Les brancher à votre outil - d'IA : séance 4. - -
-
- - {/* ── Avant/après en sous-groupes (brique 12, reformatée D116–D118) ── */} - - -
- -
-
- - - -
- {[1, 2, 3, 4].map((i) => ( -
- Sous-groupe {i} -

Cas {i}

-

Tiré de votre inventaire S1 — à compléter.

-
- ))} -
-
- - - - - -
- {SIX.map(([c, q]) => ( -
-
- {c} -
-

{q}

-
-
- ))} -
- - - {/* ── Ancrer : le Promptuaire ───────────────────────────── */} - - - Chaque prompt « après » entre au{' '} - Promptuaire - - } - center - /> -
- Sous la tâche métier de l'inventaire. - - Noms de clients remplacés par des crochets, pour qu'un collègue le reprenne. - - Ce qu'il produit. -
-

- Avant de quitter : trois questions, deux minutes, sur la plateforme. -

-
- - - -
- {[1, 2, 3].map((i) => ( -
-

« … »

-

- — prénom, cas {i} -

-
- ))} -
-
- - {/* ── Pont vers la séance 3 (briques 18, 19) ────────────── */} - - - Et si le modèle connaissait{' '} - vos documents ? - - } - sub="À chaque fois, il faut tout réexpliquer : vos gabarits, vos façons de faire. À la séance 3, on lui donne la matière." - center - /> - -

- Une seule chose à faire d'ici là :{' '} - choisir les documents que vous nous envoyez. -

-
-
- - - -
-
- -

- Dans le doute sur le niveau : on n'envoie pas, on pose la question. -

- - - - - Votre matière, pour la séance 3 - - } - center - /> -
- - 1 ou 2 documents que vous touchez chaque semaine : gabarit de soumission, - procédure, guide de normes. PDF, Word, Excel — pas de PDF scanné ni de .indd. - - - Par courriel, à l'adresse du courriel de fin de séance.{' '} - Une semaine avant la séance 3. - - - On refait le tri à la réception et on réécrit les exercices sur votre - matière. Vos documents restent dans votre cohorte. - -
-

- Merci — à la séance 3. -

-
- + + + ); } diff --git a/src/decks/s2-gc/index.tsx b/src/decks/s2-gc/index.tsx new file mode 100644 index 0000000..e31a03f --- /dev/null +++ b/src/decks/s2-gc/index.tsx @@ -0,0 +1,1231 @@ +import type { CSSProperties, ReactNode } from 'react'; +import Deck from '../../deck/Deck'; +import Slide from '../../deck/Slide'; +import Build from '../../deck/Build'; +import Reveal from '../../deck/Reveal'; +import Cover from '../../components/Cover'; +import Steps from '../../components/Steps'; +import Table from '../../components/Table'; +import Timeline from '../../components/Timeline'; +import Prompt from '../../components/Prompt'; + +/* Séance 2 — GrafiCompétences (L'Établi de l'IA). PREVIEW v1. + Sources: content/ bricks of séance 2, order and cuts per D116–D118. + Facilitator notes live in `notes` (presenter mode, P). */ + +const n = (...bullets: string[]) => bullets.map((b) => '• ' + b).join('\n'); + +const card: CSSProperties = { + padding: 'clamp(14px, 1.8vw, 22px)', + borderRadius: 'var(--radius)', + textAlign: 'left', +}; +const small: CSSProperties = { + fontSize: 'clamp(13px, 1.25vw, 17px)', + color: 'var(--fg-muted)', + lineHeight: 1.45, +}; +const h3: CSSProperties = { + fontSize: 'clamp(17px, 1.7vw, 23px)', + marginBottom: 8, +}; +const gap = (g = 20): CSSProperties => ({ marginTop: g }); +const stack: CSSProperties = { display: 'grid', gap: 10, alignContent: 'start' }; + +function Head({ + k, + title, + sub, + center, +}: { + k?: string; + title: ReactNode; + sub?: ReactNode; + center?: boolean; +}) { + const c: CSSProperties = center + ? { textAlign: 'center', marginInline: 'auto' } + : {}; + return ( + + {k && ( +
+ {k} +
+ )} +

+ {title} +

+ {sub && ( +

+ {sub} +

+ )} +
+ ); +} + +function Card({ + title, + children, + style, +}: { + title?: ReactNode; + children?: ReactNode; + style?: CSSProperties; +}) { + return ( +
+ {title &&

{title}

} + {children &&
{children}
} +
+ ); +} + +const SIX = [ + ['Objectif', "Qu'est-ce que je veux obtenir, concrètement ?"], + ['Contexte', 'Que doit-il savoir (mon rôle, la situation, le destinataire) ?'], + ['Sources', "Sur quoi doit-il s'appuyer (document, données, exemple) ?"], + ['Contraintes', 'Que doit-il respecter — et éviter ?'], + ['Format', 'Quelle forme pour la sortie (liste, tableau, courriel, longueur) ?'], + ['Critères de qualité', 'À quoi reconnaîtrai-je que c’est bon ?'], +]; + +function Situation({ + n: num, + title, + setup, + before, + missing, + after, + takeaway, +}: { + n: number; + title: string; + setup: string; + before: string; + missing: string[]; + after: string; + takeaway: string; + nav?: string; + notes?: string; +}) { + return ( + + +
+
+

{setup}

+ + {before} + +
+ Il manquait : + {missing.map((m) => ( + + {m} + + ))} +
+ +

{takeaway}

+
+
+ + {after} + +
+
+ ); +} + +export default function App() { + return ( + + {/* ── Ouverture ─────────────────────────────────────────── */} + + Utiliser efficacement{' '} + les modèles de langage + + } + subtitle="Lire un prompt, le construire, dialoguer avec le modèle — sur vos vraies tâches." + foot="En direct sur Zoom · 3 h 30" + notes={n( + 'Accueil 15 min : présence, LibreChat, tour de micro.', + 'Vérifier la veille que LibreChat répond (GPT-5.5 ; les modèles Claude répondaient 403 le 3 oct).' + )} + /> + + + + + +
+ Le mauvais prompt, ensemble. + Comment il lit, les six cases, l'ordre, l'exemple. + Continuer la conversation, faire critiquer, la boucle. + Trois situations, les images IA floues. + Avant/après en sous-groupes, puis le Promptuaire. +
+

+ Gardez sous la main votre tâche réelle — celle de + l'inventaire de la séance 1. +

+
+ + {/* ── Vivre le mauvais prompt (brique 2) ───────────────── */} + + +
+ Écris un courriel de suivi à un client. +
+ +

+ À main levée : l'enverriez-vous telle quelle, aujourd'hui, + à un vrai client ? +

+
+
+ + + +
+ + {`Objet : Suivi de notre échange + +Bonjour [Nom du client], + +Je fais suite à notre échange concernant [projet]. N'hésitez pas à me faire part de vos questions. + +Cordialement, +[Votre nom]`} + + + {`Bonjour, + +J'espère que vous allez bien ! Je souhaitais prendre de vos nouvelles et m'assurer que tout se passe bien de votre côté. Au plaisir de poursuivre notre belle collaboration.`} + + + {`Avec plaisir ! Pouvez-vous me préciser le client, l'objet du suivi et le ton souhaité ?`} + +
+ +

+ Aucune ne contient un seul fait vrai sur votre client. +

+
+
+ + + + Qu'est-ce qui manquait ? + + } + center + /> +

+ Chacun, au micro, nomme une information que le modèle aurait + eu besoin de savoir. +

+

+ Je note tout, en vrac, sur mon écran. +

+
+ + {/* ── Comment un LLM lit votre prompt (brique 3) ───────── */} + + + Il lit tout. Il ne devine rien. + + } + center + /> +
+ + + Votre message, les documents fournis, l'historique. Pas votre intention, + pas votre contexte d'entreprise, pas « ce qui va de soi ». + + + + + À partir du plus probable. Une réponse générique est presque toujours le + symptôme d'un prompt qui ne disait pas assez. + + +
+ +

+ Dire = obtenir. Avant de blâmer l'outil, relisez ce que vous lui avez + réellement donné. +

+
+
+ + {/* ── L'anatomie d'un prompt (brique 4) ─────────────────── */} + + +
+ {SIX.map(([c, q], i) => ( + + {q} + + ))} +
+
+ + + +
+
+ + Pas un sujet, une commande. + + + Aide-moi avec le devis du client Tremblay. + + + Rédige le courriel qui accompagne le devis du client Tremblay. + +
+
+ + La case qui transforme un texte passe-partout en votre texte. + + + Je suis chargée de projet dans un atelier d'impression d'une douzaine de + personnes. Le client est un organisme communautaire qui commande chez nous + pour la première fois, et le budget est serré. + +
+
+ + Le contexte se décrit, la source se colle. + + + Voici le devis en question : […]. Voici aussi un courriel du même type que + j'ai envoyé le mois dernier et qui a bien fonctionné : […]. + +
+
+
+ + + +
+
+ + La moitié « éviter » est presque toujours oubliée. + + + 150 mots maximum. Aucun terme technique de prépresse. N'annonce aucun délai + de livraison : ils ne sont pas confirmés. + +
+
+ + Un texte utilisable tel quel, pas un pavé à découper. + + + Un courriel : une ligne d'objet, deux courts paragraphes, une liste de trois + points pour les options, une phrase de clôture. + +
+
+ + Le modèle reçoit sa propre grille de correction. + + + C'est réussi si le client comprend ce qu'il paie sans avoir besoin de me + rappeler. + +
+
+
+ + + +
+
+ + Écris une publication LinkedIn sur l'impression grand format. + +

+ Un texte lisse sur l'impression grand format en général. Rien qui vous + appartienne. +

+
+ +
+ + Écris une publication LinkedIn pour annoncer que notre atelier lance un + service d'impression grand format. + +

+ Enfin une annonce, mais interchangeable : n'importe quel atelier du Québec + pourrait la publier. +

+
+
+
+
+ + + +
+
+ + Même demande. Contexte : atelier d'impression d'une douzaine de personnes, + région de Québec ; nos clients sont des chargés de marketing de PME qui + n'ont pas de designer à l'interne. Contraintes : 120 mots maximum, aucun + superlatif, ne promets aucun délai. + +

+ Le texte s'adresse enfin à quelqu'un ; les promesses vagues disparaissent. +

+
+ +
+ + Même demande, mêmes contraintes. Voici la fiche technique du service : + […]. Voici une de nos publications qui a bien fonctionné, pour le ton : + […]. Format : une accroche d'une ligne, trois puces (ce que ça permet, + pour qui, comment nous joindre), une question de clôture. C'est réussi si + un chargé de marketing comprend en dix secondes s'il est concerné. + +

+ Vos vraies capacités, votre ton, la bonne forme : il reste une relecture. +

+
+
+
+ +

+ Entre la version 1 et la version 4, le modèle n'a pas changé.{' '} + Vous, oui. +

+
+
+ + {/* ── Ordre, clarté, contradictions (brique 5) ─────────── */} + + +
+
+ + Coller trois pages de brief, puis écrire à la fin « peux-tu m'aider avec + ça ? ». Le modèle reçoit surtout un document… et il le résume. + + + La consigne en tête, la matière encadrée au milieu, la contrainte qui + compte vraiment rappelée à la fin. + +
+ + {`Rédige la réponse au brief ci-dessous, en 200 mots, pour un client sans culture graphique. + +— DÉBUT DU BRIEF — […] — FIN DU BRIEF — + +Rappel : 200 mots, aucun jargon.`} + +
+
+ + + +
+
+ + Une ambiguïté n'est jamais signalée : elle est tranchée en silence. Une + phrase, une intention. + + + Fais un truc court pour le client. + + + Réécris ce texte de 250 mots en 90 mots pour le verso d'une carte postale + 5 × 7 po, sans retirer les coordonnées ni changer les faits. + +
+
+ + « Sois exhaustif mais reste bref » donne un compromis mou. Ne supprimez pas + la tension : dites laquelle l'emporte. + + + Priorité à la brièveté : 120 mots maximum. Si tu dois couper, coupe les + détails techniques avant les coordonnées. + +
+
+
+ + {/* ── Montrez-lui un exemple (brique 6) ─────────────────── */} + + +
+ + Écris un courriel de relance à un client. Ton professionnel mais chaleureux, + dynamique sans être insistant, concis mais complet. + + + {`Voici un courriel de relance que j'ai envoyé le mois dernier et qui a bien fonctionné : + +[collez le courriel au complet] + +Écris le prochain, dans le même ton et la même structure, pour le dossier suivant : […].`} + +
+
+ + Pour trier des demandes, montrez deux ou trois cas déjà classés. + + + On le colle, on ne le décrit pas. Un contre-exemple marche aussi. + +
+
+ + {/* ── Le prompt comme conversation (brique 7) ───────────── */} + + + On ne recommence pas : on continue. + + } + sub="La première réponse est un premier jet. La conversation contient déjà votre contexte." + center + /> +
+
+ Ajouter la case qui manquait. + + Reprends ta réponse. Le destinataire est le propriétaire de l'entreprise, + pas son équipe marketing. + +
+
+ Resserrer ce qui a débordé. + + Trop long et trop enthousiaste. Refais-le en 120 mots, sans superlatif, en + gardant les trois options. + +
+
+ Demander une variante. + + Donne-moi deux autres versions de l'accroche : une plus directe, une plus + formelle. + +
+
+
+ + + +
+ + Une conversation qui a servi à un devis traîne son vocabulaire. + + + Il tourne en rond ou se contredit : repartez avec un prompt propre, enrichi de + ce que vous avez appris. + +
+ +

+ Arrivé à la bonne sortie ? Le chemin parcouru est la version réutilisable de + votre prompt : une entrée pour votre Promptuaire. +

+
+
+ + {/* ── Pause ─────────────────────────────────────────────── */} + +
Pause
+

+ 10 min +

+

+ On reprend avec la critique. +

+
+ + {/* ── Faire critiquer sa propre sortie (brique 8) ───────── */} + + +
+
+ + + Relis ta réponse. Qu'est-ce qui lui manque pour être utilisable telle quelle + par [le destinataire] ? Liste les manques, ne réécris rien pour l'instant. + +
+
+ + + Donne-moi les trois faiblesses les plus sérieuses de ce texte, du point de + vue de [le destinataire]. Puis produis une version corrigée qui les règle. + +
+
+ + + Évalue ce texte sur 10 selon ces critères : [vos critères]. Justifie la note + critère par critère, puis écris la version qui obtiendrait 10. + +
+
+
+ + + + Un deuxième avis, pas un audit + + } + sub="La critique sort de la même machine : utile pour un oubli ou une lourdeur, incapable de dire qu'un prix est faux. Les faits, les chiffres et les engagements restent à votre œil de métier." + center + /> + +
+ + « On pourrait ajouter un mot de transition. » + + + Prenez la version que vous aviez deux tours plus tôt. + +
+
+
+ + {/* ── La boucle, version courte (brique 15) ─────────────── */} + + + {/* ── Trois situations (brique 9, GC) ──────────────────── */} + + +
+ +50 %}> + de publicités produites par personne, dans des équipes marketing, grâce à + l'IA. + + 17 %}> + des tâches en arts, design et médias suscitent l'envie de les confier à l'IA + : les créatifs sont les moins pressés. + +
+

+ Source : Stanford AI Index 2026, p. 219 et 226. +

+
+ + + + + + + + {/* ── Les images IA floues (brique 11, GC) ──────────────── */} + + +
+
Le diagnostic en trente secondes
+

+ taille (cm) = pixels ÷ ppp × 2,54 +

+

+ 1024 px à 300 ppp → 8,7 cm (petite étiquette) · à 150 ppp, + grand format vu de loin → 17 cm +

+
+
+ + + +
+
+ + +

+ Premier réflexe : « avez-vous gardé votre prompt ? » +

+
+ + + + +
+ + Décris ce logo pour qu'un graphiste puisse le redessiner à l'identique : les + formes et leur disposition, chaque couleur avec son code hexadécimal, le style + (plat, dégradé, 3D, photo), le texte exact et sa place. Ne donne pas ton avis, + décris. + +
+ +
+

Relisez le texte qu'elle a lu

+

+ Lors de nos essais, « boulangerie de quartier » est devenu « boutonnerie de + quartier ». Le texte exact, c'est le client qui le confirme. +

+
+
+
+ + + +
+
+
    +
  • + Objectif : un logo imprimable, pas une jolie image. +
  • +
  • + Contexte : il servira sur [une étiquette, un autocollant + de 1,2 m, une carte]. +
  • +
  • + Contraintes : style plat, 2 à 4 couleurs franches (les + codes relevés), aucun dégradé, aucune ombre, bords nets, fond blanc uni. +
  • +
  • + Format : un fichier SVG, aux dimensions finales. +
  • +
  • + Critères : chaque forme se distingue nettement de sa + voisine. +
  • +
+
+
+ + Demandez-le « dans un bloc de code » : un bouton Copier apparaît. Collez-le + dans un fichier texte, enregistrez en .svg, ouvrez-le dans votre logiciel : + net à 5 cm comme à 5 m. + + + Bien sur un logo simple, fait de formes et d'aplats. Une image regénérée + reste une image : 768 px, soit 6,5 cm à 300 ppp. + +
+
+
+ + + +
+ + Un courriel de cinq lignes : ce qui n'allait pas, ce que vous proposez, la + question qui manque (« quelle police ? »). + + + VTracer et Potrace transforment une image en tracés vectoriels. + + + Upscayl et Real-ESRGAN reconstruisent les détails. Les brancher à votre outil + d'IA : séance 4. + +
+
+ + {/* ── Avant/après en sous-groupes (brique 12, reformatée D116–D118) ── */} + + +
+ +
+
+ + + +
+ {[1, 2, 3, 4].map((i) => ( +
+ Sous-groupe {i} +

Cas {i}

+

Tiré de votre inventaire S1 — à compléter.

+
+ ))} +
+
+ + + + + +
+ {SIX.map(([c, q]) => ( +
+
+ {c} +
+

{q}

+
+
+ ))} +
+ + + {/* ── Ancrer : le Promptuaire ───────────────────────────── */} + + + Chaque prompt « après » entre au{' '} + Promptuaire + + } + center + /> +
+ Sous la tâche métier de l'inventaire. + + Noms de clients remplacés par des crochets, pour qu'un collègue le reprenne. + + Ce qu'il produit. +
+

+ Avant de quitter : trois questions, deux minutes, sur la plateforme. +

+
+ + + +
+ {[1, 2, 3].map((i) => ( +
+

« … »

+

+ — prénom, cas {i} +

+
+ ))} +
+
+ + {/* ── Pont vers la séance 3 (briques 18, 19) ────────────── */} + + + Et si le modèle connaissait{' '} + vos documents ? + + } + sub="À chaque fois, il faut tout réexpliquer : vos gabarits, vos façons de faire. À la séance 3, on lui donne la matière." + center + /> + +

+ Une seule chose à faire d'ici là :{' '} + choisir les documents que vous nous envoyez. +

+
+
+ + + +
+
+ +

+ Dans le doute sur le niveau : on n'envoie pas, on pose la question. +

+ + + + + Votre matière, pour la séance 3 + + } + center + /> +
+ + 1 ou 2 documents que vous touchez chaque semaine : gabarit de soumission, + procédure, guide de normes. PDF, Word, Excel — pas de PDF scanné ni de .indd. + + + Par courriel, à l'adresse du courriel de fin de séance.{' '} + Une semaine avant la séance 3. + + + On refait le tri à la réception et on réécrit les exercices sur votre + matière. Vos documents restent dans votre cohorte. + +
+

+ Merci — à la séance 3. +

+
+ + ); +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// From f64ae351aeaae9d9454705587b5d98ca5b45d230 Mon Sep 17 00:00:00 2001 From: Aurelien Mhosaic Date: Mon, 5 Oct 2026 18:24:59 -0400 Subject: [PATCH 3/8] =?UTF-8?q?feat(variants):=20compare=202=E2=80=933=20r?= =?UTF-8?q?endered=20treatments=20of=20a=20screen=20(D122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit / : V cycles on the live slide, choice kept per screen id in localStorage. Skill slide-variants: layout, choosing, review. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/slide-variants/SKILL.md | 69 +++++++++++++++++++++++ src/components/Variants.tsx | 78 ++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 .claude/skills/slide-variants/SKILL.md create mode 100644 src/components/Variants.tsx diff --git a/.claude/skills/slide-variants/SKILL.md b/.claude/skills/slide-variants/SKILL.md new file mode 100644 index 0000000..2dbf907 --- /dev/null +++ b/.claude/skills/slide-variants/SKILL.md @@ -0,0 +1,69 @@ +--- +name: slide-variants +description: Use when building an Établi de l'IA séance deck from a storyboarded brief (D122) and Aurélien must compare 2–3 rendered treatments of a screen before choosing — « construis le deck », « montre-moi les variantes », « je prends B pour s1-e07 », « garde la A ». Authoring rules for the slides themselves stay in the `slides` skill. +--- + +# Variantes de slides (D122) + +The brief comes from app-formation-ai: `conception/generes/mandats//seances/seance-/brief.md`, +generated by `build_deck.py` from the storyboard (`/deck-storyboard`). Each screen carries a +Rôle and a Traitement; a screen marked **« À explorer »** carries 2–3 pistes. Deck-app builds +every piste as a live variant; Aurélien picks by looking at them on screen. + +## Layout + +- One deck per folder: `src/decks/-s/index.tsx`, served at `//` + (`npm run dev`, then `http://localhost:5173/fb-s1/`). The path, not a query: the presenter tab + keeps only `?presenter=1`. +- One file per capsule, `cNN-.tsx`, `export default [ … ]` in brief + order; `index.tsx` spreads them into ``. Capsule files can be built in parallel. +- `theme.css` in the deck folder sets `:root` tokens from the mandat's `profil.md` (colors, + fonts); `ui.tsx` holds the deck's shared pieces (Kicker with the day's path, card styles, `n()` + for notes). Logos copied into `assets/`. + +## Variants + +```tsx + + + … + … + … + + +``` + +`V` on the live slide cycles; a badge shows `s1-e07 · B animée (2/3)`. The choice is kept per id +in localStorage, shared with the presenter tab. Every variant renders the same content: D115 +holds — no fact, number or example the screen text lacks. + +## Choosing + +Aurélien says « s1-e07 : B ». Then, for that screen: delete the other ``s, inline the +winner's JSX directly in the ``, drop the `` wrapper. A deck is final when +`grep -r "` is empty. Report the choices back to the storyboard doc +(one line per screen) so the next cohort's deck starts from them. + +## Slide-by-slide review (R) + +In dev, **R** or the dock button (speech bubble) opens a panel on the current slide: Décision +`OK` / `Modifier` / `Retirer` + a note, saved by the Vite dev server (`/__review`, `vite.config.ts`) +to `src/decks//revue.json`, keyed by screen id. On « applique la revue »: +1. Read `revue.json`; handle every entry with `fait: false` and a decision other than `ok` + (`retirer` → delete the slide; `modifier` → do what the note says, D115 still holds). +2. Set `fait: true` on each handled entry (keep the note), report one line per screen. +3. Check the touched slides at 1280×720. A new note on a screen sets `fait` back to false. + +## Checks before handing over + +1. `npx tsc --noEmit -p .` clean. +2. Every slide at 1280×720 (a Zoom share) in the browser: screenshot each, variants included + (press V). Fix clipped text, overflow, contrast, a lone left-anchored block (`slides` rule 3). +3. Motion: nothing continuous, nothing faster than the engine's builds; 3D settles and stops. +4. `grep -c "{/\* s1-e" ` equals the brief's screen count; no duration on screen. + +## Common mistakes + +- Variants that differ in content (one adds an example): they must differ in form only. +- Inventing missing media or survey data instead of an empty, labelled frame. +- Leaving `` in a deck that ships. diff --git a/src/components/Variants.tsx b/src/components/Variants.tsx new file mode 100644 index 0000000..f0d2ec2 --- /dev/null +++ b/src/components/Variants.tsx @@ -0,0 +1,78 @@ +import { Children, useEffect, useState } from 'react'; +import type { ReactNode } from 'react'; +import { useDeck } from '../deck/DeckContext'; + +/* Storyboard variants (D122): a screen marked « à explorer » renders 2–3 + candidate treatments, one per piste. Press V on the live slide to cycle; + the choice is kept per screen id in localStorage (shared with the + presenter tab). Once Aurélien picks, the losing variants are deleted and + the winner is inlined: never ships in a final deck. */ +export function Variant({ children }: { label: string; children: ReactNode }) { + return <>{children}; +} + +const key = (id: string) => `deck:variant:${id}`; +const read = (id: string) => { + try { + return Number(localStorage.getItem(key(id))) || 0; + } catch { + return 0; + } +}; + +export default function Variants({ id, children }: { id: string; children: ReactNode }) { + const items = Children.toArray(children) as { props: { label: string } }[]; + const { isStatic } = useDeck(); + const [i, setI] = useState(() => read(id) % items.length); + + useEffect(() => { + const onStorage = (e: StorageEvent) => e.key === key(id) && setI(read(id) % items.length); + window.addEventListener('storage', onStorage); + if (isStatic) return () => window.removeEventListener('storage', onStorage); + const onKey = (e: KeyboardEvent) => { + if (e.key !== 'v' || e.metaKey || e.ctrlKey || e.target instanceof HTMLInputElement) return; + setI((c) => { + const n = (c + 1) % items.length; + try { + localStorage.setItem(key(id), String(n)); + } catch { + /* private mode: the choice just isn't remembered */ + } + return n; + }); + }; + window.addEventListener('keydown', onKey); + return () => { + window.removeEventListener('storage', onStorage); + window.removeEventListener('keydown', onKey); + }; + }, [id, isStatic, items.length]); + + return ( + <> + {/* ponytail: switching variants keeps the larger click count the engine + already registered for this slide; reload the slide if a build is skipped. */} +
+ {items[i] as ReactNode} +
+ {!isStatic && ( +
+ {id} · {items[i].props.label} ({i + 1}/{items.length}) · V +
+ )} + + ); +} From b33625c2b29af8528b07b3570873c4e836b4e726 Mon Sep 17 00:00:00 2001 From: Aurelien Mhosaic Date: Mon, 5 Oct 2026 18:24:59 -0400 Subject: [PATCH 4/8] feat(review): slide-by-slide review from the dock (R) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dev-only dock button and R key open a panel on the current slide: decision (OK / Modifier / Retirer) and a note, written by the dev server to src/decks//revue.json, keyed by screen id. Engine change limited to the button, the key and mounting the panel (asked by Aurélien). Co-Authored-By: Claude Opus 5.5 --- src/components/Review.tsx | 121 ++++++++++++++++++++++++++++++++++++++ src/deck/Deck.tsx | 28 ++++++++- src/deck/icons.tsx | 6 ++ vite.config.ts | 41 ++++++++++++- 4 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 src/components/Review.tsx diff --git a/src/components/Review.tsx b/src/components/Review.tsx new file mode 100644 index 0000000..3a86594 --- /dev/null +++ b/src/components/Review.tsx @@ -0,0 +1,121 @@ +import { useEffect, useState } from 'react'; + +/* Slide review (dev only, R or the dock button): a decision and a note per + screen id, saved to src/decks//revue.json through the dev server. + Claude reads that file on « applique la revue », does the work and sets + `fait: true`; a new note on the same screen reopens it. */ +export type Decision = 'ok' | 'modifier' | 'retirer'; +export type Entry = { slide: number; decision: Decision; note: string; fait: boolean; maj: string }; +type Revue = Record; + +const deck = window.location.pathname.split('/').filter(Boolean)[0] || 's2-gc'; +const url = `/__review?deck=${deck}`; +const LABELS: Record = { ok: 'OK', modifier: 'Modifier', retirer: 'Retirer' }; + +export default function Review({ id, slide, onClose }: { id: string; slide: number; onClose: () => void }) { + const [revue, setRevue] = useState({}); + const [decision, setDecision] = useState('ok'); + const [note, setNote] = useState(''); + const [etat, setEtat] = useState<'' | 'enregistré' | 'erreur'>(''); + + useEffect(() => { + fetch(url) + .then((r) => r.json() as Promise) + .then((r) => { + setRevue(r); + setDecision(r[id]?.decision ?? 'ok'); + setNote(r[id]?.note ?? ''); + setEtat(''); + }) + .catch(() => setEtat('erreur')); + }, [id]); + + const save = async () => { + const next = { ...revue, [id]: { slide: slide + 1, decision, note, fait: false, maj: new Date().toISOString() } }; + const r = await fetch(url, { method: 'POST', body: JSON.stringify(next) }).catch(() => null); + if (r?.ok) setRevue(next); + setEtat(r?.ok ? 'enregistré' : 'erreur'); + }; + + const e = revue[id]; + const ouvertes = Object.values(revue).filter((x) => !x.fait && x.decision !== 'ok').length; + + return ( +
{ + k.stopPropagation(); + if (k.key === 'Escape') onClose(); + if (k.key === 'Enter' && (k.metaKey || k.ctrlKey)) save(); + }} + style={{ + position: 'fixed', + right: 16, + bottom: 88, + zIndex: 60, + width: 'min(360px, calc(100vw - 32px))', + display: 'grid', + gap: 10, + padding: 16, + borderRadius: 'var(--radius-sm)', + background: 'var(--surface)', + border: '1px solid var(--hair)', + boxShadow: 'var(--shadow)', + color: 'var(--fg)', + font: '14px/1.4 var(--font-body)', + }} + > +
+ + Revue · {id} (slide {slide + 1}) + + +
+ {e?.fait && Traité par Claude — une nouvelle note rouvre l'écran.} +
+ {(Object.keys(LABELS) as Decision[]).map((d) => ( + + ))} +
+