feat(sasch40): add retro birthday game subproject and k8s deployment

- Add Vue 3 + Vite + TypeScript retro game: 'Saschas Kronkorken Kreuzzug: 40 Jahre Bass & Gerstensaft'
- Add dynamic GameBoy layout: controls visible only during arcade modes, fullscreen during dialogue and tavern
- Add QR code welcome & prequel screen with lore, feature preview, and audio hints
- Add competitive party highscore and ranking breakdown on royal certificate
- Add dedicated individual 1:1 character portraits for Sascha, Verena, NAT, Hansi, Mop, Lutz, Armend, Amy, Mane, Pü, and Mario
- Add Dockerfile, Makefile, and K8s deployment at sasch40.haumdaucher.de
- Update openclaw timeout configuration
This commit is contained in:
Moritz Graf
2026-09-23 21:56:34 +02:00
parent d11a84a0d3
commit 5852479c21
53 changed files with 5544 additions and 0 deletions
@@ -0,0 +1,129 @@
export interface CharacterConfig {
id: string
name: string
title: string
portrait: string
description: string
famousQuote: string
themeColor: string
}
export const CHARACTERS: Record<string, CharacterConfig> = {
sascha: {
id: 'sascha',
name: 'König Sascha I.',
title: 'Der 40-jährige Imperator',
portrait: '/assets/characters/sascha/portrait.jpg',
description: 'Herrscher über Haumdaucher, Träger der heiligen Sonnenbrille und Beschützer des 135-BPM-Basses.',
famousQuote: 'O zapft is im Reiche! Wo san meine Kronkorken?!',
themeColor: '#d4af37'
},
mop: {
id: 'mop',
name: 'Mop (Moritz)',
title: 'Der Welten-Erschaffer & Matrix-Architekt',
portrait: '/assets/characters/mop/portrait.jpg',
description: 'Sitzt auf einer Wolke über dem Schloss und baut aus Pixeln und Hopfen diese Welt.',
famousQuote: 'Just adding a few more polygons here... wait, pixels. Pixels.',
themeColor: '#38bdf8'
},
nat: {
id: 'nat',
name: 'NAT (Josef)',
title: 'Hof-Chronist & Barde des Wildschweins',
portrait: '/assets/characters/nat/portrait.jpg',
description: 'Kennt alle 3 Staffeln Bridgerton in- und auswendig, trägt florale Bomberjacken und erzählt epische Geschichten.',
famousQuote: 'Also pass auf Sascha... ich damals mit meinen Girls ab...',
themeColor: '#22c55e'
},
verena: {
id: 'verena',
name: 'Königin Verena',
title: 'Herrin des Hofes',
portrait: '/assets/characters/verena/portrait.jpg',
description: 'Liebt Sascha über alles auf der Welt, hat aber ein unumstößliches königliches Edikt erlassen.',
famousQuote: 'Ich hab dich lieb mein Schatz, aber ich putze nach der Party fei ned!',
themeColor: '#ec4899'
},
hansi: {
id: 'hansi',
name: 'Hansi (Johann)',
title: 'Ritter der floralen Garde',
portrait: '/assets/characters/hansi/portrait.jpg',
description: 'Perfekt gestylter Schnurrbart und florale Plattenrüstung.',
famousQuote: 'Habe die Ehre! Der Bass im Rittersaal ist auf Punkt.',
themeColor: '#6366f1'
},
lutz: {
id: 'lutz',
name: 'Lutz',
title: 'Der ZTUL-Hofschornsteinfeger',
portrait: '/assets/characters/lutz/portrait.jpg',
description: 'Ein Hüne von Gestalt mit Kappe, runder Brille und dem legendären ZTUL-Trikot.',
famousQuote: 'Glück auf, Euer Durchlaucht! Schornstein ist frei für den Party-Nebel.',
themeColor: '#64748b'
},
armend: {
id: 'armend',
name: 'Armend',
title: 'Meister-Schmied der Kronkorken-Manufaktur',
portrait: '/assets/characters/armend/portrait.jpg',
description: 'Prachtvoller Vollbart, formt aus 40 Kronkorken den goldenen Sound-Verstärker.',
famousQuote: 'Schlag zu, solange das Blech warm ist!',
themeColor: '#f97316'
},
amy: {
id: 'amy',
name: 'Amy (Amelie)',
title: 'Die Hof-Künstlerin',
portrait: '/assets/characters/amy/portrait.jpg',
description: 'Kreatives Genie in Latzhose mit Dutt und feinen Kräuter-Zigarren.',
famousQuote: 'Rave the Kingdom! Die Farben brauchen mehr Bass!',
themeColor: '#a855f7'
},
pue: {
id: 'pue',
name: 'Pü (Jakob)',
title: 'Der Lichtler des Hofes',
portrait: '/assets/characters/pue/portrait.jpg',
description: 'Früherer Club-Lichtmeister, bedient nun die magischen Stroboskop-Fackeln.',
famousQuote: 'Majestät, wann zünden wir die Stroboskop-Fackeln zum Drop?',
themeColor: '#eab308'
},
mario: {
id: 'mario',
name: 'Mario der Gaukler',
title: 'Hof-Jongleur & Stimmungs-Garant',
portrait: '/assets/characters/mario/portrait.jpg',
description: 'Jongliert geschickt mit Hopfen, Hefe und Bierkrügen.',
famousQuote: 'Hoppla die Waldfee! Ein Krug für den König!',
themeColor: '#14b8a6'
},
mane: {
id: 'mane',
name: 'Mane (Ramona)',
title: 'Die königliche Wanderin',
portrait: '/assets/characters/mane/portrait.jpg',
description: 'Wandert random mit Rucksack und Wanderstock quer durch alle Bildschirme.',
famousQuote: 'Schee ists hier! Noch 3 Kilometer bis zum Gipfel-Bier!',
themeColor: '#84cc16'
},
djs: {
id: 'djs',
name: 'Die Hof-DJs',
title: 'Minnesänger der 135 BPM',
portrait: '/assets/characters/pue/portrait.jpg',
description: 'Stehen an der königlichen Kanzel am doppelten Eichenholz-Plattenteller.',
famousQuote: 'Drop it like it\'s 1426! Mehr Bass auf die Burgzinnen!',
themeColor: '#06b6d4'
},
gefolge: {
id: 'gefolge',
name: 'Das Haumdaucher Gefolge',
title: 'Die feiernde Reichs-Crowd',
portrait: '/assets/characters/hansi/portrait.jpg',
description: 'Viktor der Vampir, der bärtige Guatemalteke, Fabian, Albin, Maier Flo, Markus & Nathalie.',
famousQuote: 'Ein Hoch auf König Sascha! Hoch soll er leben!',
themeColor: '#e11d48'
}
}
@@ -0,0 +1,106 @@
export interface DialogueChoice {
text: string
nextId?: string
action?: 'addKronkorken' | 'unlockZuluman' | 'unlockBridgerton' | 'startPlatformer' | 'startExpress' | 'startFinale'
reward?: number
reply?: {
speakerId: string
text: string
}
}
export interface DialogueNode {
id: string
speakerId: string
text: string
choices: DialogueChoice[]
}
export const PROLOGUE_DIALOGUES: Record<string, DialogueNode> = {
start: {
id: 'start',
speakerId: 'nat',
text: 'Seyd gegrüßet, Sascha! 40 Lenze zählet dein edles Antlitz! Aber ich sag\'s dir wie\'s is... der Hofstaat ist in heller Aufruhr. Ein Drama wie damals beim königlichen Ball in Staffel 2 von Bridgerton! Sämtliche 40 königlichen Kronkorken san über ganz Haumdaucher verstreut!',
choices: [
{
text: 'Zefix Josef! Ohne Kronkorken kein Bier, und ohne Bier kein Bass!',
nextId: 'mop_appears',
action: 'unlockBridgerton'
},
{
text: 'Bridgerton?! Josef, konzentrier dich! Wo san meine 40 Kronkorken?!',
nextId: 'mop_appears'
},
{
text: 'Lass mich raten... Du mit deinen Girls ab und zack, Leergut weg?',
nextId: 'nat_girls',
action: 'addKronkorken',
reward: 2
}
]
},
nat_girls: {
id: 'nat_girls',
speakerId: 'nat',
text: 'Ja mei, woher weißt du das immer?! Wir zogen durch die Lande, die Musik war laut, der Gerstensaft floss in Strömen... und dann hat der Wind die Korken über die Dächer geweht!',
choices: [
{
text: 'Na servus. Dann muss der König wohl selbst ran.',
nextId: 'mop_appears'
}
]
},
mop_appears: {
id: 'mop_appears',
speakerId: 'mop',
text: 'Habe die Ehre, Sascha! Keine Panik, dein Erschaffer hat schon die Sprungfedern in deine königlichen Stiefel programmiert. Sammle mindestens 15 Kronkorken auf den Hopfen-Pfaden, bevor wir in die Taverne einziehen!',
choices: [
{
text: 'Auf geht\'s Mop! Starte den Kronkorken-Kreuzzug!',
action: 'startPlatformer'
},
{
text: 'Zeig mir erst noch meine Sonnenbrille für den Style.',
nextId: 'sascha_ready'
}
]
},
sascha_ready: {
id: 'sascha_ready',
speakerId: 'sascha',
text: 'Sonnenbrille sitzt. Hermelinmantel weht im Wind. Haumdaucher, der König kommt!',
choices: [
{
text: 'Level 1 starten: Die Hopfen-Pfade!',
action: 'startPlatformer'
}
]
}
}
export const GSTANZL_DUEL = [
{
insult: 'Dein Techno-Bass ist so dünn wie schales Exportbier!',
answers: [
{ text: 'Dafür schallert mein Drop lauter als dei ganze Ahnengalerie!', correct: true },
{ text: 'Ich trinke eigentlich lieber Weißwein.', correct: false },
{ text: 'Aber meine Sonnenbrille ist dafür sehr dunkel!', correct: false }
]
},
{
insult: 'Mit 40 Lenzen bist du doch reif fürs Altenteil, alter Mann!',
answers: [
{ text: 'Mit 40 fängt der Rave erst an, und i tanz di locker an die Wand!', correct: true },
{ text: 'Ja mei, der Rücken zwickt schon a bisserl.', correct: false },
{ text: 'Ich frage mal schnell meinen Steuerberater.', correct: false }
]
},
{
insult: 'Niemand im ganzen Herzogtum feiert deine 135 BPM!',
answers: [
{ text: 'Ganz Haumdaucher bebt, wenn der König den Fader bewegt!', correct: true },
{ text: 'Vielleicht sollte ich lieber Schlager spielen.', correct: false },
{ text: 'Pü hat aber gesagt, das Licht passt dazu.', correct: false }
]
}
]