feat: plakat redesign phase 4 (chat window, contact, footer, cleanup)

- AI chat moves into a window/sheet, opened from a new speech-bubble card on
  the desk; it now respects the chatbot consent and asks before sending
  anything to n8n (previously it ignored the consent)
- Contact as red poster section with big mail address and the form on a paper
  card; validation errors are finally shown inline (aria-invalid, role=alert)
- Footer as ink strip with dk0 wordmark, legal links and a button to reopen
  the privacy settings
- "About me" in the header opens the about window on the home page
- Remove the old About section and its parts (BentoChat, ActivityFeed,
  CurrentlyReading, ReadBooks), the unused ChatWidget, Projects and BentoGrid,
  their tests, wrappers, loaders, types and texts
- scripts/directus-cleanup-redesign.mjs: dry run by default, --apply archives
  hobbies, tech stack categories and old content pages and deletes four
  unused message overrides after a full JSON backup; run only after the
  redesign is live on production

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
denshooter
2026-10-15 19:05:24 +02:00
parent 08e856120b
commit 4f5089ce38
33 changed files with 534 additions and 2572 deletions
-32
View File
@@ -1,32 +0,0 @@
import { test, expect } from "@playwright/test";
test.describe("ActivityFeed reload rendering", () => {
test("feed stays visible and dark after reload", async ({ page }) => {
await page.goto("/en", { waitUntil: "domcontentloaded" });
const feed = page.locator('[class*="bg-black/95"]').filter({ hasText: "Live Activity" }).first();
await expect(feed).toBeVisible({ timeout: 15000 });
const initialBox = await feed.boundingBox();
expect(initialBox).not.toBeNull();
expect(initialBox!.width).toBeGreaterThan(200);
expect(initialBox!.height).toBeGreaterThan(30);
const initialOpacity = await feed.evaluate((el) => getComputedStyle(el).opacity);
expect(Number(initialOpacity)).toBeGreaterThan(0.5);
await page.reload({ waitUntil: "domcontentloaded" });
const feedAfter = page.locator('[class*="bg-black/95"]').filter({ hasText: "Live Activity" }).first();
await expect(feedAfter).toBeVisible({ timeout: 15000 });
const afterBox = await feedAfter.boundingBox();
expect(afterBox).not.toBeNull();
expect(afterBox!.width).toBeGreaterThan(200);
expect(afterBox!.height).toBeGreaterThan(30);
const afterOpacity = await feedAfter.evaluate((el) => getComputedStyle(el).opacity);
expect(Number(afterOpacity)).toBeGreaterThan(0.5);
});
});