From 4abe5f0b97a2b7ab863da40cba52cfc5f4dfc9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 29 Apr 2026 08:56:26 +0200 Subject: [PATCH] Mondphase eingebaut --- .../dist/moon_small.png | Bin 0 -> 8198 bytes .../dist/sternwarte-card.js | 176 +++++++++++++++++- 2 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 sternwarte-homeassistant-card/dist/moon_small.png diff --git a/sternwarte-homeassistant-card/dist/moon_small.png b/sternwarte-homeassistant-card/dist/moon_small.png new file mode 100644 index 0000000000000000000000000000000000000000..b66bbe413ae80a439061ae17ac65c15bb5701283 GIT binary patch literal 8198 zcmV+hAo<^kP)^szTRIZEkMvy2;7Og9i>CEavj1?5@Sdd>n;XURvs| zu2t&i&!5|>ZEv2htgoMrB0t`FPeG6%h_u8jF9;Dp#6QxKX)UyN`aX`Mzte^^O(F0< zKZC)t%zN{!2B&GlU&jjrbX`}D9J%56kt5f?_~60Ax9r)!@A{mZndAkgDKy;xS;dQm zh;FZs&8_Oj@{>=VICu8!qmMoI*mo~qJpX+l3C4zt5F`~yg=aNb)l`f|9xdeW!{GGl zV|(}Qd;XnwzWnXC-~Rm99oWCmvK)s`$@4~NciQlV1Ad+;${`=qm!JlkxuKusF;W7kKWW%mCSkrZkf)Hus|Gzajy|SZ6 zZ@lpzz2qe?x%ZW?eA&TWyBAU0-bSz0*NKL`Lpf?=2tWC@WUKs78FrU}<| zA&CMyoeuob2#Tyi*HoCcg=VvZhadjy6My-a|K;81&OY%i-Y`*ly1{h9A$eH>#Nky> z`@d%Jj?w!M9=h=jzxMjCOifPg*L4M1*MT?equ=f!82RY+`$$p&8P|a%i3qw4_`?xW zNrNQB5F`a^rT|rvU}v+CbpvS}vH^h~T=60a<*8{1qJr-|^n=Tv`1r@)xLH|wWE{H^ zuS3(d@d>lbr$5!`^cOKWiXw=Tgg3n5jh}q^OOL;O&}m|OeHEr{pi*li8hR*~Cy^;l zU^EIK$r_xT3o(u0b(;{$#Ro&kvIyOF(AeI_u-!zqSb|-e;&_#%o%a*i^;K1Ys_Hm! z-BB!GUdD$$@PSW1@W8jbSAY;_;APz!gic?4vAH#M7 zhHdjA+gqC$c3Q|z&L9Xz5NPe9$S5tz63mQ)Qh6H1(iGw-#pnL`Gv`11*-zgJpluiy zM&T8M`Fi$~PJbSQ>AL0C=iT+LcfRv$mrgv6m2)TI<}#2i8{I&FqFK1{xksRBGIUKx zuh~F-a}8+}!7Y|B=nXL%4$-RDQJ9*BrYq>QTA10r55=ijcta1B)n!OY0#Oj)4TrqJ zaTvoN4G^jd>=?q^;*;B?3F z>4KlCu7kq^VM(HMjn z3`UsDSx{9OhD8XiAxb64x{cX|S=d<@L(fO8vW{@z!N@qU^Ley3w=itC5JoJ~T^!H{JX@-}qage>dWTv|c1T7~SmsP{eIu&k*;OvkR8wa3Phxc807jG)N2qFnNyjps%&%u360GPrl+P*YY*`6e)rwSyWRHp zR7Jlgp6L?!{~++j22;v-He*Yr(%kR-o8LLNbmo*ve;&&Q!Z1LZ#QXw|V<3qW$dQL^ z)<$~}pjq#skg<@6z#xdwA4L$5!qg22X@a7yLQ!P|o(ID;U}kgB6k5Chr;vxPtI!Q= zd@4zdA*Hu&7xA!G~3xiRLPP>PR zq6^j3&JTLXhAePij^YHPR6?ZKNEHL2D52*K(eHNI#mRG`B!M4A9Lr%4 zu+hU#2T}_5NZOELzXt&c+~OoQD;4bDyP*H`U;m9S?ieV`^3!g**x+kpcqfj3R`Lkf~;xqM;^|fdIBeZ{3sfg3NmgMlB%N9Y-8B# zqTgww-)h3K$ApGFNVjZ^0w1q=)hlkl?Y0;GTRKgF%p)?gbMF`&Md8&u2m&nbUOfEE zzx0~-_4-|AP4z|xp&&yQ0WBz;P_JJs+bmMm}R8YpT!{ z8I~rG*;5cfBT5)X&;>w23`GPKMS*3RNTP^idvk3aTWb~g!y%T=oJOZsgJC=9`2ojw z==*3?w-NeOFaQ#rpd`S_WPvost6p*E2P8?kZZPa&==HDO*PQSUK^eR9-FM&pep=|- z(lS3q*9lQ6pT*FNW$bzGi;xHgQqh1Pi-<)LUKn9*tB#=;B8g(iiVTHQVFF?isw}{E zbFeHMVG3j>CYhYrmI*_XVCXVT$3neYLuF+RF{LccfT*b0SXn`}vdL7H3WbT8IXJmI zI*m3YK|rohWGoTVh+a`@eip$I^29Ha;@qP5@4Y%BW`yF?E_pcsC zr`JO$C}?)cJz~hJhEUF8`TQyL8*AteBW%|C82JgjAc7yoC=~MivZEm3#X^t~$5gMz zuv{0iX`wVXgV}{$&`lFVKR~-#hY$zIxj8hO4TNz3J7aMnF(U3AC6EOH{>aC0I7DfB z3R4SHq(-okLuhpI|&vRN30jg7Sp)VC@q6bmp73*KPF;2BW{Q549U%IgzT0cXx!z{NA? zkjdxRJ0wMdOw`jb07*ccCcIG#^K-cM`7d}Y+jkAYWP?R<{A0O1IeW|Vp7;7Qr_Ujc zBW5<1AyHMwphWRO2|u7xERJ9qD!XbB#RyXwQW~SVwE;($p-P0Gbjk|nctsLf0U;vM zsyA`&)EU&ORk*H=8*jcD^ZO4no$R*S==J)jUvXDbQwZ$|Tj(|7G{wd97l9~7#&toE z<@-L@=8olH*zdzIEvTk}7rx*HcLT5~tYlev%3yLix?cR^<8RF6is<$R4C9(4aB&dD zA$miP*^;V=NLbDRvW5i1a?tC0u*gU%=rsclxh|n?7$KxmQWPNTI#Na9yi0DlzPy57 zrwcoi#mufbRxSFi76$DmJm2TElu`{10g|erKj=e549BvdXd0)mBu-)3Hpg$X(L}G; z;}8h^5xe>Ry?Zi;4qyMPbiPS4mVm~QeC0ZJ)3MhEVTfEdgK9-W--{5ZA`Df7D$9ry z0g|FYO-FpfT#`irT-$(Y=qS$2k99A92tn2$Q@yLJP!$y;e}sg~Hxb!F0SkL}!^t=p zjeM*&Vzd0Q&F~A~0hSFJzWBiF~01e?(PJ3fplo@O)N3+*|>LQh`r_PGx&z zon2E>R0I&=WF3eU+Z&r`HJe;$s4@{86~nH_=}tCG40|oqy4%=)_%Jln;E*BrURzm0 zuib{O>e#cm_)4jwvm+sw=ydfgW96vdjG%^;I;peh1XSwRpI0T0=Y zCZ=XlE|;NDEgBKwQW$6~$Ks-&Kq*(qaXl*%f=MbD7hxD;V{Hu=&z?uMQo+W01@%@J zwN?ksMjhqZSrq3N5se18;ph#RKX8auk4Vx`>ky@Nxds{yI_P!#=yW?+Sy@G~G=V~q z!m)?yc7s9p)QQL8*tRx3J^lP0JK5mfi}N#w4j;Z{I2dsKS}Ycj&185X%~p#OyC9~} z6xswEnXH3C!9^zT!f;$zb_Tv5W2>@-X1xi^F|qH!B64m9AxSU9g_4v3N7bk(NO0nb zlQ?_o90HFjsTe^Vqt@&~kp+}WMdsj=7-84KJW6xBkSGq<*@BRwy4}XbvzL*EKDIWu z84rSRgwkXgrO7GY5Yp0Ct9AZ5H8pjFcWTF_C(Gr1Q{~A+aX5met7tU_tV#J{j3n_G zEoDW8NQfN9EZx*4_>y93iSVA~EaJc<(Zx&xd%aULPjyEK6}44DEF&<*+nM6rPK z)D-7z*L4tWg=jQ8EZ3eoaRzR_imB--n2w9HXD(oMt%i2ngKf(2{Sc0=qtP01F%X3z z3w~a&i&m?NY{urW8bt|%SseJ7C{66i7mD+wj|mtQ3MMGLFdYUuRZFe(3W z!luNMFmOas$~l&5)}_mNXt$c^k{s)gU|Bl5Jw3VC50Rv#hb7pkG|*~y zpeQ0su!f0%+DRb|PhqWbd1B~~M{xdJvXUO?6{ zIrsG_zlCFomRxmpV~DJqXWrbb_L%Mw8|t-N&?gmS^94?0irnH;_oEaA*MMtJf>Phw z(h8=hCpflAm@k$JAhMUs7g%4W^sN)Si&K7o+aZ!eLSY-4D#11t_(6g=3AkLHnVvwi z-axZf$IPx-+;r?Ya0~-c;M2wwO2y*jQ`8caURBXFY&`bpDOM&XN;xRRg_9VTu5(%- zzUAmDOjSW|6r$H3Fqs!+9bUiBVtN?(sH|bL(fB@ki*PO8I#j9%;>t^AymVKZmHa%=nqM=P9e(zBvIltWM~Q$F@dRy z2*Lm>mo7mTMO=UQ09-eNRtt(OGjLou3zP~u7QwsC z2FDY%GYIafh$qcAJ2{1g{rlkeyLj^41uoMxnRvBwrP&stzO{wfxhYIc&!gSgW_mbT zE+C5YT=GRh2s3LS@T2i&)9EVXlb@TKK;F$_VzP+JMis+BhrQzHO*b)sMG;_I7SmFS zVM6}RI`yM|h=sW_vbhpk?e_M?ix)Poy7cPW>ZL}#_N0@^aH-hu_mIhEv3uWs%*;$7 z%ZO?sm&;;qb{e@t0p7?%M0GDoF7-`ptSrMD`V0uI?QM*@U1V}DD-`W+kBj*2$_k>$ zXYxR7jXZ#8Be^iSG9xdO3)<9}Un@UZ!k+!R@q!mT7rS>)!}o{SyMH%hO5~4l?3Npu zA5^!tS;dG_$}lPtAz`*fxYKU8Pqo^uQ&&&8QmLFPe;lO(v!_C_#F`hCV8mJ| zpD8j|^z1UkZV37tjTTndD@-0NCxgP&ERw*3*Y84<6?EDil*=WoudG2Ku(55XnZ&un zV8BRBG>}9%ic7E8=K7cWSW$v77{MP1=ro(C)hHAsSf&9@c#E2a!>ktjoM$d$Y>YSvc|`vGE$pr6MfH z#(HH7)vc|QSEbvXZL%`$avKaI{JpSmT2+|aT z?hu8njorH!ncv!O0qaXoqEgv_X_)YW7-=|y;QNqN8NJ~UfuAyiVj-XUTye|-At@T8 z(TG7R2z;m{+6%<6LWDugM99s~arvn0CfbcQJQC+;2v2gDo8JY0bb-5%uI*xBeg=CE zA4b2^$K$8Yt_}vhs|G)<<#6J}i3csi(vex_w7Mpq6kCRp=%0PKg6=A zIL~A~&&CLz;nl(P{Y_7JAwnNBc8|c zOCT19A(EI{rPCM=Lo8jozymCHzQ~@ydX_9=@1aAOTG-7k9-0Q|_xjkXZsX+16Zijw z!Sa>f(hq<5$OETNp14pb6i{7TV%dfAq?65X^NLQCT$_YB%7vODF^!ZJ9exmTOQdzB z%p)?Kr#4AAmWfQuLZi{-HVQ40#CvZrVAX==KU&QOS5>~}ar?x|I7}=^O^HZQq>_)r zi{?701L`wtBVCL((a;qjq3Hw}bZA6*Po6n*wpy(|K(j)$$yW^~4y-HVd5N!nGUxg1`I-U^3*W9(w2;_X;Iq0x zRT{~%7fzqXpx1$0DDp@JZS;>G`OzoICT_W2(WSY2NX&{Z>bJ_0sC?}k-&oI=$`iF} zh25H5vQVDluHMGV5~}NKd>WeRU}}CBmM$$Ji2`JDc_?}YOBXM}8+2gmI`??VS&1tj zIeH!E%fK68a(Wh>Rs-Eu3vQvrHLO7Drb3Ej8-pG-$^>qnTN#(R^XB?G+RYZ+T%ILf zlA;tzMz7mLeR~Vfx%oB(5ebVvPMR3@QO!S&{^yMzchM95s)^!=}Rv(uxZD8O2S#GLLOyn^!eFwU=b#yyD zCIeG5laOSE(+G)rnamW|(WIi-P6oMRiCI#&-Q-P(f?y0}EcQ*Zn^regsVRl>beVHG z^^s^WhQ1d>37K4O+)!&Yplc?+^PTTdt0k7G#d1y8luuZ@W;v~(OYi4 z?YU>poMyqh-)T`*#XG#Yw#xb1u`P5vLv*?xBst|q*!}~1vG@AxQLi=7s&4RRQLU@# zCKChF%?zS`ambrM02EQ2$1#sQk-O873IXLfM~~+{Sf8d6vEN6l(O?!Oq#{IF#>g9w zLl9{)bQNF!+Fv~J>Cb%TMvhrxC|53$pvEpON>A+ofG|xkKR0vq8{hiYi)cl_2ZVHp0+ zxPkW6@e)@bw^~SGyK)QE5|L%Bu5CQ=2k(9Fznou~he`PoB6`gh_lxqy60+GGYfsr+ zmR*pt3XK?!+oFWPJ!qy0%XXpLnK4QxDaU6*v`7>f&}f6`dD+4QGWjB`45gv~!)}LV zAXU@2I_tKY+-I_!EKlN5)n>Ul?tE~&)pnS){Lv>r@gK&<($U3SJUn$jrFi_rO(*xY zzxeup{KJpj^XWs^UB_fRS1Q5l574S@vmQ(x0_qSF!6*6`QI{}HV46BtEOd$lUfsqP zdyeOg2S91jG%QLA1+`sNopdS6y*`gJQDH*YXh5S*{qdwM&1=z=&J|sa=DT#mfJ&o` zlFSXePkj92fA-j8kNxfrETzx5uNq8J>N9VJm^XlX@A=r`}Wae$Kd93&np*M|@>W z@u{(;odABuwX-w-=~JKh?wzlE#ql3M@*|$>Xw~ZcIOR+?mxD?%*6VUPNU4XaCw^n3 zU@)QAN%{LL6G&Wn5wDJXA6eVL z{rBI0@}nR9*fGM~o%zdWSb_0~^Upbnch5&Y{O#Ai?yft}ojJ?kQCVN-9&Nr*>^-+5RGs6o5I07pVw zLSqTk79p0Dq>K?9%L>V}3WFI@NLAIilW_8hCveY)?|tK?OP9X5^AP}=*ZSLx{rRV9 zMV)(0Q8MoH7K*v@yWjJkFaNXOe9MalLl2iPT)<$^VQ}Huju^;hvxt)jAq|{BV$Mq% zG~GNp=#MognyjNWkeEk$vmnbH!-N5Js>If4W}0TTzVyW}J#gRWKmTh6#@uQ5hk=);Ep?Pf8Cqk@|*wu&R_cFgNB*G=@XB#q^ij}6Ajg}#(A_+ zXL2$ogE2&fdeFptBIfIqdgytqgn(S$XH|nX%^Ugn`u+Ex|H9|*d)IcY{@>_*ciex+ zkg>r(-Pr##g9Q~3sZOO<#~37z9XodWufOBB|KR4^Zhg`8RGG!J4vi%!8W#%zr6}L$ z=cimGDBO@^dT^zELo$+SJFrcYwe01~m+^xi{Lk-x^~+!Rz?m~=|2w_UQ^RvRpLDrm zh(ISzk3GAwznH-!yQCzu(l`=vZi+#PIqJ@EYN6nkj=%I}uY2ALU-;T1Hyk~_`@ms6 zmvdP}?>5I(P)cqX#YkAhce&l^djpJoAFHct;mIc+`_W%N^w2jSeDJ|9v|An4lgI8$ z@6SuWvZ`xOn11)ujr|1-*2bN@ofE#sVERhDm4>)?PMj_$tE&r(yKmTk;D(zP_wT*# zIXB<3Sj-o*Jh&!F%5cyNE?>S-y?p+Bwb^K_(nomCo;mZ#XcUY;{&Hn#Zs+4dPo3&d s8vL_-;4IxS_#e_G{{LRU-}m*u0q1c9NO~j@UH||907*qoM6N<$f~fu3BLDyZ literal 0 HcmV?d00001 diff --git a/sternwarte-homeassistant-card/dist/sternwarte-card.js b/sternwarte-homeassistant-card/dist/sternwarte-card.js index 9d32ea4..4ae6d86 100644 --- a/sternwarte-homeassistant-card/dist/sternwarte-card.js +++ b/sternwarte-homeassistant-card/dist/sternwarte-card.js @@ -22,6 +22,14 @@ const ASTRONOMY_LIB_URL = (() => { } })(); +const MOON_IMAGE_URL = (() => { + try { + return new URL("./moon_small.png", import.meta.url).toString(); + } catch (error) { + return null; + } +})(); + class SternwarteCard extends HTMLElement { constructor() { super(); @@ -31,10 +39,13 @@ class SternwarteCard extends HTMLElement { }; this._pendingRender = false; this._astronomyLoadPromise = null; + this._moonTexture = null; + this._moonTexturePromise = null; } connectedCallback() { this._ensureAstronomy(); + this._ensureMoonTexture(); this._ensureClockTimer(); } @@ -121,6 +132,29 @@ class SternwarteCard extends HTMLElement { return this._astronomyLoadPromise; } + _ensureMoonTexture() { + if (this._moonTexture) return Promise.resolve(this._moonTexture); + if (this._moonTexturePromise) return this._moonTexturePromise; + if (!MOON_IMAGE_URL) return Promise.resolve(null); + + this._moonTexturePromise = new Promise((resolve) => { + const image = new Image(); + image.addEventListener("load", () => { + this._moonTexture = image; + this._moonTexturePromise = null; + if (this._hass) this._requestRender(); + resolve(image); + }, { once: true }); + image.addEventListener("error", () => { + this._moonTexturePromise = null; + resolve(null); + }, { once: true }); + image.src = MOON_IMAGE_URL; + }); + + return this._moonTexturePromise; + } + _requestRender() { if (this._isScheduleEditing()) { this._pendingRender = true; @@ -500,7 +534,7 @@ class SternwarteCard extends HTMLElement { const astronomy = this._astronomy(); const observer = this._astroObserver(); if (!astronomy || !observer) { - return { altitude: Number.NEGATIVE_INFINITY, illumination: 0 }; + return { altitude: Number.NEGATIVE_INFINITY, illumination: 0, phaseAngle: 0 }; } try { @@ -508,16 +542,108 @@ class SternwarteCard extends HTMLElement { const equator = astronomy.Equator(bodyMoon, date, observer, true, true); const horizon = astronomy.Horizon(date, observer, equator.ra, equator.dec, "normal"); const illumination = astronomy.Illumination(bodyMoon, date); + const phaseAngle = Number(astronomy.MoonPhase(date)) || 0; return { altitude: (Number(horizon?.altitude) || 0) * Math.PI / 180, illumination: Math.max(0, Math.min(1, Number(illumination?.phase_fraction) || 0)), + phaseAngle: ((phaseAngle % 360) + 360) % 360, }; } catch (error) { - return { altitude: Number.NEGATIVE_INFINITY, illumination: 0 }; + return { altitude: Number.NEGATIVE_INFINITY, illumination: 0, phaseAngle: 0 }; } } + _moonVisualState() { + const moon = this._moonStateAt(new Date()); + const phaseAngle = moon.phaseAngle ?? 0; + const waxing = phaseAngle <= 180; + const radians = phaseAngle * Math.PI / 180; + + return { + illumination: Math.max(0, Math.min(1, moon.illumination || 0)), + illuminationPercent: Math.round((moon.illumination || 0) * 100), + waxing, + phaseAngle, + cosPhase: Math.cos(radians), + }; + } + + _renderMoonPhaseDataUrl(moonVisual, size = 64) { + const texture = this._moonTexture; + if (!texture) return ""; + + const canvas = document.createElement("canvas"); + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext("2d"); + if (!ctx) return ""; + + const radius = size / 2 - 1; + const x = size / 2; + const y = size / 2; + const cosPhase = Number(moonVisual.cosPhase) || 0; + + ctx.save(); + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.clip(); + + ctx.fillStyle = "rgba(8,10,16,0.96)"; + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.fill(); + + ctx.globalAlpha = 0.98; + ctx.drawImage(texture, x - radius, y - radius, radius * 2, radius * 2); + ctx.globalAlpha = 1; + + ctx.fillStyle = "rgba(8,10,16,0.96)"; + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.fill(); + + ctx.save(); + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.clip(); + + if (moonVisual.illumination >= 0.999) { + ctx.drawImage(texture, x - radius, y - radius, radius * 2, radius * 2); + } else if (moonVisual.illumination > 0.001) { + ctx.beginPath(); + for (let i = 0; i <= 72; i += 1) { + const t = -radius + (2 * radius * i) / 72; + const xr = Math.sqrt(Math.max(0, radius * radius - t * t)); + const xt = (moonVisual.waxing ? cosPhase : -cosPhase) * xr; + if (i === 0) { + ctx.moveTo(x + xt, y + t); + } else { + ctx.lineTo(x + xt, y + t); + } + } + for (let i = 72; i >= 0; i -= 1) { + const t = -radius + (2 * radius * i) / 72; + const xr = Math.sqrt(Math.max(0, radius * radius - t * t)); + ctx.lineTo(x + (moonVisual.waxing ? xr : -xr), y + t); + } + ctx.closePath(); + ctx.clip(); + ctx.drawImage(texture, x - radius, y - radius, radius * 2, radius * 2); + } + + ctx.restore(); + ctx.restore(); + + ctx.strokeStyle = "rgba(255,255,255,0.18)"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.stroke(); + + return canvas.toDataURL("image/png"); + } + _moonTrackState() { const now = new Date(); const cycleStart = new Date(now); @@ -709,6 +835,8 @@ class SternwarteCard extends HTMLElement { } const astro = this._astroState(); const moonTrack = this._moonTrackState(); + const moonVisual = this._moonVisualState(); + const moonImageDataUrl = this._renderMoonPhaseDataUrl(moonVisual, 56); this.shadowRoot.innerHTML = `