Le système d'identité de Ptolémé DE-SOUZA. Né du terminal : mono, vert phosphore, ASCII et discipline système. Pensé pour rester lisible quand tout escalade.
Tokens
Les variables CSS réelles du portfolio. Source de vérité : css/style.css, bloc :root.
Palette token-isée
| Token | Valeur | Rôle |
|---|---|---|
--green-primary | #00FF41 | CTA, prompts, accents, liens |
--green-dim | #00CC33 | États hover, secondaire |
--green-glow | rgba(0,255,65,0.15) | Glow box-shadow / text-shadow |
--cyan-accent | #00D4FF | Liens hover, info |
--yellow-accent | #FFD700 | Warnings, callouts |
--red-accent | #FF4444 | Erreurs, live-dot |
--orange-accent | #FF8800 | Accent secondaire |
--bg-primary | #000000 | Fond global |
--bg-secondary | #0A0E18 | Surface 2 |
--bg-terminal | #080D16 | Cards, terminaux |
--bg-terminal-header | #141E2A | Headers de cards |
--bg-card | rgba(8,13,22,0.9) | Surface translucide |
--text-primary | #D8E2EC | Body |
--text-secondary | #8899AA | Meta, captions |
--text-heading | #FFFFFF | Titres |
--border-default | #1E3044 | Bordures neutres |
--border-green | rgba(0,255,65,0.2) | Bordures terminales |
--border-active | #00FF41 | Hover, focus |
--dot-red · --dot-yellow · --dot-green | Dots window-control |
Échelle fluide via clamp()
| Token | min · vw · max | Usage |
|---|---|---|
--text-xs | 0.65rem · 1.2vw · 0.75rem | tags, meta, kickers |
--text-sm | 0.75rem · 1.4vw · 0.875rem | nav, output |
--text-base | 0.875rem · 1.6vw · 1rem | body |
--text-lg | 1rem · 1.8vw · 1.125rem | lead |
--text-xl | 1.125rem · 2vw · 1.25rem | nav__logo, h4 |
--text-2xl | 1.25rem · 2.5vw · 1.5rem | section__title |
--text-3xl | 1.5rem · 3vw · 2rem | h2 |
--text-4xl | 2rem · 4vw · 3rem | h1 article |
--text-hero | 2.5rem · 5vw · 4.5rem | hero headline display |
Familles : --font-mono = JetBrains Mono (body, UI, code) ·
--font-display = Space Grotesk (titres, lede) ·
--font-body = Space Grotesk (alias).
Coins francs uniquement
--radius-sm | 3px | tags, badges, dots, pills |
--radius-md | 6px | boutons, inputs, lang-toggle |
--radius-lg | 8px | terminal-card, widgets, sections |
Plafond : 8px. Au-dessus, on perd l'esthétique TTY. Pas de border-radius: 50% sauf pour les dots window-control et le .live-dot.
Le :root système, prêt à coller
Pour démarrer tout nouveau projet PDS : la palette canonique, les familles,
les radius et les tokens motion en un seul bloc. C'est le nommage
système (celui de ce guide) — le portfolio garde son nommage
historique (--green-primary…) documenté dans les tableaux ci-dessus.
:root {
/* identité — le vert ne change jamais */
--primary: #00FF41;
--primary-dim: #00CC33;
--primary-glow: rgba(0, 255, 65, 0.35);
--primary-soft: rgba(0, 255, 65, 0.08);
--amber: #FFD700; --cyan: #00D4FF;
--red: #FF4444; --orange: #FF8800;
--font-mono: 'JetBrains Mono', monospace;
--font-display: 'Space Grotesk', sans-serif;
--radius-sm: 3px; --radius-md: 6px; --radius-lg: 8px; /* plafond : 8px */
--t-instant: 80ms; --t-quick: 160ms; --t-base: 300ms;
--t-cinematic: 2400ms; --t-scan: 2500ms;
}
body.theme-dark {
--bg: #000000; --bg-2: #080D16; --bg-3: #0F1A2A;
--bg-card: rgba(8, 13, 22, 0.9);
--fg: #D8E2EC; --fg-dim: #8899AA; --fg-strong: #FFFFFF;
--border: rgba(0, 255, 65, 0.18); --border-soft: #1E3044;
}
body.theme-light {
--bg: #F4F2EA; --bg-2: #ECEAE0; --bg-3: #E0DDD0;
--bg-card: #FFFFFF;
--fg: #1B232E; --fg-dim: #5A6470; --fg-strong: #050810;
--border: rgba(0, 90, 25, 0.22); --border-soft: #C8C4B6;
}
Scanline ambiante
--scanline-gradient = repeating-linear-gradient(0deg, rgba(0,0,0,0) 0px, rgba(0,0,0,0) 1px, rgba(0,255,65,0.008) 1px, rgba(0,255,65,0.008) 2px)
appliqué sur body::after (z-index 9998, pointer-events none). Donne la texture CRT.
Components
Les briques UI réelles utilisées sur le portfolio. Tout dérive de terminal-card et widget.
Les deux briques mères, rendues en vrai
<div class="terminal-card">
<div class="terminal-card__header">
<div class="terminal-card__dots">
<span class="terminal-card__dot terminal-card__dot--red"></span>
<span class="terminal-card__dot terminal-card__dot--yellow"></span>
<span class="terminal-card__dot terminal-card__dot--green"></span>
</div>
<span class="terminal-card__header-title">incident_2841.log</span>
</div>
<div class="terminal-card__body">
<div class="terminal-card__prompt">status --last</div>
<div class="terminal-card__output">root cause isolée · patch shippé</div>
</div>
<div class="terminal-card__meta">closed · notes archived</div>
</div>
Le composant central
La quasi-totalité des surfaces de contenu (hero, projets, articles, widget career) sont des terminal-card.
Source : css/terminal.css.
| Classe | Rôle |
|---|---|
.terminal-card | Base : bg --bg-terminal, bordure --border-green, radius --radius-lg |
.terminal-card--hero | Variante hero : shadow 0 4px 20px rgba(0,0,0,0.5), hover translateY(-3px) |
.terminal-card--clickable | Cursor pointer, hover translateY(-4px) |
.terminal-card--widget | Hauteur 100% en flex pour s'aligner en grid |
.terminal-card--centered | max-width 500px centré |
.terminal-card__header | Bandeau : bg --bg-terminal-header, padding 0.6rem 0.85rem |
.terminal-card__dots + __dot--red/yellow/green | Window-controls, 12×12px circles |
.terminal-card__header-title | Nom de fichier en mono, ellipsis |
.terminal-card__body | Padding 1rem, font mono 0.85rem, line-height 1.6 |
.terminal-card__prompt | Préfixe "> ", couleur --green-primary |
.terminal-card__output | Réponse en --text-primary |
.terminal-card__meta | Pied de carte, séparé par border-top, mono --text-xs |
.terminal-card__file · __desc · __tags | Sous-blocs pour cards projets / articles |
Widgets sidebar (hero right + career)
| Classe | Rôle |
|---|---|
.widget | Container — souvent imbriqué dans .terminal-card |
.widget__header · __title · __live | Header avec live-dot rouge clignotant |
.widget__body · __body--centered | Zone contenu, padding 0.75rem |
.widget__counter-row · __counter-label · __counter | Lignes "metric : 42" |
.widget-career + sous-classes | Variante carrière (hero, info, name, status, section-label, cta) |
.widget-downloads · .widget-dl-btn | Boutons download (CV PDF, etc.) |
Structure de page
| Classe | Rôle |
|---|---|
.nav · .nav__inner · .nav__logo | Header sticky, blur backdrop, height 60px |
.nav__link · .nav__link--active | Underline animé en hover |
.nav__hamburger · .nav__drawer | Mobile menu |
.lang-toggle | FR/EN switcher, bordure verte |
.container | max-width 1200px, padding 0 1.5rem |
.section · .section__title | Section padding 4rem 0, titre préfixé "> " |
.hero · .hero__grid | Grid 1.4fr · 1fr · 0.75fr (3 col), --two-col alternative |
.hero__name · __headline · __jobtitle | Bloc value-prop gauche |
Petits éléments transverses
.tag / .tags | Pill 0.65rem, bg rgba(106,123,141,0.08), border --border-default |
.live-dot | 6×6px rouge, animation live-pulse 2s |
.terminal-prompt · .terminal-cursor | Préfixe "> " et caret block |
.cursor-block | Block clignotant, animation blink-cursor 1s step-end infinite |
À noter : il n'existe pas de classe .btn générique sur le portfolio.
Les CTA sont des <a> avec couleur --green-primary ou des
.widget-dl-btn, .lang-toggle selon le contexte.
Status chrome & skill bars
Trois composants validés depuis la v1.1 (joués en live au chapitre Motion · Status chrome ↓) : la page devient elle-même un système qui rend compte de son état.
| Composant | Rôle | Règles |
|---|---|---|
.scroll-progress | Barre 2px fixée en haut, suit le scroll | fill --primary + glow léger · listener passive |
.statusbar | Barre fixe en bas, grammaire tmux | gauche = état · centre = chemin ~/section · droite = % scroll + horloge |
.skill-row | Jauge htop : █░ + pourcentage | white-space: pre obligatoire · remplissage steps(18), jamais fluide |
States
Default · hover · focus · active · disabled. Transitions 0.2s ease ou 0.3s ease, jamais plus.
Comportements de survol
| Cible | Effet |
|---|---|
a standard | color --green-primary → --cyan-accent · transition 0.2s |
.nav__link | color → --green-primary + underline qui s'étend (width 0 → 100%, 0.3s) |
.nav__logo | text-shadow 0 0 10px var(--green-glow) |
.terminal-card | border-color → --green-primary, shadow 0 4px 20px rgba(0,255,65,0.08) |
.terminal-card--hero | + translateY(-3px), shadow 0 8px 30px var(--green-glow) |
.terminal-card--clickable | translateY(-4px), output color → --green-primary |
.lang-toggle | bg → --green-primary, color → --bg-primary (inversé) |
.widget-dl-btn | bg rgba(0,255,65,0.05) → 0.12, border → --green-primary |
Survoler pour sentir le contrat
État courant
.nav__link--active = même rendu que :hover (couleur --green-primary + underline 100%).
La cohérence "active = comme hover" évite les surprises au clavier.
.scroll-progress (barre de progression article) suit le scroll, fill --green-primary, opacité progressive.
Page d'erreur
404.html existe sur le portfolio : même nav, même footer, contenu en .terminal-card--centered avec message en terminal-card__output. Pas de gros 404 décoratif : l'incident reste sobre.
Patterns
Compositions récurrentes. Chaque pattern correspond à un type de page du portfolio.
index.html
Grid 3 colonnes 1.4fr · 1fr · 0.75fr, gap 1.5rem, alignement start.
- Gauche —
.hero__name(kicker),.hero__headline(display),.hero__jobtitle, value prop, CTAs. - Centre —
.terminal-card--heroavec prompt animé (terminal-card__prompt+.cursor-block). - Droite —
.widget(live status, métriques) ou.widget-career.
Variante .hero__grid--two-col (1fr · 1fr, gap 3rem) sur les pages internes.
article.html
Layout 1 colonne centrée. Header avec .section__title, body en .bg-prose-équivalent (mono, line-height 1.7).
Barre de progression scroll-progress sticky en haut. Tags .tag en pied d'article.
career.html
Combo .widget-career (sidebar gauche : status live, downloads, CTA contact) +
stack de .terminal-card à droite, une par expérience, avec .terminal-card__header daté.
projects.html · articles.html
Grille de .terminal-card--clickable, chacune avec __file (nom de fichier),
__desc (résumé), __tags. Filtrage via JS, transition fade-enter (animation fadeIn 0.3s).
La page comme système vivant
Pattern transverse à toutes les pages longues : scroll-progress
(2px, fixé en haut) + statusbar (fixée en bas : état du service,
section courante en notation ~/chemin, % de scroll, horloge live).
Le chrome est du statut, pas de l'animation — il tourne en continu, en
vanilla, sans dépendance. Spécimen vivant en §02 Components, implémentation
complète au chapitre Motion · 17 ↓.
Usage
Comment étendre le portfolio sans casser l'identité.
Bonnes pratiques
- Toute nouvelle couleur passe par un token
--*dans:root(style.css L8-32). - Toute nouvelle surface de contenu = un
.terminal-cardou un dérivé. - Toute taille de texte = un
--text-*(clamp), jamais une valeur en dur. - Toute animation au-dessus de 300ms doit être justifiée (intro, hero unique).
- Le vert
--green-primaryreste à 10% max de la surface visible.
Refus par défaut
- Inventer une nouvelle teinte de vert. #00FF41 ou rien.
- Ajouter un
border-radius> 8px (sauf50%pour dots). - Utiliser une font autre que JetBrains Mono ou Space Grotesk.
- Mettre une emoji dans la nav, un titre, un bouton.
- Ajouter une animation infinie sur du contenu textuel non-décoratif.
- Créer un composant ad hoc qui réinvente
.terminal-cardavec d'autres bordures.
Où vit quoi dans pdesouza-portfolio
| Fichier | Contenu |
|---|---|
css/style.css | Tokens :root, nav, layout, hero, widgets, sections — gros fichier |
css/terminal.css | Famille .terminal-card isolée |
css/animations.css | Keyframes blink-cursor, live-pulse, fadeIn, pulse-dot, classes .reveal / .fade-in-up |
css/about.css · career.css | Styles spécifiques aux pages about.html et career.html |
css/responsive.css | Breakpoints (cf. doc dédiée) |
js/ | Theme toggle, scroll-spy, lang switcher, scroll-reveal |
Workflow
- Vérifier si un
.terminal-card--{variant}ou un.widgetexistant suffit. Réutiliser plutôt que créer. - Si nouveau : créer une branche
preprodsurpdesouza-portfolio, ajouter la classe dansterminal.cssou un nouveau fichier. - Documenter ici (Section 02 Components) — ajouter une ligne au tableau.
- Si un nouveau token est introduit, l'ajouter à
:rootET à la Section 01 Tokens. - Audit responsive (
responsive.css) avant merge. - Merge
preprod→mainuniquement après validation visuelle sur preprod.
$ git log brandguide --oneline
| Version | Date | Contenu |
|---|---|---|
v1.3 | 2026.06 | Unification : le Motion Guide fusionné dans la page (chap. 14-18, démos vivantes anime.js) · cover générative (topographie Phosphor Vigil) · og:image · planches PL.01/02/03 en §07 & §13 |
v1.2 | 2026.06 | Spécimens vivants + code copiable (tokens, components, states) · nouveaux composants status chrome & skill bars · Motion Guide compagnon |
v1.1 | 2026.04 | Signatures articles/projets (§13) · exception terminal-card #080D16 (§10) · hover policy no-flip (§14) · asset policy |
v1.0 | 2026.04 | Structure initiale 17 sections : design system (01-05) + brand (06-16) |
Brand
Une voix, une posture, une promesse. Voici ce que PDS représente avant même la première pixel.
Stabiliser ce que les autres ont déclaré perdu.
Quand un incident escalade, c'est moi qu'on appelle. PDS est l'identité d'un opérateur cyber qui transforme le chaos en post-mortem propre — méthodique, factuel, documenté.
Les 4 piliers de l'identité
À qui PDS parle
Logo
Un prompt qui blink. La forme la plus pure de notre métier : un terminal qui attend une commande.
3 déclinaisons officielles
Espace de protection · taille minimale
Clear space
Marge minimale autour du mark = X, où X = la hauteur de la lettre p. Aucun élément ne franchit cette zone.
Minimum sizes
Mark seul : min 16px en digital, 8mm en print. Lockup complet : 120px de large minimum.
Do · Don't
Planche d'anatomie du signe
Le chevron est construit, pas dessiné : ouverture 58.2°, trait 0.16X à joints carrés, curseur compagnon 0.45X, clear space X sur les quatre côtés. La rémanence (FIG. D) est la métaphore d'extinction officielle — le phosphore ne s'éteint pas, il se souvient.
Mark minimal — onglet navigateur
À 16–32px le lockup >pds_ devient illisible. La déclinaison
favicon garde uniquement le prompt + curseur — c'est l'essence du
logo, et c'est reconnaissable même sur un onglet à 16×16.
PWA / iOS
retina tab
desktop tab
min lisible
Construction
Format SVG. Fond #0A0F1A (rounded 4px), prompt
> en mono #00FF41, curseur block 4×2px aligné
baseline. Aucun texte, aucune lettre — la forme suffit.
Quand l'utiliser
Onglet navigateur, PWA icon, Apple touch, badge GitHub repo, watermark <16px.
Pour tout ce qui dépasse 32px : revenir au mark complet >pds_.
Pourquoi ça marche
Un prompt + un curseur qui blink = l'archétype du terminal. Aucune marque concurrente n'occupe ce signe. Reconnaissance instantanée à toute taille, identité préservée jusqu'au pixel.
Architecture
Comment la marque s'organise : un seul nom maître, des sous-services nommés comme des units système.
Hiérarchie de la marque
pds.service ├── pds.web // pdesouza.fr — site & articles ├── pds.talks // conférences & meetups ├── pds.notes // post-mortems & techniques ├── pds.mail // signature email & newsletters └── pds.social // LinkedIn, Mastodon, GitHub
Règles de nommage
| Service | Format | Exemple |
|---|---|---|
| Domaine racine | pdesouza.fr | pdesouza.fr |
| Sous-produit | pds.<nom> | pds.notes, pds.talks |
| Article | verbe + objet, lowercase | "escalating quietly" |
| Talk | question ou commande | "why does it always fail at 3am?" |
| Handle social | @pdesouza partout | @pdesouza |
Shape
Pas de courbes inutiles. Tout est aligné sur une grille mono — comme un terminal.
Grille de base 8px
Patterns ASCII officiels
┌──────────────┐ │ pds │ │ $ ready │ └──────────────┘
──────────────── ▓▓▓░░░░░░░░░░░░░ ████░░░░░░░░░░░░ ▓▓▓▓▓▓▓░░░░░░░░░
> _ ▸ ◆
$ ●
▓ ▒ ░
Cadres canoniques
Colors
Vert primaire identique en dark et light. Le fond bouge, l'identité reste.
8 couleurs, point.
60 / 30 / 10
Le vert n'occupe jamais plus de 10% de la surface visible. Sa rareté est sa force.
Pairs validés
Terminal-card surface · #080D16
Le token --bg-3 du système (#0F1A2A, navy bleuté) sert
de fond pour les frames de contenu du brandguide et des sections en grille.
Mais les terminal cards du portfolio (hero, projets, articles, about,
404…) utilisent une teinte plus profonde — la swatch Terminal navy
#080D16 documentée plus haut. C'est une exception volontaire
au design system, pas une dérive.
--bg-3 · #0F1A2Aframes brandguide · sections en grille
terminal-card · #080D16cards portfolio · hero · projets · career
Pourquoi cette exception
Un terminal n'est pas une surface neutre — c'est l'écran lui-même.
Il doit paraître plus profond que le fond de page pour que l'illusion
CRT/console fonctionne. #080D16 donne ce contraste subtil que
#0F1A2A dilue.
Quand l'appliquer
Toute classe .terminal-card et ses variantes
(--hero, --widget, --clickable),
plus les cards de la page Career :
.career-tl__item, .career-edu__item,
.career-cert, .career-lang. Pour tout le reste
— sections, panels, frames, infographies — rester sur --bg-3.
Implémentation
Override unique en bout de chaîne dans
preprod/css/patch-final.css §17 :
.terminal-card { background: #080D16; }
+ sibling rule pour les .career-* avec !important
(les règles bundles dans career.css spécifient
--bg-3). Le système de tokens reste intact —
l'exception est locale et documentée.
Typography
Deux familles, deux rôles. Display pour la voix, mono pour le système.
Space Grotesk
| Hero | Quand ça escalade. | 48px / 700 |
| H1 | Section title | 32px / 700 |
| H2 | Sub-section | 24px / 600 |
| Lede | Phrase d'accroche pour intro. | 18px / 400 |
| Body display | Pour quotes courts et résumés. | 16px / 400 |
JetBrains Mono
| Code | $ systemctl status pds | 15px / 500 |
| Body | Body mono pour articles. | 14px / 400 |
| UI | nav links · labels · captions | 12px / 500 |
| Meta | 10px / 400 |
Règles de mise en page
| Élément | Famille | Détail |
|---|---|---|
| Titres | Space Grotesk 700 | letter-spacing -0.5 à -1.5px sur les grandes tailles |
| Body | JetBrains Mono 400 | line-height 1.65 · 60–72 caractères max par ligne |
| Meta / labels | JetBrains Mono 500 | UPPERCASE · letter-spacing 1.5–3px |
| Code | JetBrains Mono 500 | couleur var(--primary) pour les commandes |
| Liens | hérite | couleur var(--primary), pas de soulignement |
Iconography
Stroke unique, grille 24×24, coins carrés. Comme un schéma système.
Système d'icônes — base
Construction
À ne pas faire
Imagery
Photos sombres, traitements monochromes vert ou neutre. L'image doit ressembler à un screenshot, pas à une publicité.
Que photographier
Traitement photo
Réduction de saturation à 30%. Highlights tirés vers le vert primaire, shadows vers le navy. Grain léger toléré.
Cadrage
| Type | Cadrage | Lumière |
|---|---|---|
| Portrait | 3/4 face · regard hors champ | side-light, ombres dures |
| Setup tech | écran 1er plan, mains floues | écran = source de lumière |
| Ambiance | plan large, vide, urbain nuit | néons existants seulement |
| Article cover | 16:9 · sujet 1/3 droite | contrejour ou aucune source visible |
Mix B + C — logo géométrique + fragment terminal
Chaque article et chaque projet porte une signature visuelle à deux étages : un logo Bauhaus mono-phosphore (B) au-dessus, un fragment de commande terminal (C) en dessous. Lisible à 36px, narratif au scroll, cohérent avec le hero.
Logo (B) — specs
viewBox 80×80. Stroke 2–2.5px, linecap square,
3 à 5 formes max, aucun fill sauf accent ponctuel. Mono-couleur
via currentColor. Optionnel : enclosure pointillée
stroke-dasharray="2 3" à opacity 0.35 pour le cadre architectural.
Fragment (C) — articles only
Deux lignes mono 0.65rem. Ligne 1 = commande
($ verb --flag, < 28 chars). Ligne 2 = sortie
(> result, < 28 chars). Border-left 2px en accent,
fond rgba(0,0,0,0.35). Le $ reste en accent,
la sortie en --fg-dim.
Article-only. Sur les article-card le fragment
pose le ton terminal et clarifie la promesse. Sur les
project-card il rend le widget visuellement trop
dense (header + sig + statut + description + tags + fragment
= 6 strates). Décision : les projets gardent uniquement le
logo (B), l'accent (green / red / amber) suffit à porter
l'état.
Accents
green = défaut (live, active). red = projet archivé / échec
documenté. amber = warning, en pause. currentColor
fait propager l'accent au SVG ET au fragment d'un seul hop CSS.
Implémentation référence :
preprod/js/signatures.js (data) +
main.js (render) +
patch-final.css §20 (visuel).
Phosphor Vigil — la série
Direction artistique 2026.06, déclinée de l'identité : PL. 01 (relevé
d'escalade — source de l'og:image et de la topographie de cover) et
PL. 03 (taxonomie raisonnée — 5 signatures nouvelles prêtes à servir :
VIGIL, MESH, SIGNAL, ROOT CAUSE,
BRIDGE). Usage : posters, slides d'ouverture de talks, covers d'articles.
Motion · Principes
Le mouvement raconte un système qui pense. Pas d'easing fancy : steps, lin, ease-out court.
Quatre règles non négociables
Le mouvement vient du système, pas du décor
On anime ce qu'un terminal animerait : du texte qui s'écrit, des logs qui arrivent, des jauges qui se remplissent. Jamais des éléments qui « dansent » pour faire joli.
steps() ou ease-out court — rien d'autre
Le typewriter et les barres avancent par paliers (steps).
Les reveals utilisent un outQuad ≤ 520ms. Bounce, elastic,
overshoot : interdits.
Une seule fois, puis calme
Les animations d'entrée jouent une fois (IntersectionObserver +
unobserve). Aucune boucle infinie sauf les signaux d'état :
curseur, dot live, glow lent.
Dégradable, toujours
prefers-reduced-motion → état final direct. CDN anime.js
indisponible → la page reste complète et lisible. L'animation est un
bonus, jamais une dépendance.
À éviter en motion
- ✕ Bounce, elastic, overshoot — jamais.
- ✕ Parallax au scroll — sauf si vraiment justifié, et discret.
- ✕ Spinners ronds génériques. Préférer un [████░░░░] ASCII.
- ✕ Animations > 3s sauf cinematic intro.
- ✕ Auto-play vidéo avec son.
- ✕ Card flip 3D au hover — testé sur les 2 premières
project-cardde la page Projets, retiré : effet "show-off" qui rompt la grille calme. Hover des cards = uniquement border-color shift + box-shadow subtle (cf. tokens--t-instant/--t-quick).
Card hover — borderline only
Tous les composants type terminal-card,
project-card, article-card, career-tl__item,
career-cert, career-edu__item partagent une
même grammaire de feedback : changement de
border-color (vers --primary ou
--cyan) + box-shadow lointain (offset Y 4px, blur 16-20px,
opacity 0.06-0.08), durée --t-quick (160ms ease-out).
Pas de translation, pas de scale, pas de rotation, pas de flip.
Le pulse vient des états système (status-badge, dot live), pas
des interactions.
Pas de translate. Pas de scale. Pas de flip.
.terminal-card {
border: 1px solid var(--border);
transition: border-color var(--t-quick) ease-out,
box-shadow var(--t-quick) ease-out;
}
.terminal-card:hover {
border-color: var(--primary);
box-shadow: 0 4px 16px rgba(0, 255, 65, 0.07);
}
Images & cache discipline
Image format
WebP first, JPG fallback via CSS
image-set(). Cible : ~50% du poids JPG
à qualité visuelle équivalente. Exemple en prod :
bg-circuit est passé de 419 KB JPG à
201 KB WebP. AVIF prévu plus tard pour gagner 20%
supplémentaires sur navigateurs récents.
SVG inline · a11y
Tout <svg> purement décoratif
(icône qui accompagne un texte) doit porter
aria-hidden="true". Les SVG sémantiques
(logo, signature article) gardent un
role="img" + aria-label si
nécessaire. Le décodage automatique de chaque
<svg> par les lecteurs d'écran
est sinon vocalisé en bruit.
Cache discipline
Tous les assets statiques sont versionnés via
?v=<git short hash> partagé entre
tous les fichiers d'une même release. Un script
deploy/bump-cache.sh propage le hash
après chaque commit. Élimine les risques de
stale-cache sur fichiers interdépendants
(config-*.js, signatures.js, main.js).
Motion · Timing
Les tokens CSS + les constantes propres aux patterns animés.
| Token | Durée | Easing | Usage |
|---|---|---|---|
| --t-instant | 80ms | linear | hover · focus · feedback |
| --t-quick | 160ms | ease-out | panneaux, dropdowns, toggles |
| --t-base | 300ms | ease-out | page transitions, theme switch |
| --t-cinematic | 2400ms | ease-in-out | glow pulse, hero typewriter |
| --t-scan | 2500ms | linear | scanline, loaders longs |
| Pattern | Valeur | Easing anime.js | Note |
|---|---|---|---|
| Typewriter | 34–38ms / caractère | — (setInterval) | vitesse lecture humaine |
| Log stagger | 180ms entre lignes · 360ms / ligne | outQuad | la signature PDS |
| Barres ASCII | 1100ms | steps(18) | 18 = largeur de la barre |
| Compteurs | 1100ms | outQuad | arrondir à l'entier |
| Tracé SVG | 700ms · stagger 120ms | outQuad | nœuds : fade après tracé |
| Reveal scroll | 480–520ms · translateY 14–18px | outQuad | threshold IO : 0.18 |
| Boot sequence | 180ms × lignes + fade 450ms | outQuad | total < 3s obligatoire |
const { animate, stagger } = await import(
'https://cdn.jsdelivr.net/npm/animejs@4.0.2/+esm'
);
Motion · Patterns
Les mouvements canoniques, joués en live + code copiable. Survoler la section relance au scroll.
Caractère par caractère — jamais d'easing fluide
function typewriter(el, { speed = 38, delay = 0 } = {}) {
return new Promise(resolve => {
const text = el.dataset.text || '';
el.textContent = '';
setTimeout(() => {
let i = 0;
const id = setInterval(() => {
el.textContent = text.slice(0, ++i);
if (i >= text.length) { clearInterval(id); resolve(); }
}, speed);
}, delay);
});
}
La signature PDS — 180ms entre les lignes
animate('.term-line', {
opacity: [0, 1],
translateX: [-10, 0],
duration: 360,
ease: 'outQuad',
delay: stagger(180), // la cadence canonique
});
Le remplacement officiel de tout spinner
function asciiBar(pct, width = 18) {
const filled = Math.round((pct / 100) * width);
return '█'.repeat(filled) + '░'.repeat(width - filled);
}
document.querySelectorAll('[data-bar]').forEach(el => {
const state = { v: 0 };
animate(state, {
v: +el.dataset.bar, duration: 1100, ease: 'steps(18)',
onUpdate: () => { el.textContent = asciiBar(state.v); },
});
});
Les métriques qui montent
document.querySelectorAll('[data-count]').forEach(el => {
const state = { v: 0 };
animate(state, {
v: +el.dataset.count, duration: 1100, ease: 'outQuad',
onUpdate: () => { el.textContent = Math.round(state.v); },
});
});
Les signatures se dessinent — stroke-dashoffset
const shapes = card.querySelectorAll('.sig [stroke]');
shapes.forEach(s => {
const len = s.getTotalLength();
s.style.strokeDasharray = len;
s.style.strokeDashoffset = len; // invisible au départ
});
animate(shapes, {
strokeDashoffset: 0, duration: 700, ease: 'outQuad',
delay: stagger(120, { start: 200 }),
});
L'entrée standard — une fois, puis plus rien
const io = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
io.unobserve(entry.target); // R3 : une seule fois
animate(entry.target, {
opacity: [0, 1], translateY: [18, 0],
duration: 520, ease: 'outQuad',
delay: (+entry.target.dataset.idx || 0) * 120,
});
});
}, { threshold: 0.18 });
Cinematic intro — budget < 3 secondes
const lines = document.querySelectorAll('.boot__line');
animate(lines, {
opacity: [0, 1], translateX: [-12, 0],
duration: 300, ease: 'outQuad', delay: stagger(180),
});
await new Promise(r => setTimeout(r, 180 * lines.length + 450));
document.getElementById('boot').classList.add('is-done');
Motion · Status chrome
Statusbar fixe + scroll progress : la page elle-même devient un système vivant.
// progress bar — <div id="scroll-progress"><i></i></div>
const fill = document.querySelector('#scroll-progress i');
window.addEventListener('scroll', () => {
const max = document.documentElement.scrollHeight - innerHeight;
fill.style.width = Math.round(scrollY / max * 100) + '%';
}, { passive: true });
// horloge statusbar
setInterval(() => {
clock.textContent = new Date().toLocaleTimeString('fr-FR');
}, 1000);
Ce chrome est du statut, pas de l'animation : il tourne en
continu, listener passive, aucune dépendance anime.js. La
progress bar fait 2px, vert + glow léger ; la statusbar reprend la grammaire
tmux : gauche = état, centre = chemin, droite = métriques.
Motion · Fallbacks
R4 en pratique : la page doit être complète sans moteur, sans CDN, sans motion.
prefers-reduced-motion
Détecté en JS et en CSS. Tout passe à l'état final : barres pleines, compteurs à leur valeur, lignes visibles. Les démos restent jouables au clic — c'est une demande explicite de l'utilisateur.
CDN indisponible
L'import ESM est dans un try/catch. En échec :
staticFallback() rend tout visible et rempli. anime.js
est un enhancement, pas une dépendance.
Overlay timeout
Un setTimeout inline (hors module, donc immune aux erreurs
d'import) dissout le boot overlay après 2500ms quoi qu'il arrive.
Écran noir impossible.
const REDUCED = matchMedia('(prefers-reduced-motion: reduce)').matches;
async function run() {
if (REDUCED) return staticFallback(); // F1
let animate, stagger;
try {
({ animate, stagger } = await import(
'https://cdn.jsdelivr.net/npm/animejs@4.0.2/+esm'));
} catch {
return staticFallback(); // F2
}
/* … séquence animée … */
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation: none !important;
transition: none !important;
}
.boot { display: none; }
}
Voice
Comment PDS s'exprime à l'écrit, peu importe le canal.
Direct, factuel, système
- Phrases courtes. Verbes d'action.
- Métaphores système : escalade, post-mortem, runtime, p95.
- Citer des chiffres réels (durées, incidents, années).
- FR par défaut, EN pour articles techniques internationaux.
- Reconnaître l'incertitude quand elle existe.
- "Synergies", "leverage", "best-in-class".
- Emojis dans titres, nav, ou body professionnel.
- Promesses absolues ("100% safe", "zero risk").
- Mélanger FR/EN dans une même phrase.
- Ton défensif, victimisation, corporate-speak.
Mots qu'on aime · qu'on évite
| On utilise | On évite | Pourquoi |
|---|---|---|
| escalade · post-mortem | "crise" · "drame" | Vocabulaire ops, pas émotionnel |
| runtime · latence · p95 | "performance" vague | Précision > généralité |
| incident · root cause | "problème" · "souci" | Termes techniques nommés |
| shipper · livrer | "déployer un livrable" | Verbes courts, actifs |
| "je ne sais pas encore" | "on travaille dessus" | L'honnêteté sur l'incertitude |
Avant / après
avant
"Nous mettons un point d'honneur à offrir une expérience de qualité à tous nos clients en adressant leurs problématiques."
après
"Quand un ticket part en escalade, on prend le téléphone dans les 15 min."
avant
"Notre solution leverage les dernières technologies cloud-native pour des résultats best-in-class."
après
"On tourne sur Kubernetes. p95 sous 80ms en prod. Le reste est documenté."
Tone
La voix reste, le ton s'adapte. Voici les 6 contextes principaux.
Six contextes, six réglages
Adaptation par canal
| Canal | Ton dominant | Longueur | Exemples |
|---|---|---|---|
| pdesouza.fr | Documenté précis | 800–2500 mots | articles, post-mortems |
| Engagé direct | 120–280 mots | retours d'incidents, opinions | |
| Email signature | Sec | 3 lignes max | nom · rôle · prompt court |
| Talks | Engagé direct | 20–40 min | récit d'incident comme thriller |
| Mentorat 1:1 | Bienveillant net | réponse adaptée | écouter d'abord, répondre court |
Phrase de clôture
Tout ce que PDS publie peut se terminer par une phrase courte, un peu sèche, qui ferme l'incident.