Compare commits
15 changed files with 9 additions and 472 deletions
35
README.md
35
README.md
|
|
@ -98,7 +98,6 @@ Die E-Mail im Footer (`footer.tsx`) wird als Prop von `app/page.tsx` übergeben:
|
|||
- Öffnet eine SSH-Master-Verbindung (Passwort nur einmal eingeben)
|
||||
- rsync nach `jamulix.de:/var/www/jamulix.de/html/landingpage/`
|
||||
- Setzt Symlink: `/var/www/jamulix.de/html/index.html` → `landingpage/index.html`
|
||||
- Kopiert die Fehlerseiten `landingpage/<code>.html` in den Webroot
|
||||
|
||||
### Localhost (zum Testen)
|
||||
|
||||
|
|
@ -108,7 +107,6 @@ Die E-Mail im Footer (`footer.tsx`) wird als Prop von `app/page.tsx` übergeben:
|
|||
|
||||
- rsync nach `/var/www/html/landingpage/`
|
||||
- Symlink: `/var/www/html/index.html` → `landingpage/index.html`
|
||||
- Kopiert die Fehlerseiten in den Webroot
|
||||
- `sudo chown -R www-data:www-data`
|
||||
|
||||
### Warum `/landingpage/` als Unterverzeichnis?
|
||||
|
|
@ -117,39 +115,6 @@ Die E-Mail im Footer (`footer.tsx`) wird als Prop von `app/page.tsx` übergeben:
|
|||
unter `jamulix.de/` zu betreiben ohne Konflikte. rsync mit `--delete` löscht
|
||||
nur Dateien innerhalb von `landingpage/`, nicht andere Verzeichnisse im Webroot.
|
||||
|
||||
### Fehlerseiten
|
||||
|
||||
| Code | Quelle | Thema |
|
||||
|------|--------|-------|
|
||||
| 401 | `app/401/page.tsx` | Autorisierung erforderlich |
|
||||
| 403 | `app/403/page.tsx` | Zugriff verweigert |
|
||||
| 404 | `app/not-found.tsx` | Seite nicht gefunden |
|
||||
| 500 | `app/500/page.tsx` | Interner Serverfehler |
|
||||
| 502 | `app/502/page.tsx` | Bad Gateway |
|
||||
| 503 | `app/503/page.tsx` | Wartungsarbeiten |
|
||||
| 504 | `app/504/page.tsx` | Gateway Timeout |
|
||||
|
||||
Gemeinsames Layout: `components/error-page.tsx`. Der Build erzeugt
|
||||
`landingpage/<code>.html`; die Deploy-Skripte kopieren sie zusätzlich nach
|
||||
`<webroot>/<code>.html`. Alle Asset-Pfade sind absolut (`/landingpage/_next/…`),
|
||||
daher funktionieren die Kopien im Webroot genauso.
|
||||
|
||||
Damit Apache sie ausliefert, braucht die vHost-Konfiguration (nicht Teil
|
||||
dieses Repos) z. B.:
|
||||
|
||||
```apache
|
||||
ErrorDocument 401 /401.html
|
||||
ErrorDocument 403 /403.html
|
||||
ErrorDocument 404 /404.html
|
||||
ErrorDocument 500 /500.html
|
||||
ErrorDocument 502 /502.html
|
||||
ErrorDocument 503 /503.html
|
||||
ErrorDocument 504 /504.html
|
||||
```
|
||||
|
||||
- `ErrorDocument 401` muss ein lokaler Pfad sein, keine vollständige URL.
|
||||
- Die 401-Seite erscheint erst, wenn der Login-Dialog des Browsers abgebrochen wird.
|
||||
|
||||
---
|
||||
|
||||
## Design
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
import { ErrorPage, Cmd, Out, Err } from '@/components/error-page'
|
||||
|
||||
export const metadata = {
|
||||
title: '401 – Autorisierung erforderlich | Jamulix',
|
||||
description: 'Parole? Dieser Bereich ist passwortgeschützt.',
|
||||
robots: { index: false, follow: false },
|
||||
}
|
||||
|
||||
export default function Unauthorized() {
|
||||
return (
|
||||
<ErrorPage
|
||||
code={401}
|
||||
label="Autorisierung erforderlich"
|
||||
title="Parole?"
|
||||
terminal={
|
||||
<>
|
||||
<Cmd>ssh gast@jamulix.de</Cmd>
|
||||
<Err>gast@jamulix.de: Permission denied (publickey,password).</Err>
|
||||
<Cmd>sudo !!</Cmd>
|
||||
<Out>[sudo] Passwort für gast: ********</Out>
|
||||
<Out>Sorry, try again.</Out>
|
||||
<Out>[sudo] Passwort für gast: geheim</Out>
|
||||
<Out>Sorry, try again.</Out>
|
||||
<Out>[sudo] Passwort für gast: 123456</Out>
|
||||
<Err>sudo: 3 incorrect password attempts</Err>
|
||||
<Cmd>echo "Sesam, öffne dich"</Cmd>
|
||||
<Out>Sesam, öffne dich</Out>
|
||||
</>
|
||||
}
|
||||
tip={<>Tipp: <span className="text-accent">123456</span> ist es auch diesmal nicht.</>}
|
||||
>
|
||||
Dieser Bereich ist passwortgeschützt. Wer Zugangsdaten hat, lädt die
|
||||
Seite einfach neu und meldet sich an – alle anderen bekommen immerhin
|
||||
diese hübsche Fehlerseite.
|
||||
</ErrorPage>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { ErrorPage, Cmd, Out, Err } from '@/components/error-page'
|
||||
import { RequestedPath } from '@/components/requested-path'
|
||||
|
||||
export const metadata = {
|
||||
title: '403 – Zugriff verweigert | Jamulix',
|
||||
description: 'Hier gilt chmod 000.',
|
||||
robots: { index: false, follow: false },
|
||||
}
|
||||
|
||||
export default function Forbidden() {
|
||||
return (
|
||||
<ErrorPage
|
||||
code={403}
|
||||
label="Zugriff verweigert"
|
||||
title="Hier gilt chmod 000."
|
||||
terminal={
|
||||
<>
|
||||
<Cmd>ls -l <RequestedPath /></Cmd>
|
||||
<Err>ls: Zugriff auf '<RequestedPath />' nicht möglich: Keine Berechtigung</Err>
|
||||
<Cmd>chmod 777 <RequestedPath /></Cmd>
|
||||
<Err>chmod: Ändern der Zugriffsrechte nicht möglich: Operation nicht erlaubt</Err>
|
||||
<Cmd>sudo chmod 777 <RequestedPath /></Cmd>
|
||||
<Err>gast ist nicht in der sudoers-Datei. Dieser Vorfall wird gemeldet.</Err>
|
||||
<Cmd>whoami</Cmd>
|
||||
<Out>gast</Out>
|
||||
</>
|
||||
}
|
||||
tip={<>Tipp: Auch root war einmal <span className="text-accent">gast</span>.</>}
|
||||
>
|
||||
Die Seite gibt es – nur eben nicht für dich. Manche Verzeichnisse
|
||||
bleiben aus gutem Grund verschlossen, selbst nach 50 Jahren
|
||||
Unix-Erfahrung und mit freundlichem Fragen.
|
||||
</ErrorPage>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import { ErrorPage, Cmd, Out, Err, Code } from '@/components/error-page'
|
||||
|
||||
export const metadata = {
|
||||
title: '500 – Interner Serverfehler | Jamulix',
|
||||
description: 'Der Server hat sich verschluckt.',
|
||||
robots: { index: false, follow: false },
|
||||
}
|
||||
|
||||
export default function InternalServerError() {
|
||||
return (
|
||||
<ErrorPage
|
||||
code={500}
|
||||
label="Interner Serverfehler"
|
||||
title="Der Server hat sich verschluckt."
|
||||
terminal={
|
||||
<>
|
||||
<Cmd>./server --seite-ausliefern</Cmd>
|
||||
<Err>Segmentation fault (core dumped)</Err>
|
||||
<Cmd>gdb ./server core</Cmd>
|
||||
<Out>#0 0x00000000 in ?? ()</Out>
|
||||
<Out>#1 0x0000dead in seite_ausliefern (zeiger=NULL)</Out>
|
||||
<Cmd>cat notfall.f</Cmd>
|
||||
<Out>C WENN ALLES SCHIEFGEHT:</Out>
|
||||
<Code> STOP 500</Code>
|
||||
</>
|
||||
}
|
||||
tip={<>Tipp: Kurz warten, dann <span className="text-accent">F5</span> – das hilft öfter, als man denkt.</>}
|
||||
>
|
||||
Irgendwo im Maschinenraum ist ein Zeiger ins Leere gelaufen. Das ist
|
||||
nicht deine Schuld. Der Fehler wird untersucht – vermutlich bei einer
|
||||
Tasse Tee.
|
||||
</ErrorPage>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import { ErrorPage, Cmd, Out, Err } from '@/components/error-page'
|
||||
|
||||
export const metadata = {
|
||||
title: '502 – Bad Gateway | Jamulix',
|
||||
description: 'Der Server hinter dem Server schweigt.',
|
||||
robots: { index: false, follow: false },
|
||||
}
|
||||
|
||||
export default function BadGateway() {
|
||||
return (
|
||||
<ErrorPage
|
||||
code={502}
|
||||
label="Bad Gateway"
|
||||
title="Der Server hinter dem Server schweigt."
|
||||
terminal={
|
||||
<>
|
||||
<Cmd>ping -c 3 backend</Cmd>
|
||||
<Out>--- backend ping statistics ---</Out>
|
||||
<Err>3 packets transmitted, 0 received, 100% packet loss</Err>
|
||||
<Cmd>curl http://localhost:8080/</Cmd>
|
||||
<Err>curl: (7) Failed to connect to localhost port 8080: Connection refused</Err>
|
||||
<Cmd>echo "Hallo? Jemand da?" > /dev/backend</Cmd>
|
||||
<Out>…</Out>
|
||||
</>
|
||||
}
|
||||
tip={<>Tipp: Der Dienst startet meist von selbst neu – in ein paar Minuten nochmal versuchen.</>}
|
||||
>
|
||||
Der Webserver ist da und hat brav weitergefragt – aber die Anwendung
|
||||
dahinter antwortet gerade nicht. Wahrscheinlich startet sie neu oder
|
||||
denkt über ihr Leben nach.
|
||||
</ErrorPage>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import { ErrorPage, Cmd, Out, Code } from '@/components/error-page'
|
||||
|
||||
export const metadata = {
|
||||
title: '503 – Wartungsarbeiten | Jamulix',
|
||||
description: 'Der Server macht gerade Teepause.',
|
||||
robots: { index: false, follow: false },
|
||||
}
|
||||
|
||||
export default function ServiceUnavailable() {
|
||||
return (
|
||||
<ErrorPage
|
||||
code={503}
|
||||
label="Wartungsarbeiten"
|
||||
title="Der Server macht gerade Teepause."
|
||||
terminal={
|
||||
<>
|
||||
<Cmd>systemctl status jamulix</Cmd>
|
||||
<Out>● jamulix.service – Jamulix Webdienst</Out>
|
||||
<Code> Active: maintenance (Teepause) since vorhin</Code>
|
||||
<Out> Tasks: 1 (Wasser kocht)</Out>
|
||||
<Cmd>sleep 300 && tee tasse.txt < kanne</Cmd>
|
||||
<Out>Earl Grey, heiß.</Out>
|
||||
</>
|
||||
}
|
||||
tip={<>Tipp: Ein guter Tee zieht 3–5 Minuten – genau wie diese Wartung.</>}
|
||||
>
|
||||
Hier wird gerade gewartet, aktualisiert und ein wenig aufgeräumt.
|
||||
Gleich ist alles wieder da – frisch gepatcht und gut gelaunt.
|
||||
</ErrorPage>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import { ErrorPage, Cmd, Out, Err, Code } from '@/components/error-page'
|
||||
|
||||
export const metadata = {
|
||||
title: '504 – Gateway Timeout | Jamulix',
|
||||
description: 'Der Server denkt noch nach.',
|
||||
robots: { index: false, follow: false },
|
||||
}
|
||||
|
||||
export default function GatewayTimeout() {
|
||||
return (
|
||||
<ErrorPage
|
||||
code={504}
|
||||
label="Gateway Timeout"
|
||||
title="Der Server denkt noch nach."
|
||||
terminal={
|
||||
<>
|
||||
<Cmd>curl --max-time 60 http://backend/</Cmd>
|
||||
<Err>curl: (28) Operation timed out after 60000 milliseconds</Err>
|
||||
<Cmd>cat jobkarte.jcl</Cmd>
|
||||
<Code>//JOB504 JOB (1976),'SEITE',CLASS=A</Code>
|
||||
<Out>JOB 504 IN WARTESCHLANGE – POSITION 42</Out>
|
||||
<Out>VORAUSSICHTLICHE AUSGABE: MORGEN FRÜH AM SCHALTER</Out>
|
||||
</>
|
||||
}
|
||||
tip={<>Tipp: 1976 kam das Ergebnis am nächsten Morgen – heute reicht meist ein Neuladen.</>}
|
||||
>
|
||||
Die Anwendung hinter dem Webserver braucht gerade länger als erlaubt.
|
||||
Früher gab man seinen Lochkartenstapel ab und holte das Ergebnis am
|
||||
nächsten Tag – so lange musst du hoffentlich nicht warten.
|
||||
</ErrorPage>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import { ErrorPage, Cmd, Out, Err, Code } from '@/components/error-page'
|
||||
import { RequestedPath } from '@/components/requested-path'
|
||||
|
||||
export const metadata = {
|
||||
title: '404 – Seite nicht gefunden | Jamulix',
|
||||
description: 'Diese Seite ist vom Lochkartenstapel gefallen.',
|
||||
robots: { index: false, follow: true },
|
||||
}
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<ErrorPage
|
||||
code={404}
|
||||
label="Seite nicht gefunden"
|
||||
title="Diese Seite ist vom Lochkartenstapel gefallen."
|
||||
terminal={
|
||||
<>
|
||||
<Cmd>cd <RequestedPath /></Cmd>
|
||||
<Out>bash: cd: <RequestedPath />: Datei oder Verzeichnis nicht gefunden</Out>
|
||||
<Cmd>find / -name "seite" 2>/dev/null</Cmd>
|
||||
<Cmd>cat seite.f</Cmd>
|
||||
<Out>C GESTANZT 1976 – STAPEL LEIDER RUNTERGEFALLEN</Out>
|
||||
<Code> GO TO 404</Code>
|
||||
<Cmd>sudo !!</Cmd>
|
||||
<Err>jamulix ist nicht in der sudoers-Datei. Dieser Vorfall wird gemeldet.</Err>
|
||||
</>
|
||||
}
|
||||
tip={<>Tipp: Auch nach 50 Jahren hilft <span className="text-accent">ls</span> meistens weiter.</>}
|
||||
>
|
||||
Seit 50 Jahren Code – und trotzdem verlegt man gelegentlich etwas.
|
||||
Die gesuchte Seite existiert nicht, nicht mehr oder noch nicht.
|
||||
Vielleicht war auch nur ein übereifriges{' '}
|
||||
<code className="font-mono text-sm text-accent">rm -rf</code> am Werk.
|
||||
</ErrorPage>
|
||||
)
|
||||
}
|
||||
|
|
@ -119,7 +119,7 @@ export function AboutSection() {
|
|||
Und so gilt für mich das erste Jamulix-Gesetz:
|
||||
</p>
|
||||
<p className="font-mono text-xl lg:text-2xl text-accent mt-3">
|
||||
Je schlauer, desto Linux!
|
||||
Je schlauer, umso Linux!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,157 +0,0 @@
|
|||
import type { ReactNode } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { ArrowLeft, FolderGit2 } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
/* Terminal lines — each renders one line incl. trailing newline inside <pre> */
|
||||
|
||||
export function Cmd({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<span className="text-accent">$</span> {children}{'\n'}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function Out({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<span className="text-muted-foreground">{children}</span>{'\n'}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function Err({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<span className="text-destructive dark:text-red-400">{children}</span>{'\n'}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function Code({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<span className="text-cyan-600 dark:text-cyan-400">{children}</span>{'\n'}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function Cursor() {
|
||||
return (
|
||||
<>
|
||||
<span className="text-accent">$</span>{' '}
|
||||
<span className="inline-block w-2 h-4 align-middle bg-foreground animate-pulse" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type ErrorPageProps = {
|
||||
code: number
|
||||
label: string
|
||||
title: string
|
||||
children: ReactNode
|
||||
terminal: ReactNode
|
||||
tip: ReactNode
|
||||
}
|
||||
|
||||
export function ErrorPage({ code, label, title, children, terminal, tip }: ErrorPageProps) {
|
||||
return (
|
||||
<main className="relative min-h-screen flex flex-col overflow-hidden">
|
||||
{/* Fly.io-style gradient mesh background (dark mode only) */}
|
||||
<div className="absolute inset-0 bg-gradient-mesh hidden dark:block" />
|
||||
|
||||
{/* Subtle grid overlay */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-[0.02] dark:opacity-[0.04]"
|
||||
style={{
|
||||
backgroundImage: `
|
||||
linear-gradient(to right, currentColor 1px, transparent 1px),
|
||||
linear-gradient(to bottom, currentColor 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: '60px 60px',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Logo */}
|
||||
<div className="relative max-w-6xl w-full mx-auto px-6 lg:px-8">
|
||||
<div className="flex items-center h-16 lg:h-20">
|
||||
<Link
|
||||
href="/"
|
||||
className="font-mono text-lg tracking-tight text-foreground hover:text-accent transition-colors"
|
||||
>
|
||||
Jamulix
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative flex-1 flex items-center">
|
||||
<div className="max-w-6xl w-full mx-auto px-6 lg:px-8 py-12 lg:py-20">
|
||||
<div className="grid lg:grid-cols-12 gap-12 lg:gap-16 items-center">
|
||||
{/* Text Content */}
|
||||
<div className="lg:col-span-6 space-y-8">
|
||||
<div>
|
||||
<span className="inline-block font-mono text-xs tracking-wider text-accent uppercase mb-4">
|
||||
Fehler {code} · {label}
|
||||
</span>
|
||||
|
||||
<p
|
||||
aria-hidden="true"
|
||||
className="font-serif font-bold text-8xl sm:text-9xl leading-none tracking-tighter text-foreground/90 dark:bg-gradient-to-r dark:from-cyan-400 dark:to-violet-400 dark:bg-clip-text dark:text-transparent"
|
||||
>
|
||||
{code}
|
||||
</p>
|
||||
|
||||
<h1 className="mt-6 font-serif text-3xl sm:text-4xl lg:text-5xl leading-[1.1] tracking-tight text-balance">
|
||||
{title}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<p className="text-lg text-muted-foreground leading-relaxed max-w-xl">
|
||||
{children}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Button asChild size="lg" className="group">
|
||||
<Link href="/">
|
||||
<ArrowLeft className="mr-2 size-4 transition-transform group-hover:-translate-x-0.5" />
|
||||
cd ~ — Zur Startseite
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" size="lg" className="group">
|
||||
<Link href="/#projekte">
|
||||
<FolderGit2 className="mr-2 size-4" />
|
||||
Projekte ansehen
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Terminal */}
|
||||
<div className="lg:col-span-6">
|
||||
<div className="rounded-lg border border-border bg-card shadow-xl dark:shadow-[0_0_40px_oklch(0.70_0.22_280/0.3)] overflow-hidden">
|
||||
<div className="flex items-center gap-2 px-4 py-3 border-b border-border bg-muted/50">
|
||||
<span className="size-3 rounded-full bg-red-500/80" />
|
||||
<span className="size-3 rounded-full bg-yellow-500/80" />
|
||||
<span className="size-3 rounded-full bg-green-500/80" />
|
||||
<span className="ml-3 font-mono text-xs text-muted-foreground">
|
||||
jamulix@jamulix.de: ~
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<pre className="p-5 sm:p-6 font-mono text-xs sm:text-sm leading-relaxed whitespace-pre-wrap text-card-foreground">
|
||||
{terminal}
|
||||
<Cursor />
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<p className="mt-4 font-mono text-xs text-muted-foreground text-center lg:text-left">
|
||||
{tip}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
|
@ -50,14 +50,11 @@ export function Hero() {
|
|||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="text-lg lg:text-xl text-muted-foreground leading-relaxed max-w-2xl"
|
||||
>
|
||||
Seit 50 Jahren Code – angefangen mit Fortran IV auf Lochkarten.
|
||||
Über C und 68000-Assembler auf dem Atari ST zu Unix an der Uni
|
||||
und dann zu Linux, das mich seit seinen Anfängen begleitet.
|
||||
Heute entwickle ich KI-Systeme und Agenten mit Python, Rust & Co,
|
||||
erstelle und nutze Agentensysteme sowie angrenzende Technologien
|
||||
moderner Softwareentwicklung und des praktischen KI-Einsatzes.
|
||||
Das Augenmerk liegt dabei auf neuen Methoden, digitaler Souveränität,
|
||||
Open Source und Lizenzkostenersparnis.
|
||||
Erste Programmiersprache: Fortran{'\u00A0'}IV im VWL-Studium.
|
||||
Dann C und 68000-Assembler auf dem Atari{'\u00A0'}1040ST programmiert.
|
||||
An der Universität Unix entdeckt – und dann Linux, das mich nie wieder losgelassen hat.
|
||||
Seit Ende 2022 konzentriert auf KI-Tools und KI-Programmierung.
|
||||
Heute: KI, Python, Rust, C und vieles mehr.
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ const projects = [
|
|||
featured: true,
|
||||
},
|
||||
{
|
||||
title: 'Digitalisierung einer "Maultrommel"',
|
||||
title: 'Digitalisierung einer "Mundorgel"',
|
||||
slug: 'upcoming-1',
|
||||
description: 'Lieder aus der Schulzeit und Jugend der 60er- und 70er-Jahre singen? Die „Maultrommel" war damals weit verbreitet, doch heutige Ausgaben enthalten viele dieser Stücke nicht mehr. Antiquarische Exemplare sind selten und teuer, Kopien unpraktisch.\n\nIch habe daher eine alte Maultrommel mit KI-Tools digitalisiert und auf meinem Server durchsuchbar gemacht. So werden analoge Inhalte für moderne Nutzung erschließbar.\n\nDas Projekt diente auch dem Lernen: Die gleichen Verfahren lassen sich auf Bücher, Akten oder Zeitungen übertragen. Im Vergleich zur Anfang 2026 digitalisierten Encyclopaedia Britannica von 1911 (28 Bände) ist mein Projekt klein – doch die zugrunde liegenden Prozesse sind dieselben.',
|
||||
description: 'In unserer Gruppe wollen wir Lieder aus Schulzeit und Jugend der 60er- und 70er-Jahre singen. Die „Mundorgel" war damals weit verbreitet, doch heutige Ausgaben enthalten viele dieser Stücke nicht mehr. Antiquarische Exemplare sind selten und teuer, Kopien unpraktisch.\n\nIch habe daher eine alte Mundorgel mit KI-Tools digitalisiert und auf meinem Server durchsuchbar gemacht. So werden analoge Inhalte für moderne Nutzung erschließbar.\n\nDas Projekt diente auch dem Lernen: Die gleichen Verfahren lassen sich auf Bücher, Akten oder Zeitungen übertragen. Im Vergleich zur kürzlich digitalisierten Encyclopaedia Britannica von 1911 (28 Bände) ist mein Projekt klein – doch die zugrunde liegenden Prozesse sind dieselben.',
|
||||
tech: ['LLM-OCR', 'JSON', 'Typescript', 'Tailwind', 'Vibe-Coding'],
|
||||
status: 'Aktiv',
|
||||
url: 'https://jamulix.de/maultrommel/',
|
||||
url: 'https://jamulix.de/mundorgel/',
|
||||
featured: false,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
// Shows the requested URL path; falls back to a placeholder without JS
|
||||
export function RequestedPath() {
|
||||
const [path, setPath] = useState('/gesuchte-seite')
|
||||
|
||||
useEffect(() => {
|
||||
const raw = window.location.pathname
|
||||
try {
|
||||
setPath(decodeURI(raw))
|
||||
} catch {
|
||||
setPath(raw) // malformed percent-encoding
|
||||
}
|
||||
}, [])
|
||||
|
||||
return <span className="break-all">{path}</span>
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
set -e
|
||||
|
||||
LOCAL_DIR="/home/dschlueter/neue_jamulix.de_landing_page/jamulix.de_webpage_2"
|
||||
ERROR_PAGES="401 403 404 500 502 503 504"
|
||||
|
||||
cd "$LOCAL_DIR"
|
||||
|
||||
|
|
@ -18,12 +17,6 @@ echo "Setze symbolischen Link: index.html → landingpage/index.html"
|
|||
sudo rm -f /var/www/html/index.html
|
||||
sudo ln -s landingpage/index.html /var/www/html/index.html
|
||||
|
||||
echo "Kopiere Fehlerseiten: landingpage/<code>.html → <code>.html"
|
||||
for c in $ERROR_PAGES; do
|
||||
sudo cp "landingpage/$c.html" "/var/www/html/$c.html"
|
||||
sudo chown www-data:www-data "/var/www/html/$c.html"
|
||||
done
|
||||
|
||||
sudo chown -R www-data:www-data /var/www/html/landingpage/
|
||||
|
||||
echo ""
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ REMOTE_HOST="jamulix.de"
|
|||
REMOTE_DIR="/var/www/jamulix.de/html"
|
||||
LOCAL_DIR="/home/dschlueter/neue_jamulix.de_landing_page/jamulix.de_webpage_2"
|
||||
SSH_SOCKET="/tmp/ssh-jamulix-deploy-$$"
|
||||
ERROR_PAGES="401 403 404 500 502 503 504"
|
||||
|
||||
cleanup() {
|
||||
ssh -S "$SSH_SOCKET" -O exit "$REMOTE_USER@$REMOTE_HOST" 2>/dev/null || true
|
||||
|
|
@ -31,8 +30,5 @@ rsync -av --delete -e "ssh -S $SSH_SOCKET" landingpage/ "$REMOTE_USER@$REMOTE_HO
|
|||
echo "Setze symbolischen Link: index.html → landingpage/index.html"
|
||||
ssh -S "$SSH_SOCKET" "$REMOTE_USER@$REMOTE_HOST" "rm -f '$REMOTE_DIR/index.html' && ln -s landingpage/index.html '$REMOTE_DIR/index.html'"
|
||||
|
||||
echo "Kopiere Fehlerseiten: landingpage/<code>.html → <code>.html"
|
||||
ssh -S "$SSH_SOCKET" "$REMOTE_USER@$REMOTE_HOST" "for c in $ERROR_PAGES; do cp '$REMOTE_DIR'/landingpage/\$c.html '$REMOTE_DIR'/\$c.html; done"
|
||||
|
||||
echo ""
|
||||
echo "Deployment abgeschlossen: https://jamulix.de"
|
||||
|
|
|
|||
Loading…
Reference in a new issue