ux: hamburger + slide-out side drawer (not a kebab popover)
Replace the <details>-based kebab popover with a proper Material- style navigation drawer: - Hamburger button (menu icon) at top-left. - <aside> drawer slides in from the left via transform: translateX(). width: min(85vw, 320px). Backdrop overlay behind it. - Close paths: X button in the drawer header, backdrop click, Esc. - Padding honours Android safe-area insets on all four sides. - Model selector + label live inside the drawer; future per-app settings can move here too.
This commit is contained in:
@@ -19,7 +19,7 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- "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 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 `<details>` element so toggle/keyboard-accessibility works without extra JS.
|
||||
- Model selector moved into a slide-out side drawer (hamburger menu in the top-left). Drawer opens from the left with a backdrop, closes via the X button, backdrop click, or `Esc`. Honours Android safe-area insets.
|
||||
- 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.
|
||||
|
||||
@@ -51,44 +51,94 @@
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Kebab menu (top-left) housing the Model dropdown — keeps the main
|
||||
controls focused on the From/To pair. */
|
||||
.kebab {
|
||||
/* Hamburger menu (top-left) → slide-out side drawer that houses the
|
||||
Model selector (and future per-app settings). */
|
||||
.drawer-toggle {
|
||||
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;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
color: var(--fg);
|
||||
z-index: 50;
|
||||
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);
|
||||
.drawer-toggle .material-symbols-outlined { font-size: 22px; }
|
||||
|
||||
.drawer-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s;
|
||||
z-index: 200;
|
||||
}
|
||||
.kebab-panel label {
|
||||
.drawer-backdrop.open { opacity: 1; pointer-events: auto; }
|
||||
|
||||
.drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: min(85vw, 320px);
|
||||
background: var(--bg);
|
||||
border-right: 1px solid var(--border);
|
||||
padding:
|
||||
max(1rem, env(safe-area-inset-top, 0px))
|
||||
1.25rem
|
||||
max(1rem, env(safe-area-inset-bottom, 0px))
|
||||
max(1.25rem, env(safe-area-inset-left, 0px));
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.25s ease;
|
||||
z-index: 201;
|
||||
box-shadow: 4px 0 14px rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.drawer.open { transform: translateX(0); }
|
||||
.drawer-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.drawer-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.drawer-close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--muted);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.drawer-close:hover { color: var(--fg); background: var(--panel); }
|
||||
.drawer-close .material-symbols-outlined { font-size: 20px; }
|
||||
.drawer label {
|
||||
display: block;
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.06em;
|
||||
@@ -432,13 +482,22 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<details class="kebab">
|
||||
<summary aria-label="Settings" title="Settings"><span class="material-symbols-outlined">more_vert</span></summary>
|
||||
<div class="kebab-panel">
|
||||
<button type="button" id="drawer-toggle" class="drawer-toggle" aria-label="Open menu" title="Menu">
|
||||
<span class="material-symbols-outlined">menu</span>
|
||||
</button>
|
||||
<div id="drawer-backdrop" class="drawer-backdrop"></div>
|
||||
<aside id="drawer" class="drawer" aria-hidden="true">
|
||||
<div class="drawer-header">
|
||||
<h2>Settings</h2>
|
||||
<button type="button" id="drawer-close" class="drawer-close" aria-label="Close menu" title="Close">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<label for="model">Model</label>
|
||||
<select id="model"></select>
|
||||
</div>
|
||||
</details>
|
||||
</aside>
|
||||
<div class="wrap">
|
||||
<h1>finrod</h1>
|
||||
<div class="sub">Translation via Ollama · loremaster of tongues</div>
|
||||
@@ -1189,6 +1248,28 @@
|
||||
Vim.mapCommand("L", "action", "finrodFocusNext", {}, { context: "normal" });
|
||||
}
|
||||
|
||||
// Side drawer (Settings) wiring — opens from the top-left hamburger.
|
||||
const drawerEl = document.getElementById("drawer");
|
||||
const drawerBackdrop = document.getElementById("drawer-backdrop");
|
||||
const drawerToggle = document.getElementById("drawer-toggle");
|
||||
const drawerCloseBtn = document.getElementById("drawer-close");
|
||||
function openDrawer() {
|
||||
drawerEl.classList.add("open");
|
||||
drawerBackdrop.classList.add("open");
|
||||
drawerEl.setAttribute("aria-hidden", "false");
|
||||
}
|
||||
function closeDrawer() {
|
||||
drawerEl.classList.remove("open");
|
||||
drawerBackdrop.classList.remove("open");
|
||||
drawerEl.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
drawerToggle.addEventListener("click", openDrawer);
|
||||
drawerCloseBtn.addEventListener("click", closeDrawer);
|
||||
drawerBackdrop.addEventListener("click", closeDrawer);
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" && drawerEl.classList.contains("open")) closeDrawer();
|
||||
});
|
||||
|
||||
fillLangs();
|
||||
loadModels().then(() => refreshLoaded(true));
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user