feat(strommix): hero variant, 24h sparkline, trend, polishing
- New "hero" variant between compact and full: pct + status + sparkline + trend, no sub-cards or accordion - 24h Wind+Solar sparkline below hero pct, accent-colored - Trend arrow vs previous hour with delta percentage points - Polling pauses on tab hidden, refetches on visibility return if last fetch >5min old - Skeleton loader replaces "Lade…" text (matches final layout) - Retry button on error state (silent vs explicit fetch) - Stale-flag now prominent red badge in header bar - Stand-time wrapped in <time datetime title> for full-date hover - loadMw=0 falls back to last hourly slot instead of red error - Negative-price banner replaces standalone price card (dedup) - Production breakdown grid: clean auto/1fr label-value alignment - Print CSS: details auto-open, chevrons hidden - All German strings moved to translation keys - STROMMIX_DEFAULT_THRESHOLDS const as single source for buckets Includes data-block="LiveStrommix" attribute for the new identifier scheme + new RenewableSparkline.svelte component. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
<script lang="ts">
|
||||
import type { ResidualLoadPoint } from "$lib/strommix";
|
||||
import { useTranslate, T } from "$lib/translations";
|
||||
|
||||
/** Daily per-slot data — the sparkline plots `weatherRenewableMw / loadMw`
|
||||
* per slot. Compact, decorative, sits below the hero %. */
|
||||
let {
|
||||
points,
|
||||
accent = "currentColor",
|
||||
}: { points: ResidualLoadPoint[]; accent?: string } = $props();
|
||||
const t = useTranslate();
|
||||
|
||||
const W = 240;
|
||||
const H = 36;
|
||||
const PAD = 2;
|
||||
|
||||
const series = $derived.by(() => {
|
||||
return points
|
||||
.filter((p) => p.loadMw > 0)
|
||||
.map((p) => ({
|
||||
unix: p.unix,
|
||||
pct: (p.weatherRenewableMw / p.loadMw) * 100,
|
||||
}));
|
||||
});
|
||||
|
||||
const range = $derived.by(() => {
|
||||
if (series.length === 0) return { minX: 0, maxX: 1, minY: 0, maxY: 100 };
|
||||
const xs = series.map((s) => s.unix);
|
||||
const ys = series.map((s) => s.pct);
|
||||
return {
|
||||
minX: Math.min(...xs),
|
||||
maxX: Math.max(...xs),
|
||||
minY: Math.max(0, Math.min(...ys) - 5),
|
||||
maxY: Math.min(100, Math.max(...ys) + 5),
|
||||
};
|
||||
});
|
||||
|
||||
function xOf(unix: number): number {
|
||||
const span = range.maxX - range.minX || 1;
|
||||
return PAD + ((unix - range.minX) / span) * (W - 2 * PAD);
|
||||
}
|
||||
function yOf(pct: number): number {
|
||||
const span = range.maxY - range.minY || 1;
|
||||
return PAD + (1 - (pct - range.minY) / span) * (H - 2 * PAD);
|
||||
}
|
||||
|
||||
const linePath = $derived.by(() => {
|
||||
if (series.length < 2) return "";
|
||||
return series
|
||||
.map(
|
||||
(s, i) => `${i === 0 ? "M" : "L"} ${xOf(s.unix).toFixed(1)} ${yOf(s.pct).toFixed(1)}`,
|
||||
)
|
||||
.join(" ");
|
||||
});
|
||||
|
||||
const areaPath = $derived.by(() => {
|
||||
if (series.length < 2) return "";
|
||||
const top = linePath;
|
||||
const last = series[series.length - 1];
|
||||
const first = series[0];
|
||||
return `${top} L ${xOf(last.unix).toFixed(1)} ${(H - PAD).toFixed(1)} L ${xOf(first.unix).toFixed(1)} ${(H - PAD).toFixed(1)} Z`;
|
||||
});
|
||||
|
||||
const ariaLabel = $derived.by(() => {
|
||||
if (series.length === 0) return "";
|
||||
return t(T.strommix_sparkline_aria, {
|
||||
from: Math.round(series[0].pct),
|
||||
to: Math.round(series[series.length - 1].pct),
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if series.length >= 2}
|
||||
<svg
|
||||
viewBox="0 0 {W} {H}"
|
||||
preserveAspectRatio="none"
|
||||
class="strommix-sparkline w-full max-w-[240px] h-9 mx-auto"
|
||||
role="img"
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
<path d={areaPath} fill={accent} fill-opacity="0.15" />
|
||||
<path
|
||||
d={linePath}
|
||||
stroke={accent}
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user