From 20bf5afe28d77459b89092466d302c9485941f65 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 26 Sep 2026 14:11:42 +0000 Subject: [PATCH] Kundenbereich: App-Ansicht fuers Handy (Tab-Leiste, Konto-Sheet, installierbar) Auf dem Handy (unter 880 px und Handys quer) sieht der Kundenbereich aus wie eine App; der Desktop bleibt pixelgleich. - Kopfzeile als App-Leiste: Logo, beim Scrollen der Seitentitel, Avatar - Navigation als Tab-Leiste unten mit Symbolen und Zaehlern; neuer Tab "Wartung" mit der Abo-Karte, Stundenkonto/Bestellung und Uebernahmegutschrift als Unterseiten mit Zurueck (auch Android-Geste) - Konto als Bottom-Sheet (Ansprechpartner, Vertraege kuendigen/widerrufen, Rechtliches, Abmelden), am Desktop als Popover unter dem Avatar - Rechnungen als Karten, Fortschritt als Zeitleiste, Chat fuellt den Bildschirm; bei offener Tastatur verschwindet die Tab-Leiste (kommt auch nach der Android-Zurueck-Geste wieder) - Installierbar: Manifest, App-Icons (WEBklar-Logo), Favicon, Hinweis "Zum Home-Bildschirm"; Service Worker nur fuer die Offline-Seite, Kundendaten werden nicht zwischengespeichert - Installierte App: Kuendigen/Widerrufen mit Link zurueck in den Kundenbereich (nur dort sichtbar, Texte unveraendert); Login-Links zu Bedingungen/Datenschutz/Impressum oeffnen wie im Dashboard ein neues Fenster - Login mit App-Icon und Abstaenden fuer Statusleiste/Home-Balken - Hinweise "in der Seitenleiste" haben auf dem Handy eine eigene Fassung Co-Authored-By: Claude Opus 5.5 --- public/app.js | 410 ++++++++++++++++++++++++++- public/dashboard.html | 90 +++++- public/favicon.ico | Bin 0 -> 20373 bytes public/favicon.svg | 8 + public/img/app-icon-192.png | Bin 0 -> 4075 bytes public/img/app-icon-512.png | Bin 0 -> 11499 bytes public/img/app-icon-maskable-512.png | Bin 0 -> 10610 bytes public/img/apple-touch-icon.png | Bin 0 -> 3810 bytes public/kuendigen.html | 4 +- public/kundenbereich.css | 337 +++++++++++++++++++++- public/legal-form.css | 6 + public/login.css | 38 +++ public/login.html | 27 +- public/manifest.webmanifest | 24 ++ public/offline.html | 46 +++ public/sw.js | 41 +++ public/widerrufen.html | 4 +- 17 files changed, 999 insertions(+), 36 deletions(-) create mode 100644 public/favicon.ico create mode 100644 public/favicon.svg create mode 100644 public/img/app-icon-192.png create mode 100644 public/img/app-icon-512.png create mode 100644 public/img/app-icon-maskable-512.png create mode 100644 public/img/apple-touch-icon.png create mode 100644 public/manifest.webmanifest create mode 100644 public/offline.html create mode 100644 public/sw.js diff --git a/public/app.js b/public/app.js index dc951fa..ebc9759 100644 --- a/public/app.js +++ b/public/app.js @@ -454,19 +454,20 @@ async function loadCustomerInvoices() { body.innerHTML = '

Es liegen noch keine Rechnungen vor.

' return } + // Klassen je Spalte: in der App-Ansicht wird jede Zeile zur Karte (kundenbereich.css) body.innerHTML = ` -
+
${data.invoices.map((i) => ` - - - - - - - + + + + + + `).join('')} @@ -831,7 +832,7 @@ function renderAboAccountView(view, data) {

Wartungs-Abo

Ihr Stundenkontingent, der Verlauf und Ihr Vertrag.

- ${sub.paymentProblem ? '
Die letzte Abbuchung ist fehlgeschlagen. Bitte aktualisieren Sie Ihre Zahlungsart über „Zahlungsart aktualisieren“ in der Seitenleiste, damit Ihre Website online bleibt.
' : ''} + ${sub.paymentProblem ? '
Die letzte Abbuchung ist fehlgeschlagen. Bitte aktualisieren Sie Ihre Zahlungsart über „Zahlungsart aktualisieren“ in der Seitenleisteunter Wartung, damit Ihre Website online bleibt.
' : ''} ${sub.serviceStartsAt ? `
Wie gewählt beginnen wir nach Ablauf der Widerrufsfrist am ${fmtDateShort(sub.serviceStartsAt)}. Dann wird auch zum ersten Mal abgebucht, und Ihre erste Stunde steht bereit.
` : ''}
${fmtHours(l.balanceMinutes)}verfügbare Zeit
@@ -880,8 +881,10 @@ async function loadAboCard({ afterCheckout = false } = {}) { card.classList.remove('hidden') document.getElementById('invoices-abo-note')?.classList.toggle('hidden', !data.subscription) loadTakeoverCard({ abo: data }) + document.dispatchEvent(new CustomEvent('kb:abo', { detail: data })) } catch (err) { console.warn('Abo konnte nicht geladen werden:', err.message) + document.dispatchEvent(new CustomEvent('kb:abo', { detail: null })) } } @@ -1139,8 +1142,9 @@ async function loadTakeoverCard({ abo, notice = '' } = {}) { } /* ---------------------------------------------------------- Navigation --- */ -// Ansichten im Inhaltsbereich; Hash fuer Direktlinks (z. B. aus Mails: #abo, #gutschrift) -const VIEW_HASHES = { projects: '', activity: 'fortschritt', invoices: 'rechnungen', chat: 'chat', abo: 'abo', takeover: 'gutschrift' } +// Ansichten im Inhaltsbereich; Hash fuer Direktlinks (z. B. aus Mails: #abo, #gutschrift). +// "wartung" gibt es nur in der App-Ansicht (Tab), am Desktop zeigt sie das Wartungs-Abo. +const VIEW_HASHES = { projects: '', activity: 'fortschritt', invoices: 'rechnungen', chat: 'chat', wartung: 'wartung', abo: 'abo', takeover: 'gutschrift' } const viewLoaders = { activity: loadCustomerActivity, chat: initChat } const loadedViews = new Set() @@ -1166,8 +1170,8 @@ function showView(name, { updateHash = true } = {}) { const hash = VIEW_HASHES[name] window.history.replaceState(null, '', hash ? `#${hash}` : window.location.pathname) } - // Mobil liegt die Seitenleiste ueber dem Inhalt -> zum Inhalt springen - if (window.innerWidth <= 880) document.querySelector('.kb-main')?.scrollIntoView({ behavior: 'smooth', block: 'start' }) + // App-Ansicht: Titel, Zurueck-Pfeil, Tab und Scrollposition nachziehen (initAppShell) + document.dispatchEvent(new CustomEvent('kb:view', { detail: { name } })) } function initialsOf(name) { @@ -1183,6 +1187,8 @@ async function initDashboardPage() { const loadError = document.getElementById('load-error') const logoutBtn = document.getElementById('logout-btn') + initAppShell() + logoutBtn?.addEventListener('click', async () => { await api('/api/auth/logout', { method: 'POST' }) window.location.href = '/login.html' @@ -1277,5 +1283,381 @@ async function initDashboardPage() { } } +/* ------------------------------------------------------- App-Ansicht (Handy) --- */ +// Auf dem Handy wird der Kundenbereich zur App: die Navigation ist die Tab-Leiste unten, die +// Kopfzeile zeigt beim Scrollen den Seitentitel, Stundenkonto/Bestellung und Gutschrift sind +// Unterseiten der Wartung (mit Zurueck), das Konto ist ein Bottom-Sheet. Styles: kundenbereich.css. +// Gleiche Bedingung wie der Abschnitt "App-Ansicht" in kundenbereich.css (auch Handys quer) +const appLayout = window.matchMedia('(max-width: 880px), (pointer: coarse) and (max-height: 500px)') +const coarsePointer = window.matchMedia('(pointer: coarse)') +// Unterseite -> Tab, zu dem sie gehoert +const APP_PARENT = { abo: 'wartung', takeover: 'wartung' } +const appShell = { view: 'projects', scroll: {}, fromHistory: false, sideCards: [], installPrompt: null } +const TEXT_INPUT = 'textarea, input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=date]):not([type=button]):not([type=submit]):not([type=range]):not([type=color])' + +function isAppLayout() { + return appLayout.matches +} + +function onMediaChange(mq, fn) { + if (mq.addEventListener) mq.addEventListener('change', fn) + else mq.addListener(fn) +} + +function isStandaloneApp() { + return window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true +} + +function isIosDevice() { + return /iphone|ipad|ipod/i.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) +} + +// Karten der Seitenleiste: App-Ansicht -> Ansprechpartner ins Konto-Sheet, Abo-Karte in den Tab "Wartung" +function placeSideCards() { + for (const { el, slot, anchor } of appShell.sideCards) { + if (isAppLayout()) { + if (el.parentNode !== slot) slot.append(el) + } else if (anchor.nextSibling !== el) { + anchor.after(el) + } + } +} + +function currentViewTitle() { + return document.querySelector(`#view-${appShell.view} .kb-head h1`) +} + +// Kopfzeile: Hintergrund nach dem Scrollen, Seitentitel sobald die grosse Ueberschrift darunter verschwindet +function updateBarState() { + const header = document.querySelector('.kb-header') + const h1 = currentViewTitle() + const barBottom = header ? header.getBoundingClientRect().bottom : 0 + document.body.classList.toggle('kb-scrolled', window.scrollY > 4) + document.body.classList.toggle('kb-titled', Boolean(h1 && h1.getBoundingClientRect().bottom < barBottom + 4)) +} + +function updateAppBar() { + const sub = isAppLayout() && Boolean(APP_PARENT[appShell.view]) + document.getElementById('kb-back')?.classList.toggle('hidden', !sub) + document.body.classList.toggle('kb-subpage', sub) + const title = document.getElementById('kb-app-title') + if (title) title.textContent = currentViewTitle()?.textContent.trim() || '' + updateBarState() +} + +function onAppViewShown(name) { + // Den Tab "Wartung" gibt es nur in der App-Ansicht; am Desktop direkt das Wartungs-Abo zeigen + if (name === 'wartung' && !isAppLayout()) { + showView('abo', { updateHash: !appShell.fromHistory }) + return + } + const prev = appShell.view + const parent = APP_PARENT[name] + const nav = name === prev ? 'same' : parent === prev ? 'push' : APP_PARENT[prev] === name ? 'back' : 'tab' + appShell.view = name + document.body.dataset.kbView = name + document.body.dataset.kbNav = nav + + // Unterseiten markieren ihren Tab + document.querySelectorAll('.kb-nav-item').forEach((b) => { + const on = b.dataset.view === (parent || name) + b.classList.toggle('active', on) + if (on) b.setAttribute('aria-current', 'page') + else b.removeAttribute('aria-current') + }) + + if (isAppLayout()) { + // Unterseite mit eigenem Verlaufseintrag: "Zurück" (auch die Android-Geste) führt zur Wartung + if (nav === 'push' && !appShell.fromHistory) { + window.history.replaceState(null, '', `#${VIEW_HASHES[parent]}`) + window.history.pushState({ kbSub: name }, '', `#${VIEW_HASHES[name]}`) + } + // Tabs merken sich ihre Scrollposition, Unterseiten beginnen oben, erneutes Tippen scrollt nach oben + if (nav === 'same') window.scrollTo({ top: 0, behavior: 'smooth' }) + else window.scrollTo(0, nav === 'push' ? 0 : appShell.scroll[name] || 0) + } + updateAppBar() +} + +/* Wartungs-Tab: Hinweis bei fehlgeschlagener Zahlung oder offener Rückfrage zur Gutschrift */ +function updateWartungBadge() { + const card = document.getElementById('abo-card') + const badge = document.getElementById('kb-tab-wartung-badge') + if (!card || !badge) return + const paymentProblem = Boolean(card.querySelector('.kb-side-alert')) + const questions = Number(card.querySelector('#takeover-badge:not(.hidden)')?.textContent || 0) + badge.textContent = paymentProblem ? '!' : questions || '' + badge.title = paymentProblem ? 'Zahlung fehlgeschlagen' : questions ? 'Rückfrage offen' : '' + badge.classList.toggle('kb-badge-alert', paymentProblem) + badge.classList.toggle('hidden', !paymentProblem && !questions) +} + +/* "Als App installieren": Chrome/Android per Installationsdialog, iPhone über Teilen -> Home-Bildschirm */ +function renderInstallOffers() { + let dismissed = false + try { + dismissed = localStorage.getItem('kb-install-dismissed') === '1' + } catch { + /* ohne Speicher einfach wieder anzeigen */ + } + const canPrompt = Boolean(appShell.installPrompt) + const ios = isIosDevice() + const offer = isAppLayout() && !isStandaloneApp() && (canPrompt || ios) + const shareIcon = '' + document.querySelectorAll('[data-install]').forEach((box) => { + const dismissible = box.hasAttribute('data-install-dismissible') + const show = offer && !(dismissible && dismissed) + box.classList.toggle('hidden', !show) + if (!show) return + box.innerHTML = ` + +
+ WEBklar als App + ${canPrompt + ? 'Mit einem Tipp auf deinem Homescreen – ohne App-Store.' + : `Tippe auf ${shareIcon} „Teilen“ und dann auf „Zum Home-Bildschirm“.`} +
+ ${canPrompt ? '' : ''} + ${dismissible ? '' : ''}` + }) +} + +/* Konto: Bottom-Sheet (Handy) bzw. Popover (Desktop) mit Ansprechpartner, Rechtlichem und Abmelden */ +function initAccountSheet() { + const sheet = document.getElementById('kb-sheet') + const avatar = document.getElementById('customer-avatar') + if (!sheet || !avatar) return + if (typeof sheet.showModal !== 'function') { + // Sehr alte Browser ohne : Abmelden bleibt in der Kopfzeile + document.body.classList.add('kb-no-sheet') + avatar.disabled = true + return + } + const panel = sheet.querySelector('.kb-sheet-panel') + const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)') + // Zaehler gegen verspaetetes Schliessen, wenn das Sheet gleich wieder geoeffnet wurde + let closeToken = 0 + + const open = () => { + if (sheet.open) return + closeToken += 1 + document.getElementById('kb-sheet-name').textContent = document.getElementById('customer-name')?.textContent || 'Mein Konto' + document.getElementById('kb-sheet-meta').textContent = document.getElementById('customer-meta')?.textContent || '' + document.getElementById('kb-sheet-avatar').textContent = avatar.textContent + renderInstallOffers() + sheet.classList.remove('is-closing') + panel.style.transform = '' + sheet.showModal() + document.documentElement.classList.add('kb-sheet-open') + } + const finish = () => { + sheet.classList.remove('is-closing') + panel.style.transform = '' + if (sheet.open) sheet.close() + } + const close = () => { + if (!sheet.open || sheet.classList.contains('is-closing')) return + if (reducedMotion.matches) return finish() + const token = ++closeToken + const done = () => { + if (token === closeToken) finish() + } + sheet.classList.add('is-closing') + panel.style.transform = '' + panel.addEventListener('transitionend', done, { once: true }) + setTimeout(done, 400) + } + + avatar.addEventListener('click', open) + // Escape bzw. Android-Zurück: mit Animation schließen + sheet.addEventListener('cancel', (ev) => { + ev.preventDefault() + close() + }) + sheet.addEventListener('close', () => { + sheet.classList.remove('is-closing') + document.documentElement.classList.remove('kb-sheet-open') + avatar.focus({ preventScroll: true }) + }) + // Klick auf den Hintergrund, das X oder eine Ansicht (z. B. "Nachricht schreiben") schließt + sheet.addEventListener('click', (ev) => { + if (ev.target === sheet || ev.target.closest('[data-sheet-close], [data-view]')) close() + }) + document.getElementById('kb-sheet-logout')?.addEventListener('click', () => document.getElementById('logout-btn')?.click()) + + // Nach unten wischen schließt das Sheet (Griff und Kopf des Sheets) + let drag = null + panel.addEventListener('pointerdown', (ev) => { + if (!isAppLayout() || !ev.target.closest('.kb-sheet-grip, .kb-sheet-me') || ev.target.closest('button')) return + drag = { y: ev.clientY, t: ev.timeStamp, dy: 0 } + panel.setPointerCapture(ev.pointerId) + panel.classList.add('is-dragging') + }) + panel.addEventListener('pointermove', (ev) => { + if (!drag) return + drag.dy = Math.max(0, ev.clientY - drag.y) + panel.style.transform = `translateY(${drag.dy}px)` + }) + const endDrag = (ev) => { + if (!drag) return + const { dy, t } = drag + drag = null + panel.classList.remove('is-dragging') + if (dy > 90 || dy / Math.max(1, ev.timeStamp - t) > 0.6) close() + else panel.style.transform = '' + } + panel.addEventListener('pointerup', endDrag) + panel.addEventListener('pointercancel', endDrag) +} + +function initAppShell() { + // Karten der Seitenleiste mit Anker merken, damit sie am Desktop an ihren Platz zurueckkehren + for (const [selector, slotId] of [['.kb-side .kb-contact', 'kb-contact-slot'], ['#abo-card', 'kb-abo-slot']]) { + const el = document.querySelector(selector) + const slot = document.getElementById(slotId) + if (!el || !slot) continue + const anchor = document.createComment(`Platz: ${selector}`) + el.before(anchor) + appShell.sideCards.push({ el, slot, anchor }) + } + placeSideCards() + document.body.dataset.kbView = appShell.view + + document.addEventListener('kb:view', (ev) => onAppViewShown(ev.detail.name)) + document.addEventListener('kb:abo', (ev) => { + document.getElementById('kb-tab-wartung')?.classList.toggle('hidden', !ev.detail) + const slot = document.getElementById('kb-abo-slot') + slot?.querySelector(':scope > .kb-empty')?.remove() + if (!ev.detail) slot?.insertAdjacentHTML('afterbegin', '

Die Wartung konnte gerade nicht geladen werden. Bitte später noch einmal versuchen.

') + updateWartungBadge() + updateAppBar() + }) + const aboCard = document.getElementById('abo-card') + if (aboCard) new MutationObserver(updateWartungBadge).observe(aboCard, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] }) + + // Zurueck: ueber den Verlauf, falls die Unterseite ihn angelegt hat, sonst direkt zur Wartung + document.getElementById('kb-back')?.addEventListener('click', () => { + if (window.history.state?.kbSub) window.history.back() + else showView(APP_PARENT[appShell.view] || 'projects') + }) + window.addEventListener('popstate', () => { + const name = viewFromHash() || 'projects' + if (name === appShell.view) return + appShell.fromHistory = true + try { + showView(name, { updateHash: false }) + } finally { + appShell.fromHistory = false + } + }) + + // Scrollposition je Ansicht und Zustand der Kopfzeile (einmal pro Frame) + let scrollQueued = false + window.addEventListener('scroll', () => { + if (scrollQueued) return + scrollQueued = true + requestAnimationFrame(() => { + scrollQueued = false + appShell.scroll[appShell.view] = window.scrollY + updateBarState() + }) + }, { passive: true }) + + // Tastatur offen (Touch): Tab-Leiste ausblenden, damit Eingabe und Chat Platz haben + document.addEventListener('focusin', (ev) => { + if (coarsePointer.matches && ev.target.matches?.(TEXT_INPUT)) document.body.classList.add('kb-typing') + }) + document.addEventListener('focusout', () => { + setTimeout(() => { + if (!document.activeElement?.matches?.(TEXT_INPUT)) document.body.classList.remove('kb-typing') + }, 80) + }) + + // Sichtbare Hoehe (ohne Tastatur) fuer den Chat; neueste Nachricht bleibt sichtbar. + // Schliesst die Tastatur, ohne dass das Feld den Fokus verliert (Android-Zurueck-Geste), + // kommt die Tab-Leiste zurueck; tippt man erneut hinein, verschwindet sie wieder. + const vv = window.visualViewport + if (vv) { + let fullHeight = vv.height + let width = vv.width + let keyboardOpen = false + const syncHeight = () => { + document.documentElement.style.setProperty('--kb-vvh', `${Math.round(vv.height)}px`) + if (Math.abs(vv.width - width) > 1) { + width = vv.width + fullHeight = vv.height + } + fullHeight = Math.max(fullHeight, vv.height) + const open = vv.height < fullHeight - 150 + if (open !== keyboardOpen) { + keyboardOpen = open + if (!open) document.body.classList.remove('kb-typing') + else if (coarsePointer.matches && document.activeElement?.matches?.(TEXT_INPUT)) document.body.classList.add('kb-typing') + } + // Chat mit Tastatur: iOS schiebt die Seite hoch; App-Leiste, Verlauf und Eingabe passen genau hinein + if (keyboardOpen && appShell.view === 'chat' && isAppLayout()) window.scrollTo(0, 0) + } + vv.addEventListener('resize', syncHeight) + syncHeight() + } + const messages = document.getElementById('chat-messages') + if (messages && typeof ResizeObserver !== 'undefined') { + let atBottom = true + messages.addEventListener('scroll', () => { + atBottom = messages.scrollHeight - messages.scrollTop - messages.clientHeight < 48 + }, { passive: true }) + new ResizeObserver(() => { + if (atBottom) messages.scrollTop = messages.scrollHeight + }).observe(messages) + } + + // Installations-Hinweis + window.addEventListener('beforeinstallprompt', (ev) => { + ev.preventDefault() + appShell.installPrompt = ev + renderInstallOffers() + }) + window.addEventListener('appinstalled', () => { + appShell.installPrompt = null + renderInstallOffers() + }) + document.addEventListener('click', async (ev) => { + if (ev.target.closest('[data-install-dismiss]')) { + try { + localStorage.setItem('kb-install-dismissed', '1') + } catch { + /* ignore */ + } + renderInstallOffers() + } else if (ev.target.closest('[data-install-go]') && appShell.installPrompt) { + const prompt = appShell.installPrompt + appShell.installPrompt = null + await prompt.prompt() + renderInstallOffers() + } + }) + + onMediaChange(appLayout, () => { + placeSideCards() + if (!isAppLayout() && appShell.view === 'wartung') showView('abo') + updateAppBar() + renderInstallOffers() + }) + + initAccountSheet() + renderInstallOffers() + updateAppBar() + + // Nach der Bestellung bei Stripe: Bestaetigung steht auf der Abo-Karte im Tab "Wartung" + if (isAppLayout() && getQueryParam('abo') === 'success') showView('wartung') +} + +// App-Installation (Homescreen) und Offline-Seite; zwischengespeichert wird nur die Offline-Seite (sw.js) +if ('serviceWorker' in navigator && window.isSecureContext) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js').catch((err) => console.warn('Service Worker:', err.message)) + }) +} + window.initLoginPage = initLoginPage window.initDashboardPage = initDashboardPage diff --git a/public/dashboard.html b/public/dashboard.html index 83b2a3f..1322569 100644 --- a/public/dashboard.html +++ b/public/dashboard.html @@ -2,8 +2,19 @@ - + Kundenbereich – WEBklar + + + + + + + + + + + @@ -16,26 +27,75 @@
- + + +
+ + +
+ + +
+ +
+ +
+ Mein Konto + +
+ +
+ +
+ + + +
+
+
- +
+ +
+
+

Wartung

+

Betrieb, Pflege und Support für Ihre Website.

+
+

Lade…

+
+
diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3c01d69713f9c184e92b74f5799e6dff2f500825 GIT binary patch literal 20373 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 literal 0 HcmV?d00001 diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..ef2669e --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/img/app-icon-192.png b/public/img/app-icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..63ba5c4b68da22f6ed4b4dcf1d1689952ebbef9b GIT binary patch literal 4075 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 YXBw}6_?U49JfVQ=-PV0~%klYt0L9?8DF6Tf literal 0 HcmV?d00001 diff --git a/public/img/app-icon-512.png b/public/img/app-icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..2e3a8b617c1e1d4f820d0752c0392b9351aa6ef9 GIT binary patch literal 11499 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

^dA%8X-ll?|90=Ok&UsQ4)st?MEZ#|7}*P# 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 V7b+Enjqo3INnKa1^nzXZ{{t$|;`9Ij literal 0 HcmV?d00001 diff --git a/public/img/app-icon-maskable-512.png b/public/img/app-icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..32f9beab8bcc70cb598e5cebb5b271af838fd9cf GIT binary patch literal 10610 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 Vl9@d`egU{c23J8mDOI)#{U2GeUXB0& literal 0 HcmV?d00001 diff --git a/public/kuendigen.html b/public/kuendigen.html index a9211b3..33128ad 100644 --- a/public/kuendigen.html +++ b/public/kuendigen.html @@ -2,12 +2,14 @@ - + Verträge hier kündigen – WEBklar

+ + ‹ Zurück zum Kundenbereich
WEBKLAR

Verträge hier kündigen

diff --git a/public/kundenbereich.css b/public/kundenbereich.css index 3bd334a..734ca1a 100644 --- a/public/kundenbereich.css +++ b/public/kundenbereich.css @@ -335,17 +335,340 @@ .kb-legal a { color: var(--kb-muted); } .kb-legal a.legal-strong { color: #fff; font-weight: 700; text-decoration: underline; } -/* ----------------------------------------------------------------- Mobil --- */ -@media (max-width: 880px) { - .kb-shell { grid-template-columns: 1fr; gap: 22px; padding-top: 22px; } - .kb-side > .kb-label { display: none; } - .kb-nav { grid-template-columns: 1fr 1fr; gap: 6px; } - .kb-account-text { display: none; } +/* ------------------------------------------ Konto (Avatar-Menue, alle Groessen) --- */ +/* Desktop: Popover unter dem Avatar. App-Ansicht: Bottom-Sheet (siehe unten). */ +button.kb-avatar { + box-sizing: content-box; padding: 0; color: #fff; font: inherit; font-size: 13px; font-weight: 700; cursor: pointer; + transition: box-shadow 0.15s; } +/* Wrapper fuer Logo und App-Titel: am Desktop ohne eigene Box (Kopfzeile wie bisher) */ +.kb-bar-title { display: contents; } +button.kb-avatar:hover { box-shadow: 0 0 0 3px rgba(157, 61, 254, 0.35); } +button.kb-avatar:focus-visible { outline: 2px solid #fff; outline-offset: 2px; } +button.kb-avatar:disabled { cursor: default; box-shadow: none; } + +.kb-sheet { + inset: 0; margin: 72px clamp(16px, 3vw, 36px) auto auto; padding: 0; border: none; background: transparent; + width: 340px; max-width: calc(100vw - 32px); max-height: none; overflow: visible; color: #fff; +} +.kb-sheet::backdrop { background: rgba(0, 0, 0, 0.35); transition: opacity 0.2s; } +.kb-sheet.is-closing::backdrop { opacity: 0; } +.kb-sheet-panel { + box-sizing: border-box; max-height: calc(100vh - 96px); overflow-y: auto; padding: 16px; + border-radius: 18px; background: #111114; border: 1px solid var(--kb-border-strong); + box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6); + transition: opacity 0.16s, transform 0.16s; +} +.kb-sheet[open] .kb-sheet-panel { animation: kb-pop-in 0.18s ease-out; } +.kb-sheet.is-closing .kb-sheet-panel { opacity: 0; transform: translateY(-6px) scale(0.98); } +.kb-sheet-grip { display: none; } +.kb-sheet-me { display: flex; align-items: center; gap: 12px; padding: 2px 0 14px; } +.kb-sheet-me .kb-avatar { width: 46px; height: 46px; font-size: 16px; } +.kb-sheet-me-text { flex: 1; min-width: 0; line-height: 1.35; } +.kb-sheet-me-text strong { display: block; font-size: 16px; } +.kb-sheet-me-text span { display: block; font-size: 12.5px; color: var(--kb-muted); overflow-wrap: anywhere; } +.kb-sheet-x { + flex: none; align-self: flex-start; width: 34px; height: 34px; border-radius: 50%; border: none; + background: rgba(255, 255, 255, 0.08); color: #fff; display: grid; place-items: center; cursor: pointer; +} +.kb-sheet-x svg, .kb-install-x svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; } +#kb-contact-slot .kb-contact { margin-bottom: 12px; } +.kb-sheet-links { + display: flex; flex-direction: column; margin: 12px 0; border-radius: 14px; overflow: hidden; + border: 1px solid var(--kb-border); background: rgba(255, 255, 255, 0.02); +} +.kb-sheet-links a { + display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 13px 14px; + color: var(--kb-muted); font-size: 14.5px; text-decoration: none; +} +.kb-sheet-links a + a { border-top: 1px solid var(--kb-border); } +.kb-sheet-links a::after { content: '›'; font-size: 20px; line-height: 1; color: var(--kb-faint); } +.kb-sheet-links a[target='_blank']::after { content: '↗'; font-size: 15px; } +.kb-sheet-links a.legal-strong { color: #fff; font-weight: 700; } +.kb-sheet-links a:active { background: rgba(255, 255, 255, 0.05); } +.kb-sheet-logout { + width: 100%; padding: 13px; border-radius: 14px; border: 1px solid rgba(248, 113, 113, 0.45); + background: rgba(248, 113, 113, 0.08); color: #fca5a5; font: inherit; font-size: 15px; font-weight: 700; cursor: pointer; +} +.kb-sheet-logout:hover { background: rgba(248, 113, 113, 0.14); } + +/* "WEBklar als App": Hinweis im Tab Projekte (ausblendbar) und im Konto-Sheet */ +.kb-install { + display: flex; align-items: center; gap: 12px; margin: 12px 0; padding: 12px 12px 12px 14px; border-radius: 16px; + background: linear-gradient(135deg, rgba(157, 61, 254, 0.22), rgba(157, 61, 254, 0.06)); border: 1px solid var(--kb-purple-line); +} +.kb-install-card { margin: 0 0 16px; } +.kb-install-icon { flex: none; width: 44px; height: 44px; border-radius: 11px; } +.kb-install-text { flex: 1; min-width: 0; line-height: 1.4; } +.kb-install-text strong { display: block; font-size: 14.5px; } +.kb-install-text span { display: block; font-size: 12.5px; color: rgba(255, 255, 255, 0.72); } +.kb-inline-ico { width: 16px; height: 16px; vertical-align: -3px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; } +.kb-install-go { + flex: none; padding: 9px 14px; border-radius: 999px; border: none; background: var(--kb-purple); color: #fff; + font: inherit; font-size: 13px; font-weight: 700; cursor: pointer; +} +.kb-install-x { + flex: none; align-self: flex-start; width: 30px; height: 30px; margin: -6px -6px 0 -4px; border: none; background: none; + color: var(--kb-faint); display: grid; place-items: center; cursor: pointer; +} + +/* Elemente, die es nur in der App-Ansicht gibt */ +.kb-back, .kb-logo-mark, .kb-app-title, .kb-tab-ico, .kb-nav-item.kb-tab-wartung, .kb-app-only, .kb-install { display: none; } + +@keyframes kb-pop-in { from { opacity: 0; transform: translateY(-6px) scale(0.98); } } +@keyframes kb-sheet-up { from { transform: translateY(100%); } } +@keyframes kb-fade-in { from { opacity: 0; } } +@keyframes kb-view-in { from { opacity: 0; transform: translateY(8px); } } +@keyframes kb-push-in { from { opacity: 0; transform: translateX(32px); } } +@keyframes kb-back-in { from { opacity: 0; transform: translateX(-32px); } } +@keyframes kb-shimmer { to { background-position: -200% 0, 0 0; } } + @media (max-width: 640px) { .kb-facts { grid-template-columns: 1fr; } .kb-facts > div + div { border-left: none; border-top: 1px solid var(--kb-border); } .abo-features { grid-template-columns: 1fr; } - .kb-head h1 { font-size: 26px; } .kb-project-body, .kb-panel { padding: 16px; } } + +/* ----------------------------------------------------- App-Ansicht (Handy) --- */ +/* Unter 880px (und Handys quer): Kopfzeile als App-Leiste (Titel erscheint beim Scrollen), + Navigation als Tab-Leiste unten, Karten der Seitenleiste im Konto-Sheet bzw. Tab "Wartung". + Dieselbe Bedingung steht in app.js (appLayout). */ +@media (max-width: 880px), (pointer: coarse) and (max-height: 500px) { + .kb-page { + --kb-appbar-h: calc(56px + env(safe-area-inset-top)); + --kb-tabbar-h: calc(62px + env(safe-area-inset-bottom)); + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; + } + .kb-page.kb-typing { --kb-tabbar-h: 0px; } + html.kb-sheet-open { overflow: hidden; } + .kb-desk-only { display: none; } + .kb-app-only { display: inline; } + + /* App-Leiste oben */ + .kb-header { + flex-wrap: nowrap; gap: 8px; box-sizing: border-box; height: var(--kb-appbar-h); + padding: env(safe-area-inset-top) max(16px, env(safe-area-inset-right)) 0 max(16px, env(safe-area-inset-left)); + background: transparent; border-bottom-color: transparent; -webkit-backdrop-filter: none; backdrop-filter: none; + transition: background-color 0.2s, border-color 0.2s; + -webkit-user-select: none; user-select: none; + } + .kb-scrolled .kb-header { + background: rgba(0, 0, 0, 0.8); border-bottom-color: var(--kb-border); + -webkit-backdrop-filter: saturate(160%) blur(18px); backdrop-filter: saturate(160%) blur(18px); + } + .kb-back { + display: inline-flex; flex: none; align-items: center; gap: 1px; margin-left: -10px; padding: 8px 6px 8px 2px; + border: none; background: none; color: #d4b4ff; font: inherit; font-size: 16px; cursor: pointer; + } + .kb-back svg { width: 26px; height: 26px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; } + .kb-bar-title { position: relative; flex: 1; min-width: 0; height: 40px; display: flex; align-items: center; } + .kb-logo { + display: inline-flex; align-items: center; gap: 10px; font-size: 14px; letter-spacing: 0.22em; + transition: opacity 0.2s, transform 0.2s, visibility 0.2s; + } + .kb-logo-mark { display: block; flex: none; width: 28px; height: 28px; border-radius: 8px; } + .kb-app-title { + display: block; position: absolute; top: 50%; left: 0; right: 0; transform: translateY(calc(-50% + 8px)); opacity: 0; + font-size: 17px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none; + transition: opacity 0.2s, transform 0.2s; + } + .kb-titled .kb-app-title { opacity: 1; transform: translateY(-50%); } + .kb-titled .kb-logo, .kb-subpage .kb-logo { opacity: 0; visibility: hidden; transform: translateY(-8px); } + .kb-account { flex: none; gap: 0; } + .kb-account-text, .kb-logout { display: none; } + .kb-no-sheet .kb-logout { display: inline-block; margin-left: 10px; } + .kb-header .kb-avatar { width: 36px; height: 36px; font-size: 13.5px; } + + /* Inhalt: eine Spalte, Platz fuer die Tab-Leiste; auf Tablets mittig begrenzt */ + .kb-shell.dashboard-content-reveal { + display: block; max-width: 720px; overflow-x: clip; + padding: 6px max(16px, env(safe-area-inset-right)) calc(var(--kb-tabbar-h) + 28px) max(16px, env(safe-area-inset-left)); + animation: kb-fade-in 0.4s ease-out both; + } + .kb-side { display: block; } + .kb-side > .kb-label, .kb-side > .kb-card, .kb-side > .kb-abo { display: none; } + + /* Tab-Leiste unten (die Navigation der Seitenleiste) */ + .kb-nav { + position: fixed; left: 0; right: 0; bottom: 0; z-index: 30; display: flex; justify-content: center; gap: 0; + padding: 6px max(6px, env(safe-area-inset-right)) calc(6px + env(safe-area-inset-bottom)) max(6px, env(safe-area-inset-left)); + background: rgba(12, 12, 14, 0.94); border-top: 1px solid var(--kb-border); + -webkit-backdrop-filter: saturate(160%) blur(20px); backdrop-filter: saturate(160%) blur(20px); + -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; + transition: transform 0.22s ease; + } + .kb-typing .kb-nav { transform: translateY(110%); } + .kb-nav-item { + position: relative; flex: 1 1 0; min-width: 0; max-width: 120px; flex-direction: column; justify-content: center; gap: 3px; + padding: 4px 0 2px; border: none; border-radius: 12px; background: none; color: rgba(255, 255, 255, 0.55); + font-size: 10.5px; font-weight: 600; letter-spacing: 0; text-align: center; touch-action: manipulation; + } + .kb-nav-item.kb-tab-wartung { display: flex; } + .kb-nav-item::before { + position: absolute; top: 3px; left: 50%; width: 56px; height: 30px; margin-left: -28px; border-radius: 999px; + background: transparent; box-shadow: none; transition: background-color 0.2s; + } + .kb-nav-item:hover { background: none; color: rgba(255, 255, 255, 0.55); } + .kb-nav-item.active, .kb-nav-item.active:hover { background: none; border-color: transparent; color: #fff; font-weight: 700; } + .kb-nav-item.active::before { background: rgba(157, 61, 254, 0.3); box-shadow: none; } + .kb-tab-ico { + display: block; position: relative; width: 24px; height: 24px; margin-top: 3px; fill: none; stroke: currentColor; + stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; transition: transform 0.12s; + } + .kb-nav-item:active .kb-tab-ico { transform: scale(0.88); } + .kb-tab-label { position: relative; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .kb-nav-item .kb-count { display: none; } + .kb-nav-item .kb-badge { + position: absolute; top: 0; left: calc(50% + 6px); margin: 0; min-width: 19px; height: 19px; padding: 0 5px; + box-sizing: border-box; font-size: 10.5px; line-height: 1; border: 2px solid #0c0c0e; + } + .kb-nav-item .kb-badge-red, .kb-nav-item .kb-badge-alert { background: #e5484d; color: #fff; } + + /* Seiten: grosse Ueberschrift wie in iOS, weiche Uebergaenge */ + .kb-head { margin: 6px 0 18px; padding-bottom: 0; border-bottom: none; } + .kb-head h1 { font-size: 30px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.15; } + .kb-head p { margin-top: 4px; font-size: 14.5px; } + .kb-view.active { animation: kb-view-in 0.24s ease-out; } + body[data-kb-nav='push'] .kb-view.active { animation-name: kb-push-in; } + body[data-kb-nav='back'] .kb-view.active { animation-name: kb-back-in; } + .kb-panel { padding: 16px; border-radius: 18px; } + .viewas-banner { margin-bottom: 12px; } + + /* Projekte */ + #loading:not(.hidden) { + height: 300px; margin: 0; border-radius: 20px; border: 1px solid var(--kb-border); color: transparent; overflow: hidden; + background: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, 0.06) 50%, transparent 70%) 0 0 / 200% 100%, var(--kb-surface); + animation: kb-shimmer 1.4s linear infinite; + } + .kb-projects { gap: 16px; } + .kb-project { border-radius: 20px; } + .kb-project-body { padding: 16px 16px 18px; } + .kb-project-top { margin-bottom: 12px; } + .kb-project-top h2 { font-size: 19px; } + .kb-open { right: 10px; bottom: 10px; padding: 7px 12px; font-size: 12.5px; } + .kb-chip { padding: 7px 12px; font-size: 13px; } + .kb-project [data-details-btn] { width: 100%; justify-content: center; padding: 12px 16px; font-size: 14px; } + .kb-details .simple-table { font-size: 13px; } + + /* Fortschritt als Zeitleiste */ + #activity-body .activity-item { position: relative; padding: 0 0 18px 22px; border-bottom: none; } + #activity-body .activity-item::before { + content: ''; position: absolute; left: 0; top: 7px; width: 9px; height: 9px; border-radius: 50%; + background: var(--kb-purple); box-shadow: 0 0 0 3px rgba(157, 61, 254, 0.22); + } + #activity-body .activity-item::after { content: ''; position: absolute; left: 4px; top: 22px; bottom: 2px; width: 1px; background: var(--kb-border-strong); } + #activity-body .activity-item:last-child { padding-bottom: 0; } + #activity-body .activity-item:last-child::after { display: none; } + + /* Rechnungen: jede Zeile als Karte */ + #invoices-body:has(.kb-invoices) { padding: 0; background: none; border: none; } + .kb-invoices, .kb-invoices tbody { display: block; } + .kb-invoices thead { display: none; } + .kb-invoices tr { + display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: 'no status' 'date amount' 'open open' 'actions actions'; + column-gap: 12px; row-gap: 3px; margin-bottom: 10px; padding: 14px 16px 16px; border-radius: 18px; + background: var(--kb-surface); border: 1px solid var(--kb-border); + } + .kb-invoices td { display: block; padding: 0; border: none; } + .kb-invoices .inv-no { grid-area: no; align-self: center; font-size: 15px; font-weight: 700; } + .kb-invoices .inv-no::before { content: 'Rechnung '; } + .kb-invoices .inv-status { grid-area: status; justify-self: end; align-self: center; } + .kb-invoices .inv-date { grid-area: date; align-self: end; font-size: 13px; color: var(--kb-muted); } + .kb-invoices .inv-amount { grid-area: amount; justify-self: end; font-size: 18px; font-weight: 750; font-variant-numeric: tabular-nums; } + .kb-invoices .inv-open { grid-area: open; font-size: 13px; color: #fca5a5; } + .kb-invoices .inv-open::before { content: 'Offen: '; } + .kb-invoices tr.is-paid .inv-open, .kb-invoices .inv-sep { display: none; } + .kb-invoices .inv-actions { grid-area: actions; display: flex; gap: 8px; margin-top: 12px; white-space: normal !important; } + .kb-invoices .inv-actions .btn-link { + flex: 1; display: flex; align-items: center; justify-content: center; padding: 11px 12px; border-radius: 12px; + border: 1px solid var(--kb-border-strong); color: #fff; font-size: 14px; font-weight: 600; white-space: nowrap; text-decoration: none; + } + .kb-invoices .inv-actions .btn-link:first-child { background: rgba(157, 61, 254, 0.18); border-color: var(--kb-purple-line); } + .kb-invoices .inv-actions .btn-link:first-child:not(:last-child) { flex: 1.7; } + + /* Chat: fuellt den Platz zwischen App-Leiste und Tab-Leiste (bzw. Tastatur) */ + #view-chat.active { + display: flex; flex-direction: column; + height: calc(var(--kb-vvh, 100dvh) - var(--kb-appbar-h) - var(--kb-tabbar-h) - 20px); + } + #view-chat .kb-head { flex: none; margin-bottom: 12px; } + #view-chat .chat-box { flex: 1 1 auto; min-height: 0; height: auto; padding: 10px; border-radius: 20px; } + .chat-messages { overscroll-behavior: contain; padding: 4px 2px; } + .chat-bubble { max-width: 84%; font-size: 15px; } + .chat-input-row { gap: 8px; margin-top: 10px; } + .chat-input-row textarea { box-sizing: border-box; min-height: 46px; padding: 11px 16px; border-radius: 23px; font-size: 16px; line-height: 1.35; } + /* Senden als runder Pfeil-Button; der Text bleibt fuer Screenreader erhalten */ + .chat-send { + flex: none; width: 46px; height: 46px; padding: 0; border-radius: 50%; font-size: 0; color: transparent; + background: var(--kb-purple) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 19V5M5 12l7-7 7 7'/%3E%3C/svg%3E") center / 22px no-repeat; + } + + /* Tab "Wartung": Abo-Karte gross; darunter Unterseiten Stundenkonto/Bestellung und Gutschrift */ + #kb-abo-slot:has(> .kb-abo:not(.hidden)) > .kb-empty { display: none; } + #view-wartung .kb-abo { padding: 20px 18px 18px; border-radius: 20px; } + #view-wartung .kb-abo h3 { font-size: 22px; } + #view-wartung .kb-price strong, #view-wartung .kb-hours strong { font-size: 32px; } + #view-wartung .kb-price span, #view-wartung .kb-hours span, #view-wartung .kb-sub { font-size: 14px; } + #view-wartung .kb-checks { gap: 9px; font-size: 14.5px; } + #view-wartung .kb-fine { font-size: 12.5px; } + #view-wartung .kb-side-alert { font-size: 13.5px; } + #view-wartung .kb-side-btn { display: flex; align-items: center; gap: 8px; margin-top: 10px; padding: 14px 16px; border-radius: 14px; font-size: 15px; } + #view-wartung .kb-side-btn .kb-badge { float: none; flex: none; margin: 0 0 0 auto; } + #view-wartung .kb-btn-primary { padding: 15px 22px; font-size: 15px; } + .kb-stats { gap: 10px; } + .kb-stat strong { font-size: 24px; } + .abo-summary, .abo-summary tbody, .abo-summary tr, .abo-summary td { display: block; width: auto !important; } + .abo-summary tr { padding: 9px 0; border-bottom: 1px solid var(--kb-border); } + .abo-summary td { padding: 0; border: none; } + .abo-summary td:first-child { + margin-bottom: 3px; font-size: 10.5px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--kb-faint); + } + #abo-order .kb-actions { flex-direction: column; align-items: stretch; } + #abo-order .kb-btn-primary { padding: 15px 22px; font-size: 15px; } + .abo-actions .abo-btn { flex: 1 1 auto; } + + .kb-legal { justify-content: center; gap: 10px 16px; margin-top: 32px; text-align: center; } + + /* Konto als Bottom-Sheet */ + .kb-install:not(.hidden) { display: flex; } + .kb-sheet { inset: auto 0 0 0; width: 100%; max-width: none; margin: 0; } + .kb-sheet::backdrop { background: rgba(0, 0, 0, 0.55); } + .kb-sheet-panel { + max-height: calc(var(--kb-vvh, 100dvh) - env(safe-area-inset-top) - 24px); overscroll-behavior: contain; + padding: 8px max(18px, env(safe-area-inset-right)) calc(18px + env(safe-area-inset-bottom)) max(18px, env(safe-area-inset-left)); + border-radius: 24px 24px 0 0; border-bottom: none; box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.5); + transition: transform 0.24s ease-in; + } + .kb-sheet[open] .kb-sheet-panel { animation: kb-sheet-up 0.32s cubic-bezier(0.2, 0.9, 0.3, 1); } + .kb-sheet.is-closing .kb-sheet-panel { opacity: 1; transform: translateY(100%); } + .kb-sheet-panel.is-dragging { transition: none; } + .kb-sheet-grip { display: block; width: 40px; height: 5px; margin: 2px auto 12px; border-radius: 3px; background: rgba(255, 255, 255, 0.24); } + .kb-sheet-grip, .kb-sheet-me { touch-action: none; } + .kb-sheet-links a, .kb-sheet-logout { padding-top: 14px; padding-bottom: 14px; } +} + +/* Touch: Tastatur-Hinweise zum Chat braucht es nicht */ +@media (pointer: coarse) { + .chat-hint { display: none; } +} + +/* Handy quer: niedrige Tab-Leiste, Symbol und Text nebeneinander */ +@media (max-width: 880px) and (max-height: 500px), (pointer: coarse) and (max-height: 500px) { + .kb-page { --kb-tabbar-h: calc(46px + env(safe-area-inset-bottom)); } + .kb-nav { padding-top: 4px; padding-bottom: calc(4px + env(safe-area-inset-bottom)); } + .kb-nav-item { flex-direction: row; gap: 6px; max-width: 160px; padding: 6px 0; } + .kb-nav-item::before { display: none; } + .kb-nav-item.active { color: #d4b4ff; } + .kb-tab-ico { margin-top: 0; } + .kb-nav-item .kb-badge { position: static; margin-left: -2px; } + /* Chat: Ueberschrift weglassen (steht dann in der App-Leiste), einzeilige Eingabe */ + #view-chat .kb-head { display: none; } + .chat-input-row textarea { height: 46px; } +} + +@media (prefers-reduced-motion: reduce) { + .kb-shell.dashboard-content-reveal, .kb-view.active, .kb-sheet[open] .kb-sheet-panel, #loading:not(.hidden) { animation: none !important; } + .kb-header, .kb-nav, .kb-logo, .kb-app-title, .kb-sheet-panel { transition: none !important; } +} diff --git a/public/legal-form.css b/public/legal-form.css index 2b1fa52..cc5d300 100644 --- a/public/legal-form.css +++ b/public/legal-form.css @@ -27,3 +27,9 @@ legend { font-weight: 600; font-size: 14px; margin: 14px 0 4px; } #effective-date { margin-top: 6px; max-width: 220px; } .print-btn { margin-top: 14px; background: #fff; color: #1d1d1f; border: 1px solid #cfcac2; border-radius: 10px; padding: 10px 16px; font: inherit; cursor: pointer; } @media print { .print-btn, .no-print { display: none !important; } body { background: #fff; } .card { border: none; } } +/* Installierte App (Homescreen): Platz fuer die Statusleiste und ein Weg zurueck in den Kundenbereich */ +.app-back { display: none; } +@media (display-mode: standalone) { + main { padding-top: max(48px, calc(env(safe-area-inset-top) + 20px)); } + .app-back { display: inline-block; margin-bottom: 18px; font-size: 15px; font-weight: 600; text-decoration: none; } +} diff --git a/public/login.css b/public/login.css index 8395a51..8db3562 100644 --- a/public/login.css +++ b/public/login.css @@ -595,3 +595,41 @@ margin-top: 0; } } + +/* Login als App (Handy): App-Icon ueber dem Formular, Abstand zu Statusleiste und Home-Balken */ +.login-app-brand { + display: none; +} + +@media (max-width: 48em) { + .login-header { + padding-top: calc(24px + env(safe-area-inset-top)); + } + + .login-panel { + padding-top: calc(88px + env(safe-area-inset-top)); + padding-bottom: calc(40px + env(safe-area-inset-bottom)); + } + + .login-app-brand { + display: flex; + flex-direction: column; + align-items: center; + gap: 14px; + margin: 0 0 40px; + font-size: 0.9375rem; + font-weight: 700; + letter-spacing: 0.02em; + } + + .login-app-brand img { + width: 64px; + height: 64px; + border-radius: 16px; + box-shadow: 0 10px 30px rgba(157, 61, 254, 0.35); + } + + .auth-label { + margin-bottom: 28px; + } +} diff --git a/public/login.html b/public/login.html index 65e9a6f..734834e 100644 --- a/public/login.html +++ b/public/login.html @@ -2,8 +2,18 @@ - + Anmelden – Webklar Kundenbereich + + + + + + + + + + @@ -18,6 +28,11 @@
Nr.DatumBetragOffenStatus
${escapeHtml(i.number)}${fmtDateShort(i.date)}${fmtEuro(i.amount)}${fmtEuro(i.balance)}${escapeHtml(i.status)} - ${i.portalUrl ? `Ansehen / Zahlen · ` : ''} +
${escapeHtml(i.number)}${fmtDateShort(i.date)}${fmtEuro(i.amount)}${fmtEuro(i.balance)}${escapeHtml(i.status)} + ${i.portalUrl ? `Ansehen / Zahlen` : ''} PDF