diff --git a/CHANGELOG.md b/CHANGELOG.md index 57c9eab..adc2023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Per-word confidence filter (≥ 60) drops the low-confidence noise Tesseract still emits. Re-assembles lines from kept words; status line reports how many words were dropped. - Crop & rotate modal between image pick and OCR. After choosing an image, a modal pops up with cropperjs's drag-handle crop UI and Rotate left / Rotate right / Reset buttons. EXIF orientation is honoured automatically. Cancel / `Esc` / clicking the backdrop aborts; `Enter` accepts. Library lazy-loaded from esm.sh (no bundling). +### Changed +- "Scan image" button replaced with a 📷 floating action button at the bottom-right of the viewport (was a small button in the input pane header). Mobile thumb-friendly, doesn't crowd the input label. +- Crop modal has inner padding so cropperjs's corner handles stay away from the screen's right/bottom edges — fixes the bottom-right resize handle triggering Android's edge-swipe back gesture. Also respects iOS safe-area insets. + ### Fixed - "Hot: …" status no longer claims "unloads in X" when the server's `keep_alive` is `-1` (Ollama returns an absurd year-4001 `expires_at`). Threshold: any expiry >7 days drops the suffix and just shows the model name. diff --git a/static/index.html b/static/index.html index 80a8aa2..5b29458 100644 --- a/static/index.html +++ b/static/index.html @@ -156,32 +156,31 @@ box-shadow: none; } - /* Input-pane header: label on the left, "Scan image" button on the right */ - .input-header { - display: flex; - align-items: baseline; - justify-content: space-between; - margin-bottom: 0.25rem; - } - .input-header label { margin-bottom: 0; } - .scan-btn { - background: transparent; - border: 1px solid var(--border); - color: var(--muted); - font-size: 0.75rem; - padding: 0.2rem 0.6rem; + /* Floating action button for OCR scan — bottom-right, mobile-thumb friendly */ + .scan-fab { + position: fixed; + bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px)); + right: calc(1.5rem + env(safe-area-inset-right, 0px)); + width: 56px; + height: 56px; + padding: 0; margin: 0; - border-radius: 4px; + border-radius: 50%; + background: var(--accent); + color: #fff; + border: none; cursor: pointer; - letter-spacing: 0.04em; - text-transform: uppercase; - width: auto; + font-size: 1.75rem; + line-height: 1; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35); + z-index: 100; + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.15s; } - .scan-btn:hover:not(:disabled) { - border-color: var(--accent); - color: var(--fg); - } - .scan-btn:disabled { cursor: progress; opacity: 0.6; } + .scan-fab:hover:not(:disabled) { transform: scale(1.06); } + .scan-fab:disabled { opacity: 0.6; cursor: progress; } .scan-status { font-size: 0.75rem; color: var(--muted); @@ -198,7 +197,9 @@ display: flex; align-items: center; justify-content: center; - padding: 1rem; + /* Generous side padding pushes the modal in from Android's edge-swipe + gesture zone (~24dp from screen edges triggers back/home). */ + padding: 1rem max(1rem, env(safe-area-inset-right, 0px)) 1rem max(1rem, env(safe-area-inset-left, 0px)); z-index: 1000; } .modal { @@ -222,6 +223,8 @@ min-height: 0; max-height: 70vh; background: #000; + /* Inset so cropper's corner handles never sit on a screen-edge gesture zone */ + padding: 1.25rem; } .modal-body img { display: block; max-width: 100%; } .modal-footer { @@ -310,10 +313,7 @@