# Multi-Site Pattern: Verlinkte Fitness/Calculator-Websites Pattern aus Session 2026-07-12: Zwei zusammenhängende Single-Page-Apps auf Subdomains derselben Domain. ## Überblick - **fitness.perfekt-ohne-en.de**: Makro-Rechner mit BMR, Aktivitätslevel, PDF-Export - **kalorienrechner.perfekt-ohne-en.de**: Reiner Kalorienrechner mit Abnehm-Prognose - Beide Sites per Cross-Link verbunden, **Daten werden beim Wechsel übergeben** ## Architektur ``` perfekt-ohne-en.de (Hauptdomain) ├── fitness.perfekt-ohne-en.de → /var/www/fitness.perfekt-ohne-en.de └── kalorienrechner.perfekt-ohne-en.de → /var/www/kalorienrechner.perfekt-ohne-en.de ``` ## Datenübergabe per URL-Parameter Wenn der Nutzer auf Fitness Daten eingibt und dann zum Kalorienrechner wechselt, werden die Formularwerte als Query-String mitgegeben: ```javascript // Auf der Quellseite: function goToTarget(e) { e.preventDefault(); const params = new URLSearchParams({ g: gender, // 'male'|'female' w: weight.value, // kg h: height.value, // cm a: age.value, act: activity.value, // activity factor goal: goal.value // goal factor }); window.location.href = 'https://target.example.de/?' + params.toString(); } ``` ```html 👉 Zur anderen Seite (mit Daten) ``` Auf der Zielseite werden die Parameter beim Laden eingelesen und das Formular vorausgefüllt: ```javascript (function(){ const p = new URLSearchParams(window.location.search); if (p.get('w')) document.getElementById('weight').value = p.get('w'); if (p.get('h')) document.getElementById('height').value = p.get('h'); if (p.get('a')) document.getElementById('age').value = p.get('a'); if (p.get('g')) setGender(p.get('g'), ...); if (p.get('act')) document.getElementById('activity').value = p.get('act'); if (p.get('goal')) document.getElementById('goal').value = p.get('goal'); // Auto-calculate if all data present if (p.get('w') && p.get('h') && p.get('a')) calculate(); })(); ``` **URL-Beispiel:** ``` https://kalorienrechner.perfekt-ohne-en.de/?g=male&w=80&h=178&a=30&act=1.375&goal=0.85 ``` ## PDF-Download (jsPDF) Beide Sites nutzen jsPDF (CDN) für PDF-Export: ```html ``` **Minimale downloadPDF()-Funktion:** ```javascript async function downloadPDF() { if (typeof window.jspdf === 'undefined') return alert('PDF wird geladen...'); const { jsPDF } = window.jspdf; const pdf = new jsPDF('p','mm','a4'), m = 20; pdf.setFillColor(16,185,129); pdf.rect(0,0,210,15,'F'); pdf.setTextColor(255,255,255); pdf.setFontSize(14); pdf.text('Titel', m, 12); pdf.setTextColor(0,0,0); pdf.setFontSize(10); // ... Daten einfügen ... pdf.save('ergebnis.pdf'); } ``` **Pitfall:** `pdf.text()` existiert immer in jsPDF 2.x. Kein `typeof`-Check nötig! ## Sponsor-Coin (CSS-Animation) Kleines drehendes Logo mit Link zur Sponsoren-Seite: ```css .sponsor-coin { margin-bottom: 1rem; } .coin-link { display: inline-flex; align-items: center; gap: 0.5rem; } .coin-img { width: 45px; height: 45px; border-radius: 50%; animation: spinCoin 4s ease-in-out infinite; filter: drop-shadow(0 2px 6px rgba(16,185,129,0.3)); } .coin-link:hover .coin-img { animation: spinCoin 0.6s ease-in-out infinite; } @keyframes spinCoin { 0% { transform: rotateY(0deg); } 50% { transform: rotateY(180deg); } 100% { transform: rotateY(360deg); } } ``` ```html
``` ## Deployment-Checkliste 1. `mkdir -p /var/www/sub.example.de/{html,logs,assets}` 2. nginx VHost in `/etc/nginx/sites-available/` → `ln -sf` → `sites-enabled/` 3. `nginx -t && systemctl reload nginx` 4. HTML-Dateien erstellen → **SOFORT**: `chown www-data:www-data && chmod 644` 5. Assets (Logos) kopieren → `chown www-data:www-data` 6. `certbot --nginx -d sub.example.de --non-interactive --agree-tos --email admin@... --redirect` 7. Lokal testen: `curl -s -H "Host: sub.example.de" http://localhost/ | grep '