diff --git a/.gitignore b/.gitignore index 90150af..b10ccda 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ dist/ # dependencies node_modules/ +.worktrees/ # logs npm-debug.log* @@ -23,4 +24,4 @@ pnpm-debug.log* # jetbrains setting folder .idea/ .kilo -.env.local \ No newline at end of file +.env.local diff --git a/docs/superpowers/plans/2026-05-05-canva-redesign.md b/docs/superpowers/plans/2026-05-05-canva-redesign.md new file mode 100644 index 0000000..05da11d --- /dev/null +++ b/docs/superpowers/plans/2026-05-05-canva-redesign.md @@ -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. diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..7426fb3 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + msw: true + sharp: true diff --git a/src/components/canva-landing.tsx b/src/components/canva-landing.tsx new file mode 100644 index 0000000..068e843 --- /dev/null +++ b/src/components/canva-landing.tsx @@ -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 ( +
+
+
+
+ + Matthias Meister + + +
+ +
+

+ Projektbrief für regionale Unternehmen +

+

+ Website ohne Umweg +

+
+

+ Strategie trifft Umsetzung +

+

+ 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. +

+
+
+ +
+ Antwort in 24h + DSGVO-arm + Hosting aus DE +
+
+ + +
+ +
+
+

+ Leistungen (02) +

+

+ Vom Brief zur Seite +

+
+
+ {services.map((service) => ( +
+ + {service.number} + +

+ {service.title} +

+

+ {service.text} +

+
+ ))} +
+
+ +
+
+

+ Deliverables (03) +

+

+ Was am Ende steht +

+
+
+ {deliverables.map((item) => ( +
+ + {item} +
+ ))} +
+
+ +
+
+
+

+ Pakete (04) +

+

+ Kosten ohne Nebel +

+
+
+ {packages.map((item) => ( +
+
+

+ {item.name} +

+

+ {item.price} +

+
+

+ {item.detail} +

+
+ ))} +
+
+
+ +
+
+

+ Kontakt (05) +

+

+ Erzählen Sie mir kurz vom Projekt +

+

+ Ein paar Sätze reichen: Was bieten Sie an, was soll die Website + leisten, und wann soll sie online sein? +

+ + + Anfrage per Mail senden + +
+
+
+ + hallo@matthias-meister.com +
+
+ + Rückmeldung innerhalb von 24 Stunden +
+
+ + Regionale KMU in Deutschland +
+
+
+
+ ); +}; + +export { CanvaLanding }; diff --git a/src/pages/index.astro b/src/pages/index.astro index 30f4e14..b8dd312 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,13 +1,5 @@ --- -import { About19 } from "@/components/about19"; -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 { CanvaLanding } from "@/components/canva-landing"; import "@/styles/global.css"; --- @@ -25,16 +17,6 @@ import "@/styles/global.css"; defer> -
- - - - - - - - - -
+ diff --git a/src/styles/global.css b/src/styles/global.css index d9c4938..a050fef 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -49,38 +49,38 @@ } :root { - --background: oklch(0.982 0.012 82); - --foreground: oklch(0.235 0.024 72); - --card: oklch(0.996 0.006 82); - --card-foreground: oklch(0.235 0.024 72); - --popover: oklch(0.996 0.006 82); - --popover-foreground: oklch(0.235 0.024 72); - --primary: oklch(0.285 0.045 148); - --primary-foreground: oklch(0.985 0.011 82); - --secondary: oklch(0.915 0.028 82); - --secondary-foreground: oklch(0.275 0.031 72); - --muted: oklch(0.94 0.018 82); - --muted-foreground: oklch(0.485 0.024 72); - --accent: oklch(0.705 0.079 141); - --accent-foreground: oklch(0.17 0.026 148); - --destructive: oklch(0.56 0.19 27); - --border: oklch(0.86 0.018 80); - --input: oklch(0.84 0.018 80); - --ring: oklch(0.53 0.071 142); - --chart-1: oklch(0.66 0.08 142); - --chart-2: oklch(0.55 0.055 72); - --chart-3: oklch(0.47 0.04 215); - --chart-4: oklch(0.72 0.055 93); - --chart-5: oklch(0.38 0.046 148); - --radius: 0.5rem; - --sidebar: oklch(0.965 0.012 82); - --sidebar-foreground: oklch(0.235 0.024 72); - --sidebar-primary: oklch(0.285 0.045 148); - --sidebar-primary-foreground: oklch(0.985 0.011 82); - --sidebar-accent: oklch(0.93 0.02 82); - --sidebar-accent-foreground: oklch(0.275 0.031 72); - --sidebar-border: oklch(0.86 0.018 80); - --sidebar-ring: oklch(0.53 0.071 142); + --background: oklch(0.115 0.012 22); + --foreground: oklch(0.965 0.013 76); + --card: oklch(0.16 0.014 22); + --card-foreground: oklch(0.965 0.013 76); + --popover: oklch(0.16 0.014 22); + --popover-foreground: oklch(0.965 0.013 76); + --primary: oklch(0.61 0.235 27); + --primary-foreground: oklch(0.985 0.01 76); + --secondary: oklch(0.22 0.016 22); + --secondary-foreground: oklch(0.965 0.013 76); + --muted: oklch(0.19 0.014 22); + --muted-foreground: oklch(0.73 0.021 76); + --accent: oklch(0.61 0.235 27); + --accent-foreground: oklch(0.985 0.01 76); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.965 0.013 76 / 20%); + --input: oklch(0.965 0.013 76 / 20%); + --ring: oklch(0.61 0.235 27); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --radius: 0.625rem; + --sidebar: oklch(0.16 0.014 22); + --sidebar-foreground: oklch(0.965 0.013 76); + --sidebar-primary: oklch(0.61 0.235 27); + --sidebar-primary-foreground: oklch(0.985 0.01 76); + --sidebar-accent: oklch(0.22 0.016 22); + --sidebar-accent-foreground: oklch(0.965 0.013 76); + --sidebar-border: oklch(0.965 0.013 76 / 20%); + --sidebar-ring: oklch(0.61 0.235 27); } .dark { @@ -122,20 +122,11 @@ @apply border-border outline-ring/50; } body { - @apply bg-background text-foreground antialiased; + @apply bg-background text-foreground; + text-rendering: geometricPrecision; } 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); -} diff --git a/tests/landing-content.test.mjs b/tests/landing-content.test.mjs new file mode 100644 index 0000000..7067d91 --- /dev/null +++ b/tests/landing-content.test.mjs @@ -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)); + } +});