Hallo, ich bin an einer animierten Sonnstandskarte am basteln.
Irgendwie klappt das noch nicht so richtig.
Die Idee dahinter ist die Elevation zu nutzen um die Sonne je nach Stand über den Horizont zu schieben. Bei Noon sollte die Sonne dann voll zu sehen sein. Da liegt aber der Haken: die max-Elevation hier bei uns ist so um die 67°, das wird im Winter ja nicht erreicht. Deshalb ist das Bild am Mittag nicht voll zu sehen
Abgeschnitten wird das Bild unten durch clip-path.
Könnte da mal jemand mit testen?
Hier der Code:
type: custom:button-card
entity: sun.sun
show_icon: false
show_state: false
show_name: false
tap_action:
action: none
triggers_update:
- sun.sun
- sensor.sun_next_rising
- sensor.sun_next_dawn
- sensor.sun_next_noon
- sensor.sun_next_dusk
- sensor.sun_next_setting
- sensor.time
custom_fields:
header: |
[[[
const sun = states['sun.sun'];
const isDay = sun && sun.state === 'above_horizon';
const dawnEnt = states['sensor.sun_next_rising'];
const duskEnt = states['sensor.sun_next_setting'];
let dayLenStr = '-';
let nightLenStr = '-';
if (dawnEnt && duskEnt) {
const dawn = new Date(dawnEnt.state);
const dusk = new Date(duskEnt.state);
if (!isNaN(dawn) && !isNaN(dusk)) {
const fullDayMs = 24 * 60 * 60 * 1000;
let diffMs = dusk - dawn;
// falls Dämmerung schon vorbei ist: auf nächsten Tag korrigieren
if (diffMs <= 0) {
diffMs += fullDayMs;
}
if (diffMs > 0 && diffMs <= fullDayMs) {
const minsTotalDay = Math.floor(diffMs / 60000);
const dayHours = Math.floor(minsTotalDay / 60);
const dayMins = minsTotalDay % 60;
dayLenStr = `${dayHours} h ${String(dayMins).padStart(2,'0')} min`;
const nightMs = fullDayMs - diffMs;
const minsTotalNight = Math.floor(nightMs / 60000);
const nightHours = Math.floor(minsTotalNight / 60);
const nightMins = minsTotalNight % 60;
nightLenStr = `${nightHours} h ${String(nightMins).padStart(2,'0')} min`;
}
}
}
const label = isDay ? 'Tageslicht' : 'Nachtlänge';
const value = isDay ? dayLenStr : nightLenStr;
return `
<div style="display:flex;justify-content:space-between;align-items:baseline;width:55%;">
<span>${label}</span>
<span>${value}</span>
</div>
`;
]]]
sun: " "
horizon: " "
times: |
[[[
function fmt(id) {
const s = states[id];
if (!s) return '-';
const d = new Date(s.state);
if (isNaN(d)) return '-';
return d.toLocaleTimeString('de-DE', {
hour: '2-digit',
minute: '2-digit'
});
}
return `
<div style="display:flex;flex-direction:column;gap:8px;">
<div style="display:flex;justify-content:space-between;">
<span>Morgendämmerung</span>
<span style="font-family:poppins_medium;">${fmt('sensor.sun_next_dawn')}</span>
</div>
<div style="display:flex;justify-content:space-between;">
<span>Sonnenaufgang</span>
<span style="font-family:poppins_medium;">${fmt('sensor.sun_next_rising')}</span>
</div>
<div style="display:flex;justify-content:space-between;">
<span>Mittag</span>
<span style="font-family:poppins_medium;">${fmt('sensor.sun_next_noon')}</span>
</div>
<div style="display:flex;justify-content:space-between;">
<span>Sonnenuntergang</span>
<span style="font-family:poppins_medium;">${fmt('sensor.sun_next_setting')}</span>
</div>
<div style="display:flex;justify-content:space-between;">
<span>Abenddämmerung</span>
<span style="font-family:poppins_medium;">${fmt('sensor.sun_next_dusk')}</span>
</div>
</div>
`;
]]]
styles:
card:
- height: 272px
- padding: 20px
- border-radius: 15px
- background: |
linear-gradient(130deg, #4398C3 0%, #3380A5 100%)
- box-shadow: >
inset 4px 4px 8px #4DB0DD, inset -4px -4px 8px #2E6A89, 4px 8px 12px
#91A09C
- color: white
- position: relative
- overflow: hidden
- font-family: poppins_regular
- border: none
grid:
- grid-template-areas: "\"header\" \"sun\" \"times\""
custom_fields:
header:
- position: absolute
- top: 18px
- left: 24px
- right: 24px
- font-size: 20px
- font-family: poppins_regular
- z-index: 3
sun:
- position: absolute
- left: 20px
- width: 150px
- height: 150px
- border-radius: 50%
- background-image: url('/local/pictures/sonne_sun.png')
- background-size: 150px
- background-repeat: no-repeat
- background-position: center
- z-index: 1
- top: |
[[[
const sun = states['sun.sun'];
if (!sun) return '225px';
const elev = sun.attributes.elevation ?? 0;
const maxElev = 70;
// Elevation auf sinnvollen Bereich begrenzen
const clamped = Math.max(0, Math.min(maxElev, elev));
const radius = 75; // 150px / 2
const centerAtMin = 260; // Mittelpunkt bei Elevation 0° (unter dem Horizont)
const centerAtMax = 140; // Mittelpunkt bei hoher Elevation (Sonne voll sichtbar)
const centerY =
centerAtMin - (clamped / maxElev) * (centerAtMin - centerAtMax);
const top = centerY - radius;
return `${top}px`;
]]]
- opacity: |
[[[
const sun = states['sun.sun'];
if (!sun) return 0;
// nur ausblenden, wenn Sonne wirklich unter dem Horizont ist
return sun.state === 'below_horizon' ? 0 : 1;
]]]
- clip-path: |
[[[
const sun = states['sun.sun'];
if (!sun) return 'inset(0 0 150px 0)';
// unter dem Horizont: komplett weg
if (sun.state === 'below_horizon') {
return 'inset(0 0 150px 0)';
}
const elev = sun.attributes.elevation ?? 0;
const maxElev = 70;
const clamped = Math.max(0, Math.min(maxElev, elev));
const h = 150;
const radius = h / 2;
const centerAtMin = 260;
const centerAtMax = 140;
const centerY =
centerAtMin - (clamped / maxElev) * (centerAtMin - centerAtMax);
const topPos = centerY - radius;
const horizonY = 225; // Position der Horizontlinie
// wieviel von unten „unterhalb“ des Horizonts liegt?
let bottomClip = Math.max(0, (topPos + h) - horizonY);
if (bottomClip > h) bottomClip = h;
return `inset(0 0 ${bottomClip}px 0)`;
]]]
horizon:
- position: absolute
- left: 20px
- right: 270px
- top: 225px
- height: 2px
- background: rgba(255,255,255,0.35)
- box-shadow: 0px 1px 4px rgba(0,0,0,0.35)
- border-radius: 999px
- z-index: 2
times:
- position: absolute
- top: 100px
- right: 24px
- width: calc(100% - 215px)
- font-size: 14px
- font-family: poppins_regular
- line-height: 1.6
- text-align: left
- z-index: 3
PS: die Karte ist auf ein iPad 10 ausgerichtet.
Gruß
Frank

