diff --git a/app/globals.css b/app/globals.css
index 8be0836..ae1081f 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -130,3 +130,17 @@
.dark ::selection {
background: oklch(0.70 0.22 280 / 0.5);
}
+
+/* ===== Timeline-Illustrationen (components/timeline-holo.tsx) ===== */
+@keyframes tl-blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
+@keyframes tl-spin { to { transform: rotate(360deg); } }
+@keyframes tl-pulse { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }
+@keyframes tl-flow { to { stroke-dashoffset: -16; } }
+@keyframes tl-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
+@keyframes tl-flicker { 0%, 100% { opacity: 1; } 3% { opacity: 0.82; } 5% { opacity: 1; } 51% { opacity: 0.93; } 53% { opacity: 1; } }
+.tl-art .tl-blink { animation: tl-blink 1.1s steps(1) infinite; }
+.tl-art .tl-spin { transform-box: fill-box; transform-origin: center; animation: tl-spin 8s linear infinite; }
+.tl-art .tl-pulse { animation: tl-pulse 2.4s ease-in-out infinite; }
+.tl-art .tl-flow { stroke-dasharray: 4 4; animation: tl-flow 1s linear infinite; }
+.tl-art .tl-bob { animation: tl-bob 4s ease-in-out infinite; }
+.tl-art .tl-flicker { animation: tl-flicker 5s linear infinite; }
diff --git a/components/about-section.tsx b/components/about-section.tsx
index af52888..64ed6e3 100644
--- a/components/about-section.tsx
+++ b/components/about-section.tsx
@@ -1,55 +1,64 @@
'use client'
import { motion } from 'framer-motion'
+import { TimelineHolo, type TimelineArtId } from './timeline-holo'
-const timeline = [
+const timeline: { era: string; title: string; description: string; tech: string[]; art: TimelineArtId }[] = [
{
era: '1976',
title: 'Der Anfang',
description: 'Erste Programme in Fortran IV. Lochkarten, Großrechner, Batch-Verarbeitung. Erste Programmierkurse im Rahmen des VWL-Studiums. Die Grundlagen des algorithmischen Denkens.',
tech: ['Fortran IV'],
+ art: 'lochkarte',
},
{
era: '1984',
title: 'Der erste PC',
description: 'Erste Programme in Basic, C und Pascal auf dem Personal Computer unter DOS. Datenbanken, Textverarbeitung, Spiele.',
tech: ['Lotus 1-2-3', 'dBASE', 'QBasic', 'Turbo Pascal', 'Norton Commander', 'WordPerfect'],
+ art: 'pc',
},
{
era: '1986',
title: 'Systemnahe Programmierung',
description: 'C und 68000-Assembler auf dem Atari ST. Direkte Hardware-Ansteuerung, Interrupt-Routinen, Speicherverwaltung von Hand. Pseudo-Multitasking. DFÜ mit Akustikkoppler. Maiboxen, FidoNet, MausNet.',
tech: ['C', '68000 ASM', 'GFA Basic', 'TOS'],
+ art: 'atari',
},
{
era: '1990',
title: 'Unix an der Universität',
description: 'AIX, HP-UX, BSD. Die Philosophie kleiner, zusammenarbeitender Programme. Shell-Scripting als Werkzeug. Internet. Sysop-Aufgaben im Uni-Rechenzentrum.',
tech: ['Unix', 'Shell', 'vi', 'Gopher'],
+ art: 'unix',
},
{
era: '1992',
title: 'Linux entdeckt',
description: 'Slackware auf 40 Disketten, Debian, SUSE Linux ... Der Beginn einer dauerhaften Begeisterung für offene Systeme und Selbstbestimmung. C, C++, Pascal, Modula, Perl, Shell-Skript. Internet per Modem. Pentesting, Hacking.',
tech: ['Linux', 'GNU Tools', 'X11'],
+ art: 'linux',
},
{
era: '1995 ff.',
title: 'World Wide Web',
description: 'Permanenter privater Internet-Zugang kommerzieller Anbieter und erste eigene Domain: linix.de. World Wide Web. Eigene Webseiten in HTML und JavaScript programmiert.',
tech: ['WWW', 'Netscape', 'HTML', 'Java', 'JavaScript', 'Modem'],
+ art: 'www',
},
{
era: '2000er',
title: 'Professionelle Entwicklung',
description: 'Verschiedene Sprachen, verschiedene Domänen. Server-Administration, Automatisierung, Web-Entwicklung. Systemadministration, Networking. — Alles unter Linux.',
tech: ['Perl', 'PHP', 'Bash', 'Python', 'Go', 'Proxmox', 'Docker/Podman'],
+ art: 'server',
},
{
era: '2022',
title: 'KI-Fokus',
description: 'Seit Ende 2022 intensive Beschäftigung mit KI-Tools und KI-Programmierung. LLMs als Arbeitswerkzeug und Forschungsgegenstand.',
tech: ['Python', 'Rust', 'LLMs', 'ML'],
+ art: 'ki',
},
]
@@ -227,7 +236,7 @@ export function AboutSection() {
{/* Content */}
-
+
{item.era}
{item.title}
@@ -247,8 +256,10 @@ export function AboutSection() {
- {/* Spacer for alternating layout */}
- {index % 2 !== 0 &&
}
+ {/* Illustration on the opposite side */}
+
+
+
))}
diff --git a/components/timeline-holo.tsx b/components/timeline-holo.tsx
new file mode 100644
index 0000000..05e6504
--- /dev/null
+++ b/components/timeline-holo.tsx
@@ -0,0 +1,931 @@
+// Timeline-Illustrationen: Hologramm-Szenen im Stil des Hero-Bilds
+// (dunkler Raum, Neon-Deckenlicht, Glas-Panels, Projektor auf dem Tisch).
+// Bewusst themenunabhängig dunkel – wie ein Foto.
+
+export type TimelineArtId =
+ | 'lochkarte'
+ | 'pc'
+ | 'atari'
+ | 'unix'
+ | 'linux'
+ | 'www'
+ | 'server'
+ | 'ki'
+
+const MONO = { fontFamily: 'var(--font-jetbrains-mono), ui-monospace, monospace', whiteSpace: 'pre' } as const
+
+const CY = '#5ff3ff'
+const CY_TXT = '#a8fbff'
+const CY_DIM = '#4fc3d4'
+const MAG = '#ff4fd8'
+const SCREEN_DARK = '#062a33'
+const SCREEN_LIGHT = '#d9fbff'
+
+type U = (name: string) => string
+
+function holo(u: U) {
+ return {
+ stroke: CY,
+ fill: 'none',
+ strokeWidth: 1.1,
+ strokeLinecap: 'round' as const,
+ strokeLinejoin: 'round' as const,
+ filter: `url(#${u('glow')})`,
+ }
+}
+
+function T({
+ x,
+ y,
+ size = 6,
+ color = CY_TXT,
+ anchor,
+ className,
+ children,
+}: {
+ x: number
+ y: number
+ size?: number
+ color?: string
+ anchor?: 'start' | 'middle' | 'end'
+ className?: string
+ children: React.ReactNode
+}) {
+ return (
+
+ {children}
+
+ )
+}
+
+/* === Gemeinsame Bausteine: Raum, Projektor, Glas-Panel === */
+
+function Defs({ u }: { u: U }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const BOKEH: [number, number, number, string, number][] = [
+ [26, 70, 12, '#ff7a3d', 0.35],
+ [46, 128, 7, '#ff3d6e', 0.3],
+ [18, 186, 9, '#7a5cff', 0.25],
+ [374, 84, 11, '#3dd6ff', 0.3],
+ [356, 150, 6, '#ff7a3d', 0.22],
+ [388, 200, 8, MAG, 0.2],
+]
+
+function Backdrop({ u }: { u: U }) {
+ const rays = [-240, -120, 0, 100, 200, 300, 400, 520, 640]
+ return (
+ <>
+
+
+ {/* Rack-Silhouetten an der Rückwand */}
+ {[8, 36, 348, 376].map((x) => (
+
+ ))}
+ {BOKEH.map(([x, y, r, c, o], i) => (
+
+ ))}
+
+ {/* Decke in Perspektive mit Neon-Leisten */}
+
+ {rays.map((x) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ {/* Tisch */}
+
+
+ {[-200, -60, 80, 200, 320, 460, 600].map((x) => (
+
+ ))}
+
+
+ >
+ )
+}
+
+function Overlay({ u, caption }: { u: U; caption: string }) {
+ return (
+ <>
+
+
+
+
+ {caption}
+
+ >
+ )
+}
+
+function Emitter({ u, cx, w = 100, top = 30 }: { u: U; cx: number; w?: number; top?: number }) {
+ return (
+
+
+
+
+
+ )
+}
+
+function GlassPanel({
+ u,
+ x,
+ y,
+ w,
+ h,
+ tilt,
+ title,
+ lines = [],
+ size = 5.6,
+ children,
+}: {
+ u: U
+ x: number
+ y: number
+ w: number
+ h: number
+ tilt: number
+ title: string
+ lines?: string[]
+ size?: number
+ children?: React.ReactNode
+}) {
+ return (
+
+
+
+ {[6, 12, 18].map((cx) => (
+
+ ))}
+
+ {title}
+
+ {lines.map((line, i) => (
+
+ {line}
+
+ ))}
+ {children}
+
+
+ )
+}
+
+/* === 1976: Lochkarte === */
+
+const CARD_TEXT = 'C JAMULIX 1976 FORTRAN IV'.padEnd(28, ' ')
+
+// Zeilenindex: 0 = Zone 12, 1 = Zone 11, 2 = Ziffer 0, 3..11 = Ziffern 1..9
+function hollerith(ch: string): number[] {
+ if (ch >= '0' && ch <= '9') return [2 + Number(ch)]
+ const c = ch.charCodeAt(0) - 65
+ if (c >= 0 && c < 9) return [0, 3 + c]
+ if (c >= 9 && c < 18) return [1, 3 + c - 9]
+ if (c >= 18 && c < 26) return [2, 4 + c - 18]
+ return []
+}
+
+function Lochkarte({ u }: { u: U }) {
+ return (
+ <>
+
+
+
+
+
+
+
+ {CARD_TEXT.split('').map((ch, i) => {
+ const x = 14 + i * 7.2
+ const holes = hollerith(ch)
+ return (
+
+
+ {ch}
+
+ {Array.from({ length: 12 }, (_, r) => {
+ const y = 17 + r * 7.3
+ return holes.includes(r) ? (
+
+ ) : (
+
+ )
+ })}
+
+ )
+ })}
+
+
+
+ {/* Bandspulen */}
+
+
+ {[322, 362].map((cx, i) => (
+
+
+
+
+
+ ))}
+
+
+
+
+ >
+ )
+}
+
+/* === 1984: PC === */
+
+function Pc({ u }: { u: U }) {
+ return (
+ <>
+
+
+
+
+
+ C:\>dir /w
+
+
+ TP.EXE DBASE.EXE
+
+
+ 123.COM NC.EXE
+
+
+ WP.EXE QBASIC.EXE
+
+
+ C:\>
+
+
+
+
+
+
+
+
+ {Array.from({ length: 3 }, (_, r) =>
+ Array.from({ length: 14 }, (_, i) => (
+
+ )),
+ )}
+
+
+
+ {['A', 'B', 'C'].map((c, i) => (
+
+ {c}
+
+ ))}
+ {[
+ ['Miete', '450', '450'],
+ ['Disks', '89', '178'],
+ ['Modem', '398', '398'],
+ ['RAM', '640', '0'],
+ ['Summe', '1577', '1026'],
+ ].map((row, r) => (
+
+ {row.map((v, i) => (
+
+ {v}
+
+ ))}
+
+
+ ))}
+
+
+
+
+
+ {/* 5,25"-Diskette */}
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+/* === 1986: Atari ST === */
+
+function Atari({ u }: { u: U }) {
+ return (
+ <>
+
+
+
+
+
+
+ Desk File View Options
+
+
+
+ move.l d0,(a1)+
+
+
+ dbra d7,loop
+
+
+ trap #1
+
+
+ {[160, 171, 182].map((x) => (
+
+
+
+
+ ))}
+
+ OK
+
+
+
+ {Array.from({ length: 10 }, (_, i) => (
+
+ ))}
+ {Array.from({ length: 3 }, (_, r) =>
+ Array.from({ length: 15 }, (_, i) => (
+
+ )),
+ )}
+
+ 520ST
+
+
+
+
+
+ {/* Akustikkoppler */}
+ {[0, 0.4, 0.8].map((d, i) => (
+
+ ))}
+
+
+
+
+
+
+
+ 300 BAUD
+
+
+ >
+ )
+}
+
+/* === 1990: Unix === */
+
+function Unix({ u }: { u: U }) {
+ const tools = ['grep', 'sort', 'uniq', 'wc']
+ return (
+ <>
+
+
+
+
+
+
+ {tools.map((t, i) => {
+ const y = 52 + i * 44
+ return (
+
+
+
+ {t}
+
+ {i < tools.length - 1 && (
+ <>
+
+
+ |
+
+ >
+ )}
+
+ )
+ })}
+
+
+ Do one thing and do it well.
+
+ >
+ )
+}
+
+/* === 1992: Linux === */
+
+function Disk({ i, label }: { i: number; label?: boolean }) {
+ return (
+
+
+
+
+
+ {label && (
+ <>
+
+ SLACKWARE
+
+
+ disk a1 / 40
+
+
+ >
+ )}
+
+ )
+}
+
+function Linux({ u }: { u: U }) {
+ return (
+ <>
+
+
+ {[0, 1, 2, 3, 4].map((i) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ 40 × 1,44 MB · Kernel 0.99
+
+
+
+ {/* Tux, „echt“ auf dem Tisch */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+/* === 1995: WWW === */
+
+const CHIRP = Array.from({ length: 110 }, (_, i) => {
+ const t = i / 109
+ const x = 244 + i * 1.25
+ const y = 212 + Math.sin(i * (0.25 + t * 0.9)) * (3 + 7 * t)
+ return `${x.toFixed(1)},${y.toFixed(1)}`
+}).join(' ')
+
+function Www({ u }: { u: U }) {
+ const cx = 128
+ const cy = 128
+ const r = 74
+ const nodes: [number, number][] = [
+ [96, 92],
+ [160, 84],
+ [178, 150],
+ [110, 176],
+ [134, 128],
+ ]
+ return (
+ <>
+
+
+
+ {[0, 1.5, 3, 4.5].map((d) => (
+
+
+
+ ))}
+ {[-0.75, -0.4, 0, 0.4, 0.75].map((lat) => {
+ const y = cy + lat * r
+ const w = Math.sqrt(1 - lat * lat) * r
+ return
+ })}
+
+
+
+
+ {nodes.map(([x, y], i) => (
+
+ ))}
+
+
+
+
+
+
+ http://www.linix.de/
+
+
+ {[48, 55, 62, 69].map((y, i) => (
+
+ ))}
+
+ <a href="links.html">
+
+
+
+
+ UNDER CONSTRUCTION
+
+
+ Besucher:
+
+
+
+ 000042
+
+
+
+
+
+ ATDT … 28.8k CONNECT
+
+ >
+ )
+}
+
+/* === 2000er: Server === */
+
+function IsoBox({ cx, y0, s, label }: { cx: number; y0: number; s: number; label: string }) {
+ return (
+
+
+
+
+
+ {label}
+
+
+ )
+}
+
+function Server({ u }: { u: U }) {
+ return (
+ <>
+
+
+
+
+ {Array.from({ length: 7 }, (_, i) => {
+ const y = 48 + i * 25.5
+ return (
+
+
+
+
+
+
+ )
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+/* === 2022: KI === */
+
+const BRAIN_NODES: [number, number][] = [
+ [78, 112], [96, 84], [122, 72], [150, 62], [176, 72], [200, 86], [214, 112], [204, 140], [178, 146],
+ [150, 156], [118, 152], [92, 138], [110, 108], [140, 96], [168, 108], [132, 130], [160, 132], [188, 120],
+]
+const BRAIN_EDGES: [number, number][] = []
+BRAIN_NODES.forEach(([x1, y1], i) =>
+ BRAIN_NODES.forEach(([x2, y2], j) => {
+ if (j > i && Math.hypot(x2 - x1, y2 - y1) < 38) BRAIN_EDGES.push([i, j])
+ }),
+)
+
+function Ki({ u }: { u: U }) {
+ const probs: [string, number][] = [
+ ['Linux', 0.71],
+ ['Code', 0.17],
+ ['Kaffee', 0.08],
+ ['Windows', 0.01],
+ ]
+ return (
+ <>
+
+
+ {/* Großhirn */}
+
+ {/* Kleinhirn und Hirnstamm */}
+
+
+
+ {/* Furchen */}
+
+
+
+
+
+
+
+
+ {BRAIN_EDGES.map(([a, b]) => (
+
+ ))}
+ {BRAIN_NODES.map(([x, y], i) => (
+
+ ))}
+
+
+
+
+
+
+ > Je schlauer, desto
+
+ {probs.map(([word, p], i) => {
+ const y = 44 + i * 24
+ return (
+
+
+ {word}
+
+
+ {p.toFixed(2)}
+
+
+
+
+ )
+ })}
+
+ temperature 0.7 · top_p 0.9
+
+
+ >
+ )
+}
+
+const SCENES: Record React.JSX.Element }> = {
+ lochkarte: {
+ caption: 'FIG.01 // 1976 // LOCHKARTE · HOLLERITH-CODE',
+ alt: 'Hologramm einer Lochkarte mit gestanzter Zeile „C JAMULIX 1976 FORTRAN IV“, daneben ein Fortran-Listing und Bandspulen',
+ Scene: Lochkarte,
+ },
+ pc: {
+ caption: 'FIG.02 // 1984 // PC UNTER DOS',
+ alt: 'Hologramm eines PCs mit DOS-Prompt, daneben schwebende Panels mit Lotus-Tabelle und dBASE',
+ Scene: Pc,
+ },
+ atari: {
+ caption: 'FIG.03 // 1986 // ATARI ST · 68000 · DFÜ',
+ alt: 'Hologramm eines Atari ST mit GEM-Desktop und Bomben-Dialog, 68000-Assembler-Panel und Akustikkoppler',
+ Scene: Atari,
+ },
+ unix: {
+ caption: 'FIG.04 // 1990 // UNIX-SHELL UND PIPES',
+ alt: 'Schwebendes Terminal mit Unix-Shell-Befehlen und eine Pipeline aus grep, sort, uniq und wc als Hologramm',
+ Scene: Unix,
+ },
+ linux: {
+ caption: 'FIG.05 // 1992 // SLACKWARE AUF 40 DISKETTEN',
+ alt: 'Hologramm gefächerter Slackware-Disketten mit Installationsfortschritt, daneben der Linux-Pinguin Tux auf dem Tisch',
+ Scene: Linux,
+ },
+ www: {
+ caption: 'FIG.06 // 1995 // WORLD WIDE WEB',
+ alt: 'Rotierender Hologramm-Globus mit Hyperlinks, Browser-Panel von linix.de mit Besucherzähler und Modem-Signal',
+ Scene: Www,
+ },
+ server: {
+ caption: 'FIG.07 // 2000ER // RACK · CONTAINER · AUTOMATISIERUNG',
+ alt: 'Hologramm eines Server-Racks mit blinkenden LEDs, verbunden mit Containern, daneben ein SSH-Panel',
+ Scene: Server,
+ },
+ ki: {
+ caption: 'FIG.08 // 2022 // SPRACHMODELL · NÄCHSTES TOKEN',
+ alt: 'Gehirn aus Netzwerkknoten als Hologramm; ein Panel zeigt „Linux“ mit 71 Prozent als nächstes Token nach „Je schlauer, desto“',
+ Scene: Ki,
+ },
+}
+
+export function TimelineHolo({ id, className = '' }: { id: TimelineArtId; className?: string }) {
+ const { caption, alt, Scene } = SCENES[id]
+ const u: U = (name) => `holo-${id}-${name}`
+ return (
+
+
+
+ )
+}