diff --git a/docs/STATUS.md b/docs/STATUS.md index 0f51a8f..1257784 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,5 +1,11 @@ # Projektstatus +## Kant-IT-Startseite: Einblendungen nach Flatsome-Muster in `var/kantit.db` (14.09.2026) + +**Umgesetzt:** Auf Umuts Wunsch das Animationsmuster der alten Flatsome-Website (www.kant-it.de: fadeInLeft/Right im Hero, bei „Warum“ und Kontakt, gestaffeltes fadeInUp auf Zahlen, Karten und Fußzeilenspalten) in der lokalen Kant-IT-Datenbank nachgebaut: 55 Blöcke der Startseite umgestellt – Sektionen ohne eigene Einblendung, Hero-Text von links, Slider-Spalte von rechts, Kennzahlen und Karten gestaffelt von unten, „Warum“-Texte und -Karten von links, Kundenstimmen von rechts, Kontaktdaten links und Formular rechts – sowie die vier Fußzeilenspalten gestaffelt von unten. Als Entwurf gespeichert und veröffentlicht (Revision `155e2934…`), Sicherung `var/kantit.db.vor-bewegung-*.bak`. Umut kopiert die Datei selbst auf den Server; `web.kant-it.de` läuft bereits auf 1.11.0 (Fassung, `motion.js`, Hover und Einblendungen live geprüft). + +**Geprüft:** `scripts/motion-check.mjs` (Ziel jetzt `#abschluss-cta`, weil die Sektion `#kontakt` keine eigene Einblendung mehr trägt) auf frischer Pilotkopie bei 1600/390 px erfolgreich: 45 Einblendungen gerendert (30 von unten, 11 von links, 4 von rechts), Warten außerhalb des Bildes, einmalige Wiedergabe, Richtungen, Hover, Standardmodus unter „reduce“, `motion-respect`. Keine JavaScript-Fehler in der App-Vorschau. + ## 1.11.0 – Editor mit einer Seitenleiste nach Flatsome-Vorbild, Bewegung spielt standardmäßig (14.09.2026) **Anlass:** Umut fand den Editor mit drei Panels unübersichtlich und kam mit dem Flatsome UX Builder zurecht; außerdem liefen die Einblendungen in seinem Chrome nicht, weil der Browser „Bewegung reduzieren“ meldet, obwohl er das nicht eingestellt hat – andere Websites bewegen sich bei ihm. diff --git a/scripts/motion-check.mjs b/scripts/motion-check.mjs index b036f50..2bb4b86 100644 --- a/scripts/motion-check.mjs +++ b/scripts/motion-check.mjs @@ -53,30 +53,30 @@ try { const hovered = await hoverTarget.evaluate(node => ({ transform: getComputedStyle(node).transform, matches: node.matches(':hover'), classes: node.className })); console.log('Pilot hover:', width, JSON.stringify(hovered)); assert.equal(hovered.transform, 'matrix(1, 0, 0, 1, 0, -4)', 'Saved lift effect changes the rendered transform'); - const target = page.locator('#kontakt'); - assert.equal(await page.evaluate(() => motionCalls.some(call => call.id === 'kontakt')), false, 'Below-fold section must wait for scrolling'); + const target = page.locator('#abschluss-cta'); + assert.equal(await page.evaluate(() => motionCalls.some(call => call.id === 'abschluss-cta')), false, 'Below-fold section must wait for scrolling'); assert.equal(await target.evaluate(node => node.style.opacity), '0', 'Below-fold block waits out of sight, as in Flatsome'); await target.scrollIntoViewIfNeeded(); - await page.waitForFunction(() => motionCalls.some(call => call.id === 'kontakt')); + await page.waitForFunction(() => motionCalls.some(call => call.id === 'abschluss-cta')); const calls = await page.evaluate(() => motionCalls); - assert.equal(calls.find(call => call.id === 'kontakt').frames[0].transform, 'translateY(40px)'); - assert.equal(calls.find(call => call.id === 'kontakt').frames[0].opacity, 0, 'Entrance fades in from invisible, as in Flatsome'); + assert.equal(calls.find(call => call.id === 'abschluss-cta').frames[0].transform, 'translateY(40px)'); + assert.equal(calls.find(call => call.id === 'abschluss-cta').frames[0].opacity, 0, 'Entrance fades in from invisible, as in Flatsome'); await page.waitForTimeout(800); await page.evaluate(() => scrollTo(0, 0)); await target.scrollIntoViewIfNeeded(); await page.waitForTimeout(100); - assert.equal(await page.evaluate(() => motionCalls.filter(call => call.id === 'kontakt').length), 1, 'Entrance runs once'); + assert.equal(await page.evaluate(() => motionCalls.filter(call => call.id === 'abschluss-cta').length), 1, 'Entrance runs once'); await page.screenshot({ path: `${report}/pilot-${width}.png` }); // Exercise both existing choices on the real pilot element without editing saved content. for (const direction of ['left', 'right']) { await page.evaluate(async direction => { - const node = document.getElementById('kontakt'); + const node = document.getElementById('abschluss-cta'); node.classList.remove('fx-entrance-rise', 'fx-entrance-left', 'fx-entrance-right'); node.classList.add(`fx-entrance-${direction}`); const { initMotion } = await import(document.querySelector('script[src*="motion.js"]').src); window.stopMotionCheck?.(); window.motionCalls = []; window.stopMotionCheck = initMotion(); }, direction); - await page.waitForFunction(() => motionCalls.some(call => call.id === 'kontakt')); + await page.waitForFunction(() => motionCalls.some(call => call.id === 'abschluss-cta')); const actual = await target.evaluate(node => { const animation = node.getAnimations()[0]; animation.pause(); animation.currentTime = 150; return { transform: getComputedStyle(node).transform, frames: animation.effect.getKeyframes() }; @@ -91,7 +91,7 @@ try { // Umut's Chrome reports "reduce" without anyone having chosen it; the site must still move. await page.emulateMedia({ reducedMotion: 'reduce' }); await page.reload(); await target.scrollIntoViewIfNeeded(); - await page.waitForFunction(() => motionCalls.some(call => call.id === 'kontakt'), null, { timeout: 3000 }); + await page.waitForFunction(() => motionCalls.some(call => call.id === 'abschluss-cta'), null, { timeout: 3000 }); await page.waitForTimeout(900); assert.equal(await target.evaluate(node => getComputedStyle(node).opacity), '1', 'Block ends fully visible'); await hoverTarget.scrollIntoViewIfNeeded(); await hoverTarget.hover(); await page.waitForTimeout(350); @@ -116,7 +116,7 @@ try { }).observe(document, { childList: true }); }); const respectPage = await respect.newPage(); respectPage.on('pageerror', error => errors.push(error.message)); - const respectTarget = respectPage.locator('#kontakt'), respectHover = respectPage.locator('#leistung-app'); + const respectTarget = respectPage.locator('#abschluss-cta'), respectHover = respectPage.locator('#leistung-app'); await respectPage.goto(origin); await respectTarget.scrollIntoViewIfNeeded(); await respectPage.waitForTimeout(800); assert.equal(await respectPage.evaluate(() => motionCalls.length), 0, 'Reduced motion suppresses animations when respected'); assert.equal(await respectTarget.evaluate(node => getComputedStyle(node).opacity), '1'); @@ -124,29 +124,29 @@ try { assert.equal(await respectHover.evaluate(node => getComputedStyle(node).transform), 'none', 'Reduced motion suppresses hover movement when respected'); await respectTarget.scrollIntoViewIfNeeded(); await respectPage.emulateMedia({ reducedMotion: 'no-preference' }); - await respectPage.waitForFunction(() => motionCalls.some(call => call.id === 'kontakt'), null, { timeout: 3000 }); + await respectPage.waitForFunction(() => motionCalls.some(call => call.id === 'abschluss-cta'), null, { timeout: 3000 }); await respectPage.waitForTimeout(900); await respectPage.emulateMedia({ reducedMotion: 'reduce' }); await respectPage.emulateMedia({ reducedMotion: 'no-preference' }); await respectPage.waitForTimeout(100); - assert.equal(await respectPage.evaluate(() => motionCalls.filter(call => call.id === 'kontakt').length), 1, 'Preference changes do not replay an already seen element'); + assert.equal(await respectPage.evaluate(() => motionCalls.filter(call => call.id === 'abschluss-cta').length), 1, 'Preference changes do not replay an already seen element'); await respectHover.scrollIntoViewIfNeeded(); await respectHover.hover(); await respectPage.waitForTimeout(350); assert.equal(await respectHover.evaluate(node => getComputedStyle(node).transform), 'matrix(1, 0, 0, 1, 0, -4)', 'Hover resumes after allowing motion'); // Also cover a page that starts normally, then suppresses motion before reaching its content. await respectPage.reload(); await respectPage.waitForTimeout(800); await respectPage.emulateMedia({ reducedMotion: 'reduce' }); await respectTarget.scrollIntoViewIfNeeded(); await respectPage.waitForTimeout(100); - assert.equal(await respectPage.evaluate(() => motionCalls.filter(call => call.id === 'kontakt').length), 0); + assert.equal(await respectPage.evaluate(() => motionCalls.filter(call => call.id === 'abschluss-cta').length), 0); assert.equal(await respectTarget.evaluate(node => getComputedStyle(node).opacity), '1', 'A waiting block is shown again when motion is reduced'); await respectPage.emulateMedia({ reducedMotion: 'no-preference' }); - await respectPage.waitForFunction(() => motionCalls.some(call => call.id === 'kontakt'), null, { timeout: 3000 }); + await respectPage.waitForFunction(() => motionCalls.some(call => call.id === 'abschluss-cta'), null, { timeout: 3000 }); results.push({ width, inventory, calls, checks: ['scroll trigger', 'once only', 'hidden until visible', 'left/right computed movement', 'saved hover lift', 'default plays under reduce preference', 'motion-respect honours reduce', 'resume without reloading after initial and subsequent reduced motion', 'hover resumes after preference change'] }); await respect.close(); } const noJs = await browser.newContext({ javaScriptEnabled: false }); const page = await noJs.newPage(); await page.goto(origin); assert.ok(await page.locator('main').innerText()); - assert.equal(await page.locator('#kontakt').evaluate(node => getComputedStyle(node).opacity), '1'); + assert.equal(await page.locator('#abschluss-cta').evaluate(node => getComputedStyle(node).opacity), '1'); await noJs.close(); const editorContext = await browser.newContext({ reducedMotion: 'reduce', viewport: { width: 1600, height: 1000 } }); const editor = await editorContext.newPage(); editor.on('pageerror', error => errors.push(error.message));