128 lines
4.3 KiB
Markdown
128 lines
4.3 KiB
Markdown
# 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
|
|
<a href="#" onclick="goToTarget(event)">👉 Zur anderen Seite (mit Daten)</a>
|
|
```
|
|
|
|
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
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
|
```
|
|
|
|
**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
|
|
<div class="sponsor-coin">
|
|
<a href="https://sponsor.example.de" class="coin-link">
|
|
<img src="/assets/logo.png" alt="Sponsor" class="coin-img">
|
|
<span class="coin-text">Gesponsert von</span>
|
|
</a>
|
|
</div>
|
|
```
|
|
|
|
## 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 '<title>'` |