From 86620f9f5c0ee191d3265bf9c9efb57f5bcc6a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Battistella=20Nadas?= Date: Tue, 9 Jun 2026 19:53:16 +0200 Subject: [PATCH] ux(fab): float above the on-screen keyboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two-pronged fix so the Translate + Scan FABs stay reachable when the mobile soft keyboard is open: - Add `interactive-widget=resizes-content` to the viewport meta — Chrome on Android then shrinks the layout viewport when the keyboard opens, so `position: fixed; bottom: ...` is naturally above it. iOS Safari ignores this directive, hence: - VisualViewport listener computes inset = innerHeight - vv.height - vv.offsetTop and writes it to a `--kb-inset` CSS custom property. The FAB bottom calc uses `max(var(--kb-inset), safe-area-inset-bottom)` so the keyboard inset wins when it's open and the home-indicator inset wins when it isn't. Net: tap the editor on iOS → keyboard slides up → Translate / Scan FABs rise with it, no manual minimise required. --- CHANGELOG.md | 1 + static/index.html | 29 ++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7346388..2e5e4f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Changed - "Scan image" button replaced with a floating action button at the bottom-right of the viewport, using a Material Symbols Outlined camera glyph (`photo_camera`) for consistent rendering across platforms (was a small text button in the input pane header). - "Translate" inline button replaced with a primary FAB using the `translate` Material Symbols icon, stacked directly under the camera FAB at the bottom-right corner (closest to the thumb on phones). The camera FAB is now the *secondary* in the stack, just above. Both 56 × 56 px, same accent fill. The translate FAB gains a pulsing-ring `busy` animation during a running translation; `goBtn.textContent` swap is gone (was meaningless for an icon-only button). `body` got `~9 rem` of bottom padding so content scrolls clear of the stack. +- FAB stack rides above the on-screen keyboard on mobile. Added `interactive-widget=resizes-content` to the viewport meta (handles Chrome on Android) and a `VisualViewport` listener that exposes the keyboard's intrusion as a `--kb-inset` CSS custom property (handles iOS Safari). The FAB `bottom` uses `max(--kb-inset, env(safe-area-inset-bottom))` so the keyboard inset wins when it's open and the home-indicator inset wins when it isn't. - Crop modal: - No default crop box. User drags on the image to draw the region; falls back to full image if they confirm without drawing. - Buttons are now Material Symbols icons: `rotate_left`, `rotate_right`, `invert_colors` (new — see below), `check`. Cancel kept as text. The `check` (Use this) button is larger and more prominent. The Reset button was removed (rarely useful with no default box). diff --git a/static/index.html b/static/index.html index b7a85d5..b533b1c 100644 --- a/static/index.html +++ b/static/index.html @@ -2,7 +2,7 @@ - + finrod — translator @@ -190,12 +190,15 @@ .fab .material-symbols-outlined { font-size: 28px; } .fab:hover:not(:disabled) { transform: scale(1.06); } .fab:disabled { opacity: 0.55; cursor: progress; } + /* --kb-inset is set from JS based on VisualViewport so the FABs ride + above the on-screen keyboard. When there's no keyboard it's 0 and the + safe-area inset wins via max(). */ .fab-primary { - bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px)); + bottom: calc(1.5rem + max(var(--kb-inset, 0px), env(safe-area-inset-bottom, 0px))); } .fab-secondary { /* sits above the primary: 56 (primary height) + 0.75rem gap + base offset */ - bottom: calc(1.5rem + 56px + 0.75rem + env(safe-area-inset-bottom, 0px)); + bottom: calc(1.5rem + 56px + 0.75rem + max(var(--kb-inset, 0px), env(safe-area-inset-bottom, 0px))); } .fab.busy { animation: fab-pulse 1.4s ease-in-out infinite; @@ -1140,6 +1143,26 @@ Vim.mapCommand("L", "action", "finrodFocusNext", {}, { context: "normal" }); } + // Keep the FABs floating above the on-screen keyboard when it's open. + // On Chrome+Android the `interactive-widget=resizes-content` meta directive + // already shrinks the layout viewport — kb-inset stays 0 there. + // On iOS Safari (which ignores the meta) the layout viewport stays full + // and we compute the hidden bottom strip from VisualViewport. + function updateKbInset() { + const vv = window.visualViewport; + if (!vv) { + document.documentElement.style.setProperty("--kb-inset", "0px"); + return; + } + const inset = Math.max(0, window.innerHeight - vv.height - vv.offsetTop); + document.documentElement.style.setProperty("--kb-inset", inset + "px"); + } + if (window.visualViewport) { + window.visualViewport.addEventListener("resize", updateKbInset); + window.visualViewport.addEventListener("scroll", updateKbInset); + } + updateKbInset(); + fillLangs(); loadModels().then(() => refreshLoaded(true));