Files
Pierre MARTINandClaude Opus 5.5 c2c5d13651 Recette 1h : courbe, lignes de partie, en-tête, bouton des exercices, carte de l'accueil
Dernier point plus gros, vert seulement si le taux baisse ; valeurs au
premier et au dernier point, axe réduit à la ligne du 0. Une ligne de
partie = un lien vers le bilan, résultat court, date et niveau en
caption. Plus de « Retour », titre en h1 ; « Nouvelle partie » pleine
largeur en portrait ; « Demander des exercices à une IA ». La carte de
l'accueil sans partie en cours ne montre plus la partie finie (testé).
Phrase progression.hausse sans « un peu ». L'écran relit ses parties à
chaque visite sans montrer celles de la précédente. Test navigateur sur
trois courses jouées jusqu'au bout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 20:02:18 +01:00

3951 lines
81 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Style de l'application, selon la charte graphique (ADR 0010,
* docs/identite/charte/) : les couleurs, tailles et rayons sont les
* variables de tokens.json, sous le même nom, redéfinies pour le thème
* sombre ; les composants reprennent bundle.css. */
/* ------------------------------------------------------------ Police */
/* Alegreya Sans (SIL Open Font License 1.1, voir fonts/OFL.txt), réduite
* aux caractères latins et convertie en woff2 (voir docs/identite/README.md).
* Embarquée dans le site, donc dans le cache du service worker : elle
* fonctionne hors ligne. */
@font-face {
font-family: "Alegreya Sans";
src: url("fonts/AlegreyaSans-Regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Alegreya Sans";
src: url("fonts/AlegreyaSans-Italic.woff2") format("woff2");
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Alegreya Sans";
src: url("fonts/AlegreyaSans-Medium.woff2") format("woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Alegreya Sans";
src: url("fonts/AlegreyaSans-Bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Alegreya Sans";
src: url("fonts/AlegreyaSans-ExtraBold.woff2") format("woff2");
font-weight: 800;
font-style: normal;
font-display: swap;
}
/* ------------------------------------------------------------ Couleurs */
/* Thème clair par défaut ; thème sombre selon le système, ou forcé par le
* menu (data-theme sur <html>). Les deux blocs sombres sont identiques
* (vérifié par app/tests/ui.test.ts). */
:root {
--background: #f5efe5;
--surface: #fffcf6;
--surface-sunken: #ece3d4;
--text: #2b1d15;
--muted: #6b5c50;
--border: #ded2c0;
--border-strong: #8e7d6a;
--accent: #a8432b;
--on-accent: #ffffff;
--accent-soft: #f1d9cf;
--focus: #a8432b;
--error: #8c1c3a;
--verdict-good: #2f6b4f;
--verdict-doubtful: #7e5a0f;
--verdict-bad: #8c1c3a;
--brand-noyer: #4a2e1f;
--brand-ivoire: #f3ead8;
--brand-laiton: #8f6a1c;
--frame: #4a2e1f;
--bed: #e9dcc4;
--bar: #3f2619;
--tray: #3a2418;
--point-a: #6b4430;
--point-b: #c9ae88;
--point-number: #f3ead8;
--checker-player: #fafaf7;
--checker-player-edge: #55554f;
--checker-computer: #1c1c1c;
--checker-computer-edge: #b5b5b0;
--count-player: #1c1c1c;
--count-computer: #fafaf7;
--die-player: #fafaf7;
--pip-player: #1c1c1c;
--die-computer: #1c1c1c;
--pip-computer: #fafaf7;
--cube: #fafaf7;
--cube-text: #1c1c1c;
--highlight: #a8432b;
--target: rgba(168, 67, 43, 0.32);
/* Dernier coup joué (marques et traces) : le laiton, rien d'autre. */
--last-move: var(--brand-laiton);
--shadow-raised: 0 1px 2px rgba(43, 29, 21, 0.1);
--font-sans: "Alegreya Sans", "Gill Sans", "Segoe UI", system-ui, sans-serif;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
--space-8: 32px;
--button-height: 48px;
--page-width: 40rem;
--radius-sm: 6px;
--radius-md: 12px;
--radius-pill: 999px;
/* Mouvement (interaction.md). */
--duration-instant: 90ms;
--duration-fast: 160ms;
--duration-moderate: 240ms;
--easing-standard: cubic-bezier(0.2, 0, 0, 1);
--easing-exit: cubic-bezier(0.4, 0, 1, 1);
/* Voile sous le menu et les panneaux. */
--scrim: rgba(43, 29, 21, 0.5);
/* Hauteurs fixes de l'écran de jeu en portrait : rien ne bouge sous le
* doigt (interaction.md, principe 2). */
--topbar-height: 60px;
--strip-height: 44px;
--infoline-height: 44px;
--prompt-height: 50px;
--actions-height: 76px;
color-scheme: light;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--background: #17110d;
--surface: #221913;
--surface-sunken: #2c211a;
--text: #efe5d5;
--muted: #a99886;
--border: #3a2d24;
--border-strong: #7d6b5b;
--accent: #d0694b;
--on-accent: #1a110c;
--accent-soft: #4a2519;
--focus: #e58a6e;
--error: #f08fa6;
--verdict-good: #7ccba4;
--verdict-doubtful: #e0b04f;
--verdict-bad: #f08fa6;
--brand-laiton: #d6a64a;
--frame: #2a1a10;
--bed: #4a3626;
--bar: #24170e;
--tray: #22150d;
--point-a: #3a2519;
--point-b: #7e6142;
--point-number: #cdbfa8;
--checker-player: #f2f2ef;
--checker-player-edge: #6e6e69;
--checker-computer: #141414;
--checker-computer-edge: #b5b5b0;
--count-player: #1c1c1c;
--count-computer: #fafaf7;
--die-player: #f2f2ef;
--pip-player: #1c1c1c;
--die-computer: #141414;
--pip-computer: #f2f2ef;
--cube: #f2f2ef;
--cube-text: #1c1c1c;
--highlight: #e07a5a;
--target: rgba(224, 122, 90, 0.38);
--shadow-raised: 0 1px 2px rgba(0, 0, 0, 0.4);
--scrim: rgba(0, 0, 0, 0.6);
color-scheme: dark;
}
}
:root[data-theme="dark"] {
--background: #17110d;
--surface: #221913;
--surface-sunken: #2c211a;
--text: #efe5d5;
--muted: #a99886;
--border: #3a2d24;
--border-strong: #7d6b5b;
--accent: #d0694b;
--on-accent: #1a110c;
--accent-soft: #4a2519;
--focus: #e58a6e;
--error: #f08fa6;
--verdict-good: #7ccba4;
--verdict-doubtful: #e0b04f;
--verdict-bad: #f08fa6;
--brand-laiton: #d6a64a;
--frame: #2a1a10;
--bed: #4a3626;
--bar: #24170e;
--tray: #22150d;
--point-a: #3a2519;
--point-b: #7e6142;
--point-number: #cdbfa8;
--checker-player: #f2f2ef;
--checker-player-edge: #6e6e69;
--checker-computer: #141414;
--checker-computer-edge: #b5b5b0;
--count-player: #1c1c1c;
--count-computer: #fafaf7;
--die-player: #f2f2ef;
--pip-player: #1c1c1c;
--die-computer: #141414;
--pip-computer: #f2f2ef;
--cube: #f2f2ef;
--cube-text: #1c1c1c;
--highlight: #e07a5a;
--target: rgba(224, 122, 90, 0.38);
--shadow-raised: 0 1px 2px rgba(0, 0, 0, 0.4);
--scrim: rgba(0, 0, 0, 0.6);
color-scheme: dark;
}
/* -------------------------------------------------------------- Page */
* {
box-sizing: border-box;
}
/* Pas de « tirer pour rafraîchir » ni de rebond qui recharge la page en
* pleine partie ; pas de texte sélectionné par un appui long. */
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
overscroll-behavior: none;
}
body {
margin: 0 auto;
max-width: var(--page-width);
padding: 0 var(--space-4);
font-family: var(--font-sans);
font-size: 17px;
line-height: 25px;
color: var(--text);
background: var(--background);
overscroll-behavior: none;
user-select: none;
-webkit-user-select: none;
}
[hidden] {
display: none !important;
}
a {
color: var(--accent);
text-underline-offset: 3px;
}
/* Anneau de focus clavier, sur tout élément interactif. */
:focus-visible {
outline: 3px solid var(--focus);
outline-offset: 2px;
}
/* L'écran de jeu en portrait : des zones de hauteur fixe, de haut en bas ;
* la barre d'actions reste collée en bas de l'écran. */
.screen {
display: flex;
flex-direction: column;
min-height: 100vh;
min-height: 100dvh;
}
.game {
display: flex;
flex: 1;
flex-direction: column;
}
/* Les pages sont des éléments de la colonne flexible #screen. Leurs marges
* automatiques les centrent, mais annulent leur étirement en largeur : sans
* largeur explicite, une page au contenu court se réduit à la largeur de ce
* contenu (le plateau de l'étape 6 « à vous » du tutoriel passait de 396 à
* 300 px en 412 × 915). Leur max-width les borne toujours. */
.screen > .page {
width: 100%;
}
.spacer {
flex: 1;
}
/* Logo clair ou sombre selon le thème. */
.logo-dark {
display: none;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .logo-light { display: none; }
:root:not([data-theme="light"]) .logo-dark { display: block; }
}
:root[data-theme="dark"] .logo-light { display: none; }
:root[data-theme="dark"] .logo-dark { display: block; }
/* -------------------------------------------------------- Barre haute */
.topbar {
display: flex;
flex: none;
gap: var(--space-1);
align-items: center;
height: var(--topbar-height);
}
.topbar .brand {
flex: 1;
display: flex;
align-items: center;
min-width: 0;
margin: 0;
}
.topbar .brand img {
width: auto;
max-width: 100%;
height: 28px;
}
/* ------------------------------------------------------- Pictogrammes */
.icon {
width: 24px;
height: 24px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
flex: none;
}
/* ------------------------------------------------------------ Boutons */
/* Ronds comme des pions ; un seul bouton principal (brique) par écran,
* les autres en contour. */
button {
min-height: var(--button-height);
padding: 0 20px;
font: inherit;
font-weight: 700;
color: var(--on-accent);
background: var(--accent);
border: 2px solid var(--accent);
border-radius: var(--radius-pill);
cursor: pointer;
/* Un bouton tient sur une ligne (charte). */
white-space: nowrap;
/* Pas de délai ni de zoom sur un double toucher. */
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
transition: background-color var(--duration-instant) var(--easing-standard),
filter var(--duration-instant) var(--easing-standard);
}
button.secondary {
color: var(--text);
background: transparent;
border-color: var(--border-strong);
}
@media (hover: hover) {
button.secondary:hover:not(:disabled) {
background: var(--surface-sunken);
}
}
/* Retour visuel au toucher : le bouton descend de 1 px et s'assombrit de
* 6 %, sans ombre. */
button:active:not(:disabled) {
transform: translateY(1px);
filter: brightness(0.94);
}
button:disabled {
opacity: 0.45;
cursor: default;
}
/* Pictogramme seul (ou avec son libellé de 13 px dessous, barre haute). */
.icon-button {
display: inline-flex;
flex-direction: column;
flex: none;
align-items: center;
justify-content: center;
min-width: 44px;
min-height: 44px;
padding: 0;
color: var(--text);
background: transparent;
border: 0;
border-radius: var(--radius-md);
}
.icon-button.labelled {
width: 60px;
height: 52px;
}
.icon-button .label {
font-size: 13px;
line-height: 15px;
font-weight: 500;
}
@media (hover: hover) {
.icon-button:hover:not(:disabled) {
background: var(--surface-sunken);
}
}
/* ------------------------------------------- Bandeaux des joueurs */
/* Un bandeau par camp, au-dessus du plateau pour l'ordinateur, dessous pour
* vous ; celui qui a la main est teinté. */
.strip {
display: flex;
flex: none;
gap: 10px;
align-items: center;
height: var(--strip-height);
padding: 0 4px 0 12px;
border-radius: var(--radius-md);
}
.strip.turn {
background: var(--accent-soft);
}
.strip .who {
flex: 1;
min-width: 0;
overflow: hidden;
font-weight: 700;
white-space: nowrap;
text-overflow: ellipsis;
}
.strip .lead {
font-size: 15px;
line-height: 20px;
color: var(--muted);
white-space: nowrap;
}
/* Le compte de points : 20 px gras, chiffres alignés ; il se touche pour
* ouvrir la bulle du glossaire. */
.strip .count {
min-width: 56px;
min-height: 44px;
padding: 0 8px;
font-size: 20px;
line-height: 24px;
font-weight: 700;
font-variant-numeric: tabular-nums lining-nums;
text-align: right;
color: var(--text);
background: transparent;
border: 0;
border-radius: var(--radius-md);
}
.mini {
flex: none;
width: 18px;
height: 18px;
border-radius: 50%;
}
.mini-player {
background: var(--checker-player);
border: 2px solid var(--checker-player-edge);
}
.mini-computer {
background: var(--checker-computer);
border: 2px solid var(--checker-computer-edge);
}
/* La bulle du compte de points, posée sur le plateau, du côté du bandeau
* touché : elle ne déplace rien. */
.board-box {
position: relative;
flex: none;
}
/* Téléphone en portrait : le plateau, héros de l'écran, prend la largeur
* presque entière (8 px de marge au lieu de 16). */
@media (max-width: 40rem) {
html:not(.landscape) .board-box {
margin-inline: calc(var(--space-2) - var(--space-4));
}
}
.bubble {
position: absolute;
left: var(--space-2);
right: var(--space-2);
z-index: 5;
margin: 0;
padding: 10px 14px;
font-size: 15px;
line-height: 20px;
color: var(--text);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-raised);
animation: fade-in var(--duration-fast) var(--easing-standard);
}
.bubble[data-side="computer"] { top: var(--space-2); }
.bubble[data-side="player"] { bottom: var(--space-2); }
.bubble p {
margin: 0 0 var(--space-1);
}
.bubble-term {
font-weight: 700;
}
.link-button {
min-height: 44px;
padding: 0;
color: var(--accent);
background: transparent;
border: 0;
border-radius: 0;
text-decoration: underline;
text-underline-offset: 3px;
}
/* ----------------------------------------------- Sous le plateau */
/* Ligne d'information, toujours réservée : ce qui vient de se passer (coup
* de l'ordinateur, jet d'ouverture, chargement), et « Revoir » au bout. */
.infoline {
display: flex;
flex: none;
gap: var(--space-2);
align-items: center;
height: var(--infoline-height);
padding-left: 12px;
}
.info {
display: -webkit-box;
flex: 1;
margin: 0;
overflow: hidden;
font-size: 15px;
line-height: 20px;
color: var(--muted);
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
}
/* Un coup en notation du moteur, lien vers sa fiche du glossaire (ligne de
* l'ordinateur, feuille du maître) : couleur du texte, soulignement en
* pointillé discret. Le remplissage vertical d'un élément en ligne agrandit
* la zone d'appui sans changer la hauteur des lignes. */
a.notation {
padding-block: 8px;
color: inherit;
text-decoration-line: underline;
text-decoration-style: dotted;
text-decoration-color: var(--muted);
text-decoration-thickness: 1px;
}
.infoline .replay {
color: var(--accent);
}
/* La consigne du moment : ce qui doit ressortir de l'écran. Texte nu,
* 17 px gras, sur une hauteur réservée de deux lignes ; rien quand il n'y a
* rien à dire. La teinte « c'est à vous » est celle du bandeau du joueur. */
.prompt {
flex: none;
height: var(--prompt-height);
margin: var(--space-2) 0 0;
padding: 0 12px;
overflow: hidden;
font-weight: 700;
}
.prompt.neutral {
font-weight: 400;
color: var(--muted);
}
/* L'ordinateur réfléchit : trois points qui apparaissent l'un après
* l'autre (la seule animation en boucle). */
.prompt.thinking::after {
content: "";
animation: thinking 1.2s steps(4, end) infinite;
}
@keyframes thinking {
0% { content: ""; }
25% { content: "."; }
50% { content: ".."; }
75% { content: "..."; }
}
/* Barre d'actions collée en bas : l'action secondaire à gauche, la
* principale à droite, sous le pouce. */
.actions {
position: sticky;
bottom: 0;
z-index: 2;
display: grid;
flex: none;
grid-template-columns: 1fr 1.6fr;
gap: var(--space-2);
height: var(--actions-height);
padding: var(--space-3) 0 var(--space-4);
background: var(--background);
border-top: 1px solid var(--border);
}
.actions .slot {
display: flex;
min-width: 0;
}
.actions .slot button {
flex: 1;
min-width: 0;
padding: 0 12px;
display: inline-flex;
gap: 6px;
align-items: center;
justify-content: center;
}
.actions .slot .icon {
width: 20px;
height: 20px;
}
.move-hint {
align-self: center;
margin: 0;
font-size: 15px;
line-height: 20px;
color: var(--muted);
text-align: center;
}
/* --------------------------------------------------- Fenêtres */
/* Voile sous le menu et les panneaux : il assombrit l'écran derrière. */
.veil {
position: fixed;
inset: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-4);
background: var(--scrim);
animation: fade-in var(--duration-fast) var(--easing-standard);
}
/* Le menu : une feuille qui monte du bas. */
.sheet-veil {
z-index: 11; /* ouvert, le menu passe devant un panneau */
align-items: flex-end;
padding: 0;
}
.sheet {
width: min(100%, var(--page-width));
max-height: 100%;
overflow-y: auto;
padding: var(--space-2) var(--space-4) var(--space-6);
color: var(--text);
background: var(--surface);
border-radius: 20px 20px 0 0;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.2);
animation: sheet-up var(--duration-moderate) var(--easing-standard);
}
.sheet-head {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
min-height: 52px;
}
.grip {
position: absolute;
top: 0;
left: 50%;
width: 40px;
height: 4px;
margin-left: -20px;
background: var(--border-strong);
border-radius: 2px;
}
.sheet-title {
margin: 0;
font-size: 20px;
line-height: 26px;
}
.sheet .item {
display: flex;
gap: 14px;
align-items: center;
width: 100%;
min-height: 52px;
padding: 0;
font-weight: 500;
text-align: left;
color: var(--text);
background: transparent;
border: 0;
border-bottom: 1px solid var(--border);
border-radius: 0;
}
.sheet .item:last-child {
border-bottom: 0;
}
.sheet .item .icon {
color: var(--muted);
}
.item-value {
margin-left: auto;
font-size: 15px;
color: var(--muted);
}
/* Interrupteur (Vibrations, écran allumé). */
.switch {
position: relative;
flex: none;
width: 44px;
height: 26px;
margin-left: auto;
background: var(--border-strong);
border-radius: 13px;
transition: background-color var(--duration-fast) var(--easing-standard);
}
.switch::after {
content: "";
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
background: var(--surface);
border-radius: 50%;
transition: transform var(--duration-fast) var(--easing-standard);
}
.item[aria-checked="true"] .switch {
background: var(--accent);
}
.item[aria-checked="true"] .switch::after {
background: var(--on-accent);
transform: translateX(18px);
}
/* Panneau centré : confirmation du double, fin de partie, double de
* l'ordinateur en paysage. */
.panel {
width: min(24rem, 100%);
padding: var(--space-4);
color: var(--text);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-raised);
animation: panel-in var(--duration-moderate) var(--easing-standard);
}
.panel p {
margin: 0 0 var(--space-4);
font-size: 20px;
line-height: 26px;
font-weight: 700;
}
.panel p:empty {
display: none;
}
/* Fin de partie : surtitre, titre de 24 px, puis le détail des points. */
.panel .overline {
margin: 0 0 var(--space-1);
font-size: 13px;
line-height: 16px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
.panel .panel-title {
margin-bottom: var(--space-1);
font-size: 24px;
line-height: 30px;
}
.panel .panel-detail {
font-size: 17px;
line-height: 25px;
font-weight: 400;
}
.panel-actions {
display: flex;
gap: var(--space-2);
}
.panel-actions button {
flex: 1 1 0;
padding: 0 12px;
}
/* Fin de partie : « Changer les réglages », lien secondaire sous les
* boutons. */
.panel-link {
display: flex;
align-items: center;
justify-content: center;
min-height: 44px;
margin-top: var(--space-2);
font-weight: 700;
}
/* « Nouvelle partie commencée. Reprendre l'ancienne », 10 s, avec sa
* barre de temps. */
.banner {
position: fixed;
left: 50%;
bottom: calc(var(--actions-height) + var(--space-2));
z-index: 9;
display: flex;
gap: var(--space-3);
align-items: center;
width: min(24rem, calc(100% - 2 * var(--space-4)));
padding: var(--space-2) var(--space-3) var(--space-3);
overflow: hidden;
font-size: 15px;
line-height: 20px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-raised);
transform: translateX(-50%);
animation: fade-in var(--duration-fast) var(--easing-standard);
}
.banner span:first-child {
flex: 1;
}
.banner button {
flex: none;
min-height: 44px;
padding: 0 14px;
}
.banner-timer {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 4px;
background: var(--accent);
transform-origin: left;
}
.banner-timer.running {
animation: banner-timer 10s linear forwards;
}
@keyframes banner-timer {
from { transform: scaleX(1); }
to { transform: scaleX(0); }
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes sheet-up {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
@keyframes panel-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* Mouvement réduit : tout est instantané, rien ne roule ni ne glisse ; la
* barre de temps reste visible, immobile. */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
}
.prompt.thinking::after {
animation: none;
content: "…";
}
.banner-timer.running {
animation: none;
}
button:active:not(:disabled) {
transform: none;
}
}
/* ------------------------------------------------------------ Plateau */
.board {
display: block;
width: 100%;
height: auto;
aspect-ratio: 640 / 520;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
font-family: var(--font-sans);
/* Alegreya Sans dessine par défaut des chiffres elzéviriens (de hauteurs
* variables) : sur le plateau, des chiffres alignés se lisent mieux. */
font-variant-numeric: tabular-nums lining-nums;
}
.board-frame { fill: var(--frame); }
.board-bed { fill: var(--bed); }
.board-bar { fill: var(--bar); }
.board-tray { fill: var(--tray); }
.point-a { fill: var(--point-a); }
.point-b { fill: var(--point-b); }
/* Numéros des flèches : leur taille est une mesure du dessin (attribut
* font-size), calculée pour faire au moins 12 px à l'écran (geometry.ts).
* Posés sur le cadre, en portrait comme en paysage. */
.point-number {
fill: var(--point-number);
font-weight: 500;
text-anchor: middle;
}
.checker {
stroke-width: 2;
}
.checker-player {
fill: var(--checker-player);
stroke: var(--checker-player-edge);
}
.checker-computer {
fill: var(--checker-computer);
stroke: var(--checker-computer-edge);
}
/* Filet intérieur des pions (bundle.css), prêt pour le jour où le plateau
* le dessine. */
.checker-ring {
fill: none;
stroke-width: 1.5;
}
.ring-player { stroke: var(--checker-player-edge); }
.ring-computer { stroke: var(--checker-computer-edge); }
.checker.movable {
stroke: var(--highlight);
stroke-width: 3;
}
.checker.selected {
stroke: var(--highlight);
stroke-width: 6;
}
/* Tailles des chiffres du plateau : attribut font-size, calculé par
* geometry.ts (13 px au moins à l'écran). */
.checker-count {
font-weight: 700;
text-anchor: middle;
}
.count-player { fill: var(--count-player); }
.count-computer { fill: var(--count-computer); }
.target-zone { fill: var(--target); }
/* Le dernier coup de l'ordinateur (couleur laiton, charte) : une flèche par
* demi-coup, les pions arrivés et frappés entourés, un pion fantôme là où
* un pion est parti. */
/* Épaisseur des flèches : attribut stroke-width (2,5 px à l'écran,
* geometry.ts) ; elles passent sous les pions. */
.trace-arrow {
stroke: var(--last-move);
stroke-linecap: round;
opacity: 0.85;
}
.trace-head { fill: var(--last-move); }
/* Où le pion touché se posera : un anneau pointillé, comme les fantômes du
* coup de l'ordinateur, en brique. */
.landing {
fill: none;
stroke: var(--highlight);
stroke-width: 3;
stroke-dasharray: 6 5;
}
.trace-landed,
.trace-hit {
fill: none;
stroke: var(--last-move);
stroke-width: 4;
}
.trace-hit { stroke-dasharray: 6 4; }
.trace-ghost {
fill: none;
stroke: var(--last-move);
stroke-width: 2.5;
stroke-dasharray: 5 4;
}
.die-player { fill: var(--die-player); stroke: var(--checker-player-edge); stroke-width: 1.5; }
.die-computer { fill: var(--die-computer); stroke: var(--checker-computer-edge); stroke-width: 1.5; }
.pip-player { fill: var(--pip-player); }
.pip-computer { fill: var(--pip-computer); }
/* Dés du joueur en attente d'être lancés, et videau quand il peut doubler :
* ils se touchent comme les boutons. */
.die-waiting rect {
stroke: var(--highlight);
stroke-width: 2.5;
stroke-dasharray: 5 4;
}
.cube.can-double .cube-face {
stroke: var(--highlight);
stroke-width: 3;
}
/* Jet sans coup possible : dés estompés et barrés. */
.die-blocked { opacity: 0.5; }
.die-strike {
stroke: var(--text);
stroke-width: 3;
stroke-linecap: round;
}
/* Dé déjà utilisé par un demi-coup du coup en cours : estompé, sans être
* barré (il n'est pas perdu : « Annuler » le rend). */
.die-used { opacity: 0.35; }
.cube-face { fill: var(--cube); stroke: var(--checker-player-edge); stroke-width: 2; }
.cube-value {
fill: var(--cube-text);
font-weight: 800;
text-anchor: middle;
}
.hit {
fill: transparent;
cursor: pointer;
}
/* Paysage : comptes de points en pastilles aux bouts de la barre, consigne
* dans la bande, commandes à gauche. */
.pip-chip { stroke-width: 1.5; }
.chip-player { fill: var(--checker-player); stroke: var(--checker-player-edge); }
.chip-computer { fill: var(--checker-computer); stroke: var(--checker-computer-edge); }
.pip-chip-text {
font-weight: 700;
text-anchor: middle;
}
.chip-text-player { fill: var(--pip-player); }
.chip-text-computer { fill: var(--pip-computer); }
.band-pill { fill: var(--accent-soft); }
.band-text {
fill: var(--text);
font-weight: 700;
text-anchor: middle;
}
.control-circle {
fill: none;
stroke: var(--point-number);
stroke-width: 2;
}
.control-icon {
fill: none;
stroke: var(--point-number);
stroke-width: 2px;
stroke-linecap: round;
stroke-linejoin: round;
vector-effect: non-scaling-stroke;
}
.control.disabled { opacity: 0.35; }
/* ------------------------------------------------------- Mode paysage */
/* Téléphone tourné (ADR 0012) : le plateau est l'écran, sur la hauteur
* réelle (barre d'adresse comprise), cadre noyer jusqu'aux bords. La
* classe vient de app.ts (rotation du téléphone, ou repli du plein écran). */
html.landscape,
html.landscape body {
height: 100%;
overflow: hidden;
}
html.landscape body {
max-width: none;
padding: 0;
background: var(--frame);
}
html.landscape .topbar,
html.landscape .strip,
html.landscape .infoline,
html.landscape .prompt,
html.landscape .spacer,
html.landscape .actions {
display: none;
}
html.landscape .board-box {
position: fixed;
inset: 0;
}
html.landscape .board {
width: 100%;
height: 100%;
aspect-ratio: auto;
}
html.landscape .banner {
bottom: var(--space-2);
}
/* L'aide et la vue d'une position restent des pages qu'on fait défiler. */
html.landscape.page-open,
html.landscape.page-open body {
height: auto;
overflow: visible;
}
html.landscape.page-open body {
padding: 0 var(--space-4);
background: var(--background);
}
/* Hors de l'écran de jeu, la barre haute (Aide, Plein écran, Menu) reste
* en paysage : seul le plateau en paysage s'en passe. Les écrans qui ont
* leur propre en-tête (bare) la masquent toujours. */
html.landscape.page-open:not(.bare) .topbar {
display: flex;
}
/* Repli du plein écran : le paysage ne peut pas être verrouillé, l'écran
* entier tourne d'un quart de tour dans le portrait. Les touchers suivent
* la rotation ; le plateau et les fenêtres sont posés dans #screen. */
html.rotated .screen {
position: fixed;
top: 0;
left: 0;
width: 100vh;
width: 100dvh;
height: 100vw;
min-height: 0;
overflow: hidden;
transform-origin: top left;
transform: rotate(90deg) translateY(-100%);
}
html.rotated .board-box,
html.rotated .veil {
position: absolute;
}
html.rotated .banner {
position: absolute;
}
/* ------------------------------------------------------- Mes parties */
/* La feuille « Mes parties » (app/src/ui/games-view.ts) : une partie par
* ligne, ses actions dessous, ses branches dépliables. */
.games-list,
.games-branches {
margin: 0;
padding: 0;
list-style: none;
}
.games-item {
padding: var(--space-2) 0 var(--space-4);
border-bottom: 1px solid var(--border);
}
.games-item:last-child {
border-bottom: 0;
}
.games-item p {
margin: 0 0 var(--space-2);
font-weight: 500;
}
.games-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
.games-branch {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
align-items: center;
justify-content: space-between;
padding-top: var(--space-2);
color: var(--muted);
}
/* --------------------------------------------------------------- Aide */
.help {
max-width: var(--page-width);
margin: 0 auto;
}
.help h2 {
margin: var(--space-8) 0 var(--space-2);
font-size: 24px;
line-height: 30px;
font-weight: 700;
}
.help h3 {
margin: var(--space-6) 0 var(--space-1);
font-size: 20px;
line-height: 26px;
font-weight: 700;
}
.help p {
margin: var(--space-2) 0;
}
.steps {
margin: var(--space-2) 0;
padding-left: var(--space-6);
}
.steps li + li {
margin-top: var(--space-1);
}
.steps li::marker {
color: var(--muted);
}
/* Glossaire : une liste de définitions, sur une carte. */
.glossary {
margin: var(--space-3) 0 0;
padding: var(--space-1) var(--space-4);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-raised);
}
.glossary .term-card {
padding-top: var(--space-3);
font-weight: 700;
}
.glossary dd {
margin: 0;
padding-bottom: var(--space-3);
}
.glossary dd + dt {
border-top: 1px solid var(--border);
}
.about {
margin-top: var(--space-2);
font-size: 15px;
line-height: 20px;
color: var(--muted);
}
.help {
user-select: text;
-webkit-user-select: text;
}
/* La barre haute de l'aide (« Retour » et onglets) reste en vue quand la
* page défile. Sa hauteur est fixe (12 + 48 + 8 + 44 + 1 px de filet) : une
* question ou un terme ouvert par son adresse s'arrête 12 px sous elle. */
.help {
--help-bar-height: 113px;
}
.help-bar {
position: sticky;
top: env(safe-area-inset-top);
z-index: 3;
height: var(--help-bar-height);
margin-inline: calc(-1 * var(--space-4));
padding: var(--space-3) var(--space-4) 0;
background: var(--background);
}
.help .question,
.glossary .term-card {
scroll-margin-top: calc(var(--help-bar-height) + 12px + env(safe-area-inset-top));
}
/* Les onglets de l'aide : des liens, l'onglet courant souligné en brique ;
* sur une ligne, qui défile si l'écran est trop étroit. */
.help-tabs {
display: flex;
gap: var(--space-1);
overflow-x: auto;
white-space: nowrap;
margin-top: var(--space-2);
border-bottom: 1px solid var(--border);
}
.help-tabs a {
display: inline-flex;
align-items: center;
min-height: 44px;
padding: 0 var(--space-3);
font-weight: 500;
color: var(--muted);
text-decoration: none;
border-bottom: 3px solid transparent;
}
.help-tabs a[aria-current="page"] {
font-weight: 700;
color: var(--text);
border-bottom-color: var(--accent);
}
.help-notice:empty {
display: none;
}
/* Une question de l'aide : son titre est un lien qui l'ouvre (son adresse),
* ou la referme quand elle est ouverte. */
.question {
border-bottom: 1px solid var(--border);
}
.help .question h3 {
margin: 0;
}
.question h3 a {
display: flex;
gap: var(--space-2);
align-items: center;
min-height: 52px;
padding: var(--space-2) 0;
color: var(--text);
text-decoration: none;
}
.question h3 a::after {
content: "";
flex: none;
width: 9px;
height: 9px;
margin: 0 6px 4px auto;
border-right: 2px solid var(--muted);
border-bottom: 2px solid var(--muted);
transform: rotate(45deg);
}
.question.open h3 a::after {
margin-bottom: -4px;
transform: rotate(-135deg);
}
.answer {
padding-bottom: var(--space-3);
}
/* Le terme ouvert par son adresse (#/glossaire/<terme>). */
.glossary dt a {
color: var(--text);
text-decoration: none;
}
.glossary dt.current,
.glossary dt.current + dd {
margin-inline: calc(-1 * var(--space-4));
padding-inline: var(--space-4);
background: var(--accent-soft);
/* Le surlignage montre où l'on arrive, puis s'efface après 2 s. */
animation: term-fade var(--duration-moderate) var(--easing-exit) 2s forwards;
}
@keyframes term-fade {
to { background-color: transparent; }
}
/* -------------------------------------------------- Liens et boutons */
/* Un lien qui mène à un écran, avec l'allure d'un bouton. */
a.button {
display: inline-flex;
gap: 6px;
align-items: center;
justify-content: center;
min-height: var(--button-height);
padding: 0 20px;
font-weight: 700;
color: var(--on-accent);
text-decoration: none;
background: var(--accent);
border: 2px solid var(--accent);
border-radius: var(--radius-pill);
white-space: nowrap;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
a.button.secondary {
color: var(--text);
background: transparent;
border-color: var(--border-strong);
}
a.button:active {
transform: translateY(1px);
filter: brightness(0.94);
}
a.icon-button,
a.item,
a.link-button {
text-decoration: none;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
a.link-button {
display: inline-flex;
align-items: center;
text-decoration: underline;
}
.infoline .copy-link {
color: var(--accent);
}
/* Zone de texte de la copie de repli (presse-papiers), hors de l'écran. */
.offscreen {
position: fixed;
top: 0;
left: -10000px;
opacity: 0;
}
/* ------------------------------------------------ Vue d'une position */
.position-view {
max-width: var(--page-width);
margin: 0 auto;
padding-bottom: var(--space-6);
}
.position-title {
margin: var(--space-6) 0 var(--space-2);
font-size: 24px;
line-height: 30px;
}
.position-message:empty {
display: none;
}
.position-board {
margin: var(--space-2) 0;
}
.position-detail {
margin: var(--space-2) 0 var(--space-4);
font-size: 15px;
line-height: 20px;
color: var(--muted);
}
/* Les actions empilées sur toute la largeur, la principale en premier. */
.position-actions {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.position-actions > * {
width: 100%;
}
#position-copy {
display: inline-flex;
gap: 6px;
align-items: center;
justify-content: center;
}
#position-copy .icon {
width: 20px;
height: 20px;
}
/* ------------------------------------------- Accueil, tutoriel, aide */
/* Maquettes « Étape 0e.5 » et « Accueil » ; règles :
* docs/identite/charte/accueil-tutoriel-aide.md. */
.brand-link {
display: flex;
align-items: center;
min-height: 44px;
}
/* Le tutoriel et le premier lancement ont leur propre en-tête. */
html.bare .topbar {
display: none;
}
.home,
.tutorial {
max-width: var(--page-width);
margin: 0 auto;
padding-bottom: var(--space-6);
}
.overline {
margin: 0;
font-size: 13px;
line-height: 16px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
.muted {
color: var(--muted);
}
.caption {
margin: 0;
font-size: 13px;
line-height: 18px;
color: var(--muted);
}
.card {
display: block;
padding: var(--space-4);
color: var(--text);
text-decoration: none;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-raised);
}
/* Une étiquette d'action dans une carte-lien, en contour. */
.pill {
display: inline-flex;
flex: none;
align-items: center;
min-height: 44px;
padding: 0 16px;
font-weight: 700;
color: var(--text);
border: 2px solid var(--border-strong);
border-radius: var(--radius-pill);
}
/* Premier lancement. */
.welcome {
display: flex;
flex-direction: column;
gap: var(--space-6);
min-height: 100vh;
min-height: 100dvh;
padding: var(--space-6) 0 var(--space-6);
}
.welcome-logo {
margin: 0;
}
.welcome-logo img {
height: 34px;
width: auto;
}
.welcome-board {
margin-inline: calc(-1 * var(--space-2));
width: calc(100% + 2 * var(--space-2));
}
.welcome-text h2,
.home-intro h2 {
margin: 0 0 var(--space-2);
font-size: 32px;
line-height: 36px;
font-weight: 800;
letter-spacing: -0.01em;
}
.welcome-text p,
.home-intro p {
margin: 0;
}
.welcome-choices {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: auto;
}
.choice {
display: flex;
flex-direction: column;
justify-content: center;
min-height: 64px;
padding: 10px 20px;
color: var(--text);
text-decoration: none;
border: 2px solid var(--border-strong);
border-radius: 16px;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
.choice span {
font-size: 15px;
line-height: 20px;
color: var(--muted);
}
.choice.primary {
color: var(--on-accent);
background: var(--accent);
border-color: var(--accent);
}
.choice.primary span {
color: var(--on-accent);
}
.choice:active {
transform: translateY(1px);
filter: brightness(0.94);
}
.welcome-choices .caption {
margin-top: var(--space-1);
text-align: center;
}
/* Accueil. */
.home-main {
display: flex;
flex-direction: column;
gap: 20px;
padding-top: var(--space-2);
}
.home-game {
display: flex;
flex-direction: column;
gap: 14px;
}
.home-game-top {
display: flex;
gap: 14px;
align-items: center;
}
.home-mini {
flex: none;
width: 132px;
}
/* La miniature ne porte pas les numéros des flèches, illisibles à cette
* taille. */
.home-board .point-number {
display: none;
}
.home-facts {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.home-pips {
font-size: 15px;
line-height: 20px;
color: var(--muted);
font-variant-numeric: tabular-nums lining-nums;
}
.home-actions {
display: flex;
gap: var(--space-2);
}
.home-actions > * {
flex: 1 1 0;
min-width: 0;
padding: 0 12px;
}
.install-card {
display: flex;
gap: var(--space-3);
align-items: center;
margin-top: calc(-1 * var(--space-2));
padding: 10px 4px 10px 14px;
background: var(--surface-sunken);
border-radius: var(--radius-md);
}
.install-card .install-symbol {
flex: none;
width: 32px;
height: 32px;
}
.install-text {
display: flex;
flex: 1;
flex-direction: column;
min-width: 0;
font-size: 15px;
line-height: 20px;
}
.install-card button.secondary {
flex: none;
min-height: 44px;
padding: 0 14px;
background: var(--surface);
}
.install-card .icon-button .icon {
width: 18px;
height: 18px;
color: var(--muted);
}
.home-rules {
display: flex;
gap: var(--space-3);
align-items: center;
}
.home-rules-text {
display: flex;
flex: 1;
flex-direction: column;
min-width: 0;
}
.home-rules-text .muted {
font-size: 15px;
line-height: 20px;
}
/* La ligne de progression : un lien vers #/progression (1h), haut de
* 44 px pour le doigt, en caption. */
.home-progress {
display: flex;
gap: var(--space-1);
align-items: center;
min-height: 44px;
text-decoration: none;
}
.home-progress[hidden] {
display: none;
}
.home-progress-text {
text-decoration: underline;
text-underline-offset: 3px;
}
.home-progress .icon {
flex: none;
width: 16px;
height: 16px;
}
.home-footer {
display: flex;
gap: 10px;
align-items: center;
margin-top: var(--space-4);
font-size: 13px;
line-height: 18px;
color: var(--muted);
}
.home-footer img {
flex: none;
width: 28px;
height: 28px;
}
/* Leçons (1g) : la liste (#/lecons) et la carte « prochaine leçon » de
* l'accueil (docs/identite/charte/ecrans-etape-1.md, « 1g. Leçons »). */
.lessons {
max-width: var(--page-width);
margin: 0 auto;
padding-bottom: var(--space-6);
}
.lessons-intro {
padding-top: var(--space-2);
}
.lessons-intro h2 {
margin: 0 0 var(--space-2);
font-size: 32px;
line-height: 36px;
font-weight: 800;
letter-spacing: -0.01em;
}
.lessons-intro p {
margin: 0;
}
.lessons-rules {
display: flex;
align-items: center;
width: fit-content;
min-height: 44px;
margin-top: var(--space-2);
font-size: 15px;
line-height: 20px;
color: var(--muted);
text-decoration: none;
}
.lessons-rules span {
margin-left: var(--space-1);
font-weight: 700;
color: var(--accent);
text-decoration: underline;
}
#lessons-status:empty {
display: none;
}
.lesson-list {
display: flex;
flex-direction: column;
gap: var(--space-3);
margin: var(--space-2) 0 0;
padding: 0;
list-style: none;
}
.lesson-card {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.lesson-card.current {
border: 2px solid var(--accent);
}
.lesson-card-top {
display: flex;
gap: var(--space-3);
align-items: center;
}
.lesson-card-body {
display: flex;
flex: 1;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.lesson-card-title {
font-size: 17px;
line-height: 25px;
font-weight: 700;
}
.lesson-card-state {
font-size: 15px;
line-height: 20px;
color: var(--muted);
}
.lesson-card-state.done {
color: var(--verdict-good);
font-weight: 700;
}
/* La pastille ronde au numéro : contour pour une leçon à commencer, brique
* pour la leçon proposée, verte une fois terminée. */
.lesson-badge {
display: inline-flex;
flex: none;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
font-size: 17px;
font-weight: 800;
color: var(--text);
border: 2px solid var(--border-strong);
border-radius: var(--radius-pill);
}
.lesson-badge.current {
color: var(--on-accent);
background: var(--accent);
border-color: var(--accent);
}
.lesson-badge.done {
color: var(--on-accent);
background: var(--verdict-good);
border-color: var(--verdict-good);
}
/* La barre de progression : un segment par exercice. */
.lesson-progress {
display: grid;
grid-auto-columns: minmax(0, 1fr);
grid-auto-flow: column;
gap: var(--space-1);
margin-top: var(--space-1);
}
.lesson-progress .segment {
display: block;
height: 6px;
background: var(--surface-sunken);
border-radius: var(--radius-pill);
}
.lesson-progress .segment.done {
background: var(--accent);
}
.lesson-card-action {
align-self: stretch;
}
.home-lesson {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.home-lesson-info {
display: flex;
flex-direction: column;
gap: 2px;
}
.home-lesson-info .muted {
font-size: 15px;
line-height: 20px;
}
.home-lesson-actions {
display: flex;
gap: var(--space-3);
align-items: center;
}
.home-lesson-actions .button {
flex: 1 1 0;
min-width: 0;
padding: 0 12px;
}
.home-lessons-link {
display: inline-flex;
flex: 1 1 0;
align-items: center;
justify-content: center;
min-height: 44px;
font-weight: 700;
color: var(--accent);
}
/* Feuille d'installation. */
.sheet-head .install-symbol {
width: 40px;
height: 40px;
margin-right: var(--space-3);
}
#install-sheet .sheet-title {
flex: 1;
}
.install-lead {
margin: var(--space-2) 0 var(--space-4);
color: var(--muted);
}
.install-steps {
display: flex;
flex-direction: column;
gap: var(--space-3);
margin: 0 0 var(--space-4);
padding: 0;
list-style: none;
}
.install-steps li {
display: flex;
gap: var(--space-3);
align-items: flex-start;
}
.step-number {
display: flex;
flex: none;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
font-weight: 800;
font-variant-numeric: tabular-nums lining-nums;
background: var(--accent-soft);
border-radius: 50%;
}
.install-done {
width: 100%;
}
/* Tutoriel. */
.tutorial-head {
display: flex;
gap: var(--space-3);
align-items: center;
min-height: 52px;
padding-top: var(--space-2);
}
.tutorial-progress {
display: grid;
flex: 1;
grid-auto-columns: minmax(0, 1fr);
grid-auto-flow: column;
gap: var(--space-1);
}
/* Un segment : 4 px de trait, dans une zone de 44 px de haut quand c'est
* un lien vers une étape déjà vue. */
.tutorial-progress .segment {
position: relative;
display: block;
height: 44px;
}
.tutorial-progress .segment::before {
content: "";
position: absolute;
top: 20px;
left: 0;
right: 0;
height: 4px;
background: var(--border);
border-radius: var(--radius-pill);
}
.tutorial-progress .segment.done::before {
background: var(--accent);
}
.tutorial-progress .segment[aria-current]::before {
top: 19px;
height: 6px;
}
.tutorial-skip {
display: inline-flex;
align-items: center;
min-height: 44px;
font-size: 15px;
font-weight: 500;
color: var(--muted);
}
/* En portrait, l'écran tient au moins la hauteur de l'écran : « Retour »
* et « Continuer » restent en bas, sous le pouce. */
.tutorial {
display: flex;
flex-direction: column;
box-sizing: border-box;
min-height: 100vh;
min-height: 100dvh;
}
/* Le titre, et « Revoir » sur sa ligne, à droite. */
.tutorial-title-row {
display: flex;
gap: var(--space-2);
align-items: center;
margin: var(--space-1) 0 var(--space-3);
}
.tutorial-title {
flex: 1;
min-width: 0;
margin: 0;
font-size: 24px;
line-height: 30px;
}
.tutorial-board-box {
position: relative;
}
@media (max-width: 40rem) {
.tutorial-board-box {
margin-inline: calc(var(--space-2) - var(--space-4));
}
}
.tutorial-replay {
flex: none;
color: var(--accent);
}
.tutorial-text {
margin-top: var(--space-3);
}
.tutorial-text p {
margin: 0 0 10px;
}
.tutorial-task {
margin: var(--space-3) 0 0;
padding: 10px 14px;
font-weight: 700;
background: var(--accent-soft);
border-radius: var(--radius-md);
}
.tutorial-feedback {
display: flex;
gap: var(--space-2);
align-items: flex-start;
min-height: 25px;
margin: var(--space-3) 0 0;
}
.tutorial-feedback:empty {
min-height: 0;
}
.tutorial-feedback[data-verdict="right"] {
font-weight: 700;
}
.tutorial-feedback[data-verdict="right"] .icon {
color: var(--verdict-good);
}
.tutorial-feedback[data-verdict="info"] {
color: var(--muted);
}
.tutorial-cube {
display: flex;
gap: var(--space-2);
margin-top: var(--space-3);
}
.tutorial-cube button {
flex: 1 1 0;
}
.tutorial-nav {
display: grid;
grid-template-columns: 1fr 1.4fr;
gap: var(--space-2);
margin-top: auto;
padding-top: var(--space-6);
}
.tutorial-nav .button {
min-width: 0;
}
/* Le sens du jeu : deux flèches circulaires, claire pour le joueur, sombre
* pour l'ordinateur, aux couleurs de leurs pions, chacune avec un liseré de
* la couleur opposée pour rester lisible sur le bois ; jamais en brique (la
* brique veut dire « à faire »). */
.tutorial-path path {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
pointer-events: none;
}
.tutorial-path .direction-player.direction-line { stroke: var(--checker-player); }
.tutorial-path .direction-player.direction-edge { stroke: var(--checker-computer); }
.tutorial-path .direction-computer.direction-line { stroke: var(--checker-computer); }
.tutorial-path .direction-computer.direction-edge { stroke: var(--checker-player); }
.tutorial-path .direction-head.direction-player {
fill: var(--checker-player);
stroke: var(--checker-computer);
}
.tutorial-path .direction-head.direction-computer {
fill: var(--checker-computer);
stroke: var(--checker-player);
}
/* En paysage, comme la position partagée : le plateau à gauche sur toute
* la hauteur (avec la colonne gauche du cadre), et à droite une colonne
* avec la barre de progression, le titre, le texte, la consigne « à vous »
* et les boutons, le tout sans défilement de la page. Si le texte est trop
* long pour l'écran, lui seul défile : les boutons restent en vue. */
html.landscape .tutorial {
position: fixed;
inset: 0;
display: grid;
grid-template-columns: minmax(0, 1fr) clamp(288px, 44vw, 400px);
grid-template-rows: auto auto minmax(0, 1fr) auto auto;
max-width: none;
margin: 0;
padding: 0;
overflow: hidden;
background: var(--background);
}
html.landscape .tutorial > * {
grid-column: 2;
margin-inline: 0;
padding-inline: var(--space-4) calc(var(--space-4) + env(safe-area-inset-right));
}
html.landscape .tutorial .overline {
display: none;
}
html.landscape .tutorial-head {
grid-row: 1;
min-height: 44px;
padding-top: 0;
}
html.landscape .tutorial-title-row {
grid-row: 2;
margin: 0 0 var(--space-1);
padding-right: calc(var(--space-2) + env(safe-area-inset-right));
}
html.landscape .tutorial-title {
font-size: 20px;
line-height: 26px;
}
/* Une leçon garde en paysage sa ligne « Leçon 1 · 1 sur 4 · à vous »
* au-dessus du titre (recette 1g) : une rangée de plus dans la grille. */
html.landscape #lesson {
grid-template-rows: auto auto auto minmax(0, 1fr) auto auto;
}
html.landscape #lesson .overline {
display: block;
grid-row: 2;
}
html.landscape #lesson .tutorial-title-row {
grid-row: 3;
}
html.landscape #lesson .tutorial-body {
grid-row: 4;
}
html.landscape #lesson .tutorial-cube {
grid-row: 5;
}
html.landscape #lesson .tutorial-nav {
grid-row: 6;
}
/* La boîte du plateau s'efface : le plateau se place lui-même dans la
* grille. */
html.landscape .tutorial-board-box {
display: contents;
}
html.landscape .tutorial-board {
grid-row: 1 / -1;
grid-column: 1;
width: 100%;
height: 100%;
margin: 0;
aspect-ratio: auto;
background: var(--frame);
}
html.landscape .tutorial-body {
grid-row: 3;
min-height: 0;
overflow-y: auto;
font-size: 15px;
line-height: 20px;
}
html.landscape .tutorial-text {
margin: 0;
padding-right: 0;
}
html.landscape .tutorial-text p {
margin-bottom: var(--space-2);
}
html.landscape .tutorial-task {
margin: 0;
padding: var(--space-2) 12px;
}
html.landscape .tutorial-feedback {
min-height: 0;
margin: var(--space-2) 0 0;
}
html.landscape .tutorial-cube {
grid-row: 4;
margin-top: var(--space-2);
}
/* La réponse au double donnée et saluée, les deux boutons n'ont plus
* d'usage : en paysage, leur place revient au texte. */
html.landscape .tutorial[data-solved] .tutorial-cube {
display: none;
}
/* Écran très bas (844 × 320) : texte un cran plus petit. */
@media (max-height: 360px) {
html.landscape .tutorial-body {
font-size: 14px;
line-height: 18px;
}
html.landscape .tutorial-title {
font-size: 18px;
line-height: 24px;
}
}
html.landscape .tutorial-nav {
grid-row: 5;
margin: 0;
padding-block: var(--space-2) calc(var(--space-2) + env(safe-area-inset-bottom));
}
/* Le premier lancement, en paysage : le plateau tient dans la hauteur. */
html.landscape .welcome-board {
width: min(100%, calc(70vh * 640 / 520));
height: auto;
margin-inline: auto;
aspect-ratio: 640 / 520;
}
html.landscape .welcome {
min-height: 0;
}
/* Aide. */
.help-tutorial {
display: flex;
gap: var(--space-3);
align-items: center;
margin-top: var(--space-4);
}
.help-tutorial-text {
display: flex;
flex: 1;
flex-direction: column;
min-width: 0;
}
.help-tutorial-text .muted {
font-size: 15px;
line-height: 20px;
}
.help-intro {
margin-top: var(--space-4);
}
.see-board {
display: inline-flex;
align-items: center;
min-height: 44px;
font-weight: 500;
}
.glossary-search {
display: flex;
flex-direction: column;
gap: var(--space-1);
margin-top: var(--space-3);
}
.search-label {
font-size: 15px;
line-height: 20px;
font-weight: 500;
}
.glossary-search input {
min-height: 48px;
padding: 0 var(--space-3);
font: inherit;
color: var(--text);
background: var(--surface);
border: 1px solid var(--border-strong);
border-radius: var(--radius-md);
user-select: text;
-webkit-user-select: text;
}
.term-card + .term-card {
border-top: 1px solid var(--border);
}
.glossary .english {
margin-left: var(--space-2);
font-size: 13px;
font-style: italic;
font-weight: 400;
color: var(--muted);
}
.glossary dd p {
margin: 0 0 var(--space-1);
}
.see-also {
font-size: 15px;
line-height: 20px;
color: var(--muted);
}
.glossary .term-card.current {
margin-inline: calc(-1 * var(--space-4));
padding-inline: var(--space-4);
background: var(--accent-soft);
}
.glossary .term-card.current dt,
.glossary .term-card.current dd {
margin-inline: 0;
padding-inline: 0;
background: none;
}
/* ------------------------------------------------------------ Match */
/* « Match en 5 points » sous la barre haute (portrait), hauteur fixe. */
.match-line {
flex: none;
height: 20px;
text-align: center;
}
/* Le score de chaque camp, dans son bandeau, juste avant son compte de
* points : une pastille carrée. */
.strip .match-score {
display: inline-flex;
flex: none;
align-items: center;
justify-content: center;
min-width: 28px;
height: 28px;
padding: 0 4px;
font-weight: 700;
font-variant-numeric: tabular-nums lining-nums;
border: 2px solid var(--border-strong);
border-radius: var(--radius-sm);
}
/* Paysage : le score dans la colonne de commandes, sous le menu. */
.match-box-frame {
fill: none;
stroke: var(--point-number);
stroke-opacity: 0.5;
stroke-width: 1.5;
}
.match-box-score {
font-weight: 700;
fill: var(--point-number);
}
.match-box-of {
fill: var(--point-number);
}
/* ------------------------------------------------- Nouvelle partie */
/* La page remplit l'écran : la barre d'action reste collée en bas, même
* quand le contenu est plus court que l'écran. */
.new-game {
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
max-width: var(--page-width);
margin: 0 auto;
}
.new-game .new-game-body {
flex: 1;
}
.new-game-head {
display: flex;
gap: var(--space-2);
align-items: center;
min-height: 60px;
}
.new-game-head h2 {
margin: 0;
font-size: 22px;
line-height: 28px;
}
.new-game-body {
display: flex;
flex-direction: column;
gap: var(--space-3);
padding-bottom: var(--space-6);
}
/* Portrait : les deux colonnes l'une sous l'autre, comme une seule. */
.new-game-column {
display: flex;
flex-direction: column;
gap: var(--space-3);
min-width: 0;
}
.new-game-body .overline {
margin-top: var(--space-4);
}
/* Sélecteur à deux segments : fond creusé, segment choisi en relief. */
.segmented {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-1);
padding: var(--space-1);
background: var(--surface-sunken);
border-radius: var(--radius-pill);
}
.segmented .segment {
color: var(--text);
background: transparent;
border-color: transparent;
}
.segmented .segment[aria-checked="true"] {
background: var(--surface);
box-shadow: var(--shadow-raised);
}
.new-game-options {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.lengths {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
align-items: center;
}
.lengths .length {
width: 44px;
min-width: 44px;
height: 44px;
min-height: 44px;
padding: 0;
color: var(--text);
background: transparent;
border-color: var(--border-strong);
border-radius: var(--radius-sm);
}
.lengths .length[aria-checked="true"] {
color: var(--on-accent);
background: var(--accent);
border-color: var(--accent);
}
.new-game-rule {
margin: 0;
font-size: 15px;
line-height: 21px;
color: var(--muted);
}
/* Six barres de hauteur croissante ; celles jusqu'au niveau choisi sont en
* brique, le niveau choisi porte l'anneau de focus. */
.level-bars {
display: flex;
gap: var(--space-2);
align-items: flex-end;
}
.level-bars .level {
display: flex;
flex: 1;
align-items: flex-end;
min-width: 44px;
height: 84px;
padding: 4px;
background: transparent;
border: 0;
border-radius: var(--radius-sm);
}
.level-bar {
display: block;
width: 100%;
background: var(--border);
border-radius: 4px;
}
.level-1 .level-bar { height: 18px; }
.level-2 .level-bar { height: 28px; }
.level-3 .level-bar { height: 38px; }
.level-4 .level-bar { height: 50px; }
.level-5 .level-bar { height: 62px; }
.level-6 .level-bar { height: 76px; }
.level-bars .level.reached .level-bar {
background: var(--accent);
}
.level-bars .level[aria-checked="true"] {
outline: 3px solid var(--focus);
outline-offset: 0;
}
.level-name {
margin: 0;
font-size: 22px;
line-height: 28px;
font-weight: 700;
}
.level-text {
margin: 0;
color: var(--muted);
}
.new-game-body .item {
display: flex;
gap: 14px;
align-items: center;
width: 100%;
min-height: 60px;
padding: 0;
font-weight: 500;
text-align: left;
color: var(--text);
background: transparent;
border: 0;
border-radius: 0;
}
.new-game-body .item-label {
display: flex;
flex-direction: column;
}
.new-game-body .item small {
font-size: 15px;
line-height: 20px;
font-weight: 400;
color: var(--muted);
}
/* Barre d'action collée en bas : le seul bouton principal de l'écran. */
.new-game-actions {
position: sticky;
bottom: 0;
display: flex;
flex-direction: column;
gap: var(--space-1);
align-items: stretch;
padding: var(--space-3) 0 var(--space-4);
background: var(--background);
border-top: 1px solid var(--border);
}
.new-game-kept {
margin: 0;
font-size: 13px;
line-height: 18px;
text-align: center;
color: var(--muted);
}
/* En paysage (915 × 412, et jusqu'à 844 × 320), tout tient sans défiler :
* deux colonnes au-dessus de la barre d'action, le type de partie à
* gauche, le niveau et le conseil du maître à droite ; tout est resserré,
* et la barre d'action tient sur une ligne, le bouton à droite sous le
* pouce. */
html.landscape .new-game {
max-width: 60rem;
}
html.landscape .new-game-head {
min-height: 44px;
}
html.landscape .new-game-head h2 {
font-size: 20px;
line-height: 24px;
}
html.landscape .new-game-body {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: var(--space-6);
align-content: start;
padding-bottom: var(--space-2);
}
html.landscape .new-game-column {
gap: var(--space-2);
}
html.landscape .new-game-body .overline {
margin-top: 0;
}
html.landscape .new-game-body .during-game {
display: none;
}
html.landscape .level-bars .level {
height: 48px;
padding: 3px;
}
html.landscape .level-1 .level-bar { height: 10px; }
html.landscape .level-2 .level-bar { height: 17px; }
html.landscape .level-3 .level-bar { height: 24px; }
html.landscape .level-4 .level-bar { height: 30px; }
html.landscape .level-5 .level-bar { height: 36px; }
html.landscape .level-6 .level-bar { height: 42px; }
html.landscape .level-name {
font-size: 18px;
line-height: 22px;
}
html.landscape .level-text {
font-size: 15px;
line-height: 20px;
}
html.landscape .new-game-body .item {
min-height: 44px;
}
html.landscape .new-game-body .item-label {
line-height: 22px;
}
html.landscape .new-game-body .item small {
line-height: 18px;
}
html.landscape .new-game-actions {
flex-direction: row;
gap: var(--space-4);
align-items: center;
justify-content: flex-end;
padding: var(--space-2) 0;
}
html.landscape .new-game-kept {
order: -1;
text-align: right;
}
/* Un peu plus d'air quand la hauteur le permet (915 × 412). */
@media (min-height: 380px) {
html.landscape .new-game-column {
gap: var(--space-3);
}
html.landscape .level-bars .level {
height: 64px;
}
html.landscape .level-1 .level-bar { height: 14px; }
html.landscape .level-2 .level-bar { height: 22px; }
html.landscape .level-3 .level-bar { height: 30px; }
html.landscape .level-4 .level-bar { height: 39px; }
html.landscape .level-5 .level-bar { height: 48px; }
html.landscape .level-6 .level-bar { height: 58px; }
}
/* En paysage, comme l'écran de jeu : le plateau à gauche sur toute la
* hauteur (colonne gauche du cadre avec le videau seul), le titre, le contexte et les actions
* dans une colonne à droite. */
html.landscape .position-view.with-board {
position: fixed;
inset: 0;
display: grid;
grid-template-columns: minmax(0, 1fr) 288px;
grid-template-rows: auto auto auto auto 1fr;
max-width: none;
margin: 0;
padding: 0;
overflow-y: auto;
background: var(--background);
}
html.landscape .position-view.with-board > * {
grid-column: 2;
margin-inline: 0;
padding-inline: var(--space-4) calc(var(--space-4) + env(safe-area-inset-right));
}
html.landscape .position-view.with-board .position-title {
margin-top: var(--space-4);
}
html.landscape .position-view.with-board .position-board {
grid-row: 1 / -1;
grid-column: 1;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
aspect-ratio: auto;
background: var(--frame);
}
html.landscape .position-view.with-board .position-actions {
padding-bottom: var(--space-4);
}
html.landscape .position-view.with-board .position-actions > * {
padding-inline: 16px;
}
/* ------------------------------------------- Conseil du maître (1d) */
/* La ligne du maître : à la place du dernier coup, dans la ligne réservée
* de 44 px (elle ne pousse rien). Pictogramme et mot en couleur
* d'appréciation, phrase en italique tronquée, chevron. */
.advice-line {
display: flex;
flex: 1;
gap: var(--space-2);
align-items: center;
min-width: 0;
height: 44px;
font-size: 15px;
line-height: 20px;
color: var(--text);
text-decoration: none;
border-radius: 12px;
-webkit-tap-highlight-color: transparent;
}
.advice-line.appear {
animation: fade-in var(--duration-fast) var(--easing-standard);
}
.advice-line .icon {
width: 20px;
height: 20px;
}
.advice-line .icon-chevron {
width: 16px;
height: 16px;
color: var(--muted);
}
.advice-text {
flex: 1;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.advice-word {
font-weight: 700;
}
[data-verdict="good"] .icon:not(.icon-chevron),
[data-verdict="good"] .advice-word { color: var(--verdict-good); }
[data-verdict="doubtful"] .icon:not(.icon-chevron),
[data-verdict="doubtful"] .advice-word { color: var(--verdict-doubtful); }
[data-verdict="bad"] .icon:not(.icon-chevron),
[data-verdict="bad"] .advice-word,
[data-verdict="veryBad"] .icon:not(.icon-chevron),
[data-verdict="veryBad"] .advice-word { color: var(--verdict-bad); }
/* Paysage : la pastille du mot, au début de la bande centrale. */
.advice-chip-pill {
fill: var(--surface);
stroke: currentColor;
stroke-width: 2;
}
.advice-chip-icon {
fill: none;
stroke: currentColor;
stroke-width: 2px;
stroke-linecap: round;
stroke-linejoin: round;
vector-effect: non-scaling-stroke;
}
.advice-chip-text {
fill: currentColor;
font-weight: 700;
}
.advice-chip.verdict-good { color: var(--verdict-good); }
.advice-chip.verdict-doubtful { color: var(--verdict-doubtful); }
.advice-chip.verdict-bad,
.advice-chip.verdict-veryBad { color: var(--verdict-bad); }
/* La feuille d'explication : elle monte du bas, au-dessus du plateau
* voilé. */
.explain-sheet {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.explain-verdict {
display: inline-flex;
gap: 6px;
align-items: center;
margin: 0;
font-size: 15px;
line-height: 20px;
font-weight: 700;
}
.explain-verdict .icon {
width: 18px;
height: 18px;
}
.explain-verdict[data-verdict="good"] { color: var(--verdict-good); }
.explain-verdict[data-verdict="doubtful"] { color: var(--verdict-doubtful); }
.explain-verdict[data-verdict="bad"],
.explain-verdict[data-verdict="veryBad"] { color: var(--verdict-bad); }
.explain-title {
margin: 0;
font-size: 24px;
line-height: 30px;
}
.explain-phrase {
margin: 0;
font-style: italic;
}
.explain-body {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.explain-body .segment {
min-height: 44px;
font-size: 15px;
}
/* Le coup montré : le vôtre en couleur du texte, celui du maître en
* couleur du bon coup (charte 1d). */
.explain-board[data-move="mine"] { --last-move: var(--text); }
.explain-board[data-move="best"] { --last-move: var(--verdict-good); }
.explain-legend {
margin: 0;
font-size: 13px;
line-height: 18px;
color: var(--muted);
}
.explain-numbers {
border-top: 1px solid var(--border);
}
.explain-numbers summary {
display: flex;
align-items: center;
min-height: 44px;
font-size: 15px;
font-weight: 500;
cursor: pointer;
}
.explain-table {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 4px 12px;
font-size: 15px;
line-height: 20px;
font-variant-numeric: tabular-nums;
}
.explain-table .strong {
font-weight: 700;
}
.explain-note {
margin: var(--space-2) 0 0;
font-size: 13px;
line-height: 18px;
color: var(--muted);
}
.explain-glossary {
min-height: 44px;
}
.explain-actions {
display: flex;
justify-content: flex-end;
}
.explain-actions button {
min-width: 140px;
}
/* En paysage : la feuille prend la hauteur de l'écran ; le plateau à
* gauche, le texte à droite. */
/* Le plateau tient dans la hauteur, sous le sélecteur et sa légende. */
html.landscape .explain-board {
width: auto;
max-width: 100%;
height: calc(100vh - 140px);
height: calc(100dvh - 140px);
aspect-ratio: 640 / 520;
}
html.landscape .explain-sheet {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
grid-auto-flow: dense;
column-gap: var(--space-4);
align-content: start;
width: min(100%, 60rem);
height: 100%;
border-radius: 0;
}
html.landscape .explain-sheet > * {
grid-column: 2;
}
html.landscape .explain-sheet > .explain-body {
grid-column: 1;
grid-row: 1 / span 8;
}
/* ------------------------------------------ Bilan de fin de partie (1e) */
/* Maquette « Bilan de fin de partie » (charte, ecrans-etape-1.md) : un
* panneau centré, à la place du panneau de fin de partie ; il défile si
* l'écran est trop bas. */
.summary-panel {
display: flex;
flex-direction: column;
gap: var(--space-3);
width: min(26rem, 100%);
max-height: 100%;
overflow-y: auto;
}
.summary-panel p {
margin: 0;
font-size: 17px;
line-height: 25px;
font-weight: 400;
}
.summary-head {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 44px;
margin: calc(-1 * var(--space-2)) 0;
}
.summary-title {
margin: 0;
font-size: 28px;
line-height: 34px;
}
.summary-panel .summary-status {
font-size: 15px;
line-height: 20px;
color: var(--muted);
}
/* Trois cases : bons coups, imprécisions, erreurs. */
.summary-counts {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-2);
}
.summary-count {
display: flex;
flex-direction: column;
align-items: center;
padding: var(--space-2) var(--space-1);
background: var(--surface-sunken);
border-radius: var(--radius-sm);
}
.summary-number {
font-size: 28px;
line-height: 34px;
font-weight: 800;
font-variant-numeric: tabular-nums lining-nums;
}
.summary-word {
display: inline-flex;
gap: 4px;
align-items: center;
font-size: 13px;
line-height: 18px;
font-weight: 700;
text-align: center;
}
.summary-word .icon {
width: 16px;
height: 16px;
}
[data-verdict="good"] > .summary-word,
.summary-verdict[data-verdict="good"],
.row-word[data-verdict="good"],
.viewer-phrase[data-verdict="good"] strong { color: var(--verdict-good); }
[data-verdict="doubtful"] > .summary-word,
.summary-verdict[data-verdict="doubtful"],
.row-word[data-verdict="doubtful"],
.viewer-phrase[data-verdict="doubtful"] strong { color: var(--verdict-doubtful); }
[data-verdict="bad"] > .summary-word,
.summary-verdict[data-verdict="bad"],
.summary-verdict[data-verdict="veryBad"],
.row-word[data-verdict="bad"],
.row-word[data-verdict="veryBad"],
.viewer-phrase[data-verdict="bad"] strong,
.viewer-phrase[data-verdict="veryBad"] strong { color: var(--verdict-bad); }
/* Deux jauges sur une ligne chacune : votre jeu (niveau), les dés. */
.summary-gauges {
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.gauge {
display: grid;
grid-template-columns: 5.5rem minmax(3rem, 1fr) auto;
gap: var(--space-2);
align-items: center;
min-height: 28px;
font-size: 15px;
line-height: 20px;
}
.gauge-name {
font-weight: 700;
}
.gauge-value {
color: var(--muted);
text-align: right;
}
.gauge-track {
position: relative;
height: 8px;
overflow: hidden;
background: var(--surface-sunken);
border-radius: 4px;
}
.gauge-fill {
position: absolute;
top: 0;
bottom: 0;
left: 0;
background: var(--verdict-good);
border-radius: 4px;
}
.gauge-fill.level-1 { width: 16.7%; }
.gauge-fill.level-2 { width: 33.3%; }
.gauge-fill.level-3 { width: 50%; }
.gauge-fill.level-4 { width: 66.7%; }
.gauge-fill.level-5 { width: 83.3%; }
.gauge-fill.level-6 { width: 100%; }
/* La chance : jauge centrée, à droite si elle vous a aidé. */
.gauge-track.centred::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: calc(50% - 1px);
width: 2px;
background: var(--muted);
}
.gauge-fill.dice { background: var(--border-strong); border-radius: 0; }
.gauge-fill.dice.for-1 { left: 50%; width: 25%; }
.gauge-fill.dice.for-2 { left: 50%; width: 50%; }
.gauge-fill.dice.against-1 { left: 25%; width: 25%; }
.gauge-fill.dice.against-2 { left: 0; width: 50%; }
.gauge-fill.dice.even { left: calc(50% - 6px); width: 12px; }
.summary-glossary {
display: inline-flex;
align-items: center;
align-self: flex-start;
font-size: 15px;
}
/* Le coup à revoir : mini-plateau, mot, votre coup, celui du maître. */
.summary-review {
display: flex;
flex-direction: column;
gap: var(--space-2);
padding-top: var(--space-3);
border-top: 1px solid var(--border);
}
.summary-review-body {
display: grid;
grid-template-columns: minmax(0, 9rem) minmax(0, 1fr);
gap: var(--space-3);
align-items: start;
}
.summary-panel .summary-verdict {
display: inline-flex;
gap: 6px;
align-items: center;
font-size: 15px;
line-height: 20px;
font-weight: 700;
}
.summary-verdict .icon {
width: 18px;
height: 18px;
}
.summary-panel .summary-played,
.summary-panel .summary-master {
font-size: 15px;
line-height: 20px;
}
.summary-panel .summary-master {
font-style: italic;
}
.summary-review-actions,
.viewer-actions {
display: flex;
gap: var(--space-2);
align-items: center;
}
/* L'emplacement de « Demander à une IA » (étape 1f) : réservé, vide. */
.ask-ai-slot {
flex: 1 1 0;
min-height: 44px;
}
/* Deux boutons de 48 px, côte à côte, jamais étirés en hauteur. */
.summary-actions {
align-items: center;
}
.summary-actions > * {
flex: 1 1 0;
height: var(--button-height);
padding: 0 12px;
}
/* En paysage : deux colonnes, le résultat et les mesures à gauche, le
* coup à revoir et les actions à droite. */
html.landscape .summary-panel {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
grid-auto-flow: row dense;
column-gap: var(--space-6);
align-content: start;
width: min(52rem, 100%);
}
html.landscape .summary-panel > * {
grid-column: 1;
}
html.landscape .summary-panel > .summary-review,
html.landscape .summary-panel > .summary-end {
grid-column: 2;
}
html.landscape .summary-panel > .summary-review {
grid-row: 1 / span 4;
padding-top: 0;
border-top: 0;
}
/* Les actions en bas de la colonne de droite, « Changer les réglages »
* dessous, comme en portrait. */
html.landscape .summary-panel > .summary-end {
grid-row: 5 / span 2;
align-self: end;
}
/* ------------------------------------------- Analyse d'une partie (1e) */
/* Maquette interactive « Analyse » : une page qui défile. */
.analysis {
max-width: var(--page-width);
margin: 0 auto;
padding-bottom: var(--space-6);
}
.analysis-head {
display: flex;
gap: var(--space-3);
align-items: center;
margin: var(--space-3) 0;
}
.analysis-title {
margin: 0;
font-size: 24px;
line-height: 30px;
}
.analysis-message:empty {
display: none;
}
.analysis-card {
margin: 0 0 var(--space-4);
padding: var(--space-3);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
}
.analysis-card > .overline {
margin-bottom: var(--space-2);
}
/* La courbe de vos chances de gagner : on la parcourt du doigt. */
.analysis-curve {
display: block;
width: 100%;
height: 168px;
touch-action: pan-y;
user-select: none;
-webkit-user-select: none;
-webkit-tap-highlight-color: transparent;
font-family: var(--font-sans);
}
.analysis-curve:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
}
.curve-axis { stroke: var(--border); stroke-width: 1; }
.curve-half { stroke: var(--border-strong); stroke-width: 1; stroke-dasharray: 4 4; }
.curve-label { fill: var(--muted); font-size: 12px; }
.curve-area { fill: var(--accent-soft); }
.curve-line { fill: none; stroke: var(--text); stroke-width: 2; stroke-linejoin: round; }
.curve-dot.other { fill: var(--text); }
.curve-dot.verdict-good { fill: var(--verdict-good); }
.curve-dot.verdict-doubtful { fill: var(--verdict-doubtful); }
.curve-dot.verdict-bad,
.curve-dot.verdict-veryBad { fill: var(--verdict-bad); }
.curve-cursor { stroke: var(--accent); stroke-width: 2; }
.curve-ring { fill: none; stroke: var(--accent); stroke-width: 2; }
.curve-band { fill: transparent; cursor: pointer; }
.curve-band.selected { fill: var(--accent); fill-opacity: 0.08; }
/* La progression (#/progression, 1h) : le titre, la phrase du maître, la
* courbe du taux d'erreur, puis les parties (charte, ecrans-etape-1.md,
* « 1h »). Les cartes reprennent celles de l'analyse. */
.progression-title {
margin: var(--space-3) 0 var(--space-2);
font-size: 32px;
line-height: 36px;
font-weight: 800;
letter-spacing: -0.01em;
}
.progression-summary {
margin: 0 0 var(--space-4);
font-size: 17px;
line-height: 24px;
}
.progression-summary:empty {
display: none;
}
.progression-curve {
display: block;
width: 100%;
height: 168px;
font-family: var(--font-sans);
}
/* Le dernier point, plus gros, à l'encre du texte ; en verdict-good
* seulement si le taux baisse (le vert dit « mieux », jamais « dernier »). */
.progression-dot { fill: var(--text); }
.progression-dot.better { fill: var(--verdict-good); }
.progression-value { fill: var(--muted); font-size: 13px; }
.progression-value.last { fill: var(--text); font-weight: 700; }
.progression-value.better { fill: var(--verdict-good); }
.progression .link-button {
display: inline-flex;
align-items: center;
}
.progression-actions {
display: flex;
justify-content: flex-end;
margin: 0 0 var(--space-4);
}
/* Sans partie terminée : « Nouvelle partie », pleine largeur en portrait. */
@media (orientation: portrait) {
.progression-empty .button {
flex: 1;
}
}
.progression-list {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin: var(--space-2) 0 0;
padding: 0;
list-style: none;
}
.progression-item {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
}
/* Une ligne, un seul lien vers le bilan : résultat et date à gauche, taux
* en gras, puis le chevron. */
.progression-game {
display: flex;
gap: var(--space-3);
align-items: center;
min-height: 56px;
padding: var(--space-2) var(--space-3);
color: inherit;
text-decoration: none;
}
.progression-game > .icon {
flex: none;
width: 20px;
height: 20px;
color: var(--muted);
}
.progression-text {
display: flex;
flex: 1;
flex-direction: column;
min-width: 0;
}
.progression-rate {
display: flex;
flex: none;
flex-direction: column;
align-items: flex-end;
text-align: right;
}
.progression-rate strong {
font-size: 22px;
line-height: 26px;
font-variant-numeric: tabular-nums;
}
.progression-rate.pending {
font-size: 13px;
color: var(--muted);
}
/* Le visualiseur : « ‹ Coup 7 sur 24 · 6-1 › », le plateau d'avant le
* coup, la phrase du maître, votre coup contre celui du maître. */
.viewer-nav {
display: flex;
gap: var(--space-2);
align-items: center;
justify-content: space-between;
}
#analysis-prev .icon {
transform: rotate(180deg);
}
.viewer-step[aria-disabled="true"] {
opacity: 0.35;
pointer-events: none;
}
.viewer-turn {
margin: 0;
font-weight: 700;
text-align: center;
font-variant-numeric: tabular-nums lining-nums;
}
.viewer-body,
.viewer-text {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.viewer-phrase {
margin: 0;
}
.viewer-phrase strong {
display: inline-flex;
gap: 4px;
align-items: center;
}
.viewer-phrase strong .icon {
width: 18px;
height: 18px;
}
.viewer-legend {
margin: 0;
font-size: 13px;
line-height: 18px;
color: var(--muted);
}
.viewer-chance {
margin: 0;
font-size: 15px;
line-height: 20px;
}
.viewer-chance:empty {
display: none;
}
.viewer-text .segment {
min-height: 44px;
font-size: 15px;
}
.analysis-board[data-move="mine"] { --last-move: var(--text); }
.analysis-board[data-move="best"] { --last-move: var(--verdict-good); }
/* Les coups à revoir : filtres en pastilles, une ligne par coup. */
.filters {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
margin-bottom: var(--space-2);
}
.filters .filter {
min-height: 44px;
padding: 0 14px;
font-size: 15px;
font-weight: 500;
color: var(--text);
background: var(--surface-sunken);
border: 1px solid var(--border);
}
/* Le filtre choisi en accent-soft, comme l'onglet choisi de l'aide. */
.filters .filter[aria-pressed="true"] {
font-weight: 700;
color: var(--text);
background: var(--accent-soft);
border-color: var(--accent);
}
.analysis-list {
margin: 0;
padding: 0;
list-style: none;
}
.analysis-row {
display: grid;
grid-template-columns: 2rem 2.5rem minmax(0, 1fr) auto;
gap: var(--space-2);
align-items: center;
min-height: 44px;
padding: 0 var(--space-1);
color: var(--text);
text-decoration: none;
border-bottom: 1px solid var(--border);
}
.analysis-row[aria-current="true"] {
background: var(--accent-soft);
}
.row-number,
.row-roll {
color: var(--muted);
font-variant-numeric: tabular-nums lining-nums;
}
.row-move {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.row-word {
display: inline-flex;
gap: 4px;
align-items: center;
font-size: 15px;
font-weight: 700;
}
.row-word .icon {
width: 16px;
height: 16px;
}
.analysis-empty {
padding: var(--space-2) 0;
color: var(--muted);
}
.analysis-table {
grid-template-columns: minmax(0, 1fr) auto auto;
}
/* En paysage : le plateau du visualiseur à gauche, tenu dans la hauteur
* de l'écran ; le texte à droite. */
html.landscape .viewer-body {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: var(--space-4);
align-items: start;
}
html.landscape .analysis-board {
width: auto;
max-width: 100%;
height: calc(100vh - 120px);
height: calc(100dvh - 120px);
aspect-ratio: 640 / 520;
}
/* Le mini-plateau du coup à revoir : trop petit pour les numéros des
* flèches, qui se chevaucheraient. */
.summary-board .point-number {
display: none;
}
/* ------------------------------------------- Demander à une IA (1f) */
/* Le bouton : secondaire partout, avec l'icône de copie (charte, 1f). */
a.button.ask-ai .icon,
#prompt-copy .icon {
flex: none;
width: 20px;
height: 20px;
}
.ask-ai-slot .ask-ai {
width: 100%;
}
.explain-actions {
gap: var(--space-2);
flex-wrap: wrap;
}
.term-card .ask-ai {
margin-top: var(--space-2);
}
/* Sa propre feuille, au-dessus de l'analyse et du glossaire. */
.prompt-veil {
z-index: 12;
}
.prompt-page {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
/* Depuis la feuille du maître ou le bilan, la page du prompt prend la place
* de leur contenu, à la même hauteur : la feuille garde sa taille, son
* contenu reste en place mais invisible, et la page la couvre ; le texte du
* prompt défile dans la hauteur qui reste. Une seule feuille, une seule
* poignée, une seule croix. */
.sheet.prompting,
.panel.prompting {
position: relative;
overflow: hidden;
}
.prompting > :not(.prompt-page) {
visibility: hidden;
}
/* (plus précis que « html.landscape .explain-sheet > * », qui la placerait
* dans une colonne) */
.sheet.prompting > .prompt-page,
.panel.prompting > .prompt-page {
position: absolute;
inset: 0;
grid-area: auto;
padding: inherit;
}
.prompting .prompt-text {
flex: 1 1 auto;
max-height: none;
}
/* Le bilan est un panneau : pas de poignée. */
.panel .prompt-page .grip {
display: none;
}
/* « ‹ Explication » : revenir à l'écran d'où l'on vient. */
a.prompt-back {
display: inline-flex;
align-items: center;
min-height: 44px;
padding-right: var(--space-2);
font-weight: 500;
color: var(--accent);
text-decoration: none;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
.prompt-lead {
margin: 0;
color: var(--muted);
}
/* Le texte du prompt, à relire avant de le copier : une zone en creux qui
* défile, en chasse fixe pour que le diagramme garde ses colonnes. */
.prompt-text {
min-height: 0;
max-height: 50vh;
max-height: 50dvh;
margin: 0;
padding: var(--space-3);
overflow: auto;
font-family: ui-monospace, "DejaVu Sans Mono", "Roboto Mono", monospace;
font-size: 13px;
line-height: 18px;
white-space: pre-wrap;
overflow-wrap: anywhere;
color: var(--text);
background: var(--surface-sunken);
border: 1px solid var(--border);
border-radius: var(--radius-md);
user-select: text;
}
.prompt-status {
margin: 0;
color: var(--muted);
}
.prompt-status:empty {
display: none;
}
.prompt-actions {
display: flex;
flex: none;
gap: var(--space-2);
justify-content: flex-end;
}
.prompt-actions button {
display: inline-flex;
gap: 6px;
align-items: center;
justify-content: center;
min-width: 120px;
}
/* « ✓ Copié », 2 s : la copie se confirme sur le bouton (recette de 1f). */
#prompt-copy.copied {
color: var(--verdict-good);
background: var(--surface);
border-color: var(--verdict-good);
}
html.landscape .prompt-text {
max-height: calc(100vh - 190px);
max-height: calc(100dvh - 190px);
}
html.landscape .prompting .prompt-text {
max-height: none;
}
/* Le bouton ne se coupe pas : trop étroit à côté de « Revoir ce coup » ou
* « Rejouer d'ici », il passe à la ligne. */
.summary-review-actions,
.viewer-actions {
flex-wrap: wrap;
}