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> </svg>
</motion.span> </motion.span>
{reduceMotion ? null : ( {/* Immer gerendert, nur bei reduzierter Bewegung nie animiert (bleibt
<motion.span bei opacity 0). Ein bedingtes Rendern wich zwischen Server
initial={{ opacity: 0 }} (useReducedMotion = null) und Browser ab und loeste einen
animate={dripControls} Hydration-Fehler aus. */}
style={{ <motion.span
left: DRIP_LEFT_PX - DRIP_WIDTH_PX / 2, initial={{ opacity: 0 }}
top: DRIP_TOP_PX - 1, animate={dripControls}
width: DRIP_WIDTH_PX, style={{
transformOrigin: "50% 0%", left: DRIP_LEFT_PX - DRIP_WIDTH_PX / 2,
}} top: DRIP_TOP_PX - 1,
className="absolute" width: DRIP_WIDTH_PX,
> transformOrigin: "50% 0%",
{/* Gleiche Honigtoene wie der fliegende Tropfen in cart-flight.tsx. */} }}
<svg viewBox="0 0 6 9" width={DRIP_WIDTH_PX} height="9"> className="absolute"
<defs> >
<radialGradient id="wh-drip" cx="40%" cy="65%" r="70%"> {/* Gleiche Honigtoene wie der fliegende Tropfen in cart-flight.tsx. */}
<stop offset="0" stopColor="#e4cfa4" /> <svg viewBox="0 0 6 9" width={DRIP_WIDTH_PX} height="9">
<stop offset="0.5" stopColor="#b88a44" /> <defs>
<stop offset="1" stopColor="#8a6531" /> <radialGradient id="wh-drip" cx="40%" cy="65%" r="70%">
</radialGradient> <stop offset="0" stopColor="#e4cfa4" />
</defs> <stop offset="0.5" stopColor="#b88a44" />
<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)" /> <stop offset="1" stopColor="#8a6531" />
<ellipse cx="2" cy="5.8" rx="0.7" ry="1" fill="#fff" opacity="0.6" /> </radialGradient>
</svg> </defs>
</motion.span> <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> </span>
); );
} }
+5 -1
View File
@@ -54,7 +54,11 @@ export function CookieConsentBanner() {
verlangen, dass Ablehnen nicht schwerer faellt als Zustimmen). verlangen, dass Ablehnen nicht schwerer faellt als Zustimmen).
Vorher war "Alle akzeptieren" Waldgruen auf Waldgruen und damit Vorher war "Alle akzeptieren" Waldgruen auf Waldgruen und damit
kaum als Knopf erkennbar. */} 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")}> <Button variant="inverted" className="whitespace-nowrap px-4" onClick={() => choose("essential")}>
{t.cookieConsent.essentialOnly} {t.cookieConsent.essentialOnly}
</Button> </Button>
+4 -1
View File
@@ -142,7 +142,10 @@ function assertSound(layout: CertificateLayout, personalization: Personalization
describe.each(listTemplateVersions())("Vorlage %s", (version) => { describe.each(listTemplateVersions())("Vorlage %s", (version) => {
describe.each(Object.entries(DATASETS))("Datensatz %s", (_name, data) => { 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) { for (const occasion of ["GEBURTSTAG", "SELBST"] as const) {
const { layout, personalization } = build(version, data, occasion, locale, TERMS.einmalig); const { layout, personalization } = build(version, data, occasion, locale, TERMS.einmalig);
assertSound(layout, personalization); assertSound(layout, personalization);
+5
View File
@@ -17,5 +17,10 @@ export default defineConfig({
include: ["src/**/*.test.ts", "src/**/*.test.tsx"], include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
exclude: ["e2e/**"], exclude: ["e2e/**"],
setupFiles: ["./vitest.setup.ts"], 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,
}, },
}); });