diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e5e4f7..574c5d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,10 @@ 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. +- FAB stack rides above the on-screen keyboard on mobile via `interactive-widget=resizes-content` in the viewport meta tag (handled by Chrome on Android). +- Layout: From / To dropdowns now occupy 2 columns at the top (was 3 columns including Model). +- Model selector moved into a kebab (⋮) menu in the top-left corner. Uses a native `
` element so toggle/keyboard-accessibility works without extra JS. +- Default source language is now Dutch (was Auto-detect). Target stays English. - 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 b533b1c..f283e4b 100644 --- a/static/index.html +++ b/static/index.html @@ -46,12 +46,55 @@ .sub { color: var(--muted); font-size: 0.9rem; margin-bottom: 1.5rem; } .controls { display: grid; - grid-template-columns: 1fr 1fr 1fr; + grid-template-columns: 1fr 1fr; gap: 0.75rem; margin-bottom: 1rem; } - @media (max-width: 640px) { - .controls { grid-template-columns: 1fr; } + + /* Kebab menu (top-left) housing the Model dropdown — keeps the main + controls focused on the From/To pair. */ + .kebab { + position: fixed; + top: calc(1rem + env(safe-area-inset-top, 0px)); + left: calc(1rem + env(safe-area-inset-left, 0px)); + z-index: 50; + } + .kebab > summary { + list-style: none; + width: 40px; + height: 40px; + border-radius: 50%; + border: 1px solid var(--border); + background: var(--panel); + color: var(--fg); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18); + } + .kebab > summary::-webkit-details-marker { display: none; } + .kebab > summary::marker { display: none; } + .kebab > summary .material-symbols-outlined { font-size: 22px; } + .kebab[open] > summary { border-color: var(--accent); color: var(--accent); } + .kebab-panel { + position: absolute; + top: calc(100% + 0.5rem); + left: 0; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + padding: 0.75rem; + min-width: 220px; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3); + } + .kebab-panel label { + display: block; + font-size: 0.7rem; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--muted); + margin-bottom: 0.25rem; } label { display: block; font-size: 0.75rem; color: var(--muted); margin-bottom: 0.25rem; text-transform: uppercase; letter-spacing: 0.08em; } select, textarea, button { @@ -190,15 +233,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(). */ + /* Android Chrome shrinks the layout viewport when the soft keyboard + opens (via `interactive-widget=resizes-content` on the viewport meta), + so plain `bottom: 1.5rem` is already above the keyboard. */ .fab-primary { - bottom: calc(1.5rem + max(var(--kb-inset, 0px), env(safe-area-inset-bottom, 0px))); + bottom: calc(1.5rem + 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 + max(var(--kb-inset, 0px), env(safe-area-inset-bottom, 0px))); + bottom: calc(1.5rem + 56px + 0.75rem + env(safe-area-inset-bottom, 0px)); } .fab.busy { animation: fab-pulse 1.4s ease-in-out infinite; @@ -389,6 +432,13 @@ +
+ more_vert +
+ + +
+

finrod

Translation via Ollama · loremaster of tongues
@@ -402,10 +452,6 @@
-
- - -
@@ -906,7 +952,7 @@ o2.value = lang; o2.textContent = lang; targetSel.appendChild(o2); } - sourceSel.value = "auto"; + sourceSel.value = "Dutch"; targetSel.value = "English"; } @@ -1143,26 +1189,6 @@ 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));