fix: CI-Zeitgrenze fuer Urkundentests, Cookie-Banner mobil, Hydration im Warenkorb-Effekt
Docker Image bauen und veroeffentlichen / typecheck (push) Successful in 3m4s
Docker Image bauen und veroeffentlichen / lint (push) Successful in 3m13s
Docker Image bauen und veroeffentlichen / test (push) Successful in 4m4s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 13m1s

Die Layout-Matrix der Urkunde lief auf dem CI-Runner in die 5-Sekunden-
Grenze; Tests bekommen 30 s. Die Cookie-Knoepfe stapeln sich unter 430 px,
statt ineinanderzulaufen. Der Tropfen im Warenkorb-Effekt wird immer
gerendert und nur bei reduzierter Bewegung nicht animiert.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick.RosenloecherandClaude Opus 5.5 committed 2026-10-16 16:06:33 +02:00
1 parent 310c5ecf8c
commit ed17d14e46
4 files changed
+42 -28

No files matched your search

+28 -26
View File
@@ -85,32 +85,34 @@ export function CartLandingEffect() {
</svg>
</motion.span>
{reduceMotion ? null : (
<motion.span
initial={{ opacity: 0 }}
animate={dripControls}
style={{
left: DRIP_LEFT_PX - DRIP_WIDTH_PX / 2,
top: DRIP_TOP_PX - 1,
width: DRIP_WIDTH_PX,
transformOrigin: "50% 0%",
}}
className="absolute"
>
{/* Gleiche Honigtoene wie der fliegende Tropfen in cart-flight.tsx. */}
<svg viewBox="0 0 6 9" width={DRIP_WIDTH_PX} height="9">
<defs>
<radialGradient id="wh-drip" cx="40%" cy="65%" r="70%">
<stop offset="0" stopColor="#e4cfa4" />
<stop offset="0.5" stopColor="#b88a44" />
<stop offset="1" stopColor="#8a6531" />
</radialGradient>
</defs>
<path d="M3 0C3 2.5 6 4.2 6 6.1A3 3 0 0 1 0 6.1C0 4.2 3 2.5 3 0Z" fill="url(#wh-drip)" />
<ellipse cx="2" cy="5.8" rx="0.7" ry="1" fill="#fff" opacity="0.6" />
</svg>
</motion.span>
)}
{/* Immer gerendert, nur bei reduzierter Bewegung nie animiert (bleibt
bei opacity 0). Ein bedingtes Rendern wich zwischen Server
(useReducedMotion = null) und Browser ab und loeste einen
Hydration-Fehler aus. */}
<motion.span
initial={{ opacity: 0 }}
animate={dripControls}
style={{
left: DRIP_LEFT_PX - DRIP_WIDTH_PX / 2,
top: DRIP_TOP_PX - 1,
width: DRIP_WIDTH_PX,
transformOrigin: "50% 0%",
}}
className="absolute"
>
{/* Gleiche Honigtoene wie der fliegende Tropfen in cart-flight.tsx. */}
<svg viewBox="0 0 6 9" width={DRIP_WIDTH_PX} height="9">
<defs>
<radialGradient id="wh-drip" cx="40%" cy="65%" r="70%">
<stop offset="0" stopColor="#e4cfa4" />
<stop offset="0.5" stopColor="#b88a44" />
<stop offset="1" stopColor="#8a6531" />
</radialGradient>
</defs>
<path d="M3 0C3 2.5 6 4.2 6 6.1A3 3 0 0 1 0 6.1C0 4.2 3 2.5 3 0Z" fill="url(#wh-drip)" />
<ellipse cx="2" cy="5.8" rx="0.7" ry="1" fill="#fff" opacity="0.6" />
</svg>
</motion.span>
</span>
);
}
+5 -1
View File
@@ -54,7 +54,11 @@ export function CookieConsentBanner() {
verlangen, dass Ablehnen nicht schwerer faellt als Zustimmen).
Vorher war "Alle akzeptieren" Waldgruen auf Waldgruen und damit
kaum als Knopf erkennbar. */}
<div className="grid grid-cols-2 gap-3">
{/* Auf schmalen Telefonen untereinander: nebeneinander und ohne
Umbruch liefen "Nur notwendige" und "Alle akzeptieren" bei 390 px
ineinander. Gestapelt bleiben beide gleich breit und gleich
gewichtet. */}
<div className="grid grid-cols-1 gap-3 min-[430px]:grid-cols-2">
<Button variant="inverted" className="whitespace-nowrap px-4" onClick={() => choose("essential")}>
{t.cookieConsent.essentialOnly}
</Button>
+4 -1
View File
@@ -142,7 +142,10 @@ function assertSound(layout: CertificateLayout, personalization: Personalization
describe.each(listTemplateVersions())("Vorlage %s", (version) => {
describe.each(Object.entries(DATASETS))("Datensatz %s", (_name, data) => {
test.each(["de", "en"] as const)("Geschenk und eigene Patenschaft (%s)", (locale) => {
// Grosszuegige Zeitgrenze: jeder Fall setzt mehrere Seiten mit echten
// Schriftmetriken. Auf den CI-Runnern (geteilte CPU) reichten die
// voreingestellten 5 Sekunden nicht immer, lokal braucht es Bruchteile.
test.each(["de", "en"] as const)("Geschenk und eigene Patenschaft (%s)", { timeout: 60_000 }, (locale) => {
for (const occasion of ["GEBURTSTAG", "SELBST"] as const) {
const { layout, personalization } = build(version, data, occasion, locale, TERMS.einmalig);
assertSound(layout, personalization);
+5
View File
@@ -17,5 +17,10 @@ export default defineConfig({
include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
exclude: ["e2e/**"],
setupFiles: ["./vitest.setup.ts"],
// Urkunden-, Medaillen- und PDF-Tests setzen echte Schriften und rendern
// Bilder. Auf den CI-Runnern (geteilte CPU) dauert das ein Vielfaches
// der lokalen Zeit; die voreingestellten 5 Sekunden liessen dort einzelne
// Faelle zufaellig scheitern.
testTimeout: 30_000,
},
});