diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..dd0b1e5 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,50 @@ +# GEMINI.md - Haumdaucher Project Handbook + +This document serves as the "Source of Truth" for the Haumdaucher website. It outlines the design principles, technical architecture, and specialized features to ensure consistent future development. + +## ðŸĶĒ Project Essence +**Haumdaucher** is a community project from Regensburg, Germany. The website is designed to be humorous, culturally rich (Bavarian), and technically "surprising." + +## ðŸŽĻ Design Principles +- **Vibrant Aesthetics**: Each theme must feel like a completely different app. +- **Glassmorphism**: Use `backdrop-filter` and semi-transparent backgrounds for a premium feel. +- **Micro-interactions**: Subtle entrance animations (logo spin, hero text slide) and consistent hover states. +- **Accessibility**: Mobile-first design with safe-area support for PWA usage on iOS/Android. + +## 🛠 Technical Specifications +- **Framework**: Vue 3 (Composition API) + Vite + TypeScript. +- **State Management**: Centralized in `App.vue` using standard `ref` hooks. Persisted in `localStorage`. +- **Theming System**: + - Driven by `data-theme` attribute on `:root`. + - Defined in `src/assets/styles/global.css`. + - Themes: `Classic`, `Unicorn`, `Luxury`, `Win95`, `NAT`. +- **Localization**: + - Centralized in `src/locales/i18n.ts`. + - Supports `de` (Standard German) and `bar` (Bavarian Dialect). +- **PWA**: + - Managed via `vite-plugin-pwa`. + - Custom icons and standalone manifest for "Add to Home Screen" support. + +## ðŸ•đ The Haumdaucher Game +- **Engine**: HTML5 Canvas rendering. +- **Controls**: Touch-responsive (horizontal drag) and Keyboard (Arrow Keys). +- **Thematization**: The game visual style (backgrounds, player, obstacles) changes dynamically based on the site's active theme. +- **Difficulty**: Balanced (10 levels). Level 10 triggers a "Boar Rain" supermode. + +## 🔐 Progression & Gating +- **NAT Mode**: This theme is locked by default to maintain the "collectible" feel. +- **Unlocking**: + - **Natural**: Reach Level 10 in the game. + - **Backdoor**: Single 1x1 pixel in the bottom-left corner of the site. Clicking it triggers a prompt. Type `nat mode` to unlock. + +## ðŸšĒ Deployment & DevOps +- **Docker**: Dual-stage build (Node build -> Nginx serving). +- **Registry**: `registry.haumdaucher.de`. +- **Kubernetes**: + - Managed via `k8s-manifests.yaml`. + - Features `cert-manager` for SSL and `registry-haumdaucher-de` pull secret. +- **CI/CD Logic**: The `deploy.sh` script handles builds, pushes, and triggers a `kubectl rollout restart` to force deployment updates when utilizing the `latest` image tag. + +## 📝 Ongoing Maintenance +- **Assets**: Static images should be placed in `public/images/` to avoid bundling issues in production containers. +- **Style Overrides**: Mobile-first approach is mandatory. Always test with `max-width: 375px`. diff --git a/deploy.sh b/deploy.sh index 8a8aa51..b7971d5 100755 --- a/deploy.sh +++ b/deploy.sh @@ -2,7 +2,7 @@ # Configuration NAMESPACE="haumdaucher" -REGISTRY="registry.moritzgraf.de" +REGISTRY="registry.haumdaucher.de" IMAGE_BASE_NAME="haumdaucher-website" IMAGE_NAME="$REGISTRY/$IMAGE_BASE_NAME" TAG="latest" @@ -24,5 +24,9 @@ docker push $IMAGE_NAME:$TAG echo "ðŸŽĄ Applying Kubernetes manifests..." kubectl apply -f k8s-manifests.yaml +# Force rollout restart to pick up the new 'latest' image +echo "🔄 Restarting deployment to pull latest image..." +kubectl rollout restart deployment/haumdaucher-website -n $NAMESPACE + echo "✅ Deployment complete!" echo "Check status with: kubectl get pods -n $NAMESPACE" diff --git a/k8s-manifests.yaml b/k8s-manifests.yaml index 6c8a913..8f111cf 100644 --- a/k8s-manifests.yaml +++ b/k8s-manifests.yaml @@ -19,7 +19,7 @@ spec: - name: registry-haumdaucher-de containers: - name: haumdaucher - image: registry.moritzgraf.de/haumdaucher-website:latest + image: registry.haumdaucher.de/haumdaucher-website:latest imagePullPolicy: Always ports: - containerPort: 80 diff --git a/public/images/logo_classic.png b/public/images/logo_classic.png new file mode 100644 index 0000000..39bf1f4 Binary files /dev/null and b/public/images/logo_classic.png differ diff --git a/public/images/logo_luxury.png b/public/images/logo_luxury.png new file mode 100644 index 0000000..0ab6f68 Binary files /dev/null and b/public/images/logo_luxury.png differ diff --git a/public/images/logo_nat.png b/public/images/logo_nat.png new file mode 100644 index 0000000..02c0955 Binary files /dev/null and b/public/images/logo_nat.png differ diff --git a/public/images/logo_unicorn.png b/public/images/logo_unicorn.png new file mode 100644 index 0000000..7f3b852 Binary files /dev/null and b/public/images/logo_unicorn.png differ diff --git a/public/images/logo_win95.png b/public/images/logo_win95.png new file mode 100644 index 0000000..ca7abcf Binary files /dev/null and b/public/images/logo_win95.png differ diff --git a/src/App.vue b/src/App.vue index b2554c8..e2cc340 100644 --- a/src/App.vue +++ b/src/App.vue @@ -12,6 +12,7 @@ const theme = ref('classic') const lang = ref<'de' | 'bar'>('de') const showGame = ref(false) const showBSOD = ref(false) +const isNatUnlocked = ref(false) const boars = ref<{id: number, top: number}[]>([]) const toggleTheme = (newTheme: string) => { @@ -24,6 +25,19 @@ const toggleTheme = (newTheme: string) => { } } +const unlockNat = () => { + isNatUnlocked.value = true + localStorage.setItem('haumdaucher-nat-unlocked', 'true') +} + +const handleBackdoor = () => { + const secret = prompt('Bitte Geheimcode eigem (Unlock NAT):') + if (secret?.toLowerCase() === 'nat mode') { + unlockNat() + alert('🐗 NAT-Modus freigschaltet! Wiedaschaun, reinghaun!') + } +} + const startBoarRun = () => { setInterval(() => { if (theme.value === 'nat') { @@ -54,6 +68,9 @@ onMounted(() => { const savedLang = localStorage.getItem('haumdaucher-lang') as 'de' | 'bar' if (savedLang) lang.value = savedLang + + const savedNat = localStorage.getItem('haumdaucher-nat-unlocked') + if (savedNat === 'true') isNatUnlocked.value = true }) const t = (key: string) => { @@ -71,6 +88,7 @@ const t = (key: string) => {
{ + + +
- +

:(

@@ -126,4 +153,15 @@ const t = (key: string) => { 0% { left: -100px; } 100% { left: 100vw; } } + +.backdoor { + position: fixed; + bottom: 0; + left: 0; + width: 10px; + height: 10px; + cursor: pointer; + z-index: 9999; + opacity: 0; +} diff --git a/src/components/layout/HaumdaucherGame.vue b/src/components/layout/HaumdaucherGame.vue index c2073db..befcc47 100644 --- a/src/components/layout/HaumdaucherGame.vue +++ b/src/components/layout/HaumdaucherGame.vue @@ -1,24 +1,67 @@