From cafe67e9c83abff5c5011277ff634ea5d34fef39 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Tue, 7 Jul 2026 12:54:57 +0200 Subject: [PATCH] =?UTF-8?q?fix(offline):=20hotseat=20form=20=E2=80=94=20na?= =?UTF-8?q?tural=20scroll=20+=20focus-into-view=20(keyboard)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nested scroll region regressed the keyboard behaviour (a double scroll: the whole screen scrolled with a huge blank area under the Start button, and a focused bottom row hid behind the keyboard). Drop it: the hotseat form now flows naturally and scrolls with the screen's own scroll (.page.scrollform: no forced full height, no pinned button), and a name input scrolls itself into view (centred above the keyboard) on focus. --- ui/src/screens/NewGame.svelte | 175 +++++++++++++++++----------------- 1 file changed, 88 insertions(+), 87 deletions(-) diff --git a/ui/src/screens/NewGame.svelte b/ui/src/screens/NewGame.svelte index 64fdd0f..8ada7c3 100644 --- a/ui/src/screens/NewGame.svelte +++ b/ui/src/screens/NewGame.svelte @@ -232,6 +232,14 @@ deleteRow = null; } + // Centre a focused name input above the soft keyboard: the keyboard shrinks the screen (--vvh), + // so wait a beat for it to open + the layout to settle, then scroll the field into view (the + // default focus-scroll leaves a bottom row hidden behind the keyboard). + function bringIntoView(e: FocusEvent): void { + const el = e.currentTarget as HTMLElement; + setTimeout(() => el.scrollIntoView({ block: 'center' }), 300); + } + function setHostPlays(yes: boolean): void { askHostPlays = false; if (!yes) return; @@ -278,7 +286,10 @@ -
+ +
@@ -332,82 +343,77 @@ >{t('new.start')} {:else if offlineMode.active} -
-
-
- {t('hotseat.hostPin')} - -
- -
- {#each variants as v (v.id)} - - {/each} -
- {#if variants.some((v) => supportsMultipleWordsToggle(v.id))} - - {/if} -
- {#each rows as row, i (i)} -
- (row.committed = commitName(row.name, row.committed))} - onblur={() => (row.name = row.committed)} - /> - - {#if rows.length > 2} - {#if deleteRow === i} - - {/if} - - {/if} -
- {/each} -
- {#if rows.length < 4} - - {/if} -
- + PIN first (it gates the roster); each seat may add its own PIN. --> +
+ {t('hotseat.hostPin')} +
+ +
+ {#each variants as v (v.id)} + + {/each} +
+ {#if variants.some((v) => supportsMultipleWordsToggle(v.id))} + + {/if} +
+ {#each rows as row, i (i)} +
+ (row.committed = commitName(row.name, row.committed))} + onblur={() => (row.name = row.committed)} + /> + + {#if rows.length > 2} + {#if deleteRow === i} + + {/if} + + {/if} +
+ {/each} +
+ {#if rows.length < 4} + + {/if} + {:else if friends.length === 0}

{t('new.noFriends')}

{:else} @@ -486,6 +492,12 @@ height: 100%; box-sizing: border-box; } + /* The hotseat form: natural height (grows past the viewport → the screen's own scroll takes over) + with no full-height pinning, so a focused name input just scrolls the page above the keyboard. */ + .page.scrollform { + height: auto; + min-height: 100%; + } .subtitle { color: var(--text-muted); margin: 0; @@ -680,17 +692,6 @@ line-height: 1.4; } /* --- offline hotseat roster --- */ - /* The scrollable region (host PIN + variants + roster), pinned above the Start button — mirrors - the friends form so a focused name input's soft keyboard shrinks this region (via --vvh on the - screen) instead of leaving a full-height blank spacer painted behind the keyboard. */ - .hs-scroll { - flex: 1; - min-height: 0; - overflow-y: auto; - display: flex; - flex-direction: column; - gap: 14px; - } .hostpin { display: flex; align-items: center;