vizir.bet

Planche de composants

Passe de mutualisation, d’accessibilité et de SEO, pour une stack HTML / CSS / JS natifs : classes CSS pour la présentation, éléments personnalisés vzr- pour le comportement, et le natif d’abord (<details>, <dialog>, popover, <table>). Chaque entrée porte sa forme HTML, son API d’attributs et son contrat d’accessibilité. Toutes les valeurs viennent de tokens.css.

V1 — Août 2026
HTML · CSS · JS natifs
Comment lire cette planche
Badge
La forme à implémenter : classe CSS (présentation pure, aucun JS) ou balise personnalisée vzr- (comportement, en light DOM).
API
Classes modificatrices, attributs HTML et attributs d’état. L’état vit dans les attributs ARIA réels : une seule source de vérité pour le CSS et pour les lecteurs d’écran.
A11y
Contrat non négociable : rôles, états ARIA, clavier, cible tactile, et dégradation sans JavaScript. À implémenter dans le composant ou le gabarit, pas dans la page.
01 — Fondations (jetons)

Palette sémantique

Aucun composant ne référence un hexadécimal. Jetons complets : --surface-2 (squelettes, encarts inline), --accent-hover / --accent-active, --disabled-* (contraste ≥ 3:1), --success, --overlay.

Light — [data-theme="light"]
--bg
#FAFAFA
--surface
#FFFFFF
--surface-2
#F0F0F0
--text
#0A0A0A
--text-2
#5C5C5C
--rule
#C9C9C9
--rule-2
#E5E5E5
--border-fn
#767676
--accent
#000ADB
--disabled-bg
#E8E8E8
--danger
#C1121F
--success
#0F7A3D
Dark — [data-theme="dark"]
--bg
#0A0A0A
--surface
#141414
--surface-2
#1F1F1F
--text
#F5F5F5
--text-2
#9A9A9A
--rule
#474747
--rule-2
#262626
--border-fn
#8C8C8C
--accent
#C4DB00
--disabled-bg
#2E2E2E
--danger
#E5484D
--success
#3DD68C

Anneau de focus, cible tactile, texte masqué

Focus — 1 seule règle globale

:focus-visible → outline 2px var(--accent), offset 2px. Sur un aplat d’accent, double anneau --bg + --accent via la classe vzr-focus-inset. Jamais outline:none sans remplacement.

Cible tactile — --tap: 44px

Le glyphe peut mesurer 14px, la zone cliquable jamais moins de 44×44 — y compris les onglets de la barre basse et les cellules de jour du calendrier.

Texte pour lecteur d’écran
Niveau de confiance : Élevé

Classe .sr-only (définie une fois dans tokens.css). Elle préfixe les tags de confiance/résultat pour que la couleur ne porte jamais seule le sens.

Échelle typographique et rôles sémantiques

Jeton
Balise attendue
Rendu
display/40
h1, une seule par page
Course de galop, Longchamp
heading/24
h2
Résultats et ROI
heading/18
h3
Fantastic Wind
body/16
p
Le modèle se met à jour pendant la nuit ; les résultats ne remontent pas dans la minute qui suit une course.
body/14
p, span
Driver – J. Bazire
label/12
h2/h3 si c’est un titre de section, sinon span
R1 · Vincennes
num/tabular
font-variant-numeric: tabular-nums
4,50  12,00  2,80

Principe non négociable : la hiérarchie h1 → h2 → h3 doit être réelle et sans saut de niveau — c’est le premier levier de navigation au lecteur d’écran. Les étiquettes en capitales qui ouvrent une section (« R1 · Vincennes », « Niveau de confiance ») sont des titres, pas des décorations.

Espacement, grille, contrat de colonnes

Échelle — 8 / 16 / 24 / 32 / 48 / 64
sp-1
sp-2
sp-3
sp-4
sp-5
sp-6
Contrat de colonnes des listes de conseils
44
flex
64
150

--col-fav 44 · flex · --col-odds 64 · --col-gap 16 · --col-tag 150. En-tête et lignes lisent les mêmes jetons : un seul endroit à changer. Points de rupture : mobile 4 col. (90% de large), tablette 8 col. (85%), desktop 12 col. (75%, plafond 1600px), bascule des lignes en grille sous 480px.

02 — Primitives — classes CSS, css/components/
.btn <button class="btn btn--primary"> · modificateurs : --primary --secondary --tertiary --danger --sm --block · états : [disabled] [aria-disabled] [aria-busy] · navigation : <a class="btn">
Mutualisation. Deux tailles seulement : md = 14px / 12-24 (bordure : 11-23), sm = 13px / 10-20 (bordure : 9-19). Tout CTA de page est md, tout CTA d’en-tête ou de modale est sm.
A11y. Toujours un <button type> ou, si l’action navigue, un <a class="btn"> (jamais un div cliquable). Ne pas créer de <vzr-button> : envelopper un bouton casse la soumission de formulaire et la sémantique. disabled retire l’élément du parcours clavier : préférer aria-disabled="true" + onClick neutralisé quand l’utilisateur doit pouvoir comprendre pourquoi c’est bloqué. loadingaria-busy="true" et libellé qui change (« Envoi… »), pas seulement un spinner.
.icon-btn <button class="icon-btn"> · [aria-label] obligatoire · --sm (36px) --accent --danger · états : [aria-pressed] [aria-expanded] + [aria-controls] · enfant .icon-btn__badge
Mutualisation. Une seule classe pour favori, info, cloche (avec .icon-btn__badge), menu, fermeture de feuille/modale, flèches de semaine, œil du mot de passe. Les croix de fermeture de feuille/modale et la bascule de thème doivent aussi être de vrais boutons, jamais une icône seule.
A11y. aria-label est obligatoire (pas de bouton sans nom accessible), le glyphe porte aria-hidden. Bouton à deux états (favori) ⇒ aria-pressed et libellé qui décrit l’action à venir. Bouton qui ouvre un panneau ⇒ aria-expanded + aria-controls. Le compteur de la cloche est aria-hidden : le nombre est déjà dans le libellé.
.tag <span class="tag" data-tone="faible|moyen|eleve|won|lost|np"> · --sm · préfixe .sr-only obligatoire
Trois niveaux de confiance, pas quatre : faible, moyen, élevé. Le niveau « nul » n’existe plus ; le jeton --conf-nul-* reste employé pour l’état de résultat « Non partant ».
Confiance : Faible
Confiance : Moyen
Confiance : Élevé
Résultat : Gagné
Résultat : Perdu
Résultat : Non partant
Dark — size="sm" (usage en ligne de conseil)
Faible
Moyen
Élevé
Non partant
Gagné
Perdu
Mutualisation. Un gabarit, six valeurs de data-tone — trois de confiance (faible, moyen, eleve) et trois de résultat (won, lost, np) —, deux tailles : md 13px/10-16 en démonstration et filtre, sm 12px/8-12 en ligne de liste. Ne jamais confondre avec .chip (contour, sélectionnable) ni avec le .label de catégorie d’article.
A11y. Élément non interactif (span). Le préfixe .sr-only injecte « Confiance : » / « Résultat : » pour que l’information ne dépende ni de la couleur ni de la colonne. Le tag doit appartenir à la ligne du cheval dans l’ordre de lecture du DOM.
.chip + vzr-chip-group <button class="chip" aria-pressed> dans <vzr-chip-group> · émet vzr:change .label class="label" · --sm --accent · balise h2/h3 quand l’étiquette ouvre une section
Label md · R1 · Vincennes Label sm · Niveau de confiance Label sm accent · Football
Mutualisation. .label remplace sept déclinaisons manuelles de la même étiquette Archivo capitales : titre de section, nom de réunion, en-tête de colonne, titre de groupe de filtres, catégorie d’article, libellé de ROI, en-tête de colonne de pied de page. .label--accent couvre la catégorie d’article. .chip est toujours un vrai bouton à bascule, jamais un div.
A11y. .chip : <button aria-pressed> dans un role="group" nommé, 44px de haut minimum, sélection marquée d’une coche en plus de la couleur ; <vzr-chip-group> gère la bascule et émet vzr:change. .label : mettre la classe sur un <h2>/<h3> quand l’étiquette ouvre une section — sinon la page n’a plus de structure de titres.
.field .field > label.field__label[for] + contrôle + .field__hint + .field__error input · select · textarea · checkbox (natifs) + vzr-form class="input|select|textarea" · [aria-invalid] [aria-describedby] [autocomplete] · vzr-form : validation, focus, résumé role="alert"

Format invalide — 5 chiffres attendus.

Mutualisation. Un gabarit .field (libellé + aide + erreur + liaison des id) et les quatre contrôles natifs. Le champ date et le champ mot de passe ne sont qu’un .input suivi d’un .icon-btn. Le « sélecteur ouvert » disparaît : un <select> natif — et si une recherche devient nécessaire, un <vzr-combobox> bâti sur un <input list>, jamais une liste en div role="option" sans gestion clavier.
A11y. label forid écrit dans le HTML (pas généré en JS, il doit exister sans JavaScript). Erreur : aria-invalid="true" + aria-describedby pointant sur le message, bordure portée à 2px et icône d’alerte — la couleur seule ne suffit pas. Aide et erreur peuvent coexister dans aria-describedby. À la soumission, focus sur le premier champ invalide et résumé d’erreurs en role="alert". Cases à cocher : zone cliquable de 44px via le label.
vzr-tabs · vzr-radio-group <vzr-tabs> (role=tablist + tabpanel) ou <vzr-radio-group name value> (role=radiogroup) · gabarit .segmented · --icon --grid · tabindex tournant + flèches
variant="text" · role="tabs"
variant="icon"
variant="grid" · role="radiogroup"
Mutualisation. Type de pari (Turf), discipline (Sports), domaine (Favoris), périodicité (Formules), moyen de paiement (Paiement) : un seul composant, trois variantes visuelles, deux sémantiques.
A11y — le point le plus sensible. role="tablist" n’est légitime que si le contrôle change un panneau de contenu dans la même page : il faut alors aria-controls vers un role="tabpanel" identifié, un seul onglet dans l’ordre de tabulation (tabindex 0 / -1) et la navigation par flèches ← →, Début/Fin. Quand le contrôle est un choix de formulaire (périodicité, moyen de paiement), c’est role="radiogroup" + role="radio" aria-checked. Quand ce n’est ni l’un ni l’autre (filtre cumulable), utiliser Chip. Icône seule ⇒ nom accessible en .sr-only, jamais un aria-label qui remplacerait un texte visible.
Sans JS. Onglets : les panneaux restent visibles à la suite, précédés de leurs titres (c’est le composant qui pose hidden). Choix de formulaire : préférer de vrais <input type="radio"> stylés en .segmented — recommandé pour Paiement et Formules, où la robustesse prime sur le rendu.
.avatar class="avatar" · --sm (36px) · initiales aria-hidden .stat .stat > .label + .stat__value + .stat__meta · info : .icon-btn[popovertarget] .skeleton class="skeleton" · conteneur [aria-busy] + annonce .sr-only unique
ROI · Football
30 jours
+8,4 % tendance en hausse
412 conseils publiés
7 jours
−4,2 % tendance en baisse
86 conseils publiés
Chargement des conseils…
Mutualisation. Même .stat pour le profil du pronostiqueur ci-contre et pour le bandeau de performance sous le calendrier des pages Turf et Sports : un seul .icon-btn d’info partagé par la paire 30j/7j (jamais un par valeur, sinon les deux colonnes désalignent), .vzr-roi-row empile les deux valeurs en colonne avec un faible écart (gap:12px) et passe en ligne dès 480px (gap:20px, pas de grande marge), et le libellé porte la discipline ou le type de pari sélectionné (« ROI · Football »). Sur Turf et Sports, .vzr-cal-roi-row regroupe ce bandeau avec la navigation par date juste au-dessus : empilés en mobile (comportement inchangé), côte à côte dès 900px (calendrier 2 fr, bandeau 1 fr). La valeur (signe + nombre) porte white-space:nowrap pour ne jamais se rompre entre le signe et le chiffre quand la colonne se resserre.
A11y. .avatar : les initiales sont décoratives (aria-hidden), le nom lisible est porté par la ligne voisine ; avec src, alt="" et non le nom répété. .stat : le signe est déjà dans la valeur (« −4,2 % »), aucune couleur n’encode la polarité — conserver ce choix, il est bon. Le glyphe de tendance (ph-trend-up/down, ou ph-minus si stable) code une donnée distincte du signe — la trajectoire récente, pas la polarité : --success en hausse, --danger en baisse, --accent stable. La couleur renforce une forme déjà différente par état (jamais seule porteuse de sens) et reste toujours accompagnée d’un .sr-only (« tendance en hausse/baisse/stable ») pour les lecteurs d’écran et le mode sans couleur. Quand la sélection qui détermine la valeur (discipline, type de pari) peut changer sans rechargement de page, .stat__value porte aria-live="polite" pour annoncer la mise à jour. .skeleton : conteneur aria-busy="true", barres aria-hidden, une annonce .sr-only unique — pas une barre lue N fois.
.roi-detail <details class="roi-detail"> > table par type de pari > th colspan (période) > lignes global + 3 niveaux de confiance
Voir le détail des statistiques
Pari gagnant
Statistiques détaillées — Pari gagnant
Période / confiance Réussite ROI
Veille
Tous niveaux de confiance36,4 %+35,2 %
Confiance faible36,8 %+54,2 %
Confiance moyenne66,7 %+80,0 %
Confiance élevée27,3 %-10,0 %
30 derniers jours
Tous niveaux de confiance31,4 %+3,1 %

Reprend .stat pour l’en-tête chiffré (30j / veille) et ajoute ce panneau repliable — en superposition (position:absolute, ombre) plutôt que de repousser le contenu — pour le détail par niveau de confiance et par période (veille, 30 jours, depuis le début). Une seule table à la fois : celle du type de pari (ou marché) actif dans l’onglet au-dessus ; changer d’onglet change la table affichée. Se referme sur clic extérieur ou Échap. Contenu vertical plafonné en .vzr-scroll-y si la liste dépasse la hauteur du panneau.

Mutualisation. Un seul gabarit pour les trois disciplines : Turf, Football et Tennis partagent la même structure de données (type de pari ou marché → période → niveau de confiance → % de réussite + ROI) et le même rendu — seul INDICATEURS change de contenu par discipline. Une table par type de pari (deux tables : gagnant/placé, ou 1X2/plus pour le foot), une ligne <th colspan="3"> par période à l’intérieur du <tbody>, sans imbriquer de sous-table.
A11y. Vraies données tabulaires : <table> avec <caption> (.sr-only), en-têtes de colonnes scope="col", en-tête de période scope="colgroup", et libellé de ligne en <th scope="row"> — jamais un <td>. <details>/<summary> natif : replié par défaut, clavier et annonce d’expansion gratuits, zéro JavaScript pour l’ouverture/fermeture. Le signe du ROI est déjà porté par la valeur (+/-) ; la couleur (--success/--danger) ne fait que renforcer, jamais seule porteuse de sens.
.vzr-scroll-x · .vzr-scroll-y scrollbar-width:thin + scrollbar-color:var(--rule) transparent · ::-webkit-scrollbar assorti
.vzr-scroll-x — barre de dates (Turf, Sports, Favoris)
.vzr-scroll-y — panneau .roi-detail, listes longues
Mutualisation. Deux classes utilitaires couvrent tout défilement interne à un composant (jamais la page elle-même, qui garde son ascenseur natif — voir html ci-dessous) : .vzr-scroll-x pour un défilement horizontal (barre de dates, rangées de chips qui débordent), .vzr-scroll-y pour un défilement vertical (panneau .roi-detail, tout contenu à hauteur plafonnée). Même piste (var(--rule)), même épaisseur fine, même éclaircissement au survol (var(--text-2)) que l’ascenseur de la page — jamais l’ascenseur par défaut du navigateur. La fenêtre html applique la même palette via scrollbar-color et ::-webkit-scrollbar pour rester cohérente au niveau document.
A11y. scrollbar-width/scrollbar-color (Firefox) et ::-webkit-scrollbar-* (Chromium/Safari) sont purement visuels — le comportement clavier et molette du défilement natif reste intact, aucun ascenseur maison en JavaScript.
.msg + vzr-toasts class="msg msg--info|--success|--danger" · --toast · <vzr-toasts> dans la coque · API : toast(texte, {tone})
Données momentanément indisponibles. Réessayez dans quelques minutes.
Pseudo mis à jour.
Veuillez corriger les erreurs du mot de passe.
Mutualisation. L’encart d’indisponibilité et le toast partagent icône, ton et grammaire : un composant, deux variant. inline se pose dans le flux, --toast se pose en haut centré dans l’élément <vzr-toasts> présent dans la coque de chaque page.
A11y. Succès et information ⇒ role="status" (aria-live="polite") ; erreur ⇒ role="alert". La zone d’annonce doit exister dans le DOM avant l’apparition du message, sinon rien n’est lu. Disparition auto à ~3s : uniquement pour les toasts non critiques ; une erreur reste jusqu’à action. Respecter prefers-reduced-motion pour l’entrée.
03 — Composites — natif enrichi + js/components/
details.disclosure <details class="disclosure disclosure--label|--row|--footer" [open]> > <summary><h3> · <vzr-accordion> optionnel (un seul panneau ouvert)
variant="label" — réunion turf

Contenu de la réunion…

variant="row" — discipline

photo — turf (duotone)

variant="footer" — colonne de pied de page (< 768px)

Mutualisation. L’étiquette repliable, l’accordéon de disciplines et le pied de page mobile partagent un seul gabarit à trois modificateurs — et zéro ligne de JavaScript : <details> / <summary> natifs. Un <vzr-accordion> optionnel n’est nécessaire que si un seul panneau doit rester ouvert. Le pied de page bascule accordéon ⇄ colonnes en CSS seul.
A11y. <details> fournit l’état, le clavier et l’annonce d’expansion gratuitement, et fonctionne sans JavaScript — c’est la raison de le préférer à un accordéon maison. Le titre (h2/h3) se place dans le <summary>. Marqueur natif masqué, chevron dont le glyphe change (caret-rightcaret-down) plutôt qu’une rotation, qui disparaît sous prefers-reduced-motion. Ne jamais simuler le repli en height:0 : les liens resteraient focalisables.
ul.picks <ul class="picks"> · <li class="pick" data-discipline="turf|football|tennis" data-mode="tip|result"> · une colonne par conseil vzr-fav-toggle <vzr-fav-toggle pick="id"> autour du .icon-btn[aria-pressed] · repli : <form method="post">
Jockey/driver dans le top 3 en pari gagnant Jockey/driver dans le top 3 en pari placé
discipline="turf" · mode="tip" — ≥ 760px
R1 · Vincennes
C3 – Prix de Cornulier – 14h35
  • Fantastic Wind
    Driver – J. Bazire · Entr. – Y. Béthouart
    Cote4,50
    Élevé
  • Grand Marnier
    Driver – M. Guyon · Entr. – A. Fabre
    Cote3,20
    Moyen
discipline="football" · mode="tip" — même gabarit
Premier League · J8
  • 16h00
    Aston Villa – Everton
    1-N-2 · conseil : Aston Villa
    Cote1,90
    Fort
  • 18h30
    Fulham – Brentford
    1-N-2 · conseil : Match nul
    Cote3,20
    Faible
discipline="tennis" · mode="result" — passé
Roland-Garros · ATP
  • 15h00
    U. Humbert – H. Rune
    Vainqueur · conseil : U. Humbert
    Cote2,45
    Gagné
  • 17h20
    A. Fils – J. Sinner
    Vainqueur · conseil : J. Sinner
    Cote1,45
    Perdu
sous 760px — mêmes items, empilés
Premier League · J8
  • 16h00
    Aston Villa – Everton
    1-N-2 · conseil : Aston Villa
    Cote1,90
    Fort
Une colonne par conseil. Chaque conseil est un <li> à trois zones — favori (44px), identité, valeurs. L’identité s’empile toujours verticalement : horaire (sport) ou numéro (turf), nom, puis ligne de conseil. Les valeurs (cote + confiance/résultat) passent à droite au-delà de 760px, sous l’identité en dessous : aucun réalignement de colonnes, donc aucun chevauchement en étroit.
Échelle typographique. Nom 15px / 600, méta et ligne de conseil 12px, horaire et libellé « Cote » 10–11px capitales, cote 15px tabulaire, pastille 11px (padding 6/9). Un seul poids fort par conseil : le nom. Pas d’icône de discipline dans la liste — la discipline est déjà portée par l’onglet et l’en-tête de groupe.
Favori. Étoile systématique sur toutes les listes (turf, football, tennis, page Favoris), aria-pressed + libellé nominatif (« Ajouter Aston Villa – Everton aux favoris »), pleine et accentuée quand actif. Sur la page Favoris elle sert de retrait.
Mutualisation. Un seul gabarit pour turf / football / tennis (data-discipline) et pour les états conseil / résultat (data-mode). Ce qui varie : le contenu des trois lignes d’identité et la pastille de droite. Turf : libellé « Jockey » (Plat/Monté/Steeple/Haies) ou « Driver » (Attelé), trophée = top 3 gagnant, médaille = top 3 placé, légende avant la liste.
A11y. La liste n’étant plus alignée en colonnes, elle n’est plus un <table> : <ul> de conseils, chaque valeur préfixée de son libellé — « Cote » visible, « Confiance : » / « Résultat : » en sr-only dans la pastille. Titre de groupe (compétition ou course) en amont de la liste. Cibles tactiles 44px pour l’étoile et le bouton d’info.
.list-row <ul class="list"><li><a class="list-row" data-state="default|unread|pinned|closed"> · un seul lien par ligne
Mutualisation. Ligne de sujet de forum, entrée de menu et lignes de la page Favoris partagent le même gabarit : pastille avant, titre, méta, élément après. Un seul .list-row ; l’emplacement de tête reçoit un .avatar ou une icône.
A11y. « Épinglé », « non lu » et « clos » ne sont jamais signalés par la seule icône, pastille de couleur ou opacité : chaque état a un équivalent textuel (.sr-only ou méta visible « Clos »), et l’état clos utilise --text-2 plutôt qu’une opacité — l’opacité fait tomber le contraste sous 3:1 et ne peut pas être compensée. Liste = ul>li, ligne entièrement cliquable via un seul <a> (jamais deux liens imbriqués), hauteur minimale 44px.
.card class="card" · --highlighted --locked · titre porteur du lien .card--plan · .card--space · .card--article — trois compositions de la même coque .card
Recommandé

Tout

29 €/mois

  • Turf, football, tennis
  • Disciplines futures incluses
312 sujets

Communauté Turf

Réunions du jour, analyses de partants, retours d’expérience entre abonnés Turf.

Verrouillé

Communauté Sports

Non inclus dans votre formule Turf.

Changer de formule
photo — article 4:3
Football

Comment lire une cote de bookmaker avant un match

Décimale, fractionnaire, américaine : ce que chaque format révèle sur la probabilité implicite.

Mutualisation. Carte de formule, carte d’espace de forum et carte d’article partagent la même coque : filet 1px, aucun arrondi, 24px de padding, highlighted = filet 2px accent. Trois compositions sur une coque, pas trois composants indépendants.
A11y. Le titre de la carte porte le lien (« lien dans un titre »), et la flèche « Accéder / Lire plus » est aria-hidden : sinon le lecteur d’écran annonce deux liens vers la même cible. État locked : jamais d’opacité — couleur --text-2, filet décoratif, et une étiquette « Verrouillé » lisible ; le CTA reste un vrai lien focalisable. La liste de bénéfices d’une formule est un ul, pas des br.
vzr-date-strip <vzr-date-strip value="2026-10-14" week-of="2026-10-12"> · cellules .day en role=radio · émet vzr:change vzr-date-picker grille de .day dans [popover] (desktop) ou dialog.sheet (mobile), clonée depuis <template id="tpl-month">

Semaine 42

Anneau = aujourd’hui (aria-current="date") · soulignement = date sélectionnée (aria-checked) · jours passés en --text-2.

Mutualisation. La barre de semaine et le calendrier partagent la cellule .day (états passé / aujourd’hui / sélectionné). Le calendrier n’est pas un composant à part : c’est une grille de .day dans un [popover] (desktop) ou un <dialog class="sheet"> (mobile), clonée depuis un <template>. Sur Turf et Sports, cette section vit dans .vzr-cal-roi-row avec le bandeau ROI voisin (voir plus bas) : colonne empilée en mobile, ligne dès 900px, le calendrier prenant deux fois la largeur du bandeau.
A11y. Sélection de date = role="radiogroup" + role="radio" aria-checked, qui décrit un choix exclusif. Aujourd’hui = aria-current="date", distinct de la sélection. Un seul jour dans l’ordre de tabulation, flèches ← → pour changer de jour, ↑ ↓ pour changer de semaine. Les cellules du calendrier sont des boutons de 44px minimum, jamais des span. Fermeture du calendrier : Échap, clic extérieur, focus restitué au déclencheur.
[popover] déclencheur [popovertarget] + <div popover class="popover" role="dialog" aria-labelledby> · natif : couche supérieure, Échap, clic extérieur dialog.sheet <dialog class="sheet sheet--bottom|--right"> ouvert par showModal() · <vzr-dialog> n’ajoute que le glissement et le verrou de défilement dialog.modal <dialog class="modal modal--danger" aria-labelledby aria-describedby> · actions dans <form method="dialog"> · autofocus sur Annuler
Popover — desktop (≥ 768px)
Faible
Sheet side="bottom" — mobile, même contenu

Confiance « Faible »

Ne signifie pas donnée manquante : le modèle identifie un risque élevé sur ce pari.

Lire l’article de méthodologie →
Modal tone="danger"
Sheet notifications side="right"
Mutualisation. Infobulle ancrée, feuille d’infobulle mobile, feuille de filtres, panneau de menu et modale reposent tous sur deux mécanismes natifs : l’attribut popover + popovertarget pour le non modal, <dialog> + showModal() pour les feuilles et modales. Il n’y a plus d'Overlay maison à écrire : voile ::backdrop, Échap, clic extérieur, couche supérieure, piège de focus et inert sont fournis par le navigateur. PopoverSheet : même contenu, choix par point de rupture — un seul contenu, la bascule se fait par matchMedia dans le composant, pas dans les pages.
A11y. <dialog> ouvert par showModal() apporte le rôle, la modalité, le piège de focus, Échap, inert et la restitution du focus ; open posé à la main ne les apporte pas. aria-labelledby obligatoire, aria-describedby pour la modale destructive. Focus initial sur le premier élément focalisable — jamais sur le bouton destructeur (autofocus sur « Annuler »). Voile ::backdrop en --overlay : ferme au clic, mais toujours doublé d’un bouton « Fermer » réel de 44px, focalisable. Le titre de la modale destructive pose la question, le bouton nomme l’action (« Supprimer »), pas « OK ».
Sheet notifications. Même coque sheet--right que le panneau de menu, ouverte par la cloche : le déclencheur porte aria-haspopup="dialog", aria-expanded, aria-controls="notif-sheet" et un libellé qui contient le compte (« Notifications, 3 non lues ») — la bulle de compteur reste aria-hidden. Chaque ligne non lue est doublée d’un .sr-only « Non lue. » en plus de la pastille d’accent, « Tout marquer comme lu » annonce son effet dans une région role="status", et l’état « non lue » n’est jamais porté par la seule couleur.
.share <section class="share" aria-labelledby> · .label + ul.share__list > li > a|button.icon-btn.icon-btn--outlined vzr-share <vzr-share> autour du bloc · lit l’URL dans <link rel="canonical"> · presse-papiers → toast(…, {tone:'success'}) · repli : liens réels
Bloc de partage — pied d’article
Partager cet article

Ordre invariable : lien, Facebook, X, puis le bouton système (affiché uniquement si navigator.share existe). Le bouton « lien » copie l’adresse canonique : pas de champ URL à afficher.

Confirmation — .msg--toast
Lien de l’article copié.

Aucun composant propre à .share : la copie appelle toast('Lien de l’article copié.', { tone: 'success' }) et s’affiche dans le <vzr-toasts> de la coque, ton danger si le presse-papiers refuse (le message porte alors l’adresse en clair).

Mutualisation. Un seul bloc .share pour l’article, et demain pour un sujet de forum ou une fiche de conseil : seuls l’URL et le titre changent, et ils ne sont écrits qu’une fois<vzr-share> lit <link rel="canonical"> et document.title, jamais une URL recopiée dans le gabarit (source unique, et donc pas de partage d’une adresse avec paramètres de filtre). Les boutons réutilisent .icon-btn avec le modificateur --outlined (filet 1px --border-fn, 44px, survol en accent) : aucune nouvelle forme de bouton. Les logos viennent de Phosphor (facebook-logo, x-logo, link, share-network), à la même taille de 20px que le reste de l’interface — aucun logo redessiné, aucun aplat de couleur de marque. La confirmation réutilise .msg--toast et <vzr-toasts> : rien de spécifique au partage.
Réseaux retenus. Seuls les réseaux offrant un point de partage web réel ont une icône : Facebook (sharer.php) et X (intent/post). Instagram n’en expose aucun — pas d’icône Instagram, plutôt qu’un bouton qui copierait l’adresse en promettant un partage. Le partage vers Instagram passe par le bouton système sur mobile, où la feuille de partage de l’appareil propose la vraie entrée Instagram. Même règle pour tout réseau ajouté plus tard : pas de point de partage documenté, pas d’icône.
A11y. Le bloc est une <section aria-labelledby> dont le titre visible (« Partager cet article ») sert d’étiquette, et la <ul> annonce le nombre d’options. Chaque commande sans texte porte un aria-label qui nomme le réseau et l’effet (« (nouvelle fenêtre) », « copier le lien de l’article »), le glyphe étant aria-hidden. La copie est annoncée par le toast, dans la région role="status" présente avant l’action — jamais un simple changement d’icône ou de couleur ; auto-effacement à ~3s. Cibles de 44px, focus visible :focus-visible. Liens externes en target="_blank" rel="noopener". Le bouton système n’est rendu que si l’API existe : pas de bouton mort.
Sans JS. Facebook et X sont de vrais <a href> vers leur point de partage : ils fonctionnent sans JavaScript. Seuls « Copier le lien » et « Autre application » dépendent du script, et <vzr-share> les retire (hidden) si le presse-papiers ou navigator.share n’est pas disponible — l’adresse reste alors accessible par la barre du navigateur.
Cartes de partage <head> : og:* + twitter:card · rendu attendu chez Facebook, X et les messageries · gabarit d’image 1200 × 630
Facebook — og:type=article
VIZIR.BET
Comment lire une cote de bookmaker avant un match
Décimale, fractionnaire, américaine : ce que chaque format révèle sur la probabilité implicite.

Grande image imposée par og:image ≥ 1200 × 630 (sous 600px de large, Facebook rétrograde en vignette carrée). Domaine en capitales : ajouté par la plateforme, pas par nous. Titre coupé vers 88 caractères, description vers 200 — et souvent masquée sur mobile : jamais d’information indispensable dans la description.

X — summary_large_image
Comment lire une cote de bookmaker avant un match
Décimale, fractionnaire, américaine : ce que chaque format révèle sur la probabilité implicite.
vizir.bet

Même image que Facebook — un seul fichier à produire. X arrondit la carte et place le domaine sous le texte : le rayon et cet ordre appartiennent à la plateforme, on ne les imite nulle part dans le site. Titre coupé vers 70 caractères. Sans twitter:card, X retombe sur la vignette carrée summary.

Lien classique — messageries, Slack, LinkedIn
Comment lire une cote de bookmaker avant un match
Décimale, fractionnaire, américaine : ce que chaque format révèle…
vizir.bet
Aucune carte : https://vizir.bet/blog/lire-une-cote-de-bookmaker

Le lien copié par le bouton « lien » ne contrôle rien du rendu : chaque messagerie recadre l’image en vignette et tronque plus court. D’où deux exigences sur l’URL elle-même — lisible (slug en clair, pas d’identifiant ni de paramètre de suivi) et suffisamment parlante nue, puisque certains contextes (SMS, e-mail texte) n’affichent jamais de carte.

Métadonnées — une seule source, dans le <head>
<link rel="canonical" href="https://vizir.bet/blog/lire-une-cote-de-bookmaker">
<title>Comment lire une cote de bookmaker avant un match — vizir.bet</title>
<meta name="description" content="Décimale, fractionnaire, américaine : …">

<meta property="og:type"        content="article">
<meta property="og:site_name"   content="vizir.bet">
<meta property="og:locale"      content="fr_FR">
<meta property="og:url"         content="https://vizir.bet/blog/lire-une-cote-de-bookmaker">
<meta property="og:title"       content="Comment lire une cote de bookmaker avant un match">
<meta property="og:description" content="Décimale, fractionnaire, américaine : …">
<meta property="og:image"       content="https://vizir.bet/og/lire-une-cote-de-bookmaker.jpg">
<meta property="og:image:width"  content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt"   content="Trois formats de cote pour un même match, côte à côte.">

<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@vizirbet">
Mutualisation. X lit les og:* en repli : on n’écrit pas de twitter:title, twitter:description ni twitter:image — seuls twitter:card et twitter:site, qui n’ont pas d’équivalent Open Graph. Une image, un titre, une description, une URL : les mêmes valeurs que <title>, le h1 et le chapeau de l’article, générées par le gabarit, jamais ressaisies à la main. <vzr-share> lit ce même canonical.share) : une seule adresse pour le partage, les cartes et le référencement.
Gabarit d’image. Un seul format, 1200 × 630 (1.91:1), < 300 ko, JPEG ; les trois plateformes s’en servent. Zone de sécurité de 60px sur tout le bord (les vignettes carrées recadrent au centre) et texte incrusté ≥ 40px pour rester lisible en 400px de large. Chaque article a son image : à défaut, un visuel de repli par rubrique — jamais le logo seul, qui donne une carte identique pour tout le site.
Alt et a11y. og:image:alt est obligatoire : c’est le seul texte alternatif que les lecteurs d’écran obtiennent quand la carte est rendue par la plateforme, hors de notre HTML. Il décrit l’image, pas l’article, et ne redit pas le titre.
Note. Ces trois cartes sont des maquettes de contrôle : elles servent à valider longueurs et cadrage avant publication. Aucune ne fait partie de l’interface du site — les rendus réels appartiennent aux plateformes, et rien de ce style (rayon de X, aplat de domaine en capitales) ne doit être réemployé dans un composant.
04 — Coque applicative et navigation
Coque HTML = .skip-link + header.app-header + main#content + nav.bottom-nav (< 768px) + .compliance + footer.app-footer + <vzr-toasts> · vrai HTML dans chaque page (inclusion au build), jamais généré en JS
Aller au contenu principal
BottomNav — < 768px
ThemeToggle
Bouton unique dans l’en-tête ci-dessus — cliquez-le.

Icône réversible (soleil / lune), légère rotation au clic, thème persisté en localStorage et appliqué via data-theme.

Mockups Page Accueil : les 4 exports fournis avaient une large marge transparente autour de l’appareil — recadrés à leur contenu visible pour retrouver la taille d’affichage d’origine dans le hero.

vizir.bet fournit des conseils d’analyse, sans garantie de gain. Jouer comporte des risques : endettement, isolement, dépendance. Interdit aux mineurs. Appelez le 09 74 75 13 13 (appel non surtaxé).
Mutualisation. En-tête tablette et desktop ne diffèrent que par des tailles : un seul header.app-header, pas deux gabarits. .nav-item est partagé par l’en-tête et la barre basse (icône + libellé + état actif). footer.app-footer rend les mêmes données en colonnes ≥ 768px et en <details class="disclosure--footer"> en dessous, par CSS seul. Le bandeau légal et <vzr-toasts> font partie de la coque incluse dans chaque page — <vzr-toasts> doit exister avant toute annonce, sinon rien n’est lu.
A11y. .skip-link : premier élément focalisable, visible au focus, cible <main id="content">. Navigations dans <nav aria-label> distinct (« Navigation principale », « Pied de page ») et listes en ul>li, jamais en span non cliquables. Page courante = aria-current="page", jamais la couleur seule. Barre basse : 48px de haut, libellé texte conservé sous l’icône. Le bandeau de conformité est du contenu, pas une région ARIA. Ordre invariable des actions d’en-tête : cloche, menu, CTA.
05 — .state-screen : un gabarit pour sept écrans
.state-screen <section class="state-screen" data-tone="neutral|danger"> · --center · .state-screen__eyebrow · rendu côté serveur avec le bon code HTTP

Réservé aux abonnés

Les conseils turf, football et tennis, avec taux de confiance et cote, sont accessibles avec un abonnement vizir.bet.

Non inclus dans votre formule

Votre formule Turf ne couvre pas les conseils Sports. Passez à la formule Tout pour y accéder.

Accès à la communauté suspendu

Votre accès au forum a été suspendu le 14 octobre pour non-respect des règles. Vos accès aux conseils restent actifs.

Erreur 403

Accès refusé

Cette page est réservée aux utilisateurs disposant d’un compte.

Erreur 404

Cette page est introuvable

Le lien est peut-être erroné, ou la page a été déplacée. Les conseils et les résultats, eux, sont toujours là.

Voir la page 404 →

Pas de match aujourd’hui

Les conseils Sports reprennent demain à partir de 9h.

Les sept usages, tous par classes et attributs
Cas d’usage icon tone / align actions
Réservé aux abonnéslock-simpleneutral / startVoir les formules
Formule partiellelock-simpleneutral / startChanger + Comparer
Compte suspenduprohibitdanger / startContacter le support
Abonnement expiréclock-counter-clockwiseneutral / startRéactiver + Formules
Erreur 403lock-keyneutral / center + eyebrowConnexion + Inscription
Liste videcalendar-xneutral / center
Erreur 404compassneutral / center + eyebrowAccueil + Conseils du jour
Mutualisation — le gain le plus net de la passe. Sept usages, un même composant, dans les deux thèmes. L’indisponibilité de données reste un Message variant="inline" : c’est un incident transitoire, pas un état de page.
A11y. Le titre est le h1 de la page quand l’écran remplace tout le contenu (403, 404, compte suspendu), un h2 quand il remplace une section. tone="danger" ne colore que l’icône : le rouge reste réservé à la suspension de compte et aux actions destructrices. L’écran qui remplace une page doit aussi mettre à jour le <title> du document et être annoncé au changement de route.
06 — Contrat d’accessibilité (RGAA / WCAG 2.2 AA)

Corrigé dans cette passe

  • Éléments interactifs faits de div / span / <i> : croix de fermeture, pastilles de filtre, entrées de menu et de navigation, cellules de calendrier, bascule de thème → vrais button / a.
  • Listes de conseils passées en <table> avec caption et th scope.
  • role="tablist" détourné pour des choix de formulaire → radiogroup, avec tabindex tournant et navigation aux flèches.
  • Sélection de date en aria-pressedradiogroup + aria-current="date" pour aujourd’hui.
  • Anneau de focus généralisé (une règle :focus-visible) au lieu d’un seul état de bouton documenté.
  • Couleurs désactivées remontées à ≥ 3:1.
  • Opacité 0,55 / 0,6 des états verrouillé et clos remplacée par des jetons de couleur + une étiquette textuelle.
  • Sens porté par la couleur (confiance, résultat, épinglé, non lu) doublé d’un texte, visible ou .sr-only.
  • Erreurs de formulaire : aria-invalid, aria-describedby, icône, bordure 2px.
  • Toasts : role="status" / role="alert", toujours annoncés.
  • Feuilles et modales : role="dialog", aria-modal, piège de focus, Échap, restitution du focus, focus initial jamais sur l’action destructrice.
  • Lien d’évitement, <main id="content">, nav aria-label, aria-current="page".
  • Hiérarchie réelle h1 → h3 ; libellés d’icônes seules ; cibles 44px partout (barre basse à 48px).
  • prefers-reduced-motion respecté (chevrons, toasts, transitions).

À vérifier à l’implémentation

  • <html lang="fr"> et <title> unique par route ; annonce du changement de page au routage client.
  • Zoom texte à 200 % et largeur 320px sans perte de contenu : la table de conseils passe en dense, elle ne défile pas horizontalement.
  • Contraste vérifié après tout ajustement de jeton : --text-2 ≥ 4,5:1 sur --bg et --surface dans les deux thèmes.
  • Ordre de tabulation = ordre visuel dans l’en-tête (logo, nav, cloche, menu, CTA).
  • Formulaire de paiement : libellés persistants (pas de placeholder seul), autocomplete renseigné, erreurs regroupées en role="alert" à la soumission.
  • Contenu chargé en direct (résultats) : région aria-live="polite" unique, jamais une annonce par ligne.
  • Aucun contenu réservé aux abonnés masqué en CSS : le .state-screen remplace le contenu côté serveur.
  • Tests : axe-core en CI, parcours clavier complet sur Turf / Paiement / Forum, VoiceOver + NVDA sur la table de conseils.

Le détail complet — jetons, API d’attributs, arborescence de fichiers, classe de base des éléments personnalisés, carte page → composants, plan SEO et ordre de construction — est dans DESIGN-SYSTEM.md et tokens.css à la racine du projet.

07 — SEO et rendu

Le choix d’une stack HTML classique est un avantage SEO décisif à condition de ne pas le gâcher : ce qui rend le site indexable est exactement ce qui le rend accessible et utilisable sans JavaScript. Une seule discipline à tenir pour les trois.

Le HTML est la source de vérité

  • Une page = un fichier HTML complet. Aucun rendu de contenu côté client, aucune navigation SPA.
  • Les éléments vzr- n’injectent jamais de contenu textuel indexable : ils enrichissent du HTML déjà correct.
  • En-tête, navigation et pied de page sont du vrai HTML dans chaque page (inclusion au build), jamais générés en JS : une entrée de navigation en span reste invisible pour un robot.
  • Chaque jour de la barre de dates est un <a href> réel.
  • Recette : curl la page — le h1 et le contenu doivent y être ; JS désactivé ne doit pas vider la page.
  • Réserver la hauteur des vzr-…:not(:defined) plutôt que les masquer : zéro CLS, zéro contenu invisible.

Tête de page et structure

  • <html lang="fr">, title unique (≤ 60 car.) et description (≤ 155) rédigés à la main pour chaque page.
  • canonical absolue sur toutes les pages, y compris les variantes filtrées.
  • Open Graph + twitter:card, theme-color par schéma de couleurs (clair et sombre).
  • Un seul h1, hiérarchie sans saut, repères header / nav / main / footer.
  • Fil d’Ariane visible et balisé sur article, sujet de forum et page de date.
  • URL en minuscules à tirets, dates en /turf/2026-10-14, filtres en paramètres de requête avec canonical vers la page mère.

Données structurées (une par page)

AccueilOrganization + WebSite
ArticleBlogPosting
FormulesService + Offer
FAQFAQPage si visible dans la page
Sujet de forumDiscussionForumPosting
Pages profondesBreadcrumbList

sitemap.xml généré au build avec un lastmod réel, pages noindex exclues ; robots.txt fermant /paiement et /profil.

Contenu réservé aux abonnés — le point le plus délicat

Jamais de cloaking. Ni contenu complet servi aux robots, ni contenu payant présent dans le HTML puis masqué en CSS : c’est sanctionnable et trivialement contournable. Le .state-screen remplace le contenu côté serveur, avec le bon code HTTP.
Payant partiel. Un article dont la suite est réservée utilise le balisage paywall officiel : isAccessibleForFree: false + hasPart avec cssSelector: ".paywalled". Le début lisible doit être réellement lisible.
Intégralement réservé. Conseils du jour et forum : noindex, follow, plus une page vitrine publique et indexable par discipline qui explique la méthodologie.
Le levier principal : les archives. Les conseils passés et leurs résultats n’ont plus de valeur commerciale mais une forte valeur de recherche (« résultat R1 Vincennes 14 octobre »). Les publier en accès libre sur des URL stables /turf/2026-10-14, avec le ROI constaté : meilleure preuve de crédibilité et première source de trafic organique. Le jour J reste réservé.

Performance — Core Web Vitals

Polices hébergées en local, deux fichiers woff2 en preload, font-display: swap, sous-ensemble latin. Pas d’appel à Google Fonts en production.
Images avec width et height systématiques (zéro CLS), loading="lazy" sauf la première image visible, AVIF/WebP avec repli.
CSS de la coque en critique, reste concaténé et mis en cache long. Aucun framework CSS.
JS en type="module" (différé par nature). Budget indicatif : < 30 ko compressés pour tous les composants — atteignable puisque <details>, <dialog> et popover sont natifs. Seule exception synchrone : les six lignes anti-flash du thème.
Liens sortants vers des opérateurs : rel="nofollow sponsored noopener". Pages légales et jeu responsable réelles, indexables, liées depuis chaque pied de page.
Consentement et analytique : ne bloquent pas le rendu et ne décalent pas la mise en page.
08 — Badges de téléchargement

Ressources de marque fournies par Apple et Google, non modifiables : ni recoloration, ni recadrage, ni ajout d’ombre ou de contour, ni traduction du libellé. Version française, badge noir pour l’App Store et badge couleur pour Google Play, alignés sur une même hauteur de 46px. Ils s’emploient toujours par paire, dans l’ordre App Store puis Google Play.

Sur fond clair ou surface

Télécharger dans l’App Store Disponible sur Google Play

Hauteur de référence 46px, jamais sous 40px. Écart de 12px entre les deux badges, marge libre d’au moins un quart de la hauteur du badge tout autour.

Sur aplat d’accent

Télécharger dans l’App Store Disponible sur Google Play

Les deux badges portent leur propre fond : ils restent lisibles sur l’accent des deux thèmes, sans variante à produire.

Intégration

  • Fichiers : /assets/img/badge-app-store-fr.svg et /assets/img/badge-google-play-fr.svg.
  • Toujours dans un <a> vers la fiche de l’application, avec width et height sur l’<img> (zéro CLS).
  • Texte alternatif : « Télécharger dans l’App Store » et « Disponible sur Google Play ». Ne pas ajouter d’aria-label redondant sur l’image.
  • Emplacements prévus : accueil (bloc « Aussi sur mobile » et CTA final), pied de page.
09 — Rédaction et ponctuation

Règle non négociable : toute la ponctuation est française. Aucun texte livré — libellé d’interface, corps d’article, message système, gabarit de données — ne doit porter de ponctuation anglophone ni de tic de rédaction reconnaissable comme généré par une IA. À chaque génération de texte, relire les caractères un par un : un seul symbole fautif suffit à trahir la source.

Ponctuation française obligatoire

  • Guillemets français « … » pour toute citation ou terme rapporté, avec espace insécable après « et avant ». Jamais " … ".
  • Apostrophe typographique ' dans tout mot élidé (l’article, d’un, aujourd’hui) — jamais l’apostrophe droite '.
  • Espace insécable avant : ; ! ?, et comme séparateur de milliers (10 000).
  • Virgule décimale (4,5:1, 2,10), jamais le point décimal anglo-saxon.
  • Tiret cadratin espacé pour une incise réelle ; tiret demi-cadratin collé pour un intervalle ou un séparateur de champs.
  • Points de suspension : le caractère unique , jamais trois points bruts.

Symboles à bannir — signature IA

  • Guillemets anglais courbes " " ou droits " ", apostrophe droite '.
  • Tiret cadratin en tic de style — une incise par phrase par réflexe plutôt que par besoin réel de la phrase.
  • Émoji, coche , étoile , flèche en fil de texte courant (hors CTA de navigation déjà établi type « Lire plus → »).
  • Astérisques * pour l’emphase : utiliser <strong> / <em>.
  • Puces en prose, hors véritables listes <ul>.
  • Formulations creuses (« il est important de noter que », « en conclusion ») et gras systématique en fin de liste.