Feature: Wetter — Gassi-Score, Schnüffel-Index, Hunde-Alter-Hinweis (SW by-v692)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
759979ffce
commit
471633867c
3 changed files with 229 additions and 7 deletions
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '690'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '692'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VERSION = '1.3.0'; // ← semantische Version, wird bei make release gesetzt
|
||||
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
||||
|
||||
|
|
|
|||
|
|
@ -325,6 +325,9 @@ window.Page_wetter = (() => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gassi-Score -->
|
||||
${_gassiScoreBadge(d)}
|
||||
|
||||
<!-- Sonnenaufgang / -untergang -->
|
||||
${sunriseStr && sunsetStr ? `
|
||||
<div style="margin-bottom:var(--space-4)">
|
||||
|
|
@ -528,11 +531,23 @@ window.Page_wetter = (() => {
|
|||
if (!d) return;
|
||||
|
||||
const _POLLEN_NAMES = { erle:'Erle', birke:'Birke', graeser:'Gräser', beifuss:'Beifuß', ambrosia:'Ambrosia' };
|
||||
let html = `<h3 style="font-size:var(--text-base);font-weight:700;
|
||||
margin-bottom:var(--space-4)">
|
||||
<svg class="ph-icon" style="width:1.1em;height:1.1em;vertical-align:-2px;color:var(--c-primary)"><use href="/icons/phosphor.svg#paw-print"></use></svg>
|
||||
Hunde-Wetter
|
||||
</h3>`;
|
||||
const _wl = _dogWeatherLabel(d);
|
||||
let html = `
|
||||
<div style="border-radius:var(--radius);padding:var(--space-4);
|
||||
background:${_wl.color}18;border:1px solid ${_wl.color}44;
|
||||
margin-bottom:var(--space-4);text-align:center">
|
||||
<div style="font-size:2rem;line-height:1;margin-bottom:4px">${_wl.emoji}</div>
|
||||
<div style="font-weight:800;font-size:var(--text-lg);color:${_wl.color};line-height:1.2">
|
||||
${_esc(_wl.label)}
|
||||
</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:4px">
|
||||
${_esc(_wl.sub)}
|
||||
</div>
|
||||
</div>
|
||||
<h3 style="font-size:var(--text-base);font-weight:700;margin-bottom:var(--space-4)">
|
||||
<svg class="ph-icon" style="width:1.1em;height:1.1em;vertical-align:-2px;color:var(--c-primary)"><use href="/icons/phosphor.svg#paw-print"></use></svg>
|
||||
Hunde-Hinweise
|
||||
</h3>`;
|
||||
|
||||
// Asphalt-Temperatur
|
||||
if (d.asphalt_temp != null) {
|
||||
|
|
@ -638,6 +653,16 @@ window.Page_wetter = (() => {
|
|||
`;
|
||||
}
|
||||
|
||||
// Schnüffel-Index + Hunde-Alter Chips
|
||||
const ageYears = _dogAgeYears();
|
||||
html += _dogAgeChip(ageYears);
|
||||
|
||||
html += `
|
||||
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin-bottom:var(--space-3)">
|
||||
${_schnueffelChip(d)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Wenn keine Hunde-Daten vorhanden
|
||||
if (!d.asphalt_temp && !d.paw_cold && !d.thunderstorm
|
||||
&& !d.zecken && !(pollen && Object.keys(pollen).length)) {
|
||||
|
|
@ -651,6 +676,160 @@ window.Page_wetter = (() => {
|
|||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// GASSI-SCORE (1–10)
|
||||
// ----------------------------------------------------------
|
||||
function _gassiScore(d) {
|
||||
let score = 10;
|
||||
const temp = d.temp_max ?? 20;
|
||||
const precip = d.precip_prob ?? 0;
|
||||
const wind = d.windspeed_max ?? 0;
|
||||
const asphalt = d.asphalt_temp ?? 0;
|
||||
|
||||
// Temperatur (ideal: 10–20°C)
|
||||
if (temp > 30) score -= 3;
|
||||
else if (temp > 25) score -= 1;
|
||||
else if (temp < 0) score -= 3;
|
||||
else if (temp < 5) score -= 1;
|
||||
|
||||
// Regen
|
||||
if (precip > 70) score -= 3;
|
||||
else if (precip > 40) score -= 2;
|
||||
else if (precip > 20) score -= 1;
|
||||
|
||||
// Wind
|
||||
if (wind > 60) score -= 2;
|
||||
else if (wind > 40) score -= 1;
|
||||
|
||||
// Asphalt
|
||||
if (asphalt > 55) score -= 2;
|
||||
else if (asphalt > 45) score -= 1;
|
||||
|
||||
// Gewitter
|
||||
if (d.thunderstorm) score -= 3;
|
||||
|
||||
return Math.max(1, Math.min(10, score));
|
||||
}
|
||||
|
||||
function _gassiScoreBadge(d) {
|
||||
const score = _gassiScore(d);
|
||||
let color, text;
|
||||
if (score >= 8) {
|
||||
color = '#10B981';
|
||||
text = 'Toller Gassi-Tag!';
|
||||
} else if (score >= 5) {
|
||||
color = '#F59E0B';
|
||||
text = 'Geht so';
|
||||
} else {
|
||||
color = '#EF4444';
|
||||
text = 'Lieber drinbleiben';
|
||||
}
|
||||
return `
|
||||
<div style="display:flex;align-items:center;justify-content:center;
|
||||
gap:var(--space-3);margin-bottom:var(--space-4);
|
||||
padding:var(--space-3) var(--space-4);
|
||||
border-radius:999px;
|
||||
background:${color}1a;border:1.5px solid ${color}55">
|
||||
<span style="font-size:var(--text-xs);font-weight:700;
|
||||
color:var(--c-text-secondary);white-space:nowrap">🐾 Gassi-Score</span>
|
||||
<span style="font-size:var(--text-2xl);font-weight:900;color:${color};line-height:1">
|
||||
${score}
|
||||
</span>
|
||||
<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">/ 10</span>
|
||||
<span style="font-size:var(--text-xs);font-weight:600;color:${color};
|
||||
white-space:nowrap">— ${_esc(text)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// SCHNÜFFEL-INDEX
|
||||
// ----------------------------------------------------------
|
||||
function _schnueffelIndex(d) {
|
||||
const temp = d.temp_max ?? 20;
|
||||
const precip = d.precip_prob ?? 0;
|
||||
|
||||
// Feuchtigkeit aus precip_prob ableiten
|
||||
const feucht = precip > 60 ? 'feucht' : precip > 30 ? 'leicht-feucht' : 'trocken';
|
||||
|
||||
if (feucht === 'feucht' && temp >= 10 && temp <= 18)
|
||||
return { label:'Exzellent 👃', color:'#10B981' };
|
||||
if (feucht === 'feucht' && temp > 10 && temp <= 22)
|
||||
return { label:'Sehr gut 👃', color:'#34D399' };
|
||||
if (temp < 5)
|
||||
return { label:'Gut (kalte Luft trägt Gerüche)', color:'#60A5FA' };
|
||||
if (feucht === 'leicht-feucht' && temp >= 10 && temp <= 22)
|
||||
return { label:'Gut 👃', color:'#4CAF50' };
|
||||
if (feucht === 'trocken' && temp > 25)
|
||||
return { label:'Schwach', color:'#94A3B8' };
|
||||
return { label:'Mittel', color:'#F59E0B' };
|
||||
}
|
||||
|
||||
function _schnueffelChip(d) {
|
||||
const s = _schnueffelIndex(d);
|
||||
return `
|
||||
<span style="display:inline-flex;align-items:center;gap:4px;
|
||||
font-size:var(--text-xs);border-radius:999px;
|
||||
padding:3px 10px;background:${s.color}22;
|
||||
border:1px solid ${s.color}55;color:${s.color};font-weight:600">
|
||||
<svg class="ph-icon" style="width:12px;height:12px"><use href="/icons/phosphor.svg#nose"></use></svg>
|
||||
Schnüffel: ${_esc(s.label)}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// HUNDE-ALTER aus appState
|
||||
// ----------------------------------------------------------
|
||||
function _dogAgeYears() {
|
||||
try {
|
||||
const dog = _appState?.activeDog || _appState?.dog || _appState?.active_dog;
|
||||
if (!dog) return null;
|
||||
const geb = dog.geburtsdatum || dog.birthdate;
|
||||
if (!geb) return null;
|
||||
const birth = new Date(geb);
|
||||
if (isNaN(birth)) return null;
|
||||
const now = new Date();
|
||||
let age = now.getFullYear() - birth.getFullYear();
|
||||
const m = now.getMonth() - birth.getMonth();
|
||||
if (m < 0 || (m === 0 && now.getDate() < birth.getDate())) age--;
|
||||
return age < 0 ? 0 : age;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
function _dogAgeChip(ageYears) {
|
||||
if (ageYears === null) return '';
|
||||
if (ageYears < 1) {
|
||||
return `
|
||||
<div style="display:flex;align-items:flex-start;gap:var(--space-3);
|
||||
padding:var(--space-3);border-radius:var(--radius);
|
||||
background:#f59e0b1a;border:1px solid #f59e0b55;
|
||||
margin-bottom:var(--space-3)">
|
||||
<svg class="ph-icon" style="width:1.3rem;height:1.3rem;flex-shrink:0;color:#F59E0B"><use href="/icons/phosphor.svg#baby"></use></svg>
|
||||
<div style="font-size:var(--text-sm)">
|
||||
<strong>Welpe</strong> — kurze Spaziergänge, max. 15 Min bei Hitze.
|
||||
Gelenke und Pfoten besonders schonen.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (ageYears >= 8) {
|
||||
return `
|
||||
<div style="display:flex;align-items:flex-start;gap:var(--space-3);
|
||||
padding:var(--space-3);border-radius:var(--radius);
|
||||
background:#6b7280 1a;border:1px solid #6b728055;
|
||||
margin-bottom:var(--space-3)">
|
||||
<svg class="ph-icon" style="width:1.3rem;height:1.3rem;flex-shrink:0;color:#9CA3AF"><use href="/icons/phosphor.svg#person-simple-walk"></use></svg>
|
||||
<div style="font-size:var(--text-sm)">
|
||||
<strong>Seniorhund</strong> — Hitze und Kälte vermeiden, kurze Runden bevorzugen.
|
||||
Auf Gelenkbeschwerden achten.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// HILFSFUNKTIONEN — Wetter
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -695,6 +874,49 @@ window.Page_wetter = (() => {
|
|||
return '#F44336'; // level 4+
|
||||
}
|
||||
|
||||
function _dogWeatherLabel(d) {
|
||||
const temp = d.temp_max ?? 20;
|
||||
const tempMin = d.temp_min ?? temp;
|
||||
const precip = d.precip_prob ?? 0;
|
||||
const wind = d.windspeed_max ?? 0;
|
||||
const asphalt = d.asphalt_temp ?? 0;
|
||||
const wcode = d.weathercode ?? 0;
|
||||
const isSnow = wcode >= 71 && wcode <= 77;
|
||||
if (d.thunderstorm)
|
||||
return { label:'Gewitterangst-Wetter', sub:'Angsthasen lieber zu Hause lassen', emoji:'⛈️', color:'#7C3AED' };
|
||||
if (isSnow && temp < 3)
|
||||
return { label:'Schnee-Toben-Wetter', sub:'Pudel im Schnee — der Klassiker', emoji:'❄️', color:'#38BDF8' };
|
||||
if (isSnow)
|
||||
return { label:'Matschpfoten-Wetter', sub:'Pfoten nach der Runde gut abtrocknen', emoji:'🌨️', color:'#60A5FA' };
|
||||
if (tempMin < 0 && precip < 30)
|
||||
return { label:'Kristallklare Nasenluft', sub:'Kalt aber herrlich — Schnüffeln auf Maximum', emoji:'🌡️', color:'#60A5FA' };
|
||||
if (temp < 5 && precip > 50)
|
||||
return { label:'Kuschelwetter', sub:'Kurze Runde, dann ab auf das Sofa', emoji:'🏠', color:'#6B7280' };
|
||||
if (temp < 5)
|
||||
return { label:'Fellkuschelwetter', sub:'Frisch und klar — ideal für aktive Rassen', emoji:'🧣', color:'#93C5FD' };
|
||||
if (temp > 28 && asphalt > 50)
|
||||
return { label:'Pfoten-Alarm!', sub:'Asphalt zu heiß — früh morgens oder abends raus', emoji:'🔥', color:'#EF4444' };
|
||||
if (temp > 28)
|
||||
return { label:'Schwimm-Wetter', sub:'Bach oder See suchen — Hunde überhitzen schnell', emoji:'🏊', color:'#F97316' };
|
||||
if (precip > 70 && temp < 15)
|
||||
return { label:'Nass-Hund-Wetter', sub:'Handtuch bereit? Der Geruch kommt garantiert', emoji:'💧', color:'#3B82F6' };
|
||||
if (precip > 70)
|
||||
return { label:'Warm-Dusch-Wetter', sub:'Wer braucht noch ein Bad — der Regen übernimmt', emoji:'🌧️', color:'#60A5FA' };
|
||||
if (precip > 30 && temp >= 10 && temp <= 20)
|
||||
return { label:'Schnüffel-Wetter', sub:'Feuchte Luft = Nasenarbeit pur — Gerüche lieben das', emoji:'👃', color:'#34D399' };
|
||||
if (wind > 50)
|
||||
return { label:'Sturmfrisur-Wetter', sub:'Fell in alle Richtungen — Leine gut festhalten', emoji:'🌬️', color:'#A78BFA' };
|
||||
if (wind > 30 && temp >= 15)
|
||||
return { label:'Ohren-im-Wind-Wetter', sub:'Optimal für Hunde mit Schlappohren', emoji:'💨', color:'#A78BFA' };
|
||||
if (precip > 30 && precip <= 70)
|
||||
return { label:'Gassiregen-Wetter', sub:'Leichte Jacke, kurze Runde — Hund findet es gut', emoji:'🌦️', color:'#60A5FA' };
|
||||
if (temp >= 18 && temp <= 26 && precip < 20)
|
||||
return { label:'Perfektes Gassi-Wetter',sub:'Heute müssen alle Routen genossen werden', emoji:'🐾', color:'#10B981' };
|
||||
if (temp >= 10 && temp < 18 && precip < 30)
|
||||
return { label:'Klassisches Hunde-Wetter', sub:'Nicht zu warm, nicht zu kalt — Vierbeiner-Paradies', emoji:'🐕', color:'#4CAF50' };
|
||||
return { label:'Gutes Hunde-Wetter', sub:'Raus mit dem Hund!', emoji:'🐶', color:'#10B981' };
|
||||
}
|
||||
|
||||
function _tickLevel(risk) {
|
||||
const r = (risk || '').toLowerCase();
|
||||
if (r === 'niedrig') return ['niedrig', '#4CAF50'];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue