fix(app): keyboard resizes container, tab bar margin, HTTPS for PWA install

- Root container height now bound to visualViewport.height when keyboard
  is open — the whole layout shrinks instead of being pushed offscreen
- Tab bar gets 24px vertical margin (12px top + 12px bottom + safe area)
- Added @vitejs/plugin-basic-ssl for HTTPS dev server — required for PWA
  install on non-localhost origins (LAN IP access)
- Improved useVisualViewport to track fullHeight for accurate keyboard
  offset calculation across orientation changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-04 21:01:07 +00:00
co-authored by Claude Opus 4.6
parent a0a9cf8e54
commit 0fa78cbf1d
5 changed files with 43 additions and 10 deletions
+12 -1
View File
@@ -1,5 +1,5 @@
<template>
<div class="h-dvh flex flex-col" :class="currentTheme">
<div class="h-dvh flex flex-col" :class="currentTheme" :style="rootStyle">
<RouterView />
<ArticleOverlay />
<PlayerBar v-if="!isMobile" />
@@ -17,6 +17,7 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
import { RouterView } from 'vue-router'
import { useTheme } from '@/composables/useTheme'
import { useArchy } from '@/composables/useArchy'
import { useVisualViewport } from '@/composables/useVisualViewport'
import ArticleOverlay from '@/components/content/ArticleOverlay.vue'
import PlayerBar from '@/components/player/PlayerBar.vue'
import PassphraseDialog from '@/components/ui/PassphraseDialog.vue'
@@ -32,8 +33,18 @@ const archy = useArchy()
const windowWidth = ref(window.innerWidth)
const isMobile = computed(() => windowWidth.value < 1024)
const { viewportHeight, isKeyboardOpen } = useVisualViewport()
function onResize() { windowWidth.value = window.innerWidth }
// On mobile, bind height to visualViewport.height so the container
// actually shrinks when the keyboard opens (dvh doesn't do this reliably)
const rootStyle = computed(() => {
if (isMobile.value && isKeyboardOpen.value) {
return { height: `${viewportHeight.value}px` }
}
return {}
})
const showPassphrase = ref(false)
const isCreatingPassphrase = ref(false)
@@ -4,28 +4,46 @@ import { ref, onMounted, onUnmounted } from 'vue'
* Tracks the visual viewport to detect mobile keyboard open/close.
* Uses the VisualViewport API to compute keyboard height as the difference
* between window.innerHeight and visualViewport.height.
*
* When the keyboard opens, viewportHeight shrinks to the visible area above
* the keyboard. Bind your container's height to viewportHeight to make the
* layout resize instead of the browser pushing content offscreen.
*/
export function useVisualViewport() {
const keyboardHeight = ref(0)
const isKeyboardOpen = ref(false)
const viewportHeight = ref(typeof window !== 'undefined' ? window.innerHeight : 0)
// Store the initial full height so we can compute keyboard offset
let fullHeight = typeof window !== 'undefined' ? window.innerHeight : 0
function onViewportChange() {
const vv = window.visualViewport
if (!vv) return
const kbHeight = Math.max(0, window.innerHeight - vv.height)
const kbHeight = Math.max(0, fullHeight - vv.height)
keyboardHeight.value = kbHeight
isKeyboardOpen.value = kbHeight > 100
viewportHeight.value = vv.height
}
function onWindowResize() {
// Update full height when orientation changes or browser chrome resizes
const vv = window.visualViewport
if (vv && !isKeyboardOpen.value) {
fullHeight = vv.height
viewportHeight.value = vv.height
}
}
onMounted(() => {
const vv = window.visualViewport
if (vv) {
fullHeight = vv.height
viewportHeight.value = vv.height
vv.addEventListener('resize', onViewportChange)
vv.addEventListener('scroll', onViewportChange)
onViewportChange()
}
window.addEventListener('resize', onWindowResize)
})
onUnmounted(() => {
@@ -34,6 +52,7 @@ export function useVisualViewport() {
vv.removeEventListener('resize', onViewportChange)
vv.removeEventListener('scroll', onViewportChange)
}
window.removeEventListener('resize', onWindowResize)
})
return { keyboardHeight, isKeyboardOpen, viewportHeight }
+3 -3
View File
@@ -309,11 +309,11 @@
<!-- Inline player bar on mobile (above tab bar) -->
<PlayerBar variant="inline" />
<!-- Bottom tab bar (iOS HIG: 49pt + safe area) -->
<!-- Bottom tab bar (iOS HIG: 49pt + safe area + 24px margin) -->
<div
v-show="!isKeyboardOpen"
class="shrink-0"
:style="{ paddingBottom: 'env(safe-area-inset-bottom, 0px)' }"
class="shrink-0 pt-3 pb-3"
:style="{ paddingBottom: 'calc(12px + env(safe-area-inset-bottom, 0px))' }"
>
<div class="flex items-center h-[49px] px-2 gap-1">
<button