last test

This commit is contained in:
2026-01-14 13:42:34 +01:00
parent be3498f2da
commit 2d697221e7
+121 -214
View File
@@ -367,224 +367,131 @@ data:
widgets: widgets:
# Weather Widget # Weather Widget
- type: custom-api - type: custom-api
title: Weather Forecast title: WEATHER FORECAST
body-type: string cache: 30m
cache: 1h url: https://api.open-meteo.com/v1/forecast?latitude=47.4979&longitude=19.0402&daily=weathercode,temperature_2m_max,temperature_2m_min&timezone=auto&forecast_days=7
options: template: |
location: Budapest VIII., Hungary {{/* ---- tweakables ---- */}}
weekend_color: "#34363D" {{ $overlayColor := "rgba(0,0,0,0.20)" }}
color_clear: "#FFA500"
color_partly: "#EBE387" {{/* ---- find min/max for scaling ---- */}}
color_cloud: "#A9A9A9" {{ $minT := 9999.0 }}
color_smog: "#D3D3D3" {{ $maxT := -9999.0 }}
color_drizzle: "#5F9EA0" {{ range $i, $d := .JSON.Array "daily.time" }}
color_rain: "#4682B4" {{ $lo := $.JSON.Float (printf "daily.temperature_2m_min.%d" $i) }}
color_freezing_rain: "#B0E0E6" {{ $hi := $.JSON.Float (printf "daily.temperature_2m_max.%d" $i) }}
color_snow: "#FFFFFF" {{ if lt $lo $minT }}{{ $minT = $lo }}{{ end }}
color_thunderstorm: "#696969" {{ if gt $hi $maxT }}{{ $maxT = $hi }}{{ end }}
color_other: "#FFFFFF" {{ end }}
color_red: "#F08C46" {{ $rangeT := sub $maxT $minT }}
color_yellow: "#F0F046" {{ if le $rangeT 0.1 }}{{ $rangeT = 1.0 }}{{ end }}
color_blue: "#46F0F0"
color_white: "#FFFFFF" <div class="gf-weather">
overlay_color: "rgba(0,0,0,0.20)" #Was pink with "rgba(255,0,255,0.20)" <style>
template: | /* scoped to this widget only */
{{/* Weather widget fully customizable via options */}} .gf-weather { width: 100%; }
{{ $temp_unit := .Options.StringOr "temp_unit" "celsius" }} .gf-weather-grid {
{{ $weekend_color := .Options.StringOr "weekend_color" "var(--color-separator)" }} display: flex;
{{ $overlay_color := .Options.StringOr "overlay_color" "rgba(0,0,0,0.35)" }} gap: 14px;
{{ $color_clear := .Options.StringOr "color_clear" "var(--color-text-highlight)" }} justify-content: space-between;
{{ $color_partly := .Options.StringOr "color_partly" "var(--color-text-highlight)"}} align-items: flex-end;
{{ $color_cloud := .Options.StringOr "color_cloud" "var(--color-text-highlight)"}} padding-top: 6px;
{{ $color_smog := .Options.StringOr "color_smog" "var(--color-text-highlight)"}} }
{{ $color_drizzle := .Options.StringOr "color_drizzle" "var(--color-text-highlight)"}} .gf-weather-day { width: 44px; text-align: center; }
{{ $color_rain := .Options.StringOr "color_rain" "var(--color-text-highlight)"}} .gf-weather-dow { font-weight: 700; font-size: 12px; opacity: 0.95; }
{{ $color_freezing_rain := .Options.StringOr "color_freezing_rain" "var(--color-text-highlight)"}} .gf-weather-dom { font-size: 12px; opacity: 0.85; margin-top: 2px; }
{{ $color_snow := .Options.StringOr "color_snow" "var(--color-text-highlight)F"}} .gf-weather-icon { height: 22px; margin-top: 6px; font-size: 18px; line-height: 22px; }
{{ $color_thunderstorm := .Options.StringOr "color_thunderstorm" "var(--color-text-highlight)"}} .gf-weather-bar-wrap { position: relative; height: 92px; margin-top: 10px; }
{{ $color_other := .Options.StringOr "color_other" "var(--color-text-highlight)"}} .gf-weather-bar {
{{ $color_red := .Options.StringOr "color_red" "var(--color-negative)" }} position: absolute;
{{ $color_yellow := .Options.StringOr "color_yellow" "var(--color-text-subdue)" }} left: 50%;
{{ $color_blue := .Options.StringOr "color_blue" "var(--color-positive)" }} transform: translateX(-50%);
{{ $color_white := .Options.StringOr "color_white" "var(--color-text-highlight)" }} width: 40px;
{{ $temp_red := .Options.FloatOr "temp_red" 27 }} border-radius: 12px;
{{ $temp_yellow := .Options.FloatOr "temp_yellow" 20 }} overflow: hidden;
{{ $temp_blue := .Options.FloatOr "temp_blue" 10.0 }} /* IMPORTANT: keep colors even if custom.css sets backgrounds */
{{ $temp_white := .Options.FloatOr "temp_white" 0 }} background: transparent !important;
{{ if eq $temp_unit "fahrenheit" }} }
{{ $temp_red = .Options.FloatOr "temp_red" 80.0 }} .gf-weather-bar-overlay {
{{ $temp_yellow = .Options.FloatOr "temp_yellow" 70.0 }} position: absolute;
{{ $temp_blue = .Options.FloatOr "temp_blue" 50.0 }} inset: 0;
{{ $temp_white = .Options.FloatOr "temp_white" 30.0 }} border-radius: 12px;
{{end}} z-index: 2;
{{ $location_string := replaceAll " " "%20" (.Options.StringOr "location" "") }} pointer-events: none;
{{ $url1 := printf "https://geocoding-api.open-meteo.com/v1/search?name=%s&count=20&language=en&format=json" $location_string }} }
{{ $req1 := newRequest $url1 | getResponse }} .gf-weather-temp {
{{ $latitude := $req1.JSON.String "results.0.latitude" }} position: absolute;
{{ $longitude := $req1.JSON.String "results.0.longitude" }} left: 0;
{{ $url2 := printf "https://api.open-meteo.com/v1/forecast?latitude=%s&longitude=%s&temperature_unit=%s&daily=temperature_2m_max,temperature_2m_min,weathercode&timezone=Europe/Budapest" $latitude $longitude $temp_unit}} right: 0;
{{ $req2 := newRequest $url2 | getResponse }} z-index: 3;
<div style="display: flex; justify-content: center; align-items: center; flex-direction: column;"> font-size: 12px;
{{ $dates := $req2.JSON.Array "daily.time" }} font-weight: 700;
<div style="position: relative; width: 100%; height: 25px;"> color: #f0f0f0;
{{ range $index, $date := $dates }} text-shadow: 0 1px 2px rgba(0,0,0,0.6);
{{ $dateString := .String "" }} }
{{ $parsedDate := $dateString | parseTime "DateOnly" }} .gf-weather-temp.top { top: 6px; }
{{ $dayOfWeek := $parsedDate.Format "Monday" | trimSuffix "day" | trimSuffix "on" | trimSuffix "es" | trimSuffix "edn" | trimSuffix "urs" | trimSuffix "ri" | trimSuffix "tur" | trimSuffix "n" }} .gf-weather-temp.bot { bottom: 6px; }
{{ $day_color := "" }} </style>
{{ if eq $dayOfWeek "Sa" "Su" }}
{{ $day_color = $weekend_color }} <div class="gf-weather-grid">
{{ end }} {{ range $i, $d := .JSON.Array "daily.time" }}
<div style="text-align: center; width: 10%; height: 25px; line-height: 25px; margin: 0 10% 0 3%; left: {{ mul $index 14 }}%; position: absolute; background-color: {{ $day_color | safeCSS }} "> {{ $date := $d.String }}
<p class="size-h4 color-paragraph">{{ $dayOfWeek }}</p>
</div> {{/* Day label: W Th F Sa Su M Tu (like your screenshot) */}}
{{ end }} {{ $dow3 := $date | parseTime "2006-01-02" | formatTime "Mon" }}
</div> {{ $dow := "" }}
<div style="position: relative; width: 100%; height: 25px;"> {{ if eq $dow3 "Thu" }}{{ $dow = "Th" }}
{{ range $index, $date := $dates }} {{ else if eq $dow3 "Tue" }}{{ $dow = "Tu" }}
{{ $dateString := .String "" }} {{ else if eq $dow3 "Sat" }}{{ $dow = "Sa" }}
{{ $trimmedDate := replaceMatches "[0-9]+-[0-9]+-" "" $dateString }} {{ else if eq $dow3 "Sun" }}{{ $dow = "Su" }}
<div style="text-align: center; width: 10%; height: 25px; line-height: 25px; margin: 0 10% 0 3%; left: {{ mul $index 14 }}%; position: absolute;"> {{ else }}{{ $dow = printf "%.1s" $dow3 }}{{ end }}
<p class="size-h4 color-paragraph">{{ $trimmedDate }}</p>
</div> {{ $dom := $date | parseTime "2006-01-02" | formatTime "2" }}
{{ end }}
</div> {{ $code := $.JSON.Int (printf "daily.weathercode.%d" $i) }}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> {{ $lo := $.JSON.Float (printf "daily.temperature_2m_min.%d" $i) }}
{{ $codes := $req2.JSON.Array "daily.weathercode" }} {{ $hi := $.JSON.Float (printf "daily.temperature_2m_max.%d" $i) }}
<div style="position: relative; width: 100%; height: 30px;">
{{ range $index, $thiscode := $codes }} {{ $loPct := div (sub $lo $minT) $rangeT }}
{{ $code := .Int "" }} {{ $hiPct := div (sub $hi $minT) $rangeT }}
<div style="text-align: center; width: 10%; height: 25px; line-height: 25px; margin: 0 10% 0 3%; left: {{ mul $index 14 }}% ; position: absolute;"> {{ $bottom := mul $loPct 100 | toInt }}
{{ $wtype := "" }} {{ $height := mul (sub $hiPct $loPct) 100 | toInt }}
{{ $wicon := "" }}
{{ $wcolor := "" }} {{/* Map temp->hue: cold=blue(220) ... hot=red(0) */}}
{{ if eq $code 0 }} {{ $hueLo := sub 220 (mul 220 $loPct) | toInt }}
{{ $wtype = "Clear" }} {{ $hueHi := sub 220 (mul 220 $hiPct) | toInt }}
{{ $wicon = "fas fa-sun" }} {{ $grad := printf "linear-gradient(to top, hsl(%d,80%%,55%%), hsl(%d,80%%,55%%))" $hueLo $hueHi }}
{{ $wcolor = $color_clear }}
{{ else if or (eq $code 1) (eq $code 2) }} <div class="gf-weather-day">
{{ $wtype = "Part Clear" }} <div class="gf-weather-dow">{{ $dow }}</div>
{{ $wicon = "fas fa-cloud-sun" }} <div class="gf-weather-dom">{{ $dom }}</div>
{{ $wcolor = $color_partly }}
{{ else if eq $code 3 }} <div class="gf-weather-icon">
{{ $wtype = "Cloudy" }} {{/* very lightweight weather icons (emoji) */}}
{{ $wicon = "fas fa-cloud" }} {{ if eq $code 0 }}☀️
{{ $wcolor = $color_cloud }} {{ else if or (eq $code 1) (eq $code 2) }}
{{ else if or (eq $code 45) (eq $code 48) }} {{ else if eq $code 3 }}☁️
{{ $wtype = "Fog" }} {{ else if or (eq $code 45) (eq $code 48) }}🌫️
{{ $wicon = "fas fa-smog" }} {{ else if or (eq $code 51) (eq $code 53) (eq $code 55) (eq $code 61) (eq $code 63) (eq $code 65) }}🌧️
{{ $wcolor = $color_smog }} {{ else if or (eq $code 71) (eq $code 73) (eq $code 75) (eq $code 77) }}❄️
{{/* Drizzle */}} {{ else if or (eq $code 80) (eq $code 81) (eq $code 82) }}🌦️
{{ else if or (eq $code 51) (eq $code 53) (eq $code 55) (eq $code 56) (eq $code 57) }} {{ else if or (eq $code 95) (eq $code 96) (eq $code 99) }}⛈️
{{ $wtype = "Drizzle" }} {{ else }}☁️
{{ $wicon = "fas fa-cloud-rain" }} {{ end }}
{{ $wcolor = $color_drizzle }} </div>
{{/* Rain */}}
{{ else if or (eq $code 61) (eq $code 63) (eq $code 65) (eq $code 80) (eq $code 81) (eq $code 82) }} <div class="gf-weather-bar-wrap">
{{ $wtype = "Rain" }} <div class="gf-weather-bar"
{{ $wicon = "fas fa-cloud-showers-heavy" }} style="{{ printf "bottom:%d%%;height:%d%%;background:%s !important;" $bottom $height $grad | safeCSS }}">
{{ $wcolor = $color_rain }} <div class="gf-weather-bar-overlay" style="{{ printf "background:%s;" $overlayColor | safeCSS }}"></div>
{{ else if or (eq $code 66) (eq $code 67) }} <div class="gf-weather-temp top">{{ $hi | toInt }}</div>
{{ $wtype = "Freezing Rain" }} <div class="gf-weather-temp bot">{{ $lo | toInt }}</div>
{{ $wicon = "fas fa-snowflake" }} </div>
{{ $wcolor = $color_freezing_rain }} </div>
{{/* Snow */}}
{{ else if or (eq $code 71) (eq $code 73) (eq $code 75) (eq $code 77) (eq $code 85) (eq $code 86) }}
{{ $wtype = "Snow" }}
{{ $wicon = "fas fa-snowman" }}
{{ $wcolor = $color_snow }}
{{/* Thunderstorm */}}
{{ else if or (eq $code 95) (eq $code 96) (eq $code 99) }}
{{ $wtype = "Thunderstorm" }}
{{ $wicon = "fas fa-bolt" }}
{{ $wcolor = $color_thunderstorm }}
{{ else }}
{{ $wtype = "Other" }}
{{ $wicon = "fa-solid fa-question" }}
{{ $wcolor = $color_other }}
{{ end }}
<i class={{ $wicon }} style="font-size: 20px; color: {{ $wcolor | safeCSS }};" title={{ $wtype }}></i>
</div> </div>
{{ end }} {{ end }}
</div> </div>
</div> </div>
{{ $maxTemps := $req2.JSON.Array "daily.temperature_2m_max" }}
{{ $minTemps := $req2.JSON.Array "daily.temperature_2m_min" }}
<div style="display: flex; justify-content: flex-start; align-items: center;">
{{ $max_max := 0 }}
{{ range $maxTemps }}
{{ if gt (.Int "") $max_max }}
{{ $max_max = (.Int "") }}
{{ end }}
{{ end }}
{{ $min_min := 999 }}
{{ range $minTemps }}
{{ if lt (.Int "") $min_min }}
{{ $min_min = (.Int "") }}
{{ end }}
{{ end }}
{{ $max_max = add $max_max 1 }}
{{ $min_min = sub $min_min 1 }}
<div style="position: relative; width: 100%; height: 75px;">
{{ $temp_range := sub $max_max $min_min }}
{{ range $index, $thisHigh := $maxTemps }}
{{ $thisLow := index $minTemps $index }}
{{ $thisHigh = $thisHigh.Float "" }}
{{ $thisLow = $thisLow.Float "" }}
{{ $thisHighPct := sub 1 (div (sub $max_max $thisHigh) $temp_range) }}
{{ $thisLowPct := div (sub $thisLow $min_min) $temp_range }}
{{ $thisTempRange := sub $thisHigh $thisLow }}
{{/* Guard against division-by-zero if highs == lows */}}
{{ if eq $thisTempRange 0.0 }}
{{ $thisTempRange = 0.0001 }}
{{ end }}
{{ $red_pos := mul 100 (div (sub $thisHigh $temp_red) $thisTempRange) | toInt }}
{{ $yel_pos := mul 100 (div (sub $thisHigh $temp_yellow) $thisTempRange) | toInt }}
{{ $blu_pos := mul 100 (div (sub $thisHigh $temp_blue) $thisTempRange) | toInt }}
{{ $whi_pos := mul 100 (div (sub $thisHigh $temp_white) $thisTempRange) | toInt }}
{{/* Clamp to 0..100 */}}
{{ $pRed := $red_pos }}{{ if lt $pRed 0 }}{{ $pRed = 0 }}{{ end }}{{ if gt $pRed 100 }}{{ $pRed = 100 }}{{ end }}
{{ $pYel := $yel_pos }}{{ if lt $pYel 0 }}{{ $pYel = 0 }}{{ end }}{{ if gt $pYel 100 }}{{ $pYel = 100 }}{{ end }}
{{ $pBlu := $blu_pos }}{{ if lt $pBlu 0 }}{{ $pBlu = 0 }}{{ end }}{{ if gt $pBlu 100 }}{{ $pBlu = 100 }}{{ end }}
{{ $pWhi := $whi_pos }}{{ if lt $pWhi 0 }}{{ $pWhi = 0 }}{{ end }}{{ if gt $pWhi 100 }}{{ $pWhi = 100 }}{{ end }}
{{/* Ensure non-decreasing stops */}}
{{ if lt $pYel $pRed }}{{ $pYel = $pRed }}{{ end }}
{{ if lt $pBlu $pYel }}{{ $pBlu = $pYel }}{{ end }}
{{ if lt $pWhi $pBlu }}{{ $pWhi = $pBlu }}{{ end }}
<div style="left: {{ mul $index 14 | add 3 }}%; bottom: {{ mul $thisLowPct 100 | toInt }}%;
height: {{ mul (sub $thisHighPct $thisLowPct) 100 | toInt }}%; position: absolute;
width: 10%; text-align: center; border-radius: 10px; overflow: hidden;">
{{/* “Gradient” as stacked bands so Glance sanitizer wont kill it */}}
<div style="position:absolute; inset:0;">
<div style="position:absolute; top:0; left:0; right:0; height: {{ $pRed }}%; background-color: {{ $color_red | safeCSS }};"></div>
<div style="position:absolute; top:{{ $pRed }}%; left:0; right:0; height: {{ sub $pYel $pRed }}%; background-color: {{ $color_yellow | safeCSS }};"></div>
<div style="position:absolute; top:{{ $pYel }}%; left:0; right:0; height: {{ sub $pBlu $pYel }}%; background-color: {{ $color_blue | safeCSS }};"></div>
<div style="position:absolute; top:{{ $pBlu }}%; left:0; right:0; height: {{ sub 100 $pBlu }}%; background-color: {{ $color_white | safeCSS }};"></div>
</div>
{{ $top_pos := -2 }}
{{ $bot_pos := -2 }}
{{ $pos_thresh := 0.20 }}
{{ if lt (div $thisTempRange $temp_range) $pos_thresh }}
{{ $top_pos = -17 }}
{{ $bot_pos = -19 }}
{{ else if and (lt (div $thisTempRange $temp_range) (mul $pos_thresh 2)) (lt (sub 1 $thisHighPct) $thisLowPct) }}
{{ $bot_pos = -19 }}
{{ else if and (lt (div $thisTempRange $temp_range) (mul $pos_thresh 2)) (gt (sub 1 $thisHighPct) $thisLowPct) }}
{{ $top_pos = -17 }}
{{ end }}
{{/* Overlay layer for numbers */}}
<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background-color: {{ $overlay_color | safeCSS }}; z-index: 1; border-radius: 10px;">
<p style='color: #F0F0F0; position: absolute; top: {{ $top_pos }}px; left: 0px; right: 0px'>{{ $thisHigh | toInt }}</p>
<p style='color: #F0F0F0; position: absolute; bottom: {{ $bot_pos }}px; left: 0px; right:0px'>{{ $thisLow | toInt }}</p>
</div>
</div>
{{ end }}
</div>
</div>
# Calendar Widget # Calendar Widget
- type: calendar - type: calendar