Add hand-end scoring summary screen with acknowledgement
After each hand of an unfinished match the game now pauses in a new
hand_end phase instead of dealing immediately:
- engine: hand_points gains an 'award' map (which team won each category),
_end_hand stops at hand_end with a deadline, new acknowledge_hand deals
the next hand once all four players have acked; plays are rejected while
the summary is up
- state: acked seats, hand_end_deadline and hand_ack_timeout are persisted
and exposed in the personalized view (also on the finished state, so the
final hand is explained before the result)
- ws: new {"action": "ack"}; a per-hand timer force-deals the next hand
after HAND_ACK_TIMEOUT_SECONDS (new env var, default 30s) so an away
player cannot stall the match
- web: modal explaining each category in plain language with icons (card
images for denara/settebello/primiera), team-coloured rows, running
totals with progress bars, an 'Understood — next hand' button that turns
into 'Waiting for …' plus an auto-continue countdown; the final screen
shows the last hand's breakdown too
Verified in the browser against the compose stack: hand played to
completion, summary rendered (including a carte tie), ack from all four
players dealt the next hand live, and the auto-continue path fired when
nobody acked. 60 backend tests + mypy + cargo tests green.
This commit is contained in:
+153
@@ -9,6 +9,8 @@
|
||||
--muted: #a9b7ab;
|
||||
--accent: #e8c547;
|
||||
--danger: #d9534f;
|
||||
--team-a: #7db4e8;
|
||||
--team-b: #e8967d;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -448,3 +450,154 @@ table.matches td.lost {
|
||||
justify-content: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* ---------- hand-end scoring summary ---------- */
|
||||
|
||||
.summary-panel {
|
||||
min-width: 420px;
|
||||
max-width: 560px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.summary-panel h2 {
|
||||
text-align: center;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.score-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.score-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
background: var(--panel-light);
|
||||
border-left: 4px solid transparent;
|
||||
border-radius: 8px;
|
||||
padding: 0.45rem 0.75rem;
|
||||
}
|
||||
|
||||
.score-row.team-a {
|
||||
border-left-color: var(--team-a);
|
||||
}
|
||||
|
||||
.score-row.team-b {
|
||||
border-left-color: var(--team-b);
|
||||
}
|
||||
|
||||
.score-row.tie {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.score-icon {
|
||||
width: 32px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card-img.score-mini {
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.score-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.score-title {
|
||||
font-weight: 700;
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.score-text {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.score-points {
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.score-row.team-a .score-points {
|
||||
color: var(--team-a);
|
||||
}
|
||||
|
||||
.score-row.team-b .score-points {
|
||||
color: var(--team-b);
|
||||
}
|
||||
|
||||
.totals {
|
||||
margin: 1rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.total-row {
|
||||
display: grid;
|
||||
grid-template-columns: 4.5rem 1fr 4rem;
|
||||
gap: 0.6rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.total-label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
font-weight: 700;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.gained {
|
||||
margin-left: 0.35rem;
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.total-row.team-a .gained {
|
||||
color: var(--team-a);
|
||||
}
|
||||
|
||||
.total-row.team-b .gained {
|
||||
color: var(--team-b);
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 10px;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 999px;
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
|
||||
.total-row.team-a .progress-fill {
|
||||
background: var(--team-a);
|
||||
}
|
||||
|
||||
.total-row.team-b .progress-fill {
|
||||
background: var(--team-b);
|
||||
}
|
||||
|
||||
.summary-actions {
|
||||
text-align: center;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.final-score {
|
||||
text-align: center;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user