-
+
-
+
WEBklar Kundenbereich
@@ -86,5 +88,6 @@
+
diff --git a/public/neu/oeffentlich/oeffentlich.css b/public/neu/oeffentlich/oeffentlich.css
index 05ddd96..eba567a 100644
--- a/public/neu/oeffentlich/oeffentlich.css
+++ b/public/neu/oeffentlich/oeffentlich.css
@@ -19,8 +19,16 @@
.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)) 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; }
+/* Logo mit Glas-Animation (login-logo.js): das Feld braucht Platz für die Glaskachel (Kachel = Höhe / 1,4), das ruhende Logo
+ ist danach etwa 80 px breit. Der Name liegt im unteren Weiß des Feldes und erscheint erst, wenn das Glas losgelassen hat
+ (nach ~3,2 s); nach dem Startbild der App und bei reduzierter Bewegung steht er sofort da. */
+.kn-login-marke { display: flex; flex-direction: column; align-items: center; margin: 0 0 28px; font-size: 15px; font-weight: 600; }
+.kn-login-glas { width: 100%; height: 220px; -webkit-tap-highlight-color: transparent; }
+.kn-login-marke > span { position: relative; margin-top: -58px; animation: kn-login-name .5s ease 3.2s both; }
+@keyframes kn-login-name { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
+.kn-login-marke.kn-login-ruht > span { animation: none; }
+@media (prefers-reduced-motion: reduce) { .kn-login-marke > span { animation: none; } }
+.kn-login-titel { margin: 0 0 24px; font-size: 22px; line-height: 28px; font-weight: 700; letter-spacing: -.02em; text-align: center; }
.kn-login-form { display: flex; flex-direction: column; gap: 12px; }
.kn-feld-eingabe {
width: 100%; height: 44px; min-width: 0; margin: 0; padding: 0 12px; border: 0; border-radius: var(--kn-radius);
@@ -44,9 +52,8 @@
.kn-login-hinweis + .kn-login-hinweis { margin-top: 16px; }
.kn-seite a.kn-login-wichtig { color: var(--kn-text); font-weight: 600; }
@media (max-width: 880px), (pointer: coarse) and (max-height: 500px) {
- .kn-login-marke { display: flex; flex-direction: column; align-items: center; gap: 12px; margin: 0 0 32px; font-size: 15px; font-weight: 600; }
- .kn-login-marke img { width: 64px; height: 64px; border-radius: var(--kn-radius-lg); box-shadow: var(--kn-ring); }
- .kn-login-titel { text-align: center; }
+ .kn-login-glas { height: 190px; }
+ .kn-login-marke > span { margin-top: -50px; }
}
/* ------------------------------------------------------------------ Kündigen und Widerrufen (schlicht) --- */
diff --git a/public/neu/startbild.js b/public/neu/startbild.js
new file mode 100644
index 0000000..ed40aff
--- /dev/null
+++ b/public/neu/startbild.js
@@ -0,0 +1,88 @@
+/* Kundenansicht neu: Startbild der installierten App. Beim ersten Öffnen je App-Start (sessionStorage) deckt eine weiße
+ Fläche die Seite ab, darauf spielt das Glas-Logo (glas-logo.js); danach blendet die Fläche weich aus. Nur in der
+ installierten App (display-mode standalone, iPhone navigator.standalone), nie im Browser.
+ Klassisches Skript im von dashboard.html und der Anmeldung: es läuft vor dem ersten Bild, damit die Seite nie
+ kurz aufblitzt. Ein Tipp auf die Fläche überspringt das Startbild; hängt etwas, blendet es spätestens nach SICHERHEIT_MS aus. */
+;(function () {
+ var SCHLUESSEL = 'kn-startbild'
+ var TEMPO = 1.25 // etwas schneller als auf der Anmeldung: das Startbild soll kurz bleiben
+ var HALTEN_MS = 120 // das ruhende Logo bleibt so lange stehen, bevor die Fläche ausblendet
+ var AUSBLENDEN_MS = 450
+ var SICHERHEIT_MS = 6500
+ var html = document.documentElement
+
+ var standalone = false
+ try {
+ standalone = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true
+ } catch (e) {
+ standalone = false
+ }
+ if (!standalone) return
+ // Ohne sessionStorage kein Startbild: sonst käme es bei jedem Seitenwechsel (Anmelden → Übersicht) erneut
+ try {
+ if (window.sessionStorage.getItem(SCHLUESSEL)) return
+ window.sessionStorage.setItem(SCHLUESSEL, '1')
+ } catch (e) {
+ return
+ }
+
+ var stil = document.createElement('style')
+ stil.textContent =
+ 'html[data-kn-startbild="deckt"]::before{content:"";position:fixed;top:0;right:0;bottom:0;left:0;z-index:2147483646;background:#fff}' +
+ '#kn-startbild{position:fixed;top:0;right:0;bottom:0;left:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;background:#fff;' +
+ 'opacity:1;transition:opacity ' + AUSBLENDEN_MS + 'ms ease;-webkit-tap-highlight-color:transparent}' +
+ '#kn-startbild.kn-startbild-weg{opacity:0;pointer-events:none}' +
+ '#kn-startbild .kn-startbild-logo{width:min(300px,80vw);height:min(300px,80vw)}' +
+ '@media (prefers-reduced-motion:reduce){#kn-startbild{transition-duration:200ms}}'
+ document.head.appendChild(stil)
+ // Bis die Fläche im steht, deckt das Pseudo-Element am die Seite ab ("deckt"); danach liegt die Fläche
+ // selbst darüber ("spielt"), damit beim Ausblenden die Seite darunter schon sichtbar ist. Die Anmeldung liest das Attribut: Logo ruht.
+ html.setAttribute('data-kn-startbild', 'deckt')
+ // Das Modul schon jetzt laden, nicht erst wenn die Seite fertig ist
+ var modul = import('/neu/glas-logo.js')
+ modul.catch(function () { /* unten behandelt */ })
+
+ var flaeche = null
+ var logo = null
+ var vorbei = false
+
+ function ausblenden() {
+ if (vorbei) return
+ vorbei = true
+ if (!flaeche) return html.removeAttribute('data-kn-startbild')
+ flaeche.classList.add('kn-startbild-weg')
+ var weg = function () {
+ if (!flaeche) return
+ try { if (logo) logo.entfernen() } catch (e) { /* Zeichenfläche schon weg */ }
+ flaeche.remove()
+ flaeche = null
+ html.removeAttribute('data-kn-startbild')
+ }
+ flaeche.addEventListener('transitionend', weg, { once: true })
+ setTimeout(weg, AUSBLENDEN_MS + 150)
+ }
+
+ function zeigen() {
+ flaeche = document.createElement('div')
+ flaeche.id = 'kn-startbild'
+ flaeche.setAttribute('aria-hidden', 'true')
+ var feld = document.createElement('div')
+ feld.className = 'kn-startbild-logo'
+ flaeche.appendChild(feld)
+ document.body.appendChild(flaeche)
+ html.setAttribute('data-kn-startbild', 'spielt')
+ flaeche.addEventListener('click', ausblenden)
+ setTimeout(ausblenden, SICHERHEIT_MS)
+ modul
+ .then(function (m) {
+ if (vorbei) return
+ logo = m.glasLogo(feld, { tempo: TEMPO, einstellungen: { finalScale: 0.9 } })
+ return logo.fertig
+ })
+ .catch(function () { /* ohne Modul: die weiße Fläche blendet einfach aus */ })
+ .then(function () { setTimeout(ausblenden, HALTEN_MS) })
+ }
+
+ if (document.body) zeigen()
+ else document.addEventListener('DOMContentLoaded', zeigen, { once: true })
+})()
diff --git a/test/knAnsichtOeffentlich.test.js b/test/knAnsichtOeffentlich.test.js
index a16361e..d82c871 100644
--- a/test/knAnsichtOeffentlich.test.js
+++ b/test/knAnsichtOeffentlich.test.js
@@ -8,11 +8,15 @@ const PAARE = ['login', 'kuendigen', 'widerrufen', 'auftrag', 'offline'].map((n)
const koerper = (html) => html.slice(html.search(/ [...koerper(html).matchAll(/\s(name|id|value|type|autocomplete|maxlength|required|min|max|step|inputmode|placeholder|enterkeyhint|data-text|data-label|data-label-for)(?:="([^"]*)")?/g)].map((m) => `${m[1]}=${m[2] ?? ''}`).sort()
const skripte = (html) => [...html.matchAll(/', '']
+const ohneZusaetze = (html) => ZUSAETZE.reduce((h, z) => h.replace(z, ''), html)
for (const [alt, neu] of PAARE) {
test(`${neu}: gleicher sichtbarer Text, gleiche Felder, gleiche Skripte wie ${alt}`, () => {
// Kündigen und Widerrufen sprechen seit P7 du (Fassung 6, C14): ihr Text steht in test/knOeffentlichDu.test.js; Felder und Skripte werden weiter verglichen
- if (!['kuendigen.html', 'widerrufen.html'].includes(alt)) assert.equal(sichtbarerText(lese(neu)), sichtbarerText(lese(alt)))
- assert.deepEqual(attribute(lese(neu)), attribute(lese(alt))); assert.deepEqual(skripte(lese(neu)), skripte(lese(alt)))
+ const n = ohneZusaetze(lese(neu))
+ if (!['kuendigen.html', 'widerrufen.html'].includes(alt)) assert.equal(sichtbarerText(n), sichtbarerText(lese(alt)))
+ assert.deepEqual(attribute(n), attribute(lese(alt))); assert.deepEqual(skripte(n), skripte(lese(alt)))
})
test(`${neu}: neuer Stil, hell, ohne Verläufe, Lila und Dunkelmodus`, () => {
const n = lese(neu)
@@ -24,6 +28,20 @@ for (const [alt, neu] of PAARE) {
test('Login: Skript lädt app.js und startet initLoginPage, Statusleiste hell', () => {
const n = lese('neu/oeffentlich/login.html'); assert.match(n, /