Bilan de fin de partie et écran d'analyse : courbe des chances, visualiseur, liste filtrable, rejouer d'ici

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pierre MARTIN
2026-11-10 10:30:17 +01:00
co-authored by Claude Opus 5.5
parent a657d7d7a9
commit 006fd62704
22 changed files with 2420 additions and 106 deletions
+500
View File
@@ -2892,3 +2892,503 @@ 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;
}
.summary-actions > * {
flex: 1 1 0;
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-actions,
html.landscape .summary-panel > .panel-link {
grid-column: 2;
}
html.landscape .summary-panel > .summary-review {
grid-row: 1 / span 4;
padding-top: 0;
border-top: 0;
}
/* ------------------------------------------- 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; }
/* 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);
}
.filters .filter[aria-pressed="true"] {
color: var(--surface);
background: var(--text);
border-color: var(--text);
}
.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;
}
+8
View File
@@ -101,6 +101,10 @@
app/src/ui/new-game-view.ts. -->
<section id="new-game" class="new-game page" hidden></section>
<!-- L'analyse d'une partie (#/parties/<id>/analyse/<coup>), construite par
app/src/ui/analysis-view.ts. -->
<section id="analysis" class="analysis page" hidden></section>
<!-- L'aide : un onglet par adresse (#/aide, #/glossaire, #/a-propos). -->
<section id="help" class="help page" hidden>
<!-- La barre haute de l'aide reste en vue quand la page défile. -->
@@ -158,6 +162,10 @@
construite par app/src/ui/advice-view.ts. -->
<div id="explain-veil" class="veil sheet-veil" hidden></div>
<!-- Le bilan de fin de partie (#/parties/<id>/bilan) : un panneau centré,
construit par app/src/ui/summary-view.ts. -->
<div id="summary-veil" class="veil summary-veil" hidden></div>
<!-- La marche à suivre pour installer (#/installer) : une feuille. -->
<div id="install-veil" class="veil sheet-veil" hidden>
<div id="install-sheet" class="sheet" role="dialog" aria-modal="true" aria-labelledby="install-title">