// 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 (
)
}