Files
tavolo/web/style.css
T
woggioni 353f012906
CI / Build and push docker image (push) Successful in 5m35s
Add briscola chiamata
Five-player briscola with a descending calling auction and a secret
partner. Starting with the player after the dealer, each player bids a
lower rank (A > 3 > K > H > J > 7 > 6 > 5 > 4 > 2) or passes; once the
standing bid is the two the auction continues on points (62, 64, ...).
The last player standing must bid, and the winner names any card of the
won rank: its holder is the secret partner, or the caller plays alone
when it is one of their own cards, and its suit becomes the trump. The
calling side must reach 61 (or the raised target); a 60-60 deal with the
plain target is a patta and is replayed. A match is a single deal, so
the caller and partner (or the lone caller) against the other players
map onto the platform's two-team result, Elo and match history.

The rules live in tavolo.briscola.chiamata with the
BriscolaChiamataEngine adapter registered alongside the other briscola
variants; the partner's role and the side totals stay hidden until the
called card is played. The web client gets a five-seat table, the
auction panel with legal bids and point raises, the call picker, the
reveal bands and a chiamata-aware final summary.
2026-10-08 21:50:56 +08:00

829 lines
14 KiB
CSS

/* tavolo frontend — game tables, cards and scoring summaries.
*
* The felt palette and the Bootstrap theme overrides live in theme.css,
* which is loaded before this file. */
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "Segoe UI", system-ui, sans-serif;
}
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ---------- shared chrome ---------- */
.app-toast {
position: fixed;
top: 1rem;
left: 50%;
transform: translateX(-50%);
background: var(--danger);
color: #fff;
padding: 0.6rem 1.2rem;
border-radius: 8px;
z-index: 30;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}
/* ---------- game table ---------- */
.hud {
display: flex;
flex-wrap: wrap;
gap: 0.4rem 1.5rem;
align-items: center;
justify-content: center;
padding: 0.6rem 1rem;
background: rgba(0, 0, 0, 0.3);
}
.hud a {
color: var(--accent);
text-decoration: none;
}
.hud-scores {
font-weight: 600;
}
.turn-note {
color: var(--muted);
}
.turn-note.you {
color: var(--accent);
font-weight: 700;
}
.turn-timer {
color: var(--muted);
font-variant-numeric: tabular-nums;
}
.table-wrap {
display: flex;
flex-direction: column;
flex: 1;
}
.table-grid {
flex: 1;
display: grid;
/* minmax(0, …) lets the tracks shrink below their content's
* min-content width; without it the center's move banner and the
* seat panels push the grid wider than the screen on phones. */
grid-template-columns: minmax(0, 1fr) minmax(0, 2.4fr) minmax(0, 1fr);
grid-template-rows: auto 1fr auto;
grid-template-areas:
". top ."
"left center right"
"bottom bottom bottom";
gap: 0.5rem;
padding: 0.75rem;
}
.seat-top {
grid-area: top;
}
.seat-left {
grid-area: left;
}
.seat-right {
grid-area: right;
}
.seat-bottom {
grid-area: bottom;
text-align: center;
}
.seat {
background: var(--panel);
border: 2px solid transparent;
border-radius: 10px;
padding: 0.5rem 0.75rem;
text-align: center;
/* Fixed height keeps the table from jumping as cards are played. */
display: flex;
flex-direction: column;
justify-content: flex-start;
min-height: 116px;
min-width: 0;
align-self: center;
width: 100%;
}
.seat.active {
border-color: var(--accent);
box-shadow: 0 0 12px rgba(232, 197, 71, 0.45);
}
.seat-name {
font-weight: 600;
display: flex;
flex-wrap: wrap;
gap: 0.15rem 0.5rem;
align-items: center;
justify-content: center;
}
.team-badge {
font-size: 0.7rem;
background: var(--panel-light);
border-radius: 4px;
padding: 0.1rem 0.35rem;
color: var(--muted);
}
/* Opponents' hidden hands: a tidy grid so the count reads at a glance. */
.seat-cards {
display: grid;
grid-template-columns: repeat(5, auto);
gap: 2px;
justify-content: center;
margin: 0.35rem 0;
}
.seat-stats {
font-size: 0.8rem;
color: var(--muted);
margin-top: auto;
}
/* The viewer's own stats sit above the hand; no flex context here. */
.seat-bottom .seat-stats {
margin-bottom: 0.35rem;
}
.center {
grid-area: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.75rem;
min-height: 230px;
min-width: 0;
}
.table-cards {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
justify-content: center;
min-height: 90px;
align-items: center;
}
.table-empty {
color: var(--muted);
font-style: italic;
}
/* ---------- cards ---------- */
.playing-card {
border-radius: 6px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
background: #fff;
user-select: none;
}
.playing-card.mini {
height: 34px;
}
.playing-card.move-card {
height: 54px;
vertical-align: middle;
}
.playing-card.table-card {
height: 84px;
}
.playing-card.hand-card {
height: 120px;
}
.hand {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
justify-content: center;
align-items: flex-end;
min-height: 132px;
}
.hand-slot {
background: none;
border: none;
padding: 0;
cursor: pointer;
transition: transform 0.1s ease;
}
.hand-slot:hover {
transform: translateY(-8px);
}
.hand-slot.selected {
outline: 3px solid var(--accent);
border-radius: 8px;
transform: translateY(-10px);
}
/* ---------- move banner ---------- */
.move-banner {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 0.4rem;
max-width: 100%;
background: rgba(0, 0, 0, 0.35);
border-radius: 999px;
padding: 0.35rem 1rem;
font-size: 0.95rem;
}
.scopa-badge {
background: var(--accent);
color: #1d1d1d;
font-weight: 700;
border-radius: 999px;
padding: 0.15rem 0.6rem;
margin-left: 0.25rem;
}
.pigliatutto-badge {
background: var(--danger, #d9534f);
color: #fff;
font-weight: 700;
border-radius: 999px;
padding: 0.15rem 0.6rem;
margin-left: 0.25rem;
}
.spectator-badge {
background: var(--panel-light);
color: var(--muted);
font-weight: 700;
border-radius: 999px;
padding: 0.15rem 0.6rem;
border: 1px solid var(--muted);
}
/* ---------- overlays ---------- */
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.65);
display: flex;
align-items: center;
justify-content: center;
z-index: 20;
}
.picker {
background: var(--panel);
border-radius: 14px;
padding: 1.5rem 2rem;
text-align: center;
min-width: min(320px, calc(100vw - 1.5rem));
max-width: calc(100vw - 1.5rem);
}
.capture-options {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin: 1rem 0;
}
.capture-option {
background: var(--panel-light);
border: 1px solid #4c6a54;
border-radius: 10px;
padding: 0.4rem;
cursor: pointer;
display: flex;
gap: 0.4rem;
justify-content: center;
}
.capture-option:hover {
border-color: var(--accent);
}
.gameover-actions {
display: flex;
gap: 0.75rem;
justify-content: center;
margin-top: 1rem;
}
/* ---------- hand-end scoring summary ---------- */
.summary-panel {
min-width: min(420px, calc(100vw - 1.5rem));
max-width: min(560px, calc(100vw - 1.5rem));
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;
}
.playing-card.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;
}
/* ---------- briscola ---------- */
.briscola-badge {
background: var(--panel-light);
color: var(--accent);
font-weight: 700;
border-radius: 999px;
padding: 0.15rem 0.6rem;
border: 1px solid var(--accent);
}
/* The face-down stock with the face-up trump card beside it. */
.stock {
display: flex;
align-items: center;
gap: 0.75rem;
}
.stock-pile {
display: flex;
}
.playing-card.stock-card {
height: 48px;
margin-left: -18px;
}
.playing-card.stock-card:first-child {
margin-left: 0;
}
.playing-card.briscola-card {
height: 64px;
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.stock-count {
color: var(--muted);
font-size: 0.8rem;
font-variant-numeric: tabular-nums;
}
/* Current-trick cards with the player's name above each. */
.trick-slot {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.15rem;
}
.trick-name {
font-size: 0.7rem;
color: var(--muted);
}
/* Two-player table: opponent on top, player at the bottom. */
.table-grid.duo {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"top"
"center"
"bottom";
}
/* ---------- phones ---------- */
@media (max-width: 576px) {
.hud {
gap: 0.3rem 0.75rem;
padding: 0.45rem 0.5rem;
font-size: 0.85rem;
}
.table-grid {
gap: 0.35rem;
padding: 0.4rem;
}
.seat {
min-height: 92px;
padding: 0.35rem 0.4rem;
}
.seat-name {
font-size: 0.85rem;
}
.team-badge {
font-size: 0.6rem;
}
.seat-stats {
font-size: 0.7rem;
}
/* Flex-wrap lets the hidden-hand backs drop to the next row instead
* of forcing the seat wider than its track. */
.seat-cards {
display: flex;
flex-wrap: wrap;
gap: 2px;
justify-content: center;
margin: 0.2rem 0;
}
.center {
min-height: 180px;
gap: 0.45rem;
}
.move-banner {
font-size: 0.8rem;
padding: 0.25rem 0.6rem;
}
.playing-card.mini {
height: 26px;
}
.playing-card.table-card {
height: 62px;
}
.playing-card.move-card {
height: 42px;
}
.playing-card.hand-card {
height: 92px;
}
.hand {
min-height: 100px;
gap: 0.35rem;
}
.stock {
gap: 0.4rem;
}
.playing-card.stock-card {
height: 38px;
margin-left: -12px;
}
.playing-card.briscola-card {
height: 50px;
}
.picker {
padding: 1rem 1.1rem;
}
.summary-panel h2 {
font-size: 1.25rem;
}
.score-row {
gap: 0.5rem;
padding: 0.35rem 0.5rem;
}
.score-text {
font-size: 0.85rem;
}
.score-icon {
width: 26px;
}
.playing-card.score-mini {
height: 30px;
}
.total-row {
grid-template-columns: 3.5rem 1fr 3.2rem;
gap: 0.4rem;
}
}
/* ---------- briscola chiamata ---------- */
/* Five seats: two across the top and one on each side of the viewer. */
.table-grid.cinque {
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) minmax(0, 1fr);
grid-template-rows: auto 1fr auto;
grid-template-areas:
"tl . tr"
"left center right"
"bottom bottom bottom";
}
.seat-tl {
grid-area: tl;
}
.seat-tr {
grid-area: tr;
}
.called-badge {
background: var(--panel-light);
color: var(--accent);
font-weight: 700;
border-radius: 999px;
padding: 0.15rem 0.6rem;
border: 1px solid var(--accent);
}
.role-badge {
background: var(--accent);
color: var(--felt-dark);
}
.reveal-banner {
background: rgba(232, 197, 71, 0.15);
border: 1px solid var(--accent);
border-radius: 8px;
padding: 0.35rem 0.75rem;
}
.patta-banner {
background: rgba(217, 83, 79, 0.25);
}
/* The calling auction: standing bid, trail and the bid buttons. */
.auction-panel {
background: rgba(0, 0, 0, 0.25);
border-radius: 10px;
padding: 0.75rem 1rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
max-width: 32rem;
}
.auction-standing {
font-weight: 600;
text-align: center;
}
.auction-history {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
justify-content: center;
}
.auction-chip {
background: var(--panel-light);
color: var(--muted);
border-radius: 4px;
padding: 0.1rem 0.4rem;
font-size: 0.75rem;
}
.bid-options {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
justify-content: center;
}
.bid-button {
background: var(--panel-light);
color: var(--text);
border: 1px solid var(--accent);
border-radius: 6px;
padding: 0.25rem 0.6rem;
cursor: pointer;
}
.bid-button:hover {
background: var(--accent);
color: var(--felt-dark);
}
.bid-button.points {
border-color: var(--team-a);
}
.pass-button {
background: transparent;
color: var(--muted);
border: 1px solid var(--muted);
border-radius: 6px;
padding: 0.25rem 0.6rem;
cursor: pointer;
}
.pass-button:hover {
color: var(--text);
border-color: var(--text);
}
/* The auction winner's call picker: one card per suit. */
.call-option {
flex-direction: column;
align-items: center;
}
.call-option .playing-card {
margin: 0 auto;
}
.call-label {
display: block;
font-size: 0.85rem;
color: var(--text);
margin-top: 0.25rem;
}
.solo-note {
display: block;
font-size: 0.7rem;
color: var(--accent);
}
.chiamata-row .score-title {
color: var(--accent);
}
@media (max-width: 576px) {
.table-grid.cinque {
grid-template-columns: 1fr 1fr;
grid-template-areas:
"tl tr"
"left right"
"center center"
"bottom bottom";
}
.auction-panel {
padding: 0.5rem;
max-width: 100%;
}
}