Compare commits

...
2 Commits
Author SHA1 Message Date
ClaudeandClaude Opus 5.5 d5bfd1be3d App-Ansicht iOS: Chat-Eingabe springt nicht mehr weg, kein Zoom beim Tippen
Auf dem iPhone sprang die Seite beim Tippen ins Chatfeld weg (nur die
Fusszeile war zu sehen), zoomte hinein und liess sich danach seitlich
schieben. Beides sind bekannte Safari-Effekte:

- window.scrollTo(0, 0) bei offener Tastatur kaempft gegen Safaris eigenes
  Hochschieben des Feldes und ist entfernt, ebenso das Verkleinern des
  Chats auf die sichtbare Hoehe, das das schon hochgeschobene Feld wieder
  aus dem Bild rutschen liess. Der Chat nutzt jetzt 100dvh: auf Android
  folgt das der Tastatur (interactive-widget=resizes-content), auf iOS
  bleibt die Hoehe stabil.
- Auf iOS aendert sich beim Fokus keine Hoehe mehr (die Tab-Leiste wird
  nur optisch ausgeblendet). Ist die Chat-Eingabe nach der
  Tastatur-Animation doch verdeckt, wird sie gezielt nachgeschoben
  (gemessen am visualViewport).
- iOS zoomt beim Fokus, wenn die Schrift effektiv unter 16 px liegt, z. B.
  bei Safari-Seitenzoom unter 100 %. maximum-scale=1 nur auf iOS verhindert
  diesen automatischen Zoom; mit zwei Fingern zoomen bleibt moeglich
  (WebKit ignoriert die Sperre dafuer seit iOS 10). Android bleibt
  unveraendert, dort wuerde es das Zoomen sperren.

Im Ruhezustand pixelgleich zu main (28 Ansichten).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 15:17:36 +00:00
ClaudeandClaude Opus 5.5 163ab88c35 App-Ansicht: ueberholte Chat-Regeln entfernt, Verlauf bleibt ueber data-stick unten
Der Chat-Composer bringt den runden Senden-Knopf fuer die App-Ansicht selbst
mit (0d47b7c). Die alten Regeln der App-Ansicht fuer .chat-send und
.chat-input-row waren dadurch wirkungslos und sind entfernt; die
Eingabe-Pille bleibt.

- Verlauf: der ResizeObserver nutzt data-stick aus ChatComposer.trackScroll
  statt eines eigenen Scroll-Zaehlers
- Hinweis unter dem Chat nur noch in der App-Ansicht mit Touch ausgeblendet
  (vorher auf allen Touch-Geraeten, also auch auf Tablets quer)
- Handy quer: feste Hoehe der Eingabe entfernt, der Composer waechst selbst mit

Handy, Tablet hochkant und Desktop sind pixelgleich zu main (28 Ansichten).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 14:53:27 +00:00
2 changed files with 54 additions and 29 deletions

No files matched your search

+41 -15
View File
@@ -1379,6 +1379,22 @@ function onAppViewShown(name) {
updateAppBar()
}
/* Chat-Eingabe ueber der Tastatur halten: nur nachschieben, wenn sie wirklich verdeckt ist, gemessen am
sichtbaren Bereich (visualViewport). Die Seite nicht nach oben pinnen – das kaempft auf iOS gegen
Safaris eigenes Hochschieben und laesst die Eingabe aus dem Bild springen. */
function revealChatComposer() {
const composer = document.getElementById('chat-composer')
const vv = window.visualViewport
if (!composer || !vv || !isAppLayout() || !composer.contains(document.activeElement)) return
const rect = composer.getBoundingClientRect()
const visibleTop = vv.offsetTop
const visibleBottom = vv.offsetTop + vv.height
let delta = 0
if (rect.bottom > visibleBottom - 8) delta = rect.bottom - visibleBottom + 8
else if (rect.top < visibleTop + 8) delta = rect.top - visibleTop - 8
if (Number.isFinite(delta) && Math.abs(delta) > 1) window.scrollBy(0, delta)
}
/* Wartungs-Tab: Hinweis bei fehlgeschlagener Zahlung oder offener Rückfrage zur Gutschrift */
function updateWartungBadge() {
const card = document.getElementById('abo-card')
@@ -1563,9 +1579,16 @@ function initAppShell() {
})
}, { passive: true })
// Tastatur offen (Touch): Tab-Leiste ausblenden, damit Eingabe und Chat Platz haben
// Tastatur offen (Touch): Tab-Leiste ausblenden. Auf Android schrumpft die Seite mit der Tastatur
// (interactive-widget=resizes-content), dort nutzt der Chat den frei werdenden Platz. Auf iOS liegt
// die Tastatur ueber der Seite und Safari schiebt das Feld selbst hoch – dort darf sich beim Fokus
// keine Hoehe aendern (kb-ios, siehe kundenbereich.css), sonst rutscht das Feld wieder aus dem Bild.
if (isIosDevice()) document.body.classList.add('kb-ios')
document.addEventListener('focusin', (ev) => {
if (coarsePointer.matches && ev.target.matches?.(TEXT_INPUT)) document.body.classList.add('kb-typing')
if (!coarsePointer.matches || !ev.target.matches?.(TEXT_INPUT)) return
document.body.classList.add('kb-typing')
// Nach der Tastatur-Animation (iOS ca. 350 ms) pruefen, ob die Chat-Eingabe sichtbar ist
if (ev.target.closest('#chat-composer')) setTimeout(revealChatComposer, 350)
})
document.addEventListener('focusout', () => {
setTimeout(() => {
@@ -1573,7 +1596,6 @@ function initAppShell() {
}, 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
@@ -1581,8 +1603,7 @@ function initAppShell() {
let fullHeight = vv.height
let width = vv.width
let keyboardOpen = false
const syncHeight = () => {
document.documentElement.style.setProperty('--kb-vvh', `${Math.round(vv.height)}px`)
vv.addEventListener('resize', () => {
if (Math.abs(vv.width - width) > 1) {
width = vv.width
fullHeight = vv.height
@@ -1594,20 +1615,15 @@ function initAppShell() {
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()
if (open) requestAnimationFrame(() => requestAnimationFrame(revealChatComposer))
})
}
// Wird der Verlauf kleiner (Tastatur, Tab-Leiste), bleibt die neueste Nachricht sichtbar – sofern
// man unten war (data-stick setzt ChatComposer.trackScroll)
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
if (messages.dataset.stick !== 'false') messages.scrollTop = messages.scrollHeight
}).observe(messages)
}
@@ -1652,6 +1668,16 @@ function initAppShell() {
if (isAppLayout() && getQueryParam('abo') === 'success') showView('wartung')
}
// iOS zoomt beim Tippen in ein Feld hinein, wenn dessen Schrift effektiv unter 16 px liegt – z. B. bei
// verkleinertem Seitenzoom in Safari, obwohl die Felder 16 px haben. maximum-scale=1 verhindert nur
// diesen automatischen Zoom; mit zwei Fingern zoomen bleibt auf iOS moeglich (WebKit ignoriert die
// Sperre dafuer seit iOS 10). Nur iOS: Android wuerde damit jedes Zoomen sperren.
function preventIosFocusZoom() {
const meta = document.querySelector('meta[name="viewport"]')
if (meta && isIosDevice() && !/maximum-scale/.test(meta.content)) meta.content += ', maximum-scale=1'
}
preventIosFocusZoom()
// App-Installation (Homescreen) und Offline-Seite; zwischengespeichert wird nur die Offline-Seite (sw.js)
if ('serviceWorker' in navigator && window.isSecureContext) {
window.addEventListener('load', () => {
+13 -14
View File
@@ -441,7 +441,8 @@ button.kb-avatar:disabled { cursor: default; box-shadow: none; }
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
}
.kb-page.kb-typing { --kb-tabbar-h: 0px; }
/* Tastatur offen: auf Android wird der Platz der Tab-Leiste frei; auf iOS keine Hoehenaenderung (app.js) */
.kb-page.kb-typing:not(.kb-ios) { --kb-tabbar-h: 0px; }
html.kb-sheet-open { overflow: hidden; }
.kb-desk-only { display: none; }
.kb-app-only { display: inline; }
@@ -588,22 +589,19 @@ button.kb-avatar:disabled { cursor: default; box-shadow: 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) */
/* Chat: fuellt den Platz zwischen App-Leiste und Tab-Leiste. 100dvh folgt auf Android der Tastatur
(interactive-widget=resizes-content); auf iOS bleibt die Hoehe, Safari schiebt das Feld selbst hoch */
#view-chat.active {
display: flex; flex-direction: column;
height: calc(var(--kb-vvh, 100dvh) - var(--kb-appbar-h) - var(--kb-tabbar-h) - 20px);
height: calc(100vh - var(--kb-appbar-h) - var(--kb-tabbar-h) - 20px);
height: calc(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; }
/* Eingabe des Chat-Composers als Pille, passend zu dessen runden Knoepfen (Senden-Knopf: chat-composer.css) */
.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; }
@@ -636,7 +634,8 @@ button.kb-avatar:disabled { cursor: default; box-shadow: none; }
.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;
max-height: calc(100vh - env(safe-area-inset-top) - 24px); max-height: calc(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;
@@ -649,8 +648,9 @@ button.kb-avatar:disabled { cursor: default; box-shadow: 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) {
/* App-Ansicht mit Touch: der Hinweis unter dem Chat (Enter-Taste, Anhaenge) kostet zu viel Hoehe,
Bueroklammer und Mikrofon sprechen fuer sich */
@media (max-width: 880px) and (pointer: coarse), (pointer: coarse) and (max-height: 500px) {
.chat-hint { display: none; }
}
@@ -663,9 +663,8 @@ button.kb-avatar:disabled { cursor: default; box-shadow: 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 */
/* Chat: Ueberschrift weglassen, sie steht dann in der App-Leiste */
#view-chat .kb-head { display: none; }
.chat-input-row textarea { height: 46px; }
}
@media (prefers-reduced-motion: reduce) {