feat: add PWA support and mobile-responsive layouts
- Add web app manifest, maskable/apple-touch icons, and a production-only service worker that caches the app shell for offline start - Improve mobile UX: responsive breakpoints from ~320px, 44px touch targets, safe-area insets, dvh heights, 16px inputs to avoid iOS zoom - Serve .webmanifest with correct content type in the Go static handler - Document PWA behavior and mobile features in the README
This commit is contained in:
parent
f220d96e3b
commit
cbc03c56bf
18 changed files with 277 additions and 9 deletions
|
|
@ -232,8 +232,16 @@ function syncedAgo(ts: number | null): string {
|
|||
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
|
||||
.form a { color: var(--accent); }
|
||||
.conn { display: flex; align-items: center; gap: 14px; padding: 14px 16px; }
|
||||
.conn .grow { min-width: 0; }
|
||||
.conn-icon { font-size: 22px; }
|
||||
.err { color: var(--danger); }
|
||||
.err { color: var(--danger); overflow-wrap: anywhere; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.conn { flex-wrap: wrap; }
|
||||
.conn .btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
a { color: var(--accent); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -101,4 +101,17 @@ const statusLabel = { pending: 'Offen', accepted: 'Angenommen', declined: 'Abgel
|
|||
font-size: 14px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.req-head {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.req-actions {
|
||||
justify-content: stretch;
|
||||
}
|
||||
.req-actions .btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -43,4 +43,13 @@ async function copy() {
|
|||
white-space: nowrap;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.link-row .link {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
.link-row .btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -11,3 +11,12 @@ const app = createApp(App)
|
|||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
|
||||
// PWA: Service Worker registrieren (nur im Production-Build; in dev stört er HMR).
|
||||
if (import.meta.env.PROD && 'serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/sw.js').catch(() => {
|
||||
/* PWA offline-fallback nicht verfügbar – App läuft trotzdem */
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,10 @@ html[data-theme='light'] {
|
|||
--ok: #0c9e63;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html, body {
|
||||
|
|
@ -88,11 +92,22 @@ body {
|
|||
linear-gradient(180deg, var(--bg-1), var(--bg-0));
|
||||
background-attachment: fixed;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
letter-spacing: 0.01em;
|
||||
overflow-wrap: break-word;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
#app { min-height: 100vh; }
|
||||
/* dvh berücksichtigt die dynamischen Browser-Leisten auf Mobilgeräten */
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
}
|
||||
|
||||
/* Doppel-Tap-Zoom auf Steuerflächen unterbinden */
|
||||
.btn, .tab, .slot-chip, .day-chip, .tile, .weekday {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
/* Farbliche "Orbs" hinter allem – geben dem Glass etwas zum Brechen */
|
||||
.bg-scene { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }
|
||||
|
|
@ -372,5 +387,23 @@ textarea.input { resize: vertical; min-height: 84px; }
|
|||
.fade-enter-from, .fade-leave-to { opacity: 0; transform: translateY(6px); }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.page { padding: 16px 14px 64px; }
|
||||
.page {
|
||||
padding: 14px 12px calc(64px + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
/* 16px verhindert den automatischen Zoom-Fokus von iOS Safari */
|
||||
.input, select.input, textarea.input {
|
||||
font-size: 16px;
|
||||
}
|
||||
.btn {
|
||||
padding: 12px 18px;
|
||||
}
|
||||
.slot-chip {
|
||||
padding: 14px 0;
|
||||
}
|
||||
.slot-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
|
||||
}
|
||||
.toasts {
|
||||
bottom: calc(16px + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -276,8 +276,7 @@ onMounted(async () => {
|
|||
</template>
|
||||
|
||||
<style scoped>
|
||||
.booking { display: flex; flex-direction: column; gap: 16px; max-width: 780px; }
|
||||
.state {
|
||||
.booking { display: flex; flex-direction: column; gap: 16px; max-width: 780px; }.state {
|
||||
margin-top: 18vh;
|
||||
padding: 48px 36px;
|
||||
display: flex;
|
||||
|
|
@ -299,7 +298,7 @@ onMounted(async () => {
|
|||
box-shadow: 0 14px 44px rgba(70, 224, 165, 0.4);
|
||||
}
|
||||
.head { padding: 22px 24px; display: flex; flex-direction: column; gap: 12px; }
|
||||
.head h1 { margin: 0; font-size: 22px; }
|
||||
.head h1 { margin: 0; font-size: 22px; overflow-wrap: anywhere; }
|
||||
.legend { display: flex; align-items: center; gap: 8px; color: var(--text-muted); }
|
||||
.legend-row { justify-content: space-between; flex-wrap: wrap; }
|
||||
.dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
|
||||
|
|
@ -311,4 +310,13 @@ onMounted(async () => {
|
|||
.chosen { margin: 0; font-size: 15px; }
|
||||
.form { display: flex; flex-direction: column; gap: 14px; }
|
||||
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.state { margin-top: 8vh; padding: 36px 22px; }
|
||||
.head { padding: 18px 16px; }
|
||||
.head h1 { font-size: 19px; }
|
||||
.block { padding: 16px; }
|
||||
.day-chip { min-width: 64px; }
|
||||
.form button[type='submit'] { width: 100%; }
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -125,6 +125,8 @@ function logout() {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
padding: 14px 20px;
|
||||
}
|
||||
.brand { font-size: 17px; letter-spacing: -0.01em; }
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import ThemeToggle from '../components/ThemeToggle.vue'
|
|||
gap: 22px;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
padding-top: 6vh;
|
||||
}
|
||||
|
||||
|
|
@ -106,4 +107,10 @@ h1 {
|
|||
.foot { text-align: center; }
|
||||
|
||||
a.btn { text-decoration: none; display: inline-block; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.hero { padding: 46px 20px 38px; }
|
||||
.row.center { flex-direction: column; align-items: stretch; }
|
||||
.features { gap: 12px; }
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ async function submit() {
|
|||
<style scoped>
|
||||
.auth {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ async function submit() {
|
|||
<style scoped>
|
||||
.auth {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue