Merge branch 'codex/canva-redesign'
# Conflicts: # backlog/config.yml # src/components/cta.tsx # src/components/faq7.tsx # src/components/feature284.tsx # src/pages/index.astro # src/styles/global.css
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,6 +5,7 @@ dist/
|
|||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
|
.worktrees/
|
||||||
|
|
||||||
# logs
|
# logs
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
@@ -23,4 +24,4 @@ pnpm-debug.log*
|
|||||||
# jetbrains setting folder
|
# jetbrains setting folder
|
||||||
.idea/
|
.idea/
|
||||||
.kilo
|
.kilo
|
||||||
.env.local
|
.env.local
|
||||||
|
|||||||
41
docs/superpowers/plans/2026-05-05-canva-redesign.md
Normal file
41
docs/superpowers/plans/2026-05-05-canva-redesign.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Canva Redesign Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Rework the dev landing page into a bold black, red, and white editorial site inspired by the Canva creative brief deck.
|
||||||
|
|
||||||
|
**Architecture:** Replace the current stacked section imports on the homepage with one focused React landing component. Keep existing UI primitives available but avoid broad refactors of old sections so the redesign remains easy to review or revert.
|
||||||
|
|
||||||
|
**Tech Stack:** Astro 6, React 19, Tailwind CSS 4, lucide-react, Node test runner.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Landing Smoke Test
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `tests/landing-content.test.mjs`
|
||||||
|
- Modify: `package.json`
|
||||||
|
|
||||||
|
- [x] Add a Node smoke test that checks the new component source for key content anchors: `Projektbrief`, `01`, `Website`, `Kontakt`.
|
||||||
|
- [x] Run `node --test tests/landing-content.test.mjs` and confirm it fails before the component exists.
|
||||||
|
|
||||||
|
### Task 2: Canva-Inspired Landing Page
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/components/canva-landing.tsx`
|
||||||
|
- Modify: `src/pages/index.astro`
|
||||||
|
- Modify: `src/styles/global.css`
|
||||||
|
|
||||||
|
- [ ] Build a single-page layout with a dark editorial shell, red accent panels, large German headline, numbered sections, pricing/service strips, and a contact brief section.
|
||||||
|
- [ ] Replace the existing homepage component stack with the new `CanvaLanding` component.
|
||||||
|
- [ ] Update global tokens for the dark, high-contrast Canva reference style.
|
||||||
|
|
||||||
|
### Task 3: Verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `backlog/tasks/task-1 - Redesign-dev-website-from-Canva-reference.md`
|
||||||
|
|
||||||
|
- [ ] Run `node --test tests/landing-content.test.mjs` and confirm it passes.
|
||||||
|
- [ ] Run `CI=true pnpm run build` and confirm Astro builds the page.
|
||||||
|
- [ ] Start the local dev server and visually review the page in a browser/screenshot.
|
||||||
|
- [ ] Check off remaining acceptance criteria that have direct evidence.
|
||||||
4
pnpm-workspace.yaml
Normal file
4
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
allowBuilds:
|
||||||
|
esbuild: true
|
||||||
|
msw: true
|
||||||
|
sharp: true
|
||||||
249
src/components/canva-landing.tsx
Normal file
249
src/components/canva-landing.tsx
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
import {
|
||||||
|
ArrowUpRight,
|
||||||
|
Check,
|
||||||
|
CornerDownRight,
|
||||||
|
Mail,
|
||||||
|
MapPin,
|
||||||
|
Phone,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
const services = [
|
||||||
|
{
|
||||||
|
number: "01",
|
||||||
|
title: "Website",
|
||||||
|
text: "Eine klare Startseite oder ein kompletter Auftritt, der sofort zeigt, warum man Ihnen vertrauen kann.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "02",
|
||||||
|
title: "Struktur",
|
||||||
|
text: "Angebot, Beweise, Ablauf und Kontakt werden so sortiert, dass Besucher nicht suchen müssen.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "03",
|
||||||
|
title: "Technik",
|
||||||
|
text: "Schnell, mobil sauber, DSGVO-arm und so gebaut, dass spätere Änderungen nicht zum Projekt werden.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const deliverables = [
|
||||||
|
"Strategie und Seitenstruktur",
|
||||||
|
"Individuelles Screen-Design",
|
||||||
|
"Astro/React Umsetzung",
|
||||||
|
"Kontaktformular und Datenschutz",
|
||||||
|
"Hosting, Wartung und Analytics",
|
||||||
|
];
|
||||||
|
|
||||||
|
const packages = [
|
||||||
|
{
|
||||||
|
name: "Basis",
|
||||||
|
price: "799 EUR",
|
||||||
|
detail: "Eine starke Seite für ein klares Angebot.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Profi",
|
||||||
|
price: "1.499 EUR",
|
||||||
|
detail: "Mehrere Seiten für Betriebe mit erklärungsbedürftigem Angebot.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Maßarbeit",
|
||||||
|
price: "2.499 EUR+",
|
||||||
|
detail: "Individuelle Struktur, CMS und besondere Anforderungen.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const CanvaLanding = () => {
|
||||||
|
return (
|
||||||
|
<main className="min-h-screen overflow-hidden bg-background text-foreground">
|
||||||
|
<section className="relative grid min-h-screen grid-cols-1 border-b border-border lg:grid-cols-[1.08fr_0.92fr]">
|
||||||
|
<div className="flex min-h-[640px] flex-col justify-between px-5 py-5 sm:px-8 lg:px-12">
|
||||||
|
<header className="grid gap-4 border-b border-border pb-5 text-xs uppercase tracking-[0.28em] text-muted-foreground sm:grid-cols-[1fr_auto]">
|
||||||
|
<a href="/" className="font-semibold text-foreground">
|
||||||
|
Matthias Meister
|
||||||
|
</a>
|
||||||
|
<nav className="flex flex-wrap gap-5">
|
||||||
|
<a href="#leistungen">Leistungen</a>
|
||||||
|
<a href="#pakete">Pakete</a>
|
||||||
|
<a href="#kontakt">Kontakt</a>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="max-w-5xl py-16 sm:py-20 lg:py-24">
|
||||||
|
<p className="mb-6 max-w-sm text-sm uppercase tracking-[0.32em] text-primary">
|
||||||
|
Projektbrief für regionale Unternehmen
|
||||||
|
</p>
|
||||||
|
<h1 className="max-w-[11ch] text-[clamp(4.25rem,13vw,11.5rem)] font-black uppercase leading-[0.78] tracking-normal text-foreground">
|
||||||
|
Website ohne Umweg
|
||||||
|
</h1>
|
||||||
|
<div className="mt-8 grid gap-7 border-t border-border pt-7 lg:grid-cols-[0.72fr_1fr]">
|
||||||
|
<p className="text-sm uppercase tracking-[0.24em] text-muted-foreground">
|
||||||
|
Strategie trifft Umsetzung
|
||||||
|
</p>
|
||||||
|
<p className="max-w-2xl text-lg leading-8 text-foreground/78">
|
||||||
|
Ich baue Websites für Handwerk, Praxen, Salons und
|
||||||
|
Dienstleister aus der Region. Direkt, glaubwürdig und so
|
||||||
|
reduziert, dass der nächste Kontakt naheliegt.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 border-t border-border pt-5 text-sm uppercase tracking-[0.2em] text-muted-foreground sm:grid-cols-3">
|
||||||
|
<span>Antwort in 24h</span>
|
||||||
|
<span>DSGVO-arm</span>
|
||||||
|
<span>Hosting aus DE</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside className="relative flex min-h-[520px] flex-col justify-between border-t border-primary-foreground/25 bg-primary px-5 py-5 text-primary-foreground sm:px-8 lg:border-l lg:border-t-0 lg:px-12">
|
||||||
|
<div className="flex items-start justify-between border-b border-primary-foreground/30 pb-5 text-xs uppercase tracking-[0.28em]">
|
||||||
|
<span>Creative Direction</span>
|
||||||
|
<span>2026</span>
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-32 right-10 h-36 w-36 border border-primary-foreground/35" />
|
||||||
|
<div className="relative mt-28 max-w-xl">
|
||||||
|
<p className="text-[clamp(4rem,10vw,9rem)] font-black uppercase leading-[0.75] tracking-normal">
|
||||||
|
01
|
||||||
|
</p>
|
||||||
|
<p className="mt-7 max-w-sm text-2xl font-semibold uppercase leading-none">
|
||||||
|
Klarer Auftritt. Harte Kante. Weniger Agenturlärm.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="#kontakt"
|
||||||
|
className="group inline-flex w-fit items-center gap-3 border border-primary-foreground px-5 py-4 text-sm font-semibold uppercase tracking-[0.18em] transition hover:bg-primary-foreground hover:text-primary"
|
||||||
|
>
|
||||||
|
Projekt anfragen
|
||||||
|
<ArrowUpRight className="size-5 transition group-hover:-translate-y-0.5 group-hover:translate-x-0.5" />
|
||||||
|
</a>
|
||||||
|
</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="leistungen"
|
||||||
|
className="grid border-b border-border lg:grid-cols-[0.36fr_0.64fr]"
|
||||||
|
>
|
||||||
|
<div className="border-b border-border px-5 py-12 sm:px-8 lg:border-b-0 lg:border-r lg:px-12 lg:py-20">
|
||||||
|
<p className="text-sm uppercase tracking-[0.3em] text-primary">
|
||||||
|
Leistungen (02)
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-6 max-w-[9ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
|
||||||
|
Vom Brief zur Seite
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="divide-y divide-border">
|
||||||
|
{services.map((service) => (
|
||||||
|
<article
|
||||||
|
key={service.number}
|
||||||
|
className="grid gap-8 px-5 py-10 sm:px-8 md:grid-cols-[7rem_0.4fr_1fr] lg:px-12"
|
||||||
|
>
|
||||||
|
<span className="text-5xl font-black text-primary">
|
||||||
|
{service.number}
|
||||||
|
</span>
|
||||||
|
<h3 className="text-3xl font-black uppercase leading-none">
|
||||||
|
{service.title}
|
||||||
|
</h3>
|
||||||
|
<p className="max-w-2xl text-lg leading-8 text-muted-foreground">
|
||||||
|
{service.text}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="grid border-b border-border lg:grid-cols-2">
|
||||||
|
<div className="bg-foreground px-5 py-14 text-background sm:px-8 lg:px-12 lg:py-24">
|
||||||
|
<p className="text-sm uppercase tracking-[0.3em] text-primary">
|
||||||
|
Deliverables (03)
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-8 max-w-[10ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
|
||||||
|
Was am Ende steht
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="grid content-center gap-4 px-5 py-12 sm:px-8 lg:px-12">
|
||||||
|
{deliverables.map((item) => (
|
||||||
|
<div
|
||||||
|
key={item}
|
||||||
|
className="flex items-center gap-4 border-b border-border pb-4 text-lg font-semibold uppercase tracking-[0.08em]"
|
||||||
|
>
|
||||||
|
<Check className="size-5 text-primary" />
|
||||||
|
<span>{item}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="pakete" className="border-b border-border px-5 py-14 sm:px-8 lg:px-12 lg:py-24">
|
||||||
|
<div className="grid gap-8 lg:grid-cols-[0.45fr_0.55fr]">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm uppercase tracking-[0.3em] text-primary">
|
||||||
|
Pakete (04)
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-8 max-w-[9ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
|
||||||
|
Kosten ohne Nebel
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-4">
|
||||||
|
{packages.map((item) => (
|
||||||
|
<article
|
||||||
|
key={item.name}
|
||||||
|
className="grid gap-4 border border-border p-5 sm:grid-cols-[0.5fr_0.5fr] sm:p-6"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm uppercase tracking-[0.24em] text-primary">
|
||||||
|
{item.name}
|
||||||
|
</p>
|
||||||
|
<p className="mt-4 text-4xl font-black uppercase">
|
||||||
|
{item.price}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p className="self-end text-lg leading-7 text-muted-foreground">
|
||||||
|
{item.detail}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="kontakt"
|
||||||
|
className="grid min-h-[620px] lg:grid-cols-[0.72fr_0.28fr]"
|
||||||
|
>
|
||||||
|
<div className="px-5 py-14 sm:px-8 lg:px-12 lg:py-24">
|
||||||
|
<p className="text-sm uppercase tracking-[0.3em] text-primary">
|
||||||
|
Kontakt (05)
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-8 max-w-[12ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl lg:text-8xl">
|
||||||
|
Erzählen Sie mir kurz vom Projekt
|
||||||
|
</h2>
|
||||||
|
<p className="mt-8 max-w-2xl text-xl leading-8 text-muted-foreground">
|
||||||
|
Ein paar Sätze reichen: Was bieten Sie an, was soll die Website
|
||||||
|
leisten, und wann soll sie online sein?
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="mailto:hallo@matthias-meister.com"
|
||||||
|
className="mt-10 inline-flex items-center gap-3 bg-primary px-6 py-5 text-sm font-black uppercase tracking-[0.18em] text-primary-foreground transition hover:bg-foreground hover:text-background"
|
||||||
|
>
|
||||||
|
<CornerDownRight className="size-5" />
|
||||||
|
Anfrage per Mail senden
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-end gap-6 bg-primary px-5 py-10 text-primary-foreground sm:px-8 lg:px-10">
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<Mail className="size-5" />
|
||||||
|
<span>hallo@matthias-meister.com</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<Phone className="size-5" />
|
||||||
|
<span>Rückmeldung innerhalb von 24 Stunden</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<MapPin className="size-5" />
|
||||||
|
<span>Regionale KMU in Deutschland</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { CanvaLanding };
|
||||||
@@ -1,13 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { About19 } from "@/components/about19";
|
import { CanvaLanding } from "@/components/canva-landing";
|
||||||
import { Contact21 } from "@/components/contact21";
|
|
||||||
import { Faq7 } from "@/components/faq7";
|
|
||||||
import { Feature284 } from "@/components/feature284";
|
|
||||||
import { Footer27 } from "@/components/footer27";
|
|
||||||
import { Hero235 } from "@/components/hero235";
|
|
||||||
import { Pricing4 } from "@/components/pricing4";
|
|
||||||
import { Stats11 } from "@/components/stats11";
|
|
||||||
import CTASection from "@/components/cta";
|
|
||||||
import "@/styles/global.css";
|
import "@/styles/global.css";
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -25,16 +17,6 @@ import "@/styles/global.css";
|
|||||||
defer></script>
|
defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main class="w-full overflow-x-hidden">
|
<CanvaLanding />
|
||||||
<Hero235 client:load />
|
|
||||||
<CTASection client:load />
|
|
||||||
<Feature284 client:load />
|
|
||||||
<Stats11 client:load />
|
|
||||||
<Pricing4 client:load />
|
|
||||||
<Faq7 client:load />
|
|
||||||
<About19 client:load />
|
|
||||||
<Contact21 client:load />
|
|
||||||
<Footer27 client:load />
|
|
||||||
</main>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -49,38 +49,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: oklch(0.982 0.012 82);
|
--background: oklch(0.115 0.012 22);
|
||||||
--foreground: oklch(0.235 0.024 72);
|
--foreground: oklch(0.965 0.013 76);
|
||||||
--card: oklch(0.996 0.006 82);
|
--card: oklch(0.16 0.014 22);
|
||||||
--card-foreground: oklch(0.235 0.024 72);
|
--card-foreground: oklch(0.965 0.013 76);
|
||||||
--popover: oklch(0.996 0.006 82);
|
--popover: oklch(0.16 0.014 22);
|
||||||
--popover-foreground: oklch(0.235 0.024 72);
|
--popover-foreground: oklch(0.965 0.013 76);
|
||||||
--primary: oklch(0.285 0.045 148);
|
--primary: oklch(0.61 0.235 27);
|
||||||
--primary-foreground: oklch(0.985 0.011 82);
|
--primary-foreground: oklch(0.985 0.01 76);
|
||||||
--secondary: oklch(0.915 0.028 82);
|
--secondary: oklch(0.22 0.016 22);
|
||||||
--secondary-foreground: oklch(0.275 0.031 72);
|
--secondary-foreground: oklch(0.965 0.013 76);
|
||||||
--muted: oklch(0.94 0.018 82);
|
--muted: oklch(0.19 0.014 22);
|
||||||
--muted-foreground: oklch(0.485 0.024 72);
|
--muted-foreground: oklch(0.73 0.021 76);
|
||||||
--accent: oklch(0.705 0.079 141);
|
--accent: oklch(0.61 0.235 27);
|
||||||
--accent-foreground: oklch(0.17 0.026 148);
|
--accent-foreground: oklch(0.985 0.01 76);
|
||||||
--destructive: oklch(0.56 0.19 27);
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
--border: oklch(0.86 0.018 80);
|
--border: oklch(0.965 0.013 76 / 20%);
|
||||||
--input: oklch(0.84 0.018 80);
|
--input: oklch(0.965 0.013 76 / 20%);
|
||||||
--ring: oklch(0.53 0.071 142);
|
--ring: oklch(0.61 0.235 27);
|
||||||
--chart-1: oklch(0.66 0.08 142);
|
--chart-1: oklch(0.87 0 0);
|
||||||
--chart-2: oklch(0.55 0.055 72);
|
--chart-2: oklch(0.556 0 0);
|
||||||
--chart-3: oklch(0.47 0.04 215);
|
--chart-3: oklch(0.439 0 0);
|
||||||
--chart-4: oklch(0.72 0.055 93);
|
--chart-4: oklch(0.371 0 0);
|
||||||
--chart-5: oklch(0.38 0.046 148);
|
--chart-5: oklch(0.269 0 0);
|
||||||
--radius: 0.5rem;
|
--radius: 0.625rem;
|
||||||
--sidebar: oklch(0.965 0.012 82);
|
--sidebar: oklch(0.16 0.014 22);
|
||||||
--sidebar-foreground: oklch(0.235 0.024 72);
|
--sidebar-foreground: oklch(0.965 0.013 76);
|
||||||
--sidebar-primary: oklch(0.285 0.045 148);
|
--sidebar-primary: oklch(0.61 0.235 27);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0.011 82);
|
--sidebar-primary-foreground: oklch(0.985 0.01 76);
|
||||||
--sidebar-accent: oklch(0.93 0.02 82);
|
--sidebar-accent: oklch(0.22 0.016 22);
|
||||||
--sidebar-accent-foreground: oklch(0.275 0.031 72);
|
--sidebar-accent-foreground: oklch(0.965 0.013 76);
|
||||||
--sidebar-border: oklch(0.86 0.018 80);
|
--sidebar-border: oklch(0.965 0.013 76 / 20%);
|
||||||
--sidebar-ring: oklch(0.53 0.071 142);
|
--sidebar-ring: oklch(0.61 0.235 27);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@@ -122,20 +122,11 @@
|
|||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground antialiased;
|
@apply bg-background text-foreground;
|
||||||
|
text-rendering: geometricPrecision;
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
@apply scroll-smooth font-sans;
|
@apply font-sans;
|
||||||
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
|
||||||
background-image:
|
|
||||||
linear-gradient(to bottom, oklch(0.988 0.01 82), var(--background) 46rem),
|
|
||||||
linear-gradient(90deg, oklch(0.86 0.018 80 / 0.28) 1px, transparent 1px);
|
|
||||||
background-size: auto, 6rem 6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
background: oklch(0.78 0.075 141 / 0.35);
|
|
||||||
}
|
|
||||||
|
|||||||
32
tests/landing-content.test.mjs
Normal file
32
tests/landing-content.test.mjs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
const componentPath = new URL("../src/components/canva-landing.tsx", import.meta.url);
|
||||||
|
|
||||||
|
test("Canva landing component contains the core brief anchors", async () => {
|
||||||
|
const source = await readFile(componentPath, "utf8");
|
||||||
|
|
||||||
|
for (const phrase of ["Projektbrief", "01", "Website", "Kontakt", "für", "müssen", "Änderungen"]) {
|
||||||
|
assert.match(source, new RegExp(phrase));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Canva landing component uses real German umlauts in visible copy", async () => {
|
||||||
|
const source = await readFile(componentPath, "utf8");
|
||||||
|
|
||||||
|
for (const asciiFallback of [
|
||||||
|
"fuer",
|
||||||
|
"muessen",
|
||||||
|
"spaetere",
|
||||||
|
"Aenderungen",
|
||||||
|
"glaubwuerdig",
|
||||||
|
"naechste",
|
||||||
|
"Agenturlaerm",
|
||||||
|
"Erzaehlen",
|
||||||
|
"Saetze",
|
||||||
|
"Rueckmeldung",
|
||||||
|
]) {
|
||||||
|
assert.doesNotMatch(source, new RegExp(asciiFallback));
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user