From 690e7b7c43acd24c5174f27d142fa679265ea0ef Mon Sep 17 00:00:00 2001 From: colinislit Date: Sat, 15 Nov 2025 22:06:48 +0100 Subject: [PATCH] Content loader utility, Content directory structuur --- .gitignore | 1 + app/globals.css | 117 +- app/globals.css.backup | 248 +++ components.json | 22 + components/ui/dot-shader-background.tsx | 191 +++ content/nl/common.json | 15 + content/nl/manifesto.json | 105 ++ content/nl/metadata.json | 11 + content/nl/navigation.json | 14 + content/schemas/manifesto.ts | 64 + docs/design/dot-shader-design-review.md | 214 +++ .../design/marketing-shader-implementation.md | 403 +++++ docs/manifesto.md | 0 docs/specs/archive/bouwplan.md | 979 ++++++++++++ docs/specs/{ => archive}/fo-mini-ecd.md | 0 docs/specs/{ => archive}/prd-mini-ecd.md | 0 docs/specs/{ => archive}/to-mini-ecd.md | 0 ...uwplan-ai-speedrun-marketing-first-v1.1.md | 618 ++++++++ docs/specs/bouwplan.md | 608 -------- docs/specs/design-specs-manifesto-website.md | 1109 ++++++++++++++ docs/specs/fo-mini-ecd-v2.md | 1119 ++++++++++++++ docs/specs/prd-mini-ecd-v1.2.md | 448 ++++++ docs/specs/to-mini-ecd-v1_2.md | 1331 +++++++++++++++++ docs/templates/aispeedrun-manifesto.html | 756 ++++++++++ docs/templates/prd_template.md | 394 +---- lib/content/loader.ts | 43 + lib/supabase/README.md | 67 + lib/supabase/client.ts | 20 + lib/supabase/index.ts | 6 + lib/supabase/server.ts | 25 + lib/supabase/test-connection.ts | 33 + lib/supabase/types.ts | 39 + lib/utils.ts | 6 + package.json | 19 +- pnpm-lock.yaml | 1287 +++++++++++++++- postcss.config.js | 6 + tailwind.config.ts | 164 ++ 37 files changed, 9447 insertions(+), 1035 deletions(-) create mode 100644 app/globals.css.backup create mode 100644 components.json create mode 100644 components/ui/dot-shader-background.tsx create mode 100644 content/nl/common.json create mode 100644 content/nl/manifesto.json create mode 100644 content/nl/metadata.json create mode 100644 content/nl/navigation.json create mode 100644 content/schemas/manifesto.ts create mode 100644 docs/design/dot-shader-design-review.md create mode 100644 docs/design/marketing-shader-implementation.md create mode 100644 docs/manifesto.md create mode 100644 docs/specs/archive/bouwplan.md rename docs/specs/{ => archive}/fo-mini-ecd.md (100%) rename docs/specs/{ => archive}/prd-mini-ecd.md (100%) rename docs/specs/{ => archive}/to-mini-ecd.md (100%) create mode 100644 docs/specs/bouwplan-ai-speedrun-marketing-first-v1.1.md delete mode 100644 docs/specs/bouwplan.md create mode 100644 docs/specs/design-specs-manifesto-website.md create mode 100644 docs/specs/fo-mini-ecd-v2.md create mode 100644 docs/specs/prd-mini-ecd-v1.2.md create mode 100644 docs/specs/to-mini-ecd-v1_2.md create mode 100644 docs/templates/aispeedrun-manifesto.html create mode 100644 lib/content/loader.ts create mode 100644 lib/supabase/README.md create mode 100644 lib/supabase/client.ts create mode 100644 lib/supabase/index.ts create mode 100644 lib/supabase/server.ts create mode 100644 lib/supabase/test-connection.ts create mode 100644 lib/supabase/types.ts create mode 100644 lib/utils.ts create mode 100644 postcss.config.js create mode 100644 tailwind.config.ts diff --git a/.gitignore b/.gitignore index 5ef6a52..7b8da95 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +!.env.example # vercel .vercel diff --git a/app/globals.css b/app/globals.css index 988de70..563cdad 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,4 +1,6 @@ -@import "tailwindcss"; +@tailwind base; +@tailwind components; +@tailwind utilities; /* ============================================================================= Mini-ECD Design System @@ -76,47 +78,83 @@ /* Fonts */ --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); -} - -@theme inline { - /* Basiskleuren */ - --color-bg: var(--color-bg); - --color-surface: var(--color-surface); - --color-text: var(--color-text); - --color-border: var(--color-border); - - /* Brand */ - --color-brand: var(--color-brand); - --color-success: var(--color-success); - --color-warning: var(--color-warning); - --color-error: var(--color-error); - --color-info: var(--color-info); - - /* Shadows */ - --shadow-sm: var(--shadow-sm); - --shadow-md: var(--shadow-md); - --shadow-lg: var(--shadow-lg); - - /* Fonts */ - --font-sans: var(--font-sans); - --font-mono: var(--font-mono); + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.129 0.042 264.695); + --card: oklch(1 0 0); + --card-foreground: oklch(0.129 0.042 264.695); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.129 0.042 264.695); + --primary: oklch(0.208 0.042 265.755); + --primary-foreground: oklch(0.984 0.003 247.858); + --secondary: oklch(0.968 0.007 247.896); + --secondary-foreground: oklch(0.208 0.042 265.755); + --muted: oklch(0.968 0.007 247.896); + --muted-foreground: oklch(0.554 0.046 257.417); + --accent: oklch(0.968 0.007 247.896); + --accent-foreground: oklch(0.208 0.042 265.755); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.929 0.013 255.508); + --input: oklch(0.929 0.013 255.508); + --ring: oklch(0.704 0.04 256.788); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.984 0.003 247.858); + --sidebar-foreground: oklch(0.129 0.042 264.695); + --sidebar-primary: oklch(0.208 0.042 265.755); + --sidebar-primary-foreground: oklch(0.984 0.003 247.858); + --sidebar-accent: oklch(0.968 0.007 247.896); + --sidebar-accent-foreground: oklch(0.208 0.042 265.755); + --sidebar-border: oklch(0.929 0.013 255.508); + --sidebar-ring: oklch(0.704 0.04 256.788); } /* Dark mode (optioneel, MVP gebruikt licht thema) */ -@media (prefers-color-scheme: dark) { - :root { - --color-bg: #0a0a0a; - --color-surface: #1a1a1a; - --color-text: #ededed; - --color-text-secondary: #a1a1a1; - --color-border: #333333; - } +.dark { + --color-bg: #0a0a0a; + --color-surface: #1a1a1a; + --color-text: #ededed; + --color-text-secondary: #a1a1a1; + --color-border: #333333; + + --background: oklch(0.129 0.042 264.695); + --foreground: oklch(0.984 0.003 247.858); + --card: oklch(0.208 0.042 265.755); + --card-foreground: oklch(0.984 0.003 247.858); + --popover: oklch(0.208 0.042 265.755); + --popover-foreground: oklch(0.984 0.003 247.858); + --primary: oklch(0.929 0.013 255.508); + --primary-foreground: oklch(0.208 0.042 265.755); + --secondary: oklch(0.279 0.041 260.031); + --secondary-foreground: oklch(0.984 0.003 247.858); + --muted: oklch(0.279 0.041 260.031); + --muted-foreground: oklch(0.704 0.04 256.788); + --accent: oklch(0.279 0.041 260.031); + --accent-foreground: oklch(0.984 0.003 247.858); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.551 0.027 264.364); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.208 0.042 265.755); + --sidebar-foreground: oklch(0.984 0.003 247.858); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.984 0.003 247.858); + --sidebar-accent: oklch(0.279 0.041 260.031); + --sidebar-accent-foreground: oklch(0.984 0.003 247.858); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.551 0.027 264.364); } /* Global styles */ body { - background: var(--color-bg); - color: var(--color-text); font-family: var(--font-sans), system-ui, -apple-system, sans-serif; } @@ -132,3 +170,12 @@ body { border-radius: 2px; padding: 1px 0; } + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground antialiased; + } +} diff --git a/app/globals.css.backup b/app/globals.css.backup new file mode 100644 index 0000000..af0a500 --- /dev/null +++ b/app/globals.css.backup @@ -0,0 +1,248 @@ +@import "tailwindcss"; + +@plugin "tailwindcss-animate"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +/* ============================================================================= + Mini-ECD Design System + Gebaseerd op ux-stylesheet.md (v2, UX-geoptimaliseerd) + ============================================================================= */ + +:root { + /* Basiskleuren */ + --color-bg: #F8FAFC; + --color-surface: #FFFFFF; + --color-surface-secondary: #F1F5F9; + --color-text: #0F172A; + --color-text-secondary: #475569; + --color-border: #E2E8F0; + + /* Brand & Primary */ + --color-brand: #3B82F6; + --color-brand-hover: #2563EB; + --color-brand-active: #1D4ED8; + --color-brand-subtle: #EFF6FF; + + /* Neutral CTA */ + --color-neutral: #334155; + --color-neutral-hover: #1F2937; + + /* Module-accenten */ + --color-module-appointments: #16A34A; + --color-module-appointments-bg: #E8F8EF; + --color-module-appointments-border: #CDECDC; + + --color-module-meds: #F59E0B; + --color-module-meds-bg: #FEF6DC; + --color-module-meds-border: #F6E7B6; + + --color-module-labs: #F97316; + --color-module-labs-bg: #FFEBDC; + --color-module-labs-border: #FFD2B8; + + /* Status & Feedback */ + --color-success: #16A34A; + --color-success-subtle: #ECFDF5; + --color-warning: #EAB308; + --color-warning-subtle: #FEFCE8; + --color-error: #DC2626; + --color-error-subtle: #FEF2F2; + --color-info: #3B82F6; + --color-info-subtle: #EFF6FF; + + /* Badges (Severity DSM-light) */ + --color-badge-low-bg: #E5E7EB; + --color-badge-low-text: #374151; + --color-badge-medium-bg: #FEF3C7; + --color-badge-medium-text: #92400E; + --color-badge-high-bg: #FEE2E2; + --color-badge-high-text: #991B1B; + + /* Formulieren */ + --color-input-bg: #FFFFFF; + --color-input-text: #0F172A; + --color-input-placeholder: #94A3B8; + --color-input-border: #CBD5E1; + --color-input-border-hover: #94A3B8; + --color-input-focus: #3B82F6; + --color-input-focus-border: #2563EB; + --color-input-disabled-bg: #F1F5F9; + --color-input-disabled-text: #94A3B8; + --color-input-invalid-border: #DC2626; + --color-input-invalid-text: #B91C1C; + + /* Shadows */ + --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06); + --shadow-md: 0 2px 6px rgba(15, 23, 42, 0.08); + --shadow-lg: 0 8px 20px rgba(15, 23, 42, 0.10); + + /* Fonts */ + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.129 0.042 264.695); + --card: oklch(1 0 0); + --card-foreground: oklch(0.129 0.042 264.695); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.129 0.042 264.695); + --primary: oklch(0.208 0.042 265.755); + --primary-foreground: oklch(0.984 0.003 247.858); + --secondary: oklch(0.968 0.007 247.896); + --secondary-foreground: oklch(0.208 0.042 265.755); + --muted: oklch(0.968 0.007 247.896); + --muted-foreground: oklch(0.554 0.046 257.417); + --accent: oklch(0.968 0.007 247.896); + --accent-foreground: oklch(0.208 0.042 265.755); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.929 0.013 255.508); + --input: oklch(0.929 0.013 255.508); + --ring: oklch(0.704 0.04 256.788); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.984 0.003 247.858); + --sidebar-foreground: oklch(0.129 0.042 264.695); + --sidebar-primary: oklch(0.208 0.042 265.755); + --sidebar-primary-foreground: oklch(0.984 0.003 247.858); + --sidebar-accent: oklch(0.968 0.007 247.896); + --sidebar-accent-foreground: oklch(0.208 0.042 265.755); + --sidebar-border: oklch(0.929 0.013 255.508); + --sidebar-ring: oklch(0.704 0.04 256.788); +} + +@theme inline { + /* Basiskleuren */ + --color-bg: var(--color-bg); + --color-surface: var(--color-surface); + --color-text: var(--color-text); + --color-border: var(--color-border); + + /* Brand */ + --color-brand: var(--color-brand); + --color-success: var(--color-success); + --color-warning: var(--color-warning); + --color-error: var(--color-error); + --color-info: var(--color-info); + + /* Shadows */ + --shadow-sm: var(--shadow-sm); + --shadow-md: var(--shadow-md); + --shadow-lg: var(--shadow-lg); + + /* Fonts */ + --font-sans: var(--font-sans); + --font-mono: var(--font-mono); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-foreground: var(--foreground); + --color-background: var(--background); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); +} + +/* Dark mode (optioneel, MVP gebruikt licht thema) */ +@media (prefers-color-scheme: dark) { + :root { + --color-bg: #0a0a0a; + --color-surface: #1a1a1a; + --color-text: #ededed; + --color-text-secondary: #a1a1a1; + --color-border: #333333; + } +} + +/* Global styles */ +body { + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-sans), system-ui, -apple-system, sans-serif; +} + +/* Focus styles (toegankelijkheid) */ +*:focus-visible { + outline: 2px solid var(--color-brand); + outline-offset: 2px; +} + +/* AI source highlighting (gebruikt in Intake editor) */ +.ai-source-highlight { + background-color: #fef08a; /* lichtgeel */ + border-radius: 2px; + padding: 1px 0; +} + +.dark { + --background: oklch(0.129 0.042 264.695); + --foreground: oklch(0.984 0.003 247.858); + --card: oklch(0.208 0.042 265.755); + --card-foreground: oklch(0.984 0.003 247.858); + --popover: oklch(0.208 0.042 265.755); + --popover-foreground: oklch(0.984 0.003 247.858); + --primary: oklch(0.929 0.013 255.508); + --primary-foreground: oklch(0.208 0.042 265.755); + --secondary: oklch(0.279 0.041 260.031); + --secondary-foreground: oklch(0.984 0.003 247.858); + --muted: oklch(0.279 0.041 260.031); + --muted-foreground: oklch(0.704 0.04 256.788); + --accent: oklch(0.279 0.041 260.031); + --accent-foreground: oklch(0.984 0.003 247.858); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.551 0.027 264.364); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.208 0.042 265.755); + --sidebar-foreground: oklch(0.984 0.003 247.858); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.984 0.003 247.858); + --sidebar-accent: oklch(0.279 0.041 260.031); + --sidebar-accent-foreground: oklch(0.984 0.003 247.858); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.551 0.027 264.364); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/components.json b/components.json new file mode 100644 index 0000000..043aa66 --- /dev/null +++ b/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/components/ui/dot-shader-background.tsx b/components/ui/dot-shader-background.tsx new file mode 100644 index 0000000..2e58472 --- /dev/null +++ b/components/ui/dot-shader-background.tsx @@ -0,0 +1,191 @@ +'use client' + +import { useMemo, useEffect } from 'react' +import { Canvas, ThreeEvent, useFrame, useThree } from '@react-three/fiber' +import { shaderMaterial, useTrailTexture } from '@react-three/drei' +import { useTheme } from 'next-themes' // lub twój provider +import * as THREE from 'three' + +const DotMaterial = shaderMaterial( + { + time: 0, + resolution: new THREE.Vector2(), + dotColor: new THREE.Color('#FFFFFF'), + bgColor: new THREE.Color('#121212'), + mouseTrail: null, + render: 0, + rotation: 0, + gridSize: 50, + dotOpacity: 0.05 + }, + /* glsl */ ` + void main() { + gl_Position = vec4(position.xy, 0.0, 1.0); + } + `, + /* glsl */ ` + uniform float time; + uniform int render; + uniform vec2 resolution; + uniform vec3 dotColor; + uniform vec3 bgColor; + uniform sampler2D mouseTrail; + uniform float rotation; + uniform float gridSize; + uniform float dotOpacity; + + vec2 rotate(vec2 uv, float angle) { + float s = sin(angle); + float c = cos(angle); + mat2 rotationMatrix = mat2(c, -s, s, c); + return rotationMatrix * (uv - 0.5) + 0.5; + } + + vec2 coverUv(vec2 uv) { + vec2 s = resolution.xy / max(resolution.x, resolution.y); + vec2 newUv = (uv - 0.5) * s + 0.5; + return clamp(newUv, 0.0, 1.0); + } + + float sdfCircle(vec2 p, float r) { + return length(p - 0.5) - r; + } + + void main() { + vec2 screenUv = gl_FragCoord.xy / resolution; + vec2 uv = coverUv(screenUv); + + vec2 rotatedUv = rotate(uv, rotation); + + // Create a grid + vec2 gridUv = fract(rotatedUv * gridSize); + vec2 gridUvCenterInScreenCoords = rotate((floor(rotatedUv * gridSize) + 0.5) / gridSize, -rotation); + + // Calculate distance from the center of each cell + float baseDot = sdfCircle(gridUv, 0.25); + + // Screen mask + float screenMask = smoothstep(0.0, 1.0, 1.0 - uv.y); // 0 at the top, 1 at the bottom + vec2 centerDisplace = vec2(0.7, 1.1); + float circleMaskCenter = length(uv - centerDisplace); + float circleMaskFromCenter = smoothstep(0.5, 1.0, circleMaskCenter); + + float combinedMask = screenMask * circleMaskFromCenter; + float circleAnimatedMask = sin(time * 2.0 + circleMaskCenter * 10.0); + + // Mouse trail effect + float mouseInfluence = texture2D(mouseTrail, gridUvCenterInScreenCoords).r; + + float scaleInfluence = max(mouseInfluence * 0.5, circleAnimatedMask * 0.3); + + // Create dots with animated scale, influenced by mouse + float dotSize = min(pow(circleMaskCenter, 2.0) * 0.3, 0.3); + + float sdfDot = sdfCircle(gridUv, dotSize * (1.0 + scaleInfluence * 0.5)); + + float smoothDot = smoothstep(0.05, 0.0, sdfDot); + + float opacityInfluence = max(mouseInfluence * 50.0, circleAnimatedMask * 0.5); + + // Mix background color with dot color, using animated opacity to increase visibility + vec3 composition = mix(bgColor, dotColor, smoothDot * combinedMask * dotOpacity * (1.0 + opacityInfluence)); + + gl_FragColor = vec4(composition, 1.0); + + #include + #include + } + ` +) + +function Scene() { + const size = useThree((s) => s.size) + const viewport = useThree((s) => s.viewport) + const { theme } = useTheme() + + const rotation = 0 + const gridSize = 100 + + const getThemeColors = () => { + switch (theme) { + case 'dark': + return { + dotColor: '#FFFFFF', + bgColor: '#121212', + dotOpacity: 0.025 + } + case 'light': + return { + dotColor: '#e1e1e1', + bgColor: '#F4F5F5', + dotOpacity: 0.15 + } + default: + return { + dotColor: '#FFFFFF', + bgColor: '#121212', + dotOpacity: 0.05 + } + } + } + + const themeColors = getThemeColors() + + const [trail, onMove] = useTrailTexture({ + size: 512, + radius: 0.1, + maxAge: 400, + interpolate: 1, + ease: function easeInOutCirc(x) { + return x < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2 + } + }) + + const dotMaterial = useMemo(() => { + return new DotMaterial() + }, []) + + useEffect(() => { + dotMaterial.uniforms.dotColor.value.setHex(themeColors.dotColor.replace('#', '0x')) + dotMaterial.uniforms.bgColor.value.setHex(themeColors.bgColor.replace('#', '0x')) + dotMaterial.uniforms.dotOpacity.value = themeColors.dotOpacity + }, [theme, dotMaterial, themeColors]) + + useFrame((state) => { + dotMaterial.uniforms.time.value = state.clock.elapsedTime + }) + + const handlePointerMove = (e: ThreeEvent) => { + onMove(e) + } + + const scale = Math.max(viewport.width, viewport.height) / 2 + + return ( + + + + + ) +} + +export const DotScreenShader = () => { + return ( + + + + ) +} \ No newline at end of file diff --git a/content/nl/common.json b/content/nl/common.json new file mode 100644 index 0000000..a0c6157 --- /dev/null +++ b/content/nl/common.json @@ -0,0 +1,15 @@ +{ + "buttons": { + "readMore": "Lees verder", + "scrollDown": "Scroll naar beneden", + "followProgress": "Volg de voortgang", + "viewDemo": "Bekijk Demo", + "contact": "Contact" + }, + "labels": { + "readingTime": "minuten lezen", + "lastUpdated": "Laatst bijgewerkt", + "share": "Deel" + } +} + diff --git a/content/nl/manifesto.json b/content/nl/manifesto.json new file mode 100644 index 0000000..c177b72 --- /dev/null +++ b/content/nl/manifesto.json @@ -0,0 +1,105 @@ +{ + "hero": { + "quote": "Software is eating the world, but AI is going to eat software", + "attribution": "Jensen Huang, CEO van Nvidia", + "attributionContext": "zei dit tijdens zijn keynote op GTC in maart 2024", + "subtitle": "Nu, in 2025, zien we het gebeuren. En bijna niemand heeft het door." + }, + "sections": [ + { + "id": "opening", + "type": "paragraph", + "content": "Het traditionele SaaS-model is simpel: één gebruiker = één licentie. Logisch in een wereld waar elke gebruiker ongeveer evenveel waarde uit software haalt. Maar ook beperkend - je groei is gekoppeld aan het aantal medewerkers bij je klant. Dit model heeft de software-industrie 20 jaar gedomineerd. Vendors optimaliseren voor meer seats, meer modules, meer lock-in. Klanten betalen voor potentieel, niet voor werkelijke waarde." + }, + { + "id": "problem", + "type": "paragraph", + "content": "Maar er gebeurt iets fundamenteels. AI maakt het mogelijk om software te genereren in plaats van te configureren. Niet meer kiezen uit wat bestaat, maar bouwen wat je nodig hebt. McKinsey noemt het \"Software on Demand\" - adaptieve diensten die via natuurlijke taal ontstaan. De implicaties zijn enorm." + }, + { + "id": "mckinsey-insight", + "type": "insight", + "content": "McKinsey noemt het \"Software on Demand\" - adaptieve diensten die via natuurlijke taal ontstaan." + }, + { + "id": "solution", + "type": "paragraph", + "content": "Waar traditionele implementaties 6-12 maanden duren, bouw je met AI een werkende applicatie in 4 weken. Niet omdat AI magisch is, maar omdat je 80% van de standaard-code niet meer hoeft te schrijven. De kostenbasis verschuift compleet - van €100k per jaar naar €50 per maand. Geen armies van consultants. Geen jarenlange developmenttrajecten. Infrastructuur die schaalt met gebruik. AI die de heavy lifting doet. En het belangrijkste: je bezit de code. Je controleert de roadmap. Aanpassing nodig? Dagen, geen kwartalen." + }, + { + "id": "proof-numbers", + "type": "insight", + "content": "Van €100k per jaar naar €50 per maand. Geen armies van consultants. Geen jarenlange developmenttrajecten." + }, + { + "id": "reality", + "type": "paragraph", + "content": "Dit is geen toekomstmuziek. Ik zie het nu al gebeuren - startups die complete workflows bouwen in de tijd dat enterprises nog aan het onderhandelen zijn over licenties. De vraag is niet óf dit de norm wordt, maar wanneer." + }, + { + "id": "impact-vendors", + "type": "statement", + "variant": "dark", + "heading": "Voor software vendors is dit existentieel", + "content": "Hun hele businessmodel - recurring revenue op basis van seats - verdampt als klanten hun eigen oplossingen kunnen bouwen. Voor enterprises opent dit ongekende mogelijkheden. Software die past bij hoe je werkt, niet andersom. Innovatie in weken, niet jaren." + }, + { + "id": "shift", + "type": "paragraph", + "content": "We staan nog aan het begin van deze shift. De grote vraag wordt: wie durft eerst? Wie accepteert dat de SAP-implementatie van 5 jaar geleden misschien wel de laatste traditionele software-aankoop was?" + }, + { + "id": "experiment", + "type": "paragraph", + "content": "Tijd voor een experiment. Ik ga live bouwen hoe ver je komt met moderne AI-tools. Een EPD als testcase - daar ligt mijn ervaring, daar ken ik de pijn. Van niets naar een werkende applicatie in 4 weken, voor de maandelijkse kosten van één SaaS-licentie." + }, + { + "id": "closing", + "type": "paragraph", + "content": "De AI Speedrun. Volg de voortgang. Doe suggesties. Kijk mee hoe het nieuwe development er in de praktijk uitziet. Want de beste manier om de toekomst te voorspellen, is hem bouwen." + } + ], + "comparison": { + "heading": "Traditioneel vs AI Speedrun", + "items": [ + { + "label": "Tijd", + "traditional": "6-12 maanden", + "aiSpeedrun": "4 weken" + }, + { + "label": "Kosten", + "traditional": "€100k+ per jaar", + "aiSpeedrun": "€50 per maand" + }, + { + "label": "Setup", + "traditional": "€50k consultants", + "aiSpeedrun": "€200 build cost" + }, + { + "label": "Aanpassingen", + "traditional": "18 maanden", + "aiSpeedrun": "Dagen" + }, + { + "label": "Code ownership", + "traditional": "Vendor lock-in", + "aiSpeedrun": "Jouw code" + } + ] + }, + "cta": { + "heading": "Tijd voor een experiment", + "subheading": "Bouw hem gewoon zelf. In 4 weken.", + "primaryButton": { + "text": "Volg de voortgang", + "href": "/build-log" + }, + "secondaryButton": { + "text": "Bekijk Demo", + "href": "/demo" + } + } +} + diff --git a/content/nl/metadata.json b/content/nl/metadata.json new file mode 100644 index 0000000..2be753c --- /dev/null +++ b/content/nl/metadata.json @@ -0,0 +1,11 @@ +{ + "manifesto": { + "title": "Software on Demand - AI Speedrun Manifesto", + "description": "Jensen Huang: 'AI is going to eat software'. Een experiment: bouw een EPD in 4 weken voor €200.", + "ogTitle": "Software on Demand - AI Speedrun", + "ogDescription": "Van €100k en 12 maanden naar €200 en 4 weken. Het nieuwe development.", + "ogImage": "/og-manifesto.png", + "keywords": ["AI", "Software on Demand", "EPD", "Development", "Build in Public"] + } +} + diff --git a/content/nl/navigation.json b/content/nl/navigation.json new file mode 100644 index 0000000..eeaf269 --- /dev/null +++ b/content/nl/navigation.json @@ -0,0 +1,14 @@ +{ + "logo": "AI SPEEDRUN", + "links": [ + { + "label": "Build Log", + "href": "/build-log" + }, + { + "label": "Demo", + "href": "/demo" + } + ] +} + diff --git a/content/schemas/manifesto.ts b/content/schemas/manifesto.ts new file mode 100644 index 0000000..77812e6 --- /dev/null +++ b/content/schemas/manifesto.ts @@ -0,0 +1,64 @@ +export interface HeroContent { + quote: string + attribution: string + attributionContext?: string + subtitle: string +} + +export type SectionType = 'paragraph' | 'insight' | 'statement' + +export interface ParagraphSection { + id: string + type: 'paragraph' + content: string +} + +export interface InsightSection { + id: string + type: 'insight' + content: string +} + +export interface StatementSection { + id: string + type: 'statement' + variant?: 'dark' | 'light' + heading: string + content: string +} + +export type ManifestoSection = + | ParagraphSection + | InsightSection + | StatementSection + +export interface ComparisonItem { + label: string + traditional: string + aiSpeedrun: string +} + +export interface ComparisonContent { + heading: string + items: ComparisonItem[] +} + +export interface CTAButton { + text: string + href: string +} + +export interface CTAContent { + heading: string + subheading?: string + primaryButton: CTAButton + secondaryButton: CTAButton +} + +export interface ManifestoContent { + hero: HeroContent + sections: ManifestoSection[] + comparison: ComparisonContent + cta: CTAContent +} + diff --git a/docs/design/dot-shader-design-review.md b/docs/design/dot-shader-design-review.md new file mode 100644 index 0000000..0d9ad06 --- /dev/null +++ b/docs/design/dot-shader-design-review.md @@ -0,0 +1,214 @@ +# 🎨 Design Review: Dot Shader Background Component + +**Datum:** 15-11-2024 +**Review Team:** Design & UX +**Vraag:** Kan het dot-shader-background component gebruikt worden voor marketingpagina en/of EPD app? + +--- + +## 📋 Context Analyse + +### Manifesto Kernwaarden +- **Innovatie & Disruptie:** "AI gaat software eten" - technologische vooruitgang centraal +- **Snelheid:** "4 weken vs 12 maanden" - efficiency en moderniteit +- **Bewijs door doen:** "Beste manier om toekomst te voorspellen is hem bouwen" +- **Digitale transformatie:** Software on Demand als nieuwe realiteit + +### UX Stylesheet Principes +- **Lage cognitieve belasting:** Focus op content, niet op decoratie +- **Toegankelijkheid:** WCAG AA contrast, geen afleidende elementen +- **"Less is more":** Accentpalet bewust klein gehouden → minder visuele ruis +- **Functioneel design:** Elke visuele keuze moet doel dienen + +### Shader Component Eigenschappen +- **Technisch:** Three.js shader met WebGL rendering +- **Visueel:** Geanimeerde dots met mouse trail interactie +- **Performance:** High-performance mode, GPU-accelerated +- **Theme support:** Dark/light mode compatible +- **Subtiel:** Lage opacity (0.025-0.15), niet dominant + +--- + +## 🎯 Design Team Advies + +### ✅ **AANBEVELING: Strategische Inzet** + +Het design team adviseert **gecontroleerd gebruik** van het shader component, met duidelijke context-specifieke richtlijnen: + +--- + +## 📍 Marketing Website: **JA, met voorwaarden** + +### Waarom het werkt: +1. **Manifesto alignment:** Het component straalt technologische innovatie uit - perfect voor "Software on Demand" messaging +2. **Differentiatie:** Visueel onderscheidend van traditionele SaaS-landing pages +3. **Engagement:** Mouse trail interactie verhoogt tijd op pagina +4. **Credibility:** Toont technische vaardigheid zonder te overdrijven + +### Implementatie Richtlijnen: + +**Hero Section (Primaire Inzet)** +- ✅ Shader als full-screen achtergrond achter hero content +- ✅ Zeer lage opacity (0.02-0.05) - subtiel, niet dominant +- ✅ Content overlay met sterke contrast (wit op donker, of donker op licht) +- ✅ Performance: Lazy load alleen wanneer hero in viewport + +**Secties (Secundaire Inzet)** +- ⚠️ Optioneel in "How it Works" of "Technology" secties +- ❌ NIET in comparison tables, ROI calculator, of formulier secties +- ❌ NIET op mobile (performance + UX overwegingen) + +**Technische Aanpassingen Nodig:** +```typescript +// Marketing-specifieke configuratie +const marketingConfig = { + dotOpacity: 0.03, // Zeer subtiel + gridSize: 80, // Fijner grid voor eleganter effect + disableMouseTrail: false, // Interactiviteit behouden + performanceMode: 'high', // GPU optimization + mobileFallback: 'gradient' // Fallback voor mobile +} +``` + +**Contrast Check:** +- Tekst op shader achtergrond moet voldoen aan WCAG AA (4.5:1) +- Gebruik semi-transparante overlay indien nodig +- Test met verschillende tekstgroottes + +--- + +## 🏥 EPD App: **NEE, tenzij zeer subtiel** + +### Waarom het risicovol is: +1. **Cognitieve belasting:** Medische professionals hebben focus nodig - animaties zijn afleidend +2. **UX Stylesheet conflict:** Direct tegenstrijdig met "lage cognitieve belasting" principe +3. **Toegankelijkheid:** Kan problemen veroorzaken voor gebruikers met motion sensitivity +4. **Performance:** Elke milliseconde telt in productie-omgevingen + +### Uitzondering: Onboarding/Welcome Screen +- ✅ **Alleen** op eerste login/welcome screen +- ✅ Zeer korte duur (3-5 seconden), dan fade-out +- ✅ Optioneel: "Skip animation" knop voor toegankelijkheid +- ❌ **NOOIT** tijdens actieve workflows (intake, profiel, behandelplan) + +**Implementatie Als Uitzondering:** +```typescript +// EPD-specifieke configuratie (alleen welcome) +const epdWelcomeConfig = { + dotOpacity: 0.01, // Extreem subtiel + gridSize: 120, // Zeer fijn grid + disableMouseTrail: true, // Geen interactiviteit + autoFadeOut: true, // Fade na 3 seconden + skipButton: true, // Toegankelijkheid + reducedMotion: true // Respecteer prefers-reduced-motion +} +``` + +--- + +## 🎨 Stylesheet Matching Analyse + +### Kleuren Compatibiliteit + +**Light Theme Match:** +- ✅ Shader bg: `#F4F5F5` matcht stylesheet `#F8FAFC` (zeer dichtbij) +- ✅ Shader dots: `#e1e1e1` matcht border kleur `#E2E8F0` (harmonisch) +- ⚠️ Aanpassing nodig: Shader moet exact `#F8FAFC` gebruiken voor consistency + +**Dark Theme Match:** +- ✅ Shader bg: `#121212` is acceptabel voor dark mode +- ✅ Shader dots: `#FFFFFF` met lage opacity werkt goed +- ⚠️ Stylesheet heeft geen dark mode spec gedefinieerd - dit moet eerst worden uitgewerkt + +**Aanbevolen Aanpassingen:** +```typescript +// Update shader theme colors om exact te matchen +const getThemeColors = () => { + switch (theme) { + case 'light': + return { + dotColor: '#E2E8F0', // Match border color + bgColor: '#F8FAFC', // Match app background + dotOpacity: 0.03 // Zeer subtiel voor marketing + } + case 'dark': + return { + dotColor: '#475569', // Match secondary text + bgColor: '#0F172A', // Match primary text (inverted) + dotOpacity: 0.02 // Nog subtieler voor dark + } + } +} +``` + +--- + +## ⚖️ "Less is More" vs "Digital Innovation" + +### Design Team Consensus: + +**Marketing Website:** +- **"Less is more"** geldt voor **content en copy** - niet voor visuele impact +- Shader component kan **strategisch** gebruikt worden om innovatie te communiceren +- **Voorwaarde:** Het moet de boodschap versterken, niet afleiden +- **Test:** A/B test met en zonder shader - meet engagement metrics + +**EPD App:** +- **"Less is more"** is hier **absoluut** - elke pixel moet functioneel zijn +- Shader component is **decoratief** en voegt geen functionele waarde toe +- **Uitzondering:** Welcome screen kan één keer indruk maken, daarna weg + +--- + +## 📊 Risico Analyse + +| Risico | Kans | Impact | Mitigatie | +|--------|------|--------|-----------| +| **Performance impact** | Medium | Hoog | Lazy loading, mobile fallback, performance monitoring | +| **Toegankelijkheid issues** | Medium | Hoog | `prefers-reduced-motion` respecteren, skip optie | +| **Cognitieve overload** | Hoog | Medium | Zeer lage opacity, alleen hero section | +| **Stylesheet mismatch** | Laag | Laag | Kleuren aanpassen naar exacte stylesheet waarden | +| **Mobile performance** | Hoog | Medium | Automatische fallback naar gradient | + +--- + +## ✅ Finale Aanbeveling + +### Marketing Website: **GO** ✅ +- Implementeer in hero section met zeer lage opacity +- Pas kleuren aan naar exacte stylesheet waarden +- Voeg mobile fallback toe +- Monitor performance metrics +- Test toegankelijkheid met screen readers + +### EPD App: **NO GO** ❌ +- **Behalve:** Welcome screen (één keer, met skip optie) +- Focus op functioneel design volgens UX stylesheet +- Gebruik subtiele gradients of solid colors voor achtergronden + +### Implementatie Prioriteit: +1. **Week 1:** Marketing hero section met shader (als MVP) +2. **Week 2:** Performance optimalisatie + mobile fallback +3. **Week 4:** A/B test resultaten evalueren +4. **Post-launch:** Beslissing over permanente implementatie + +--- + +## 🎯 Design Principes Samenvatting + +**Voor Marketing:** +> "Innovatie moet zichtbaar zijn, maar niet opdringerig" + +**Voor EPD:** +> "Elke visuele keuze moet de gebruiker helpen, niet afleiden" + +**Algemeen:** +> "Technologie moet de boodschap dienen, niet domineren" + +--- + +**Design Team Sign-off:** +✅ Marketing website: Goedkeuring met voorwaarden +❌ EPD app: Afwijzing (behalve welcome screen) +📝 Stylesheet aanpassingen: Vereist voor consistency + diff --git a/docs/design/marketing-shader-implementation.md b/docs/design/marketing-shader-implementation.md new file mode 100644 index 0000000..2e83be8 --- /dev/null +++ b/docs/design/marketing-shader-implementation.md @@ -0,0 +1,403 @@ +# 🚀 Marketing Shader Implementation Guide + +**Doel:** Praktische implementatie van dot-shader component voor marketing website hero section. + +--- + +## 📐 Design Specificaties + +### Hero Section Layout + +``` +┌─────────────────────────────────────┐ +│ [Shader Background - zeer subtiel] │ +│ │ +│ ┌─────────────────────────────┐ │ +│ │ Hero Content Overlay │ │ +│ │ - Headline (wit/donker) │ │ +│ │ - Subheadline │ │ +│ │ - CTA Buttons │ │ +│ │ - Live Metrics Counter │ │ +│ └─────────────────────────────┘ │ +│ │ +└─────────────────────────────────────┘ +``` + +### Visuele Hiërarchie +1. **Shader:** Opacity 0.02-0.03 (bijna onzichtbaar, maar aanwezig) +2. **Content Overlay:** Semi-transparant of solid (afhankelijk van contrast) +3. **Tekst:** Hoog contrast (wit op donker, of donker op licht) + +--- + +## 🔧 Technische Implementatie + +### Stap 1: Aangepaste Marketing Variant + +```typescript +// components/ui/marketing-shader-background.tsx +'use client' + +import { DotScreenShader } from './dot-shader-background' +import { useTheme } from 'next-themes' +import { useEffect, useState } from 'react' + +interface MarketingShaderProps { + variant?: 'hero' | 'section' + className?: string +} + +export function MarketingShader({ variant = 'hero', className }: MarketingShaderProps) { + const { theme } = useTheme() + const [mounted, setMounted] = useState(false) + const [reducedMotion, setReducedMotion] = useState(false) + + useEffect(() => { + setMounted(true) + // Check for reduced motion preference + const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)') + setReducedMotion(mediaQuery.matches) + + const handleChange = (e: MediaQueryListEvent) => setReducedMotion(e.matches) + mediaQuery.addEventListener('change', handleChange) + return () => mediaQuery.removeEventListener('change', handleChange) + }, []) + + // Fallback voor mobile of reduced motion + if (!mounted || reducedMotion) { + return ( +