Kundenansicht neu E-Fix F3b: Safe-Area links und rechts in Hülle, öffentlichen Seiten und Chat-Vollbild

dashboard.html und die öffentlichen Seiten setzen viewport-fit=cover, aber kein Rand nutzte env(safe-area-inset-left|right): Im Querformat
am iPhone rutschten die ersten Zeichen unter Notch und Insel. Kopf, Hauptseite, Blatt, Streifen, Konto-Sheet, Fehlerzeile, Leiste, Banner und die
Sperre sowie Login, Rechtsseiten, Auftrag, Offline-Seite und die Kopfzeile des Chat-Vollbilds bauen ihre Ränder jetzt mit
max(Wert, env(safe-area-inset-left|right)). Am Desktop und ohne Notch ändert sich nichts (Inset 0). Pins in test/knEfixCss.test.js.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 21:31:33 +00:00
1 parent 8e349f6eca
commit 3962074af6
5 files changed
+29 -16

No files matched your search

+1 -1
View File
@@ -182,7 +182,7 @@ html.chat-overlay-open, html.chat-overlay-open body { overflow: hidden; }
position: fixed; inset: 0; z-index: 1000; display: flex; flex-direction: column;
background: rgba(4, 4, 6, 0.95); color: #fff; font-family: inherit;
}
.chat-ov-top { flex: none; display: flex; align-items: center; gap: 10px; padding: 12px 14px; padding-top: max(12px, env(safe-area-inset-top)); }
.chat-ov-top { flex: none; display: flex; align-items: center; gap: 10px; padding: 12px max(14px, env(safe-area-inset-right)) 12px max(14px, env(safe-area-inset-left)); padding-top: max(12px, env(safe-area-inset-top)); }
.chat-ov-title { flex: 1; min-width: 0; font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-ov-btn {
flex: none; display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-width: 44px; height: 44px; padding: 0 12px;
+9 -9
View File
@@ -38,7 +38,7 @@
.kn-tastatur .kn-streifen { display: none; }
@media (max-width: 880px), (pointer: coarse) and (max-height: 500px) {
.kn-streifen { position: fixed; left: 0; right: 0; bottom: calc(var(--kn-zeile-h) + env(safe-area-inset-bottom)); z-index: 30; padding: 0 16px; }
.kn-streifen { position: fixed; left: 0; right: 0; bottom: calc(var(--kn-zeile-h) + env(safe-area-inset-bottom)); z-index: 30; padding: 0 max(16px, env(safe-area-inset-right)) 0 max(16px, env(safe-area-inset-left)); }
.kn-streifen-link { min-height: 44px; display: inline-flex; align-items: center; }
.kn-hat-streifen { --kn-unten: calc(var(--kn-zeile-h) + env(safe-area-inset-bottom) + 44px); }
.kn-hat-streifen .kn-haupt { padding-bottom: calc(var(--kn-zeile-h) + env(safe-area-inset-bottom) + 28px + 44px); }
@@ -70,7 +70,7 @@
@media (max-width: 880px), (pointer: coarse) and (max-height: 500px) {
.kn-konto-scrim { background: var(--kn-overlay); animation: kn-einblenden .3s ease-out; }
.kn-konto { top: auto; right: 0; bottom: 0; left: 0; width: auto; max-height: 92vh; padding: 8px 20px calc(22px + env(safe-area-inset-bottom)); border-radius: 12px 12px 0 0; animation: kn-sheet-hoch 500ms var(--kn-bremse); }
.kn-konto { top: auto; right: 0; bottom: 0; left: 0; width: auto; max-height: 92vh; padding: 8px max(20px, env(safe-area-inset-right)) calc(22px + env(safe-area-inset-bottom)) max(20px, env(safe-area-inset-left)); border-radius: 12px 12px 0 0; animation: kn-sheet-hoch 500ms var(--kn-bremse); }
.kn-konto-zu { min-height: 44px; }
.kn-konto-zeile { min-height: 46px; }
}
@@ -80,7 +80,7 @@
public/kundenansicht.js hängt bei 409 view_replaced bzw. 401 view_expired ein div.kb-view-overlay (role alertdialog) an den Body. Die alte Seite
gestaltet es in kundenbereich.css, die dashboard.html nicht lädt: hier die Regeln für die neue Seite. Deckend weiß über allem (Konto 60, Leiste 40),
damit nichts Veraltetes bedienbar bleibt. */
.kb-view-overlay { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: max(24px, env(safe-area-inset-top)) 24px max(24px, env(safe-area-inset-bottom)); background: var(--kn-bg); }
.kb-view-overlay { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: max(24px, env(safe-area-inset-top)) max(24px, env(safe-area-inset-right)) max(24px, env(safe-area-inset-bottom)) max(24px, env(safe-area-inset-left)); background: var(--kn-bg); }
.kb-view-overlay-card { width: 100%; max-width: 420px; text-align: center; }
.kb-view-overlay-card p { margin: 0 0 24px; font-size: 17px; line-height: 26px; color: var(--kn-text); }
.kb-view-overlay-card button { min-height: 44px; padding: 0 24px; border-radius: var(--kn-radius); background: var(--kn-accent); color: #fff; font-weight: 600; box-shadow: var(--kn-shadow-btn); }
@@ -94,7 +94,7 @@
.kn-skip:focus { top: 8px; }
.kn-nur-leser { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.kn-banner { position: relative; z-index: 30; display: flex; align-items: center; justify-content: center; gap: 12px; min-height: 36px; padding: 6px 16px; background: var(--kn-accent); color: #fff; font-size: 13px; line-height: 20px; }
.kn-banner { position: relative; z-index: 30; display: flex; align-items: center; justify-content: center; gap: 12px; min-height: 36px; padding: 6px max(16px, env(safe-area-inset-right)) 6px max(16px, env(safe-area-inset-left)); background: var(--kn-accent); color: #fff; font-size: 13px; line-height: 20px; }
.kn-banner > span { min-width: 0; overflow-wrap: anywhere; }
.kn-banner button { flex: none; min-height: 28px; padding: 0 4px; color: #fff; font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }
.kn-banner:not([hidden]) ~ .kn-huelle .kn-menue { top: 36px; }
@@ -123,22 +123,22 @@
@media (max-width: 880px), (pointer: coarse) and (max-height: 500px) {
.kn-menue { display: none; }
.kn-spalte { margin-left: 0; }
.kn-kopf { justify-content: space-between; height: auto; min-height: calc(var(--kn-kopf-h) + env(safe-area-inset-top)); margin-bottom: 0; padding: env(safe-area-inset-top) 8px 0 20px; border-bottom: 1px solid transparent; background: var(--kn-bg); transition: border-color 300ms; pointer-events: auto; }
.kn-kopf { justify-content: space-between; height: auto; min-height: calc(var(--kn-kopf-h) + env(safe-area-inset-top)); margin-bottom: 0; padding: env(safe-area-inset-top) max(8px, env(safe-area-inset-right)) 0 max(20px, env(safe-area-inset-left)); border-bottom: 1px solid transparent; background: var(--kn-bg); transition: border-color 300ms; pointer-events: auto; }
.kn-scrolled .kn-kopf { border-bottom-color: var(--kn-line); }
.kn-kopf-titel { display: block; font-size: 15px; line-height: 22px; font-weight: 600; opacity: 0; transform: translateY(5px); transition: opacity 250ms, transform 250ms; }
.kn-titled .kn-kopf-titel { opacity: 1; transform: none; }
.kn-haupt { max-width: none; padding: 2px 16px calc(var(--kn-zeile-h) + env(safe-area-inset-bottom) + 28px); }
.kn-blatt { max-width: none; padding: 0 16px 24px; }
.kn-haupt { max-width: none; padding: 2px max(16px, env(safe-area-inset-right)) calc(var(--kn-zeile-h) + env(safe-area-inset-bottom) + 28px) max(16px, env(safe-area-inset-left)); }
.kn-blatt { max-width: none; padding: 0 max(16px, env(safe-area-inset-right)) 24px max(16px, env(safe-area-inset-left)); }
.kn-haupt .kn-titel, .kn-blatt .kn-titel { margin: 6px 0 10px; font-size: 28px; line-height: 34px; }
.kn-banner button { min-height: 44px; }
#kn-fehler { margin: 12px 16px 0; }
#kn-fehler { margin: 12px max(16px, env(safe-area-inset-right)) 0 max(16px, env(safe-area-inset-left)); }
}
/* ---------- Leiste (Handy): Fahrstuhl-Zeile mit Balken ---------- */
.kn-leiste { display: none; }
.kn-leiste-reel { height: var(--kn-zeile-h); overflow: hidden; }
.kn-rw { display: flex; align-items: center; height: var(--kn-zeile-h); padding: 0 4px; transition: transform 550ms var(--kn-bremse), visibility 0s; }
.kn-rw { display: flex; align-items: center; height: var(--kn-zeile-h); padding: 0 max(4px, env(safe-area-inset-right)) 0 max(4px, env(safe-area-inset-left)); transition: transform 550ms var(--kn-bremse), visibility 0s; }
.kn-leiste-reel[data-etage="offen"] .kn-rw { transform: translateY(-56px); }
.kn-leiste-reel[data-etage="offen"] #kn-zeile-haupt, .kn-leiste-reel:not([data-etage="offen"]) #kn-zeile-etage { visibility: hidden; transition: transform 550ms var(--kn-bremse), visibility 0s 550ms; }
+5 -5
View File
@@ -14,10 +14,10 @@
/* ------------------------------------------------------------------ Anmeldung --- */
.kn-login-seite { min-height: 100svh; }
.kn-login-kopf { position: fixed; top: 0; left: 0; right: 0; z-index: 2; padding: calc(24px + env(safe-area-inset-top)) 24px 16px; pointer-events: none; }
.kn-login-kopf { position: fixed; top: 0; left: 0; right: 0; z-index: 2; padding: calc(24px + env(safe-area-inset-top)) max(24px, env(safe-area-inset-right)) 16px max(24px, env(safe-area-inset-left)); pointer-events: none; }
.kn-login-logo { pointer-events: auto; font-size: 18px; line-height: 24px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
.kn-seite a.kn-login-logo { text-decoration: none; }
.kn-login-haupt { min-height: 100svh; display: flex; align-items: center; justify-content: center; padding: calc(88px + env(safe-area-inset-top)) 24px calc(40px + env(safe-area-inset-bottom)); }
.kn-login-haupt { min-height: 100svh; display: flex; align-items: center; justify-content: center; padding: calc(88px + env(safe-area-inset-top)) max(24px, env(safe-area-inset-right)) calc(40px + env(safe-area-inset-bottom)) max(24px, env(safe-area-inset-left)); }
.kn-login-spalte { width: 100%; max-width: 360px; }
.kn-login-marke { display: none; }
.kn-login-titel { margin: 0 0 24px; font-size: 22px; line-height: 28px; font-weight: 700; letter-spacing: -.02em; }
@@ -50,7 +50,7 @@
}
/* ------------------------------------------------------------------ Kündigen und Widerrufen (schlicht) --- */
.kn-recht main { max-width: 620px; margin: 0 auto; padding: 48px 20px 80px; }
.kn-recht main { max-width: 620px; margin: 0 auto; padding: 48px max(20px, env(safe-area-inset-right)) 80px max(20px, env(safe-area-inset-left)); }
.kn-recht h1 { margin: 0 0 8px; font-size: 28px; line-height: 34px; font-weight: 700; letter-spacing: -.02em; }
.kn-recht h2 { margin: 0 0 12px; font-size: 18px; line-height: 26px; font-weight: 600; }
.kn-markenzeile { margin-bottom: 8px; font-size: 13px; font-weight: 700; letter-spacing: .12em; color: var(--kn-text-2); }
@@ -140,7 +140,7 @@ body.ab-seite {
font: 16px/1.55 var(--kn-font); -webkit-text-size-adjust: 100%; -webkit-font-smoothing: antialiased;
}
.ab-seite .hidden { display: none !important; }
.ab-seite .ab-innen { max-width: 40rem; margin: 0 auto; padding: 0 16px; box-sizing: border-box; width: 100%; }
.ab-seite .ab-innen { max-width: 40rem; margin: 0 auto; padding: 0 max(16px, env(safe-area-inset-right)) 0 max(16px, env(safe-area-inset-left)); box-sizing: border-box; width: 100%; }
.ab-seite .ab-kopf { padding: max(20px, env(safe-area-inset-top)) 0 12px; }
.ab-seite .ab-kopf .ab-innen { display: flex; align-items: center; gap: 10px; }
.ab-seite .ab-logo { width: 36px; height: 36px; border-radius: 6px; display: block; }
@@ -234,7 +234,7 @@ body.ab-seite {
.ab-app-back { display: none; }
@media (display-mode: standalone) {
.ab-app-back {
display: inline-block; margin: calc(env(safe-area-inset-top) + 12px) 16px 0; font-size: 15px; font-weight: 600; color: var(--ab-text); text-decoration: none;
display: inline-block; margin: calc(env(safe-area-inset-top) + 12px) max(16px, env(safe-area-inset-right)) 0 max(16px, env(safe-area-inset-left)); font-size: 15px; font-weight: 600; color: var(--ab-text); text-decoration: none;
}
.ab-seite .ab-kopf { padding-top: 12px; }
}
+1 -1
View File
@@ -9,7 +9,7 @@
<style>
html, body { height: 100%; margin: 0; }
body {
display: grid; place-items: center; padding: 24px; box-sizing: border-box;
display: grid; place-items: center; padding: 24px max(24px, env(safe-area-inset-right)) 24px max(24px, env(safe-area-inset-left)); box-sizing: border-box;
background: #fff; color: #1a1f36;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
text-align: center; line-height: 1.5; -webkit-font-smoothing: antialiased;
+13
View File
@@ -40,3 +40,16 @@ test('F3: Pflicht- und Zahlungslinks haben am Handy eine Tippfläche von 44 px',
const handy = handyTeil(lies('ansichten.css'))
assert.match(handy, /\.kn-kuendigen a,\s*\.kn-widerruf a,\s*\.kn-links a,\s*\.kn-rechnung-offen a\s*\{[^}]*display:\s*inline-flex[^}]*min-height:\s*44px/)
})
// F3: Safe-Area links und rechts (Querformat am iPhone, viewport-fit=cover): jeder feste Seitenrand der Hülle und der öffentlichen Seiten
// wird mit max(Wert, env(safe-area-inset-left|right)) gebaut
const alleRegeln = (css) => [...css.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map((m) => ({ selektoren: m[1].replace(/\/\*[\s\S]*?\*\//g, '').split(',').map((x) => x.trim()), text: m[2] }))
const hatInsets = (css, selektor) => alleRegeln(css).some((r) => r.selektoren.includes(selektor) && /env\(safe-area-inset-left\b/.test(r.text) && /env\(safe-area-inset-right\b/.test(r.text))
test('F3: Seitenränder der Hülle berücksichtigen die Safe-Area links und rechts', () => {
for (const sel of ['.kn-kopf', '.kn-haupt', '.kn-blatt', '.kn-streifen', '.kn-konto', '#kn-fehler', '.kn-rw', '.kn-banner', '.kb-view-overlay']) assert.ok(hatInsets(HUELLE, sel), `huelle.css: ${sel}`)
})
test('F3: Seitenränder der öffentlichen Seiten und des Chats berücksichtigen die Safe-Area links und rechts', () => {
const oeff = lies('oeffentlich/oeffentlich.css')
for (const sel of ['.kn-login-kopf', '.kn-login-haupt', '.kn-recht main', '.ab-seite .ab-innen']) assert.ok(hatInsets(oeff, sel), `oeffentlich.css: ${sel}`)
assert.ok(hatInsets(lies('chat-composer.css'), '.chat-ov-top'), 'chat-composer.css: .chat-ov-top')
})