Files
Ustagammon/web/app.css
T

1243 lines
27 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 */
/* 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: #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;
/* 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: 74px;
--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: #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);
--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: #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);
--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;
}
.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;
/* 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;
}
.infoline .replay {
color: var(--accent);
}
/* La consigne du moment : ce qui doit ressortir de l'écran. Hauteur fixe
* (deux lignes) ; fond d'accent léger quand c'est au joueur. */
.prompt {
flex: none;
height: var(--prompt-height);
margin: 0;
padding: var(--space-3) var(--space-4);
overflow: hidden;
font-weight: 700;
background: var(--accent-soft);
border-radius: var(--radius-md);
}
.prompt.neutral,
body.busy .prompt {
font-weight: 400;
background: var(--surface-sunken);
}
/* 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 {
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-actions {
display: flex;
gap: var(--space-2);
}
.panel-actions button {
flex: 1 1 0;
padding: 0 12px;
}
/* « 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). */
.point-number {
fill: var(--point-label);
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. */
.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-weight: 800;
text-anchor: middle;
}
.hit {
fill: transparent;
cursor: pointer;
}
/* Paysage : numéros sur le cadre (ivoire), comptes de points en pastilles
* aux bouts de la barre, consigne dans la bande, commandes à gauche. */
.point-number.on-frame { fill: var(--point-number); }
.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 reste une page qu'on fait défiler. */
html.landscape.help-open,
html.landscape.help-open body {
height: auto;
overflow: visible;
}
html.landscape.help-open body {
padding: 0 var(--space-4);
background: var(--background);
}
/* 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;
}
/* --------------------------------------------------------------- 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);
}
.help {
user-select: text;
-webkit-user-select: text;
}