feat(privat/CV): sync to latest upstream (cv-upstream/main, 30f9608b)
This commit is contained in:
@@ -0,0 +1,794 @@
|
||||
# Design Document: personal-homepage (andreknie.de)
|
||||
|
||||
## Overview
|
||||
|
||||
A personal homepage for Dr. Andre Knie at andreknie.de, built as a modern single-page application with multiple routes. The site serves as a central platform for trust-building, content access, speaking visibility, consulting positioning, and lead generation. It uses the same tech stack and architectural patterns as the Projekt-KIQ-HP reference implementation: Vite 8 + React 19 (JSX), react-router-dom v7, Express 5 backend, CSS custom properties for theming, glass morphism design, and Docker + Caddy deployment.
|
||||
|
||||
Content is managed via markdown and YAML files in a `content/` directory (file-based CMS), eliminating the need for a database for content storage. The backend handles only form submissions (contact, talk requests, newsletter signup, resource lead capture) via nodemailer.
|
||||
|
||||
All colors are defined exclusively through CSS custom properties, making the entire color scheme changeable from a single `:root` block.
|
||||
|
||||
### Content Visibility Model
|
||||
|
||||
Each content object has a `visibility` field:
|
||||
- **primary** — actively featured on the homepage, content highlights, and CTAs. These are the core topics (KI, Transformation, Leadership, Digitalisierung).
|
||||
- **secondary** — visible in content archives and when browsing/filtering, but NOT featured on the homepage hero, highlights section, or CTAs. These are adjacent topics (Fehlerkultur, Agilität-Seminare, non-AI workshops) that demonstrate breadth without diluting the core positioning.
|
||||
|
||||
Default visibility is `primary` if omitted. The homepage highlights section and ContentHighlights component only pull `primary` content. The full archive pages (Kniepunkt, Podcast, Speaking, etc.) show all content regardless of visibility.
|
||||
|
||||
## Architecture
|
||||
|
||||
### High-Level Architecture
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Client (Browser)"
|
||||
SPA[React 19 SPA]
|
||||
Router[react-router-dom v7]
|
||||
end
|
||||
|
||||
subgraph "Build Time"
|
||||
Vite[Vite 8 Build]
|
||||
ContentLoader[Content Loader Plugin]
|
||||
MD[Markdown Files]
|
||||
YAML[YAML Metadata Files]
|
||||
end
|
||||
|
||||
subgraph "Production Server"
|
||||
Caddy[Caddy Reverse Proxy]
|
||||
Static[Static Files /opt/andreknie/site]
|
||||
Express[Express 5 Backend :3003]
|
||||
Nodemailer[Nodemailer]
|
||||
end
|
||||
|
||||
subgraph "External"
|
||||
SMTP[SMTP Server]
|
||||
PodcastHost[Podcast Hosting Platform]
|
||||
end
|
||||
|
||||
MD --> ContentLoader
|
||||
YAML --> ContentLoader
|
||||
ContentLoader --> Vite
|
||||
Vite --> Static
|
||||
|
||||
SPA --> Router
|
||||
Router --> Caddy
|
||||
Caddy -->|/api/*| Express
|
||||
Caddy -->|static| Static
|
||||
Express --> Nodemailer
|
||||
Nodemailer --> SMTP
|
||||
SPA -->|audio links| PodcastHost
|
||||
```
|
||||
|
||||
### Content Pipeline
|
||||
|
||||
Content flows from markdown/YAML files through a Vite build-time plugin that transforms them into JSON modules importable by React components. This means:
|
||||
|
||||
1. Stakeholder creates/edits `.md` or `.yaml` files in `content/`
|
||||
2. A Vite plugin (`vite-plugin-content`) reads and transforms these at build time
|
||||
3. The built SPA includes all content as static JSON data
|
||||
4. New content becomes visible after the next `npm run build` + deploy
|
||||
|
||||
### Request Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant V as Visitor
|
||||
participant C as Caddy
|
||||
participant S as Static Files
|
||||
participant E as Express Backend
|
||||
|
||||
V->>C: GET /kniepunkt
|
||||
C->>S: Serve index.html (SPA)
|
||||
S-->>V: React app loads
|
||||
|
||||
V->>C: POST /api/contact
|
||||
C->>E: Proxy to Express
|
||||
E->>E: Validate + store
|
||||
E->>E: Send confirmation email
|
||||
E-->>V: 201 Created
|
||||
```
|
||||
|
||||
## Components and Interfaces
|
||||
|
||||
### Component Hierarchy
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
App[App.jsx]
|
||||
App --> BG[BackgroundPattern]
|
||||
App --> Nav[Navigation]
|
||||
App --> Routes[Routes]
|
||||
App --> Footer[Footer]
|
||||
App --> Dock[BottomDock]
|
||||
|
||||
Routes --> Home[HomePage]
|
||||
Routes --> Kniepunkt[KniepunktPage]
|
||||
Routes --> Podcast[PodcastPage]
|
||||
Routes --> Speaking[SpeakingPage]
|
||||
Routes --> Consulting[ConsultingPage]
|
||||
Routes --> Resources[ResourcesPage]
|
||||
Routes --> Contact[ContactPage]
|
||||
Routes --> Impressum[ImpressumPage]
|
||||
Routes --> Datenschutz[DatenschutzPage]
|
||||
|
||||
Home --> Hero[Hero]
|
||||
Home --> Stats[Stats]
|
||||
Home --> ContentHighlights[ContentHighlights]
|
||||
Home --> AudienceSection[AudienceSection]
|
||||
Home --> CTA[CTASection]
|
||||
|
||||
Kniepunkt --> IssueList[IssueList]
|
||||
Kniepunkt --> IssueDetail[IssueDetail]
|
||||
|
||||
Podcast --> EpisodeList[EpisodeList]
|
||||
Podcast --> EpisodeCard[EpisodeCard]
|
||||
|
||||
Speaking --> TalkList[TalkList]
|
||||
Speaking --> TalkRequestForm[TalkRequestForm]
|
||||
|
||||
Consulting --> ExampleGrid[ExampleGrid]
|
||||
Consulting --> ExampleCard[ExampleCard]
|
||||
|
||||
Resources --> ResourceList[ResourceList]
|
||||
Resources --> LeadCaptureForm[LeadCaptureForm]
|
||||
|
||||
Contact --> ContactForm[ContactForm]
|
||||
|
||||
Footer --> NewsletterForm[NewsletterForm]
|
||||
Footer --> DHiveLink[dHive Cross-Reference]
|
||||
|
||||
```
|
||||
|
||||
### File/Folder Structure
|
||||
|
||||
```
|
||||
andreknie.de/
|
||||
├── content/ # File-based CMS (stakeholder edits here)
|
||||
│ ├── posts/ # LinkedIn posts & articles (.md)
|
||||
│ │ └── 2025-01-15-ai-act.md
|
||||
│ ├── kniepunkt/ # Column issues (.md)
|
||||
│ │ └── 038-sovereign-ai.md
|
||||
│ ├── podcast/ # Episode metadata (.yaml)
|
||||
│ │ └── ep-012-llm-limits.yaml
|
||||
│ ├── talks/ # Talk metadata (.yaml)
|
||||
│ │ └── ki-fraitag-2025.yaml
|
||||
│ ├── consulting/ # Consulting examples (.yaml)
|
||||
│ │ └── mittelstand-predictive.yaml
|
||||
│ ├── resources/ # Resource metadata (.yaml)
|
||||
│ │ └── ai-readiness-checklist.yaml
|
||||
│ ├── events/ # Events (.yaml)
|
||||
│ │ └── 2025-09-ai-masterclass.yaml
|
||||
│ └── meta/ # Site-wide metadata
|
||||
│ ├── profile.yaml # Stakeholder profile data
|
||||
│ ├── stats.yaml # Stats section numbers
|
||||
│ └── audiences.yaml # Target audience descriptions
|
||||
├── public/
|
||||
│ ├── images/ # Static images
|
||||
│ ├── logos/ # Logos (personal + dHive)
|
||||
│ ├── resources/ # Downloadable files (PDFs etc.)
|
||||
│ └── patterns/ # Background pattern SVGs
|
||||
├── src/
|
||||
│ ├── components/ # Shared UI components
|
||||
│ │ ├── BackgroundPattern.jsx + .css
|
||||
│ │ ├── BottomDock.jsx + .css
|
||||
│ │ ├── Navigation.jsx + .css
|
||||
│ │ ├── Footer.jsx + .css
|
||||
│ │ ├── Hero.jsx + .css
|
||||
│ │ ├── Stats.jsx + .css
|
||||
│ │ ├── ContentHighlights.jsx + .css
|
||||
│ │ ├── AudienceSection.jsx + .css
|
||||
│ │ ├── CTASection.jsx + .css
|
||||
│ │ ├── NewsletterForm.jsx + .css
|
||||
│ │ ├── ContactForm.jsx + .css
|
||||
│ │ ├── TalkRequestForm.jsx + .css
|
||||
│ │ ├── LeadCaptureForm.jsx + .css
|
||||
│ │ ├── ContentFilter.jsx + .css
|
||||
│ │ ├── Pagination.jsx + .css
|
||||
│ │ ├── GlassCard.jsx + .css
|
||||
│ │ ├── BentoGrid.jsx + .css
|
||||
│ │ ├── ScrollToTop.jsx
|
||||
│ │ └── SEOHead.jsx
|
||||
│ ├── pages/
|
||||
│ │ ├── Home.jsx
|
||||
│ │ ├── Kniepunkt.jsx + .css
|
||||
│ │ ├── KniepunktIssue.jsx + .css
|
||||
│ │ ├── Podcast.jsx + .css
|
||||
│ │ ├── Speaking.jsx + .css
|
||||
│ │ ├── Consulting.jsx + .css
|
||||
│ │ ├── Resources.jsx + .css
|
||||
│ │ ├── Contact.jsx + .css
|
||||
│ │ ├── Impressum.jsx
|
||||
│ │ └── Datenschutz.jsx
|
||||
│ ├── data/ # Build-time generated JSON (via Vite plugin)
|
||||
│ │ └── (auto-generated)
|
||||
│ ├── hooks/ # Custom React hooks
|
||||
│ │ ├── useContent.js # Content loading/filtering
|
||||
│ │ └── useFormSubmit.js # Form submission with validation
|
||||
│ ├── utils/
|
||||
│ │ ├── contentLoader.js # Content transformation utilities
|
||||
│ │ ├── validation.js # Form validation logic
|
||||
│ │ └── formatDate.js # Date formatting (German locale)
|
||||
│ ├── App.jsx
|
||||
│ ├── index.css # Global styles + CSS variables
|
||||
│ └── main.jsx
|
||||
├── server/
|
||||
│ ├── index.js # Express 5 entry point
|
||||
│ ├── routes/
|
||||
│ │ ├── contact.js # Contact form handler
|
||||
│ │ ├── talk-request.js # Talk request handler
|
||||
│ │ ├── newsletter.js # Newsletter signup handler
|
||||
│ │ └── resource-download.js # Lead capture handler
|
||||
│ ├── middleware/
|
||||
│ │ ├── rateLimiter.js # Rate limiting
|
||||
│ │ └── validator.js # Input validation
|
||||
│ ├── services/
|
||||
│ │ ├── mailer.js # Nodemailer configuration
|
||||
│ │ └── confirmationToken.js # Token generation/verification
|
||||
│ └── data/ # Runtime data (leads, pending confirmations)
|
||||
│ └── .gitkeep
|
||||
├── plugins/
|
||||
│ └── vite-plugin-content.js # Vite plugin for content transformation
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
├── Caddyfile
|
||||
├── package.json
|
||||
├── vite.config.js
|
||||
└── .env.example
|
||||
```
|
||||
|
||||
### Shared Components
|
||||
|
||||
| Component | Purpose | Pattern from Blueprint |
|
||||
|-----------|---------|----------------------|
|
||||
| `BackgroundPattern` | Animated drifting SVG background | Same mirrored-quadrant technique |
|
||||
| `BottomDock` | Fixed bottom navigation pill with rotating light border | Same dock pattern, adapted nav items |
|
||||
| `Navigation` | Top bar with logo + hamburger overlay menu | Same overlay pattern |
|
||||
| `Footer` | CTA section + newsletter form + legal links + dHive reference | Extended from blueprint |
|
||||
| `GlassCard` | Reusable glass-morphism card | Extracted from blueprint's inline usage |
|
||||
| `BentoGrid` | Responsive grid layout for content cards | Same bento pattern |
|
||||
| `ContentFilter` | Tag-based filtering UI | New component |
|
||||
| `Pagination` | Page navigation for lists (10 items/page) | New component |
|
||||
|
||||
### Page Composition
|
||||
|
||||
| Page | URL | Key Components |
|
||||
|------|-----|---------------|
|
||||
| Home | `/` | Hero, Stats, ContentHighlights, AudienceSection, CTASection |
|
||||
| Kniepunkt | `/kniepunkt` | IssueList with ContentFilter |
|
||||
| Kniepunkt Issue | `/kniepunkt/:slug` | Full issue content (rendered markdown) |
|
||||
| Podcast | `/podcast` | EpisodeList with Pagination |
|
||||
| Speaking | `/speaking` | TalkList + TalkRequestForm |
|
||||
| Consulting | `/consulting` | ExampleGrid (BentoGrid of ExampleCards) |
|
||||
| Resources | `/resources` | ResourceList + LeadCaptureForm (modal) |
|
||||
| Contact | `/kontakt` | ContactForm |
|
||||
| Impressum | `/impressum` | Static legal content |
|
||||
| Datenschutz | `/datenschutz` | Static legal content |
|
||||
|
||||
## Data Models
|
||||
|
||||
### Content File Schemas
|
||||
|
||||
#### Post (Markdown with Frontmatter)
|
||||
|
||||
```yaml
|
||||
# content/posts/2025-01-15-ai-act.md frontmatter
|
||||
---
|
||||
title: "Der AI Act ist da — was bedeutet das für den Mittelstand?"
|
||||
date: 2025-01-15
|
||||
tags: [ai-act, regulation, mittelstand]
|
||||
summary: "Eine nüchterne Einordnung des EU AI Act für deutsche Unternehmen."
|
||||
source: linkedin # or "original"
|
||||
visibility: primary # primary = featured on homepage/highlights | secondary = visible in archive only
|
||||
---
|
||||
# Markdown body follows...
|
||||
```
|
||||
|
||||
#### Kniepunkt Issue (Markdown with Frontmatter)
|
||||
|
||||
```yaml
|
||||
# content/kniepunkt/038-sovereign-ai.md frontmatter
|
||||
---
|
||||
title: "Souveräne KI — mehr als ein Buzzword"
|
||||
issue: 38
|
||||
date: 2025-06-01
|
||||
tags: [sovereignty, infrastructure, germany]
|
||||
summary: "Warum digitale Souveränität bei KI kein Marketing-Claim sein darf."
|
||||
---
|
||||
# Markdown body follows...
|
||||
```
|
||||
|
||||
#### Podcast Episode (YAML)
|
||||
|
||||
```yaml
|
||||
# content/podcast/ep-012-llm-limits.yaml
|
||||
id: ep-012-llm-limits
|
||||
title: "Die Grenzen großer Sprachmodelle"
|
||||
date: 2025-05-15
|
||||
summary: "Was LLMs können, was nicht — und warum das wichtig ist."
|
||||
duration: "42:15"
|
||||
tags: [llm, limits, practical-ai]
|
||||
audio_url: "https://podcast-host.example/episodes/012.mp3"
|
||||
external_links:
|
||||
spotify: "https://open.spotify.com/episode/..."
|
||||
apple: "https://podcasts.apple.com/..."
|
||||
```
|
||||
|
||||
#### Talk (YAML)
|
||||
|
||||
```yaml
|
||||
# content/talks/ki-fraitag-2025.yaml
|
||||
id: ki-fraitag-2025
|
||||
title: "KI im Mittelstand — Chancen ohne Hype"
|
||||
description: "Keynote über praktische KI-Anwendungen für mittelständische Unternehmen."
|
||||
format: keynote # keynote | workshop | panel
|
||||
target_audience: [geschaeftsfuehrung, it-leitung]
|
||||
tags: [mittelstand, practical-ai, sovereignty]
|
||||
visibility: primary # primary = actively promoted on homepage | secondary = visible in archive but not featured
|
||||
events:
|
||||
- name: "KI-FreiTag Nordhessen"
|
||||
date: 2025-03-14
|
||||
location: "Science Park Kassel"
|
||||
- name: "RKW Hessen Workshop"
|
||||
date: 2025-01-20
|
||||
location: "Frankfurt am Main"
|
||||
```
|
||||
|
||||
#### Consulting Example (YAML)
|
||||
|
||||
```yaml
|
||||
# content/consulting/mittelstand-predictive.yaml
|
||||
id: mittelstand-predictive
|
||||
title: "Predictive Maintenance für Produktionsunternehmen"
|
||||
industry: manufacturing
|
||||
organization_size: "200-500" # employees range
|
||||
problem_domain: "Ungeplante Maschinenausfälle verursachten hohe Kosten."
|
||||
approach: "Aufbau eines ML-Modells auf Basis vorhandener Sensordaten, souverän gehostet."
|
||||
outcomes: "40% weniger ungeplante Stillstände innerhalb von 6 Monaten."
|
||||
tags: [predictive-maintenance, manufacturing, ml]
|
||||
```
|
||||
|
||||
#### Resource (YAML)
|
||||
|
||||
```yaml
|
||||
# content/resources/ai-readiness-checklist.yaml
|
||||
id: ai-readiness-checklist
|
||||
title: "AI Readiness Checklist für den Mittelstand"
|
||||
description: "Praktische Checkliste zur Bewertung der KI-Bereitschaft."
|
||||
file: "ai-readiness-checklist.pdf" # relative to public/resources/
|
||||
requires_lead: true
|
||||
tags: [checklist, readiness, mittelstand]
|
||||
```
|
||||
|
||||
#### Event (YAML)
|
||||
|
||||
```yaml
|
||||
# content/events/2025-09-ai-masterclass.yaml
|
||||
id: 2025-09-ai-masterclass
|
||||
title: "AI Masterclass — Science Park Kassel"
|
||||
date: 2025-09-18
|
||||
location: "Science Park Kassel"
|
||||
event_type: workshop # talk | workshop | conference | appearance
|
||||
description: "Ganztägiger Workshop zu praktischer KI-Implementierung."
|
||||
tags: [workshop, kassel, hands-on]
|
||||
```
|
||||
|
||||
#### Profile Metadata (YAML)
|
||||
|
||||
```yaml
|
||||
# content/meta/profile.yaml
|
||||
name: "Dr. Andre Knie"
|
||||
roles:
|
||||
- "AI Expert"
|
||||
- "Consultant"
|
||||
- "Speaker"
|
||||
- "Content Author"
|
||||
- "Managing Director, dHive"
|
||||
summary: "Nüchterne KI-Expertise für den deutschen Mittelstand. Souverän, praktisch, evidenzbasiert."
|
||||
photo: "/images/andre-knie.png"
|
||||
positioning:
|
||||
sovereignty: "Unabhängig von US-Hyperscalern — KI-Lösungen aus und für Deutschland."
|
||||
data_sovereignty: "100% DSGVO- und AI-Act-konform, EU-Hosting."
|
||||
practical: "Fokus auf anwendbare KI, nicht auf Hype."
|
||||
dhive:
|
||||
role: "Founder & Managing Director"
|
||||
url: "https://d-hive.de"
|
||||
description: "Data Hive Cassel — souveräne KI-Infrastruktur für den Mittelstand."
|
||||
audiences:
|
||||
- id: mittelstand
|
||||
label: "Mittelständische Unternehmen"
|
||||
- id: public-admin
|
||||
label: "Öffentliche Verwaltung"
|
||||
- id: social-institutions
|
||||
label: "Soziale Einrichtungen"
|
||||
- id: universities
|
||||
label: "Universitäten & Hochschulen"
|
||||
- id: hr-od
|
||||
label: "Personal- & Organisationsentwicklung"
|
||||
- id: it-leadership
|
||||
label: "IT-Leitung & CTO"
|
||||
- id: executives
|
||||
label: "Geschäftsführung"
|
||||
- id: event-organizers
|
||||
label: "Veranstalter & Speaker-Booking"
|
||||
```
|
||||
|
||||
#### Stats (YAML)
|
||||
|
||||
```yaml
|
||||
# content/meta/stats.yaml
|
||||
items:
|
||||
- value: "50+"
|
||||
label: "Projekte"
|
||||
- value: "13+"
|
||||
label: "Jahre KI"
|
||||
- value: "60+"
|
||||
label: "Publikationen"
|
||||
- value: "100%"
|
||||
label: "DSGVO & AI-Act"
|
||||
```
|
||||
|
||||
### API Data Models
|
||||
|
||||
#### Contact Request (POST /api/contact)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "string (required, max 100)",
|
||||
"email": "string (required, valid email)",
|
||||
"message": "string (required, max 2000)"
|
||||
}
|
||||
```
|
||||
|
||||
#### Talk Request (POST /api/talk-request)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "string (required, max 100)",
|
||||
"email": "string (required, valid email)",
|
||||
"event_name": "string (required, max 200)",
|
||||
"topic": "string (required, max 200)",
|
||||
"message": "string (required, max 2000)"
|
||||
}
|
||||
```
|
||||
|
||||
#### Newsletter Signup (POST /api/newsletter)
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "string (required, valid email)"
|
||||
}
|
||||
```
|
||||
|
||||
#### Resource Lead Capture (POST /api/resource-download)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "string (required, max 100)",
|
||||
"email": "string (required, valid email)",
|
||||
"company": "string (required, max 150)",
|
||||
"resource_id": "string (required)"
|
||||
}
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
|
||||
| Method | Path | Purpose | Auth |
|
||||
|--------|------|---------|------|
|
||||
| POST | `/api/contact` | Submit contact form, triggers confirmation email | Public |
|
||||
| POST | `/api/contact/confirm/:token` | Confirm contact request, forwards to stakeholder | Public |
|
||||
| POST | `/api/talk-request` | Submit talk request, triggers confirmation email | Public |
|
||||
| POST | `/api/talk-request/confirm/:token` | Confirm talk request, forwards to stakeholder | Public |
|
||||
| POST | `/api/newsletter` | Newsletter signup, triggers double opt-in email | Public |
|
||||
| GET | `/api/newsletter/confirm/:token` | Confirm newsletter subscription | Public |
|
||||
| POST | `/api/resource-download` | Submit lead data, returns download URL | Public |
|
||||
| GET | `/api/health` | Health check | Public |
|
||||
|
||||
### Confirmation Token Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant V as Visitor
|
||||
participant API as Express API
|
||||
participant DB as JSON File Store
|
||||
participant Mail as Nodemailer
|
||||
|
||||
V->>API: POST /api/contact {name, email, message}
|
||||
API->>API: Validate input
|
||||
API->>DB: Store pending request + token (expires 24h)
|
||||
API->>Mail: Send confirmation email with link
|
||||
Mail-->>V: Email with confirmation link
|
||||
V->>API: POST /api/contact/confirm/:token
|
||||
API->>DB: Verify token, mark confirmed
|
||||
API->>Mail: Forward message to stakeholder
|
||||
API-->>V: 200 OK (success page)
|
||||
```
|
||||
|
||||
### Theming System
|
||||
|
||||
All visual theming is controlled via CSS custom properties in `:root`. Changing the accent color updates the entire site.
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Base colors — dark theme */
|
||||
--bg-color: #0a0a0a;
|
||||
--bg-elevated: #141414;
|
||||
--text-primary: #f0f0f0;
|
||||
--text-secondary: #999999;
|
||||
|
||||
/* Accent — CHANGE THESE to retheme the entire site */
|
||||
--accent-color: #4a9eff; /* Placeholder: neutral blue */
|
||||
--accent-color-rgb: 74, 158, 255; /* Same as above in RGB for rgba() usage */
|
||||
--accent-gradient: linear-gradient(135deg, #4a9eff, #2563eb);
|
||||
--accent-hover: #2563eb;
|
||||
|
||||
/* Glass morphism */
|
||||
--glass-bg: rgba(25, 25, 25, 0.6);
|
||||
--glass-border: rgba(255, 255, 255, 0.08);
|
||||
--glass-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
||||
|
||||
/* Spacing */
|
||||
--section-padding: 120px;
|
||||
--container-max: 1200px;
|
||||
|
||||
/* Typography */
|
||||
--font-family: 'Inter', sans-serif;
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 800;
|
||||
|
||||
/* Transitions */
|
||||
--transition-smooth: 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--transition-fast: 0.3s ease;
|
||||
|
||||
/* Border radius */
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 16px;
|
||||
--radius-lg: 24px;
|
||||
--radius-pill: 50px;
|
||||
}
|
||||
```
|
||||
|
||||
**Design rationale:** Using `Inter` (from Google Fonts) instead of `Outfit` from the blueprint, as Inter is the stakeholder's established corporate font. The placeholder accent is a neutral blue — the stakeholder will decide final colors later. All accent references use the CSS variables, so a single edit to `:root` changes everything.
|
||||
|
||||
### Visual Effects (from Blueprint)
|
||||
|
||||
| Effect | Implementation | CSS Pattern |
|
||||
|--------|---------------|-------------|
|
||||
| Glass panels | `backdrop-filter: blur(10px)` + semi-transparent bg + subtle border | `.glass-panel` class |
|
||||
| Gradient text | `background-clip: text` with accent gradient | `.text-gradient` class |
|
||||
| Animated background | Fixed SVG pattern with CSS `@keyframes` drift animation | `BackgroundPattern` component |
|
||||
| Hover glow | Radial gradient overlay on hover | `.bento-glow` pattern |
|
||||
| Card lift | `transform: translateY(-8px)` on hover | Applied to all interactive cards |
|
||||
| Dock light | `conic-gradient` rotating border via `::before` pseudo-element | `BottomDock` component |
|
||||
| Fade-up entrance | `@keyframes fadeUp` with opacity + translateY | Hero and section entrances |
|
||||
| Smooth scrolling | `scroll-behavior: smooth` on html | Global CSS |
|
||||
|
||||
### Deployment Architecture
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph "VPS / Server"
|
||||
subgraph "Docker"
|
||||
Backend[Express Container :3003]
|
||||
end
|
||||
Caddy[Caddy Server]
|
||||
StaticFiles[/opt/andreknie/site/]
|
||||
end
|
||||
|
||||
DNS[andreknie.de DNS] --> Caddy
|
||||
Caddy -->|HTTPS auto-cert| Caddy
|
||||
Caddy -->|/api/*| Backend
|
||||
Caddy -->|static| StaticFiles
|
||||
```
|
||||
|
||||
**Caddyfile** for andreknie.de:
|
||||
- Auto HTTPS via Let's Encrypt
|
||||
- Reverse proxy `/api/*` to Express backend on port 3003
|
||||
- Serve static SPA files with `try_files {path} /index.html`
|
||||
- Security headers (HSTS, X-Content-Type-Options, CSP, etc.)
|
||||
- Gzip/zstd compression
|
||||
- Immutable cache for `/assets/*`
|
||||
|
||||
**Docker Compose:**
|
||||
- Single `backend` service running Express
|
||||
- Volume mount for `server/data/` (persistent lead/confirmation data)
|
||||
- Environment variables via `.env` file
|
||||
|
||||
**Build & Deploy workflow:**
|
||||
1. `npm run build` → produces `dist/` with static SPA
|
||||
2. Copy `dist/` to `/opt/andreknie/site/` on server
|
||||
3. `docker compose up -d` for backend
|
||||
4. Caddy serves both static files and proxies API
|
||||
|
||||
### Content Management Workflow
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Stakeholder creates .md or .yaml file] --> B[Commits to git repository]
|
||||
B --> C[CI/CD triggers build]
|
||||
C --> D[Vite plugin transforms content to JSON]
|
||||
D --> E[Static site built to dist/]
|
||||
E --> F[Deploy to server]
|
||||
F --> G[Content visible on andreknie.de]
|
||||
```
|
||||
|
||||
**How the stakeholder adds content:**
|
||||
1. Create a new file in the appropriate `content/` subdirectory
|
||||
2. Follow the schema (frontmatter for markdown, full YAML for metadata-only content)
|
||||
3. Commit and push (or manually trigger deploy)
|
||||
4. The Vite build picks up the new file automatically
|
||||
5. No code changes required for new content
|
||||
|
||||
**Content validation:** The Vite plugin validates content files at build time against expected schemas. Missing required fields cause build warnings (not failures, to avoid blocking deploys for minor issues).
|
||||
|
||||
|
||||
## Correctness Properties
|
||||
|
||||
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
|
||||
|
||||
### Property 1: Content rendering completeness
|
||||
|
||||
*For any* content object of a given type (Post, Talk, Consulting_Example, Newsletter_Issue, Podcast_Episode, Resource, Event), rendering that object must produce output containing all required fields defined for that type (e.g., a Post must show title, date, and body; a Talk must show title, description, and at least one past event; a Consulting_Example must show title, industry, org-size, problem, approach, and outcomes).
|
||||
|
||||
**Validates: Requirements 3.1, 3.2, 3.5, 4.1, 5.2, 6.1, 6.3, 7.2, 8.2, 8.4**
|
||||
|
||||
### Property 2: Content list sorting
|
||||
|
||||
*For any* list of content objects displayed within a content area (posts, kniepunkt issues, podcast episodes, or any other content type), the items must be ordered by publication date in descending order (newest first).
|
||||
|
||||
**Validates: Requirements 3.3, 7.3, 8.2, 14.5**
|
||||
|
||||
### Property 3: Form validation rejects invalid input
|
||||
|
||||
*For any* form submission (contact, talk request, newsletter, resource download) where one or more required fields are empty or the email field does not conform to a valid email format, the system must reject the submission, display error indications for each invalid field, and retain all previously entered valid data.
|
||||
|
||||
**Validates: Requirements 4.4, 10.5, 11.5, 12.5**
|
||||
|
||||
### Property 4: Confirmation token expiration
|
||||
|
||||
*For any* confirmation token (contact: 24h, talk request: 48h, newsletter: 48h) that has exceeded its expiration period, attempting to confirm that token must be rejected and the associated pending record must be discarded without forwarding to the stakeholder or activating the subscription.
|
||||
|
||||
**Validates: Requirements 10.4, 11.4, 12.4**
|
||||
|
||||
### Property 5: Valid submission triggers confirmation email
|
||||
|
||||
*For any* valid form submission (contact, talk request, or newsletter signup) with all required fields present and a correctly formatted email address, the system must generate a unique confirmation token, store the pending request, and send exactly one confirmation email to the provided address.
|
||||
|
||||
**Validates: Requirements 10.3, 11.2, 12.2**
|
||||
|
||||
### Property 6: Valid token confirmation forwards to stakeholder
|
||||
|
||||
*For any* confirmation token that is valid and unexpired, confirming that token must result in the associated message/request being forwarded to the stakeholder via email notification, and the pending record being marked as confirmed.
|
||||
|
||||
**Validates: Requirements 11.3, 12.3**
|
||||
|
||||
### Property 7: Content filtering returns only matching items
|
||||
|
||||
*For any* tag-based filter criterion and any set of content objects, applying the filter must return exactly those content objects whose metadata tags include the selected criterion — no more, no less.
|
||||
|
||||
**Validates: Requirements 13.1, 13.2**
|
||||
|
||||
### Property 8: Dynamic filter options derived from content
|
||||
|
||||
*For any* set of content objects with tags, the available filter options presented to the visitor must equal the complete set of distinct tags present across all content objects (up to the 20-tag maximum).
|
||||
|
||||
**Validates: Requirements 13.4, 13.5**
|
||||
|
||||
### Property 9: Event chronological grouping
|
||||
|
||||
*For any* set of events containing both upcoming (date ≥ today) and past (date < today) entries, the display must show all upcoming events before all past events, with each group internally sorted by date descending, and events grouped by year.
|
||||
|
||||
**Validates: Requirements 9.2**
|
||||
|
||||
### Property 10: Content file validation
|
||||
|
||||
*For any* content file, if it is missing a required attribute (title, date, content-area for general content; title, date, location, event_type for events) or specifies an invalid content-area value, the validation system must reject it with an error message indicating the specific missing or invalid fields.
|
||||
|
||||
**Validates: Requirements 9.4, 14.1, 14.4**
|
||||
|
||||
### Property 11: Lead data storage integrity
|
||||
|
||||
*For any* successful resource download lead capture (valid name, email, company, resource_id), the system must store a record containing all submitted fields plus the resource identifier and a timestamp, and that record must be retrievable.
|
||||
|
||||
**Validates: Requirements 4.5**
|
||||
|
||||
### Property 12: Pagination constraint
|
||||
|
||||
*For any* list of podcast episodes (or other paginated content) with more than 10 items, each page must contain at most 10 items, and pagination controls must be present to access all items.
|
||||
|
||||
**Validates: Requirements 8.3**
|
||||
|
||||
### Property 13: Duplicate subscription detection
|
||||
|
||||
*For any* email address that is already associated with an active newsletter subscription, submitting that email again must inform the visitor that the email is already subscribed and must not create a duplicate entry.
|
||||
|
||||
**Validates: Requirements 10.6**
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Client-Side Errors
|
||||
|
||||
| Scenario | Handling |
|
||||
|----------|----------|
|
||||
| Form validation failure | Inline error messages next to invalid fields; valid data retained |
|
||||
| Content not found (404 route) | Friendly error page with navigation back to home |
|
||||
| Network error on form submit | Toast notification with retry option |
|
||||
| Content loading failure | Graceful fallback message per section |
|
||||
|
||||
### Server-Side Errors
|
||||
|
||||
| Scenario | Handling |
|
||||
|----------|----------|
|
||||
| Invalid request body | 400 response with field-level error details |
|
||||
| Rate limit exceeded | 429 response with retry-after header |
|
||||
| SMTP failure | Log error, return 500 with user-friendly message; store submission for retry |
|
||||
| Invalid confirmation token | 404 response, display "link expired or invalid" page |
|
||||
| Expired confirmation token | 410 Gone response, display "link expired" message |
|
||||
| Server crash | Express error middleware catches unhandled errors, returns 500 |
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
- Contact form: 3 submissions per IP per hour
|
||||
- Talk request: 3 submissions per IP per hour
|
||||
- Newsletter signup: 5 submissions per IP per hour
|
||||
- Resource download: 10 submissions per IP per hour
|
||||
|
||||
### Data Persistence
|
||||
|
||||
- Pending confirmations stored in JSON files in `server/data/`
|
||||
- Confirmed leads stored in `server/data/leads.json`
|
||||
- Expired tokens cleaned up by a scheduled interval (every hour)
|
||||
- All data files are volume-mounted in Docker for persistence across container restarts
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Property-Based Testing
|
||||
|
||||
This feature is suitable for property-based testing in the following areas:
|
||||
- **Content transformation logic** (Vite plugin parsing, validation, sorting)
|
||||
- **Form validation logic** (email validation, field presence, length constraints)
|
||||
- **Token management** (generation, expiration, confirmation)
|
||||
- **Content filtering** (tag matching, dynamic filter derivation)
|
||||
- **Pagination logic** (page size constraints, offset calculation)
|
||||
|
||||
**Library:** [fast-check](https://github.com/dubzzz/fast-check) (JavaScript property-based testing library)
|
||||
|
||||
**Configuration:**
|
||||
- Minimum 100 iterations per property test
|
||||
- Each test tagged with: `Feature: personal-homepage, Property {number}: {property_text}`
|
||||
|
||||
### Unit Tests (Example-Based)
|
||||
|
||||
| Area | What to Test |
|
||||
|------|-------------|
|
||||
| Component rendering | Hero shows name, photo, summary; Audience section shows all segments |
|
||||
| Form UI | All required fields present; submit button disabled when invalid |
|
||||
| Empty states | Sections hidden when no content; appropriate messages shown |
|
||||
| dHive link | Correct href, target="_blank", role text present |
|
||||
| Routing | All pages accessible, 404 for unknown routes |
|
||||
|
||||
### Integration Tests
|
||||
|
||||
| Area | What to Test |
|
||||
|------|-------------|
|
||||
| Content pipeline | Add content file → build → verify in output |
|
||||
| Form submission flow | Submit → confirmation email → confirm → stakeholder notification |
|
||||
| Lead capture flow | Submit lead data → receive download URL |
|
||||
| Build validation | Invalid content files produce warnings |
|
||||
|
||||
### End-to-End Tests
|
||||
|
||||
- Full contact form flow (submit → email → confirm → delivered)
|
||||
- Full talk request flow
|
||||
- Newsletter double opt-in flow
|
||||
- Resource download with lead capture
|
||||
- Content browsing and filtering
|
||||
|
||||
### Test Tools
|
||||
|
||||
- **fast-check** for property-based tests
|
||||
- **Vitest** for unit and integration tests
|
||||
- **React Testing Library** for component tests
|
||||
- **Playwright** for E2E tests (optional, for critical flows)
|
||||
|
||||
Reference in New Issue
Block a user