Initial: Mesh-Skills, Configs, Scripts

This commit is contained in:
root
2026-09-02 23:49:07 +02:00
commit 26c7980750
783 changed files with 145393 additions and 0 deletions
@@ -0,0 +1,128 @@
# 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>'`