Files
wetter/templates/weather.html
simon e59e88cafe modified: app.py
modified:   templates/base.html
	modified:   templates/weather.html
2026-04-22 10:38:42 +02:00

312 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}{{ display_name.split(',')[0] }} Skywatcher{% endblock %}
{% block head %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.2/dist/chart.umd.min.js"></script>
{% endblock %}
{% block content %}
{# ── Wetterklasse für den Hero-Gradient ─────────────────────────── #}
{% if current.icon == "🌧️" %}{% set wclass = "w-rain" %}
{% elif current.icon == "❄️" %}{% set wclass = "w-snow" %}
{% elif current.icon == "☁️" %}{% set wclass = "w-cloudy" %}
{% elif current.icon == "⛅" %}{% set wclass = "w-partcloud" %}
{% else %}{% set wclass = "w-clear" %}{% endif %}
<!-- HERO -->
<header class="hero {{ wclass }}">
<div class="hero-inner">
<div class="hero-meta">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
{{ display_name.split(',')[0] }}
<span class="hero-meta-sub">{{ display_name.split(',')[1:3]|join(',') if ',' in display_name else '' }}</span>
<span class="hero-meta-time">{{ now_local }} Uhr</span>
</div>
<div class="hero-main">
<div class="hero-temp">{{ current.temp_c if current.temp_c is not none else "" }}°</div>
<div class="hero-desc">
<div class="hero-icon-big">{{ current.icon or "☁️" }}</div>
<div class="hero-stats-mini">
{% if current.wind_kmh is not none %}
<span>{{ current.wind_kmh }} km/h {{ wind_dir_name(current.wind_dir) }}</span>
{% endif %}
{% if current.cloud_pct is not none %}
<span>{{ current.cloud_pct }}% Bedeckung</span>
{% endif %}
{% if current.pressure_hpa is not none %}
<span>{{ current.pressure_hpa }} hPa</span>
{% endif %}
</div>
</div>
</div>
<div class="hero-metrics">
{% set items = [
("Gefühlt wie", (current.feels_like|string + " °C") if current.feels_like is not none else ""),
("Böen", (current.gust_kmh|string + " km/h") if current.gust_kmh is not none else ""),
("Niederschlag", (current.precip_mm|string + " mm") if (current.precip_mm is not none and current.precip_mm > 0) else ((current.rain_prob|string + " %") if (current.rain_prob is not none and current.rain_prob > 0) else "0 mm")),
("Sonne", (current.sun_min|string + " min/h") if (current.sun_min is not none and current.sun_min > 0) else ""),
] %}
{% for label, val in items %}
<div class="hero-metric">
<span class="hm-val">{{ val }}</span>
<span class="hm-label">{{ label }}</span>
</div>
{% endfor %}
</div>
</div>
<div class="hero-station">
📡 Station <strong>{{ station_name }}</strong> ({{ station_dist }} km)
{% if sunrise %}
&nbsp;·&nbsp; 🌅 {{ sunrise }} &nbsp;·&nbsp; 🌇 {{ sunset }}
{% endif %}
{% if current.uv_index is not none %}
&nbsp;·&nbsp; UV {{ current.uv_index }}
{% endif %}
</div>
</header>
{% if warnings %}
<div class="warnings">
{% for w in warnings %}
<div class="warn-item warn-lvl-{{ w.level }}">
<span class="warn-icon">{{ '⚠️' if w.level == 1 else ('🟠' if w.level == 2 else ('🔴' if w.level >= 3 else '⚠️')) }}</span>
<div>
<strong>{{ w.headline }}</strong>
{% if w.description %}<p class="warn-desc">{{ w.description[:140] }}{% if w.description|length > 140 %}…{% endif %}</p>{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- STÜNDLICH -->
<section class="section">
<h2 class="section-title">Stundenweise</h2>
<div class="hourly-strip-wrap">
<div class="hourly-strip">
{% for h in forecast %}
<div class="hcard {% if loop.first %}hcard--now{% endif %}">
<div class="hcard-time">
{% if loop.first %}Jetzt
{% elif h.datetime is string %}{{ h.datetime[11:16] }}
{% else %}{{ h.datetime.strftime('%H:%M') }}{% endif %}
</div>
<div class="hcard-date">
{% if h.datetime is string %}{{ h.datetime[8:10] }}.{{ h.datetime[5:7] }}.
{% else %}{{ h.datetime.strftime('%d.%m.') }}{% endif %}
</div>
<div class="hcard-icon">{{ h.icon }}</div>
<div class="hcard-temp">
{% if h.temp_c is not none %}{{ h.temp_c }}°{% else %}{% endif %}
</div>
{% if h.precip_mm and h.precip_mm > 0 %}
<div class="hcard-precip">{{ h.precip_mm }} mm</div>
{% elif h.rain_prob is not none and h.rain_prob > 0 %}
<div class="hcard-precip hcard-precip--prob">{{ h.rain_prob }}%</div>
{% else %}
<div class="hcard-precip hcard-precip--none"></div>
{% endif %}
{% if h.wind_kmh is not none %}
<div class="hcard-wind">{{ h.wind_kmh }}<small>km/h</small></div>
{% endif %}
{% if h.uv_index is not none and h.uv_index > 0 %}
<div class="hcard-uv">UV {{ h.uv_index }}</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</section>
<!-- CHART -->
<section class="section">
<div class="chart-head">
<h2 class="section-title">Temperatur &amp; Niederschlag</h2>
<span class="chart-range">48 Stunden</span>
</div>
<div class="chart-box">
<canvas id="wxChart"></canvas>
</div>
</section>
<!-- TAGESÜBERSICHT -->
<section class="section">
<h2 class="section-title">Tagesübersicht</h2>
<div class="daily-list">
{# calc global min/max for bar scaling do it in a loop to stay Jinja2-safe #}
{% set ns = namespace(g_min=99, g_max=-99) %}
{% for d in daily %}
{% if d.temp_min is not none and d.temp_min < ns.g_min %}{% set ns.g_min = d.temp_min %}{% endif %}
{% if d.temp_max is not none and d.temp_max > ns.g_max %}{% set ns.g_max = d.temp_max %}{% endif %}
{% endfor %}
{% set g_min = ns.g_min %}
{% set g_max = ns.g_max %}
{% set g_range = (g_max - g_min) if (g_max - g_min) > 0 else 1 %}
{% for d in daily %}
<div class="drow">
<div class="drow-left">
<span class="drow-icon">{{ d.icon }}</span>
<div class="drow-date-wrap">
<span class="drow-dow">
{% if d.date is string %}
{% set y = d.date[:4]|int %}{% set mo = d.date[5:7]|int %}{% set dy = d.date[8:10]|int %}
{% set days = ["Mo","Di","Mi","Do","Fr","Sa","So"] %}
{{ dy }}.{{ d.date[5:7] }}.
{% else %}
{{ d.date.strftime('%a') }}
{% endif %}
</span>
</div>
</div>
<div class="drow-bar-wrap">
{% if d.temp_min is not none and d.temp_max is not none %}
{% set left_pct = ((d.temp_min - g_min) / g_range * 100)|round(1) %}
{% set width_pct = ((d.temp_max - d.temp_min) / g_range * 100)|round(1) %}
<div class="drow-bar-track">
<div class="drow-bar" style="left:{{ left_pct }}%; width:{{ [width_pct, 4]|max }}%"></div>
</div>
{% endif %}
</div>
<div class="drow-right">
<span class="drow-min">{{ d.temp_min }}°</span>
<span class="drow-max">{{ d.temp_max }}°</span>
{% if d.precip > 0 %}
<span class="drow-precip">🌧 {{ d.precip }}mm</span>
{% elif d.rain_prob is not none and d.rain_prob > 0 %}
<span class="drow-precip">💧 {{ d.rain_prob }}%</span>
{% endif %}
{% if d.uv_max is not none and d.uv_max > 0 %}
<span class="drow-uv">UV {{ d.uv_max }}</span>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</section>
<p class="data-note">
Wetterdaten: <a href="https://opendata.dwd.de" target="_blank" rel="noopener noreferrer">Deutscher Wetterdienst Open Data (MOSMIX)</a>
</p>
{% endblock %}
{% block scripts %}
<script>
// ── Letzte Suchen speichern ──────────────────────────────────────────────────
(function(){
const key = "sw_recent";
const ort = {{ ort | tojson }};
let recent = JSON.parse(localStorage.getItem(key) || "[]");
recent = [ort, ...recent.filter(x => x !== ort)].slice(0, 6);
localStorage.setItem(key, JSON.stringify(recent));
})();
</script>
<script>
(function() {
const labels = {{ chart_labels | tojson }};
const temps = {{ chart_temps | tojson }};
const precip = {{ chart_precip | tojson }};
const rainProb = {{ chart_rain_prob | tojson }};
// Prüfen ob echte Niederschlagsmengen vorhanden sind
const hasRealPrecip = precip.some(v => v > 0);
const barData = hasRealPrecip ? precip : rainProb;
const barLabel = hasRealPrecip ? "Niederschlag (mm)" : "Regenwahrsch. (%)";
const barMax = hasRealPrecip ? undefined : 100;
// Nur jeden 3. Label anzeigen, Rest leer lassen
const sparseLabels = labels.map((l, i) => i % 3 === 0 ? l : "");
const ctx = document.getElementById("wxChart").getContext("2d");
// Gradient für Temperatur-Linie
const grad = ctx.createLinearGradient(0, 0, 0, 300);
grad.addColorStop(0, "rgba(255,140,50,0.35)");
grad.addColorStop(1, "rgba(255,140,50,0)");
new Chart(ctx, {
data: {
labels,
datasets: [
{
type: "line",
label: "Temperatur (°C)",
data: temps,
borderColor: "#ff8c32",
backgroundColor: grad,
borderWidth: 2.5,
tension: 0.45,
fill: true,
yAxisID: "yT",
pointRadius: 0,
pointHoverRadius: 5,
pointHoverBackgroundColor: "#ff8c32",
},
{
type: "bar",
label: barLabel,
data: barData,
backgroundColor: "rgba(80,180,255,0.55)",
borderColor: "rgba(80,180,255,0.9)",
borderWidth: 1,
borderRadius: 3,
yAxisID: "yR",
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: "index", intersect: false },
plugins: {
legend: {
labels: { color: "#9aa8b8", font: { family: "Inter", size: 12 }, boxWidth: 14 }
},
tooltip: {
backgroundColor: "rgba(10,12,18,0.92)",
borderColor: "rgba(255,255,255,0.1)",
borderWidth: 1,
titleColor: "#fff",
bodyColor: "#9aa8b8",
padding: 12,
callbacks: {
title: items => labels[items[0].dataIndex],
}
}
},
scales: {
x: {
ticks: {
color: "#5a6a7a",
font: { family: "Inter", size: 11 },
maxRotation: 0,
callback: (_, i) => i % 6 === 0 ? labels[i] : "",
autoSkip: false,
},
grid: { color: "rgba(255,255,255,0.04)" }
},
yT: {
position: "left",
ticks: { color: "#ff8c32", font: { family: "Inter", size: 11 }, callback: v => v + "°" },
grid: { color: "rgba(255,255,255,0.06)" }
},
yR: {
position: "right",
min: 0,
...(barMax !== undefined ? { max: barMax } : {}),
ticks: { color: "#50b4ff", font: { family: "Inter", size: 11 }, callback: v => hasRealPrecip ? v + "mm" : v + "%" },
grid: { display: false }
}
}
}
});
})();
</script>
{% endblock %}