674 lines
15 KiB
CSS
674 lines
15 KiB
CSS
/* 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 */
|
|
|
|
: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: #f7f0e2;
|
|
--checker-player-edge: #8c7a62;
|
|
--checker-computer: #2e1c12;
|
|
--checker-computer-edge: #120a05;
|
|
--count-player: #2b1d15;
|
|
--count-computer: #f3ead8;
|
|
--die-player: #f7f0e2;
|
|
--pip-player: #2b1d15;
|
|
--die-computer: #2e1c12;
|
|
--pip-computer: #f3ead8;
|
|
--cube: #f7f0e2;
|
|
--cube-text: #2b1d15;
|
|
--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);
|
|
|
|
/* Numéros des flèches. La charte les pose sur le cadre (point-number,
|
|
* ivoire) ; notre plateau les pose sur le fond, où l'ivoire ne se lirait
|
|
* pas : texte secondaire en thème clair (4,7:1 sur bed), point-number en
|
|
* thème sombre (6,3:1). */
|
|
--point-label: var(--muted);
|
|
|
|
--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;
|
|
|
|
/* Hauteur de la barre haute : boutons de 44 px et marges. */
|
|
--topbar-height: 60px;
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--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: #ede3cf;
|
|
--checker-player-edge: #8e7c64;
|
|
--checker-computer: #140d09;
|
|
--checker-computer-edge: #a8957d;
|
|
--count-player: #2b1d15;
|
|
--count-computer: #f3ead8;
|
|
--die-player: #ede3cf;
|
|
--pip-player: #2b1d15;
|
|
--die-computer: #140d09;
|
|
--pip-computer: #ede3cf;
|
|
--cube: #ede3cf;
|
|
--cube-text: #2b1d15;
|
|
--highlight: #e07a5a;
|
|
--target: rgba(224, 122, 90, 0.38);
|
|
|
|
--point-label: var(--point-number);
|
|
|
|
--shadow-raised: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------- Page */
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html {
|
|
-webkit-text-size-adjust: 100%;
|
|
text-size-adjust: 100%;
|
|
}
|
|
|
|
body {
|
|
margin: 0 auto;
|
|
max-width: var(--page-width);
|
|
padding: 0 var(--space-4) var(--space-6);
|
|
font-family: var(--font-sans);
|
|
font-size: 17px;
|
|
line-height: 25px;
|
|
color: var(--text);
|
|
background: var(--background);
|
|
}
|
|
|
|
[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;
|
|
}
|
|
|
|
/* -------------------------------------------------------- Barre haute */
|
|
|
|
.topbar {
|
|
display: flex;
|
|
gap: var(--space-2);
|
|
align-items: center;
|
|
min-height: var(--topbar-height);
|
|
padding-block: var(--space-2);
|
|
}
|
|
|
|
.topbar .brand {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
min-width: 0;
|
|
margin: 0;
|
|
}
|
|
|
|
.topbar .brand img {
|
|
display: block;
|
|
width: auto;
|
|
height: 32px;
|
|
}
|
|
|
|
/* ------------------------------------------------------------ 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;
|
|
/* Pas de délai ni de zoom sur un double toucher. */
|
|
touch-action: manipulation;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
|
|
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. */
|
|
button:active:not(:disabled) {
|
|
transform: translateY(1px);
|
|
}
|
|
|
|
button:disabled {
|
|
opacity: 0.45;
|
|
cursor: default;
|
|
}
|
|
|
|
.topbar button {
|
|
min-height: 44px;
|
|
padding: 0 14px;
|
|
font-size: 15px;
|
|
line-height: 20px;
|
|
}
|
|
|
|
.actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
.actions button {
|
|
flex: 1 1 auto;
|
|
}
|
|
|
|
/* --------------------------------------------- Messages sous le plateau */
|
|
|
|
/* Compte de points : information secondaire, discrète. */
|
|
.pips {
|
|
margin: var(--space-2) 0 0;
|
|
font-size: 15px;
|
|
line-height: 20px;
|
|
color: var(--muted);
|
|
text-align: center;
|
|
font-variant-numeric: tabular-nums lining-nums;
|
|
}
|
|
|
|
/* Ce qui vient de se passer (coup de l'ordinateur, jet d'ouverture) :
|
|
* secondaire, mais lisible. */
|
|
.info {
|
|
min-height: 25px;
|
|
margin: var(--space-3) 0 0;
|
|
color: var(--muted);
|
|
}
|
|
|
|
.info:empty {
|
|
margin-top: 0;
|
|
}
|
|
|
|
/* La consigne du moment : ce qui doit ressortir de l'écran. Fond d'accent
|
|
* léger quand c'est au joueur, neutre pendant que l'ordinateur réfléchit. */
|
|
.prompt {
|
|
min-height: calc(25px + 2 * var(--space-3));
|
|
margin: var(--space-3) 0;
|
|
padding: var(--space-3) var(--space-4);
|
|
font-weight: 700;
|
|
background: var(--accent-soft);
|
|
border-radius: var(--radius-md);
|
|
}
|
|
|
|
.prompt.thinking,
|
|
body.busy .prompt {
|
|
font-weight: 400;
|
|
background: var(--surface-sunken);
|
|
}
|
|
|
|
/* Fin de partie : le résultat en titre. */
|
|
#game:has(#again:not([hidden])) .prompt {
|
|
font-size: 24px;
|
|
line-height: 30px;
|
|
}
|
|
|
|
/* 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: "..."; }
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.prompt.thinking::after {
|
|
animation: none;
|
|
content: "…";
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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) ; chaque pixel compte pour
|
|
* toucher un pion. */
|
|
@media (max-width: 40rem) and (orientation: portrait) {
|
|
.board {
|
|
width: calc(100% + 2 * (var(--space-4) - var(--space-2)));
|
|
margin-inline: calc(var(--space-2) - var(--space-4));
|
|
}
|
|
}
|
|
|
|
.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); }
|
|
|
|
.point-number {
|
|
fill: var(--point-label);
|
|
font-size: 13px;
|
|
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;
|
|
}
|
|
|
|
.checker-count {
|
|
font-size: 18px;
|
|
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. */
|
|
.trace-arrow {
|
|
stroke: var(--last-move);
|
|
stroke-width: 5;
|
|
stroke-linecap: round;
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.trace-head { fill: var(--last-move); }
|
|
|
|
.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-size: 17px;
|
|
font-weight: 800;
|
|
text-anchor: middle;
|
|
}
|
|
|
|
.hit {
|
|
fill: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* ------------------------------------------------ Confirmation (videau) */
|
|
|
|
/* Une carte au milieu de l'écran, comme le glossaire ; le fond de la page
|
|
* s'assombrit derrière. */
|
|
.confirm {
|
|
width: min(22rem, calc(100vw - 2 * var(--space-4)));
|
|
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);
|
|
}
|
|
|
|
.confirm::backdrop {
|
|
background: rgba(43, 29, 21, 0.5);
|
|
}
|
|
|
|
.confirm p {
|
|
margin: 0 0 var(--space-4);
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* --------------------------------------------------------------- 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 dt {
|
|
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-8);
|
|
font-size: 15px;
|
|
line-height: 20px;
|
|
color: var(--muted);
|
|
}
|
|
|
|
/* ------------------------------------------------------- Mode paysage */
|
|
|
|
/* Téléphone tourné : le plateau prend toute la hauteur de l'écran, à
|
|
* gauche ; la barre haute, les messages et les boutons passent dans une
|
|
* colonne à sa droite. */
|
|
@media (orientation: landscape) and (max-height: 40rem) {
|
|
body {
|
|
max-width: none;
|
|
padding: 0 var(--space-4);
|
|
}
|
|
|
|
/* Le jeu en deux colonnes : #game laisse ses enfants dans la grille de
|
|
* la page. Quand l'aide est affichée, #game est caché, la première
|
|
* colonne n'a plus de largeur et l'aide prend toute la place. */
|
|
body {
|
|
display: grid;
|
|
grid-template-columns: auto minmax(13rem, 1fr);
|
|
grid-template-rows: auto auto auto auto auto 1fr;
|
|
column-gap: var(--space-4);
|
|
align-items: start;
|
|
}
|
|
|
|
#game {
|
|
display: contents;
|
|
}
|
|
|
|
.topbar {
|
|
grid-column: 2;
|
|
grid-row: 1;
|
|
min-height: 0;
|
|
padding-block: var(--space-2) 0;
|
|
}
|
|
|
|
.board {
|
|
grid-column: 1;
|
|
grid-row: 1 / -1;
|
|
width: auto;
|
|
height: calc(100vh - 2 * var(--space-2));
|
|
height: calc(100dvh - 2 * var(--space-2));
|
|
max-width: 70vw;
|
|
margin-block: var(--space-2);
|
|
}
|
|
|
|
.pips,
|
|
.info,
|
|
.prompt,
|
|
.actions {
|
|
grid-column: 2;
|
|
}
|
|
|
|
.pips {
|
|
text-align: left;
|
|
}
|
|
|
|
.actions {
|
|
margin-bottom: var(--space-2);
|
|
}
|
|
|
|
.help {
|
|
grid-column: 1 / -1;
|
|
}
|
|
}
|