From c746a88cfb8303f31ad0c37dfbbf4e416b399e0c Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Thu, 8 Oct 2026 22:30:06 +0000 Subject: [PATCH] Stop the chiamata call picker from clipping on short windows The call picker stacked the four 120px cards vertically, making the dialog about 780px tall, and the overlay centered its content with no scroll fallback. On typical laptop window heights that pushed the title and options off-screen (the heading sat at y=-110 on a 560px viewport), leaving what looked like a free-floating selection with no explanation and no way to scroll. Make dialogs scroll instead of clipping: the overlay scrolls and the panel centers with `margin: auto`, which keeps every modal (the capture picker and the scoring summaries too) centered when it fits and reachable when it does not. Lay the call options out in a compact grid (four across, 2x2 on phones) with 84px cards, name the won rank in the title ("You won with the Re - name the card") and label each option "Partner" or "Plays alone" so the choice reads at a glance. --- web/src/pages/chiamata.rs | 35 ++++++++++++++------ web/style.css | 67 ++++++++++++++++++++++++++++++++------- 2 files changed, 82 insertions(+), 20 deletions(-) diff --git a/web/src/pages/chiamata.rs b/web/src/pages/chiamata.rs index e3aa365..c6c1563 100644 --- a/web/src/pages/chiamata.rs +++ b/web/src/pages/chiamata.rs @@ -464,10 +464,25 @@ pub fn chiamata_table( } /// Overlay for the auction winner: name the called card. +/// +/// The winning bid fixes the rank, so the choice is which of the four +/// suits to call: its holder becomes the secret partner, unless the +/// caller holds it too — then that option means playing alone. pub fn call_picker(game: GameView, socket: Signal>) -> View { let Some(cards) = game.callable_cards.clone().filter(|cards| !cards.is_empty()) else { return view! {}; }; + let rank = cards + .first() + .and_then(|code| code.get(..2)) + .and_then(|rank| rank.parse::().ok()) + .map(rank_name) + .unwrap_or_default(); + let title = if rank.is_empty() { + "Name the called card".to_string() + } else { + format!("You won with the {rank} — name the card") + }; let own_hand = game .players .iter() @@ -479,33 +494,35 @@ pub fn call_picker(game: GameView, socket: Signal>) -> View { .map(|code| { let held = own_hand.contains(&code); let label = card_label(&code); - let img = card_img(code.clone(), "hand-card"); - let solo_note = held.then(|| view! { - span(class="solo-note") { "plays alone" } - }); + let img = card_img(code.clone(), "call-card"); + let note = if held { + view! { span(class="call-note solo") { "Plays alone" } } + } else { + view! { span(class="call-note partner") { "Partner" } } + }; let click_code = code.clone(); view! { - button(class="capture-option call-option", on:click=move |_| { + button(class="call-option", on:click=move |_| { if let Some(s) = socket.get_clone() { s.call_card(&click_code); } }) { (img) span(class="call-label") { (label) } - (solo_note) + (note) } } }) .collect::>(); view! { div(class="overlay") { - div(class="picker") { - h3 { "Name the called card" } + div(class="picker call-picker") { + h3 { (title) } p(class="text-secondary small") { "Its holder becomes your secret partner; name one of your " "own cards to play alone." } - div(class="capture-options") { (options) } + div(class="call-options") { (options) } } } } diff --git a/web/style.css b/web/style.css index 2e9f133..2413ffd 100644 --- a/web/style.css +++ b/web/style.css @@ -292,8 +292,12 @@ body { inset: 0; background: rgba(0, 0, 0, 0.65); display: flex; - align-items: center; + align-items: flex-start; justify-content: center; + /* Tall dialogs (the chiamata call picker, the scoring summaries) can + * exceed short windows: allow scrolling instead of clipping them. */ + overflow-y: auto; + padding: 1rem; z-index: 20; } @@ -302,8 +306,11 @@ body { border-radius: 14px; padding: 1.5rem 2rem; text-align: center; - min-width: min(320px, calc(100vw - 1.5rem)); - max-width: calc(100vw - 1.5rem); + /* Centers the dialog when it fits and keeps it reachable (top + * aligned, scrollable via the overlay) when it does not. */ + margin: auto; + min-width: min(320px, calc(100vw - 3rem)); + max-width: calc(100vw - 3rem); } .capture-options { @@ -784,26 +791,64 @@ body { border-color: var(--text); } -/* The auction winner's call picker: one card per suit. */ -.call-option { - flex-direction: column; - align-items: center; +/* The auction winner's call picker: the four cards of the won rank, + * laid out in a compact grid so the dialog stays short. */ +.call-picker { + max-width: min(34rem, calc(100vw - 3rem)); } -.call-option .playing-card { +.call-options { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.5rem; + margin: 1rem 0 0; +} + +@media (min-width: 576px) { + .call-options { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } +} + +.call-option { + background: var(--panel-light); + border: 1px solid #4c6a54; + border-radius: 10px; + padding: 0.4rem 0.3rem; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.15rem; +} + +.call-option:hover { + border-color: var(--accent); +} + +/* Smaller than a hand card: the picker must fit short laptop windows. */ +.playing-card.call-card { + height: 84px; margin: 0 auto; } .call-label { display: block; - font-size: 0.85rem; + font-size: 0.8rem; color: var(--text); - margin-top: 0.25rem; + margin-top: 0.15rem; } -.solo-note { +.call-note { display: block; font-size: 0.7rem; +} + +.call-note.partner { + color: var(--team-a); +} + +.call-note.solo { color: var(--accent); }