From d3ceae0504b776903f89584f1c900d28950f443c Mon Sep 17 00:00:00 2001 From: Serverlele30 <46974281+Serverlele30@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:56:59 +0100 Subject: [PATCH] Add files via upload --- DotMatrix.ttf | Bin 0 -> 45804 bytes index.html | 379 ++++++++------- script.js | 1222 +++++++++++++++++++++++++++++++++++++++++++++++++ styles.css | 95 ++++ 4 files changed, 1503 insertions(+), 193 deletions(-) create mode 100644 DotMatrix.ttf diff --git a/DotMatrix.ttf b/DotMatrix.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d81f87910a3962f61382e33bdb668a78e783175c GIT binary patch literal 45804 zcmd6Q4Rl;*ncjP6Ml%{|WZAMuBO-e&#Y$A1*jB9AiGzp~5eH`z|0a--U(3I8V#mfP z{z+1xunQ^eh7bbF0*5UNyU^{P9!P*g&Qi#xO9F)L0_D)0a`r$^x7&92v`c~ICyS*% z-_P^TJ2SfS2rs4A9)0iq?sva?@B9D0-*>+&iHLN|h-Bs2owsb*xckn#PK$i{`>1{W zuGidr>&-v^FYgq2>T2w7-hXWB#DU+vtRV7<6C&B^{ikLIrtaVOvPkYq93MJ#;_$Jz zk3P2(`vcftaro%jL)SmF>ATqfW08;CF@50JnUz;|Zxs2XmqZHf(+8&xy!fX5-$eiD zPc~1ZqV3Ju4E8_i`kOvBbLN|ab7w_9{;WvynWM+|PszaO7eqeRj{VOcn>uqszL!~w z_FqT)fd{6J9lYmDUpNH#7r4Kn6UW~)Q@XPBQIS8Kadjt79z5~pZwyc2es0J99{l=} zOYt{s*T|{}C9*=$%5NE2B9BBkD>uu3h;R;Vo{VstY)h_l$`wy_c|JNRT;NZ!rnS%!g_MIKL;rIhH#}A*JIx&6lCy$(w z>(OQgXKeRq zj~hBJ$NcRc!Txz~7A@wv$%U@C@yhihqw7aT{6qaR*W2jPG$Nxw)`*PY3F@$AaCP0l z^+%3BFm-Th;1H;D`M~MZr`I0{HS6~uKPK1tyFcQ|F@~pFENLD7h^8Ve zE?YC6oSuE*<(IPqzxe)m=3|maW06C6>Q1T18rdMP0`ni9Sf6a$HgRKqd~~>ba`W1r zd|Nd)HZs&#YLjBVT+R;;=GU#uZ`qpkXR=wnqw(<#=a%|^$%e|x*+&sFmx4a_1=Hg4s7XVkw<*NOyDwkK;pFQT_%%dw-ty+QK4p*Y>&K1?_3iPon zmOEqp(<@f4Tv1-RQgWcskCOEUPq$1|laY~$E2_iYJ^5TohWc`At3}|YysqEiBv7eF zB7=_+i;t0o@Nt#FO~Js`UiaL+RhBOhNUFWAwzr!3=k5vc+Yz7aRDANC5kmqyBm0w| zWNxeDfLr8VxgQerc6qmaNIoG?%a=qfx$G^q0^8fRq_*eUHpAvI)8L9}WBW;i$p(kW z8wU&!D89d14v0ET46>YN+rz7S%d1zHdspvYRSqZFZUS2O@lrXqNj$%S=bmRj$)83h zBePZ_vz24HJd$ijAZ3+I>B#Ar((j{>vu+bkr_}jI7<;vx#|88vmJy$MYwQ5=1 z7b!X6Pu^p=+DX5qy(~94w)jq?iKU@!Z)sxdZTqx7ZF^<$!^G?%Jh(^5LDEhMvzMeg zLcutwXORreXYJ!znRi7D?u=p<=?x4Ab+IF2a#zGSkE=TA>F@7Z-rv8%^@CqeY)`f0 zd{jq)pDaLuAboYRXYpb2YH@jqp_{E=ENo@f_L+8FOG665A+ltKz%)sfJkCZL3ffc? zJuQiXe6elvO48~G39Etz2@Yga9Y`{siR9WHb>PE}Ecoq+`@15!DPSZQb?6=yGS7D_ z&$#yZksVIQe&Ay}>i+aR)J>JKR%m7EWA*bUL;o}xwtQsiX0L1Op&Y}Lsshat$0~9P z@CYf*5&|6b85F9+V|kV8Th!y@{;6;`V9`KT_+^s0Q>NgTIp65Ii4{p--^45O!Q8_Q%9iQ*Y}l~$oHy}O z^LF~Ib?;lqx|82oW`00^jA{m6q2nX#eFQ!I_K8;|x86GOx*a#cPuo_7AA%sTw0Ug~ zuG&~BH#wQxVcoUy{MN1c-MfR+))(E#@p0#})f_kMz-NIs>8jzrSs$)XZnaN!t9vTT zT$XVxY?;);mH{pN6|%-5a@;;@4rQw-+^Vcq0n}I}qK||oYwU_Fvpo`AU7?8Q4Nda; zmWiu9o~yuffA@}ju4klgD7SfSwP!LnRvOD~tMiJ9X5$4m;Mij~VA(nF9>q1zX~wY? zY>Ougr{UYyTiA$Lz{h@~Hbb4tQje%`D~>%5DRz)+;+Ep6;u>)-kfq>nzAnYzth01i z87~2^ z%rsnE+*#P-&cc62L?9q>ro(CeP{Mr+EOCIihGQI_Ea9PPYD9GjG8adr-Rzo@?}AbC z{q#neza@HHMA~0QB>GF>JaLgPBL1(-^L02nF|ol%zSYrn<;k@W^ip4LY)D3On|rF+ ze0P7oxFuhNfDdZVW&fpj7Gpf)rFWd`+z}8M%tfyhuac^HksXj+ z9w_U{RjawR!`XbXzk6M|m>pCzSz$CBG3CsQ;%4D+*y1J)CMT(A)&esgO74n}(i^PC^6E9^t@>J;`WKb_=KfCJYhATTier(r&~9e?;q|jTP~$cFrQi!WIdwz!rf- z5zBp4Ng9K|$&9LqK#pyQ&eKuFJQM|kCDBWPVPp{_>t;`)mQ_jB!&a)x#wN8=Zy($u zuoZY8)q^@+O-ftSN;F~zh$uC5L~~lHG4(EQwOq@VRFmiJNgzfKlq zMSXA!Hl?>qua;i#?#RMENHto57BN$ZaZu#l_od*^Wdz0YlYRCy*!o{-ZEU^8VH#{{ zN7QOkNMlM^zwqF*-=qtMtWb2=Sfd)Cjf|}xhYExGzE?zsWtxrgBGY!Kqso2QT^A0I z_t#LzECKyD-_Gnjy&RD)kKy+&z~LWSe74(S*6IdZ zpBSoGeouq#xwcO-H9UcU>*myq+Azw7D(~6vD5+EcnmSbCQ6iFiqS9C%uDFV>Hq+Ns zG(^9~C!dU(okI^SDL`z<4kYHyerG}Yd}8qwYBSik*i6HInta%EEro_n{}xu{DxhQ| zC7gDO3ObfY82s#|7(o>U_ajfUFi@!sxZhr$Jk7SMzrs+LvzB(x-V)0_v?DRMU5WkDK9hP+oR4~GvA4yj#dIsMrC1tOEoC;PP|UV@fvBtLDvH$k z%EQbEAZrjvr}g66z6oq3-LfG{zrZ8Y(8otTHF;$uEqz12vgYM8>BW}pj>UQ!C2f5gY^hD|*0@JajH2vD_6E7x7IC z&EyvG&2fULx1L-K=UaSP?oGqXa0tkIb$G08+sYKXLXzj%G}BhBu;N{(VK(Ae0-t2b{M zERS%Asg%R`$6BO?d``4`ozDmPRFDoUg)ERmG_W0pmtqPiBE3(>?1PQD2eS`jdD+6Y zjfKBTW2**K8DB$hHOLzx3!M&;;A(FQp)sfp$f`b1EdM?kt;5M{5TlQJT4!`T;k8jvlzK~N&ZTHAiqf3lNHH8a(Qxfg4vXo(bHJKMjp1k z#hpFZ*4r4;o_p0ALmYcTD{x&$rk}L>Rvy5fYwsc3LjMUSLxx!svCL3I+()NE(==Ju zu>5oKMKa1NG!CqAWSU}=q1T`t?WHg9Ew)j;bbyEUvLns!on_c`56@DMCBvEr2t3ul zJVdQ`vIaxjj$wPNk7Fcu1qK&SxdBE9y)RKW`?t$4x@b9m>UYE?X;sJ(k;va`|L3ErjN-%+0RBQSArt0 zGP|}Xu&0JEoV1esLF|xm41;?g%%el3$Gp8cUo`4w_P)6IjD8ya)9PE{C9AS5Yio9An zq`{Vkmd+MVE+dweFe;b|C}kRiob}D~e|0KT=8}jf>R*pX)YS!{pbfLq3_h6z^2s;| zH0gOp{a!btej)rWaz?$yRvOmR>aB!W>{{4TxD_e2ZJH&GY_B76n$jUXbz%ye&VHxX zP0TuIzVs_Rq-BFxX|Mi82Sbn8Q|%RxXlF^k=HzKm(4;3z&Z^?Dp2}gATPk?Y071_c|J95a$U5!j8j^9Pr{~N(r|0*TcN3KZ{aj)w>&^n zm>g?-PL-|MZ`%-SkP6HraN-r`flw|%j^a7|Pqe`b_64Z2b7q|Ou5 z@J$C5GnYC#;n!&AxXD ziCz^Zoj1tfk0n*Eq8*wxkWpWNQr)Z5wm62ta`$}k9>__&GKa>oNxCUKU%KXgZ|<6u zEF<6N6pI1NrxpVi9yZ!>(;{KpkA-cY7Dg}?+&Cm7iN46tYL^NO-#8ehWtev1&5coG?KIw_L#veo?*I3&!_7&(<-sv0RRiPS~XjL zRZ4q}0aB$m*h5m9gub6S>?~~?CIkkGsn??3p}udWy+k&F+d}0L*i~7Ke5~Fh`G=xU zFOr&;CCRUI%c-(`41lYU(Rc!bK+X^$e?1f*oY`^)8#+uM-n2en?&X5Ci{%7MK?^UI z; zSBlAjI1_vRF>%_4y0;Ci99;-(sb=Adkei*0`*3nTPxaTBbFnnA zd|>M>Z2e!Qh0-pfOTDBZZME?uQd2!wOy7+tu%ZLzQ&p(+?DEgP8F zuFYklHdU>O5#AuRI>w{`ri@XbLrFEtvhjGbo@p2Lw|Kp#94W<+^j9?--7G)r0wkOL z8?R&C=y3>4NNDO&%0KPm?BB*7$Wb(`T1hQw(`{qY^j{qr>!Uc4sx~z z-rWq6s&bg67h~r7(e>f!ES2oF{wS;?CQNrWjRZ;)G@slAfq0y+DAC?A`ANaWd?q(0 zn7+S&^9R@kt+| z>)gL<;Clw-%J-4YY4769`(LU5XLU=(=$SMMwSsX=A=?l6yw!4wxW)@W4HAOsQ1yxo z%X`F0uWGuYEmQxVZo1bZAVrD zA$1wBu3D27a1nxw$%{ZoQYeF8sT`n6lgYsb*jxWdXmk)BrSOa>KN4ZL*t zm9(AxCP{~-eF7~tjMkJ~6YXYKJ4XXaQU=UtB9R`6VoLGTG7{l4ady{Ju^j_8YG!7r z)2Q=nqYIIfR{nB0|Hnsc)!c=(^#-{&IJA$F|9_%_D1vs(n4B zyxdewCc|2Q8#W~F9i-Y87N(2)@m-tAdDZ~DgcjJt0!Y3tr*OFnt!R6(Tsdh^~vUk@+g( zEx0!MM&^qRm~^YhfYWLZYHzk|>K6+a%(=GDqYz)`v#kQl1z=U+q*ZApI1u$7<7_|cN?)1c5U{? zF06y?-fhIPSW?RMV8JLXja7E9at#i4*VYF2FJ1b#8#kQidiqJR{h##b00|Fi$I)fo!>Tb@%(F-^>=5p>jul&B469Xdgk~a-_{H3 zV(PnR%?p=VWn&*94W<^>r)btPS4P#@5X=z~)tF&;Z}LNZy85-8>$If35sO!Ai|j*K z*lNQx*!FMXnziSRtE;8)QB98q3aaz0xjBuXYTYqI6c*KXvK)U*fKjE=^rV@bvr)y) zME%k0Mm8Y~F~hJ>{>-h^T<0NJtX&9fF_;EhN^CMV(AHa+EVtOw=@y@hRNP}oI`C$e zDR5RykqXMr>M<~ErDwoQH~m>o(V#`-A~P(#TE}HQxj?GZQ9SrsO(l{)dJf5Z=9PfiFQ|?lTGYlvDQbhw4|-K&)*7c`O?C+&osEw0{w__1k2GiAt&j;l&0;} z)q^26 zUG<9S?~uM^2>MNAl*O;1XQ_W7@Fj*)mMhXIZrfOx)U1^;Vu-}8)dzJ$q9^MZY^uwu z&_BRr~Hus%CEm8?9i;wy`{EVO`IrIiZ(T-i)rd4zUu8#G3M7vp!Oee5w(F zQ4VMW8adUg0^riuqx6VJAEz8gLRL7}V4*j*E+|g2R7ivE3=zxocFe$HTQM5_z8WH( z^!HZDxr#>(LUl=t*>BW32`Eg~Hqk*NASzNuK2)@ne@>C}@(%W8Is1;t#;jmR+>dkB z)4011^A>xSC#`g+)mKuqf$eiX46F5QnkSHA&bs@S!iqi|kXH=@oVh@Yo{92!tmlegkGS*UzWWu_aLm6K27e?Ce_WqO zEWYgXUkGgRYvD=@oNGCHLA*C6tsA*IYWbRtaZ zCltr(1H~AF^mCs`wX}1KzJYf0(JfBvtI;jCHFR@nb*xqO|D104%C^gP&ZDg!owzpH zyLV!0^6uNVVEE^bTit@4?z6W!$!#9XP438T+vYy|g_R1s`4yDiTl3>Ljn{t$#V_#4 zkDp-CLiZ&Uw`yU5?!PYWzK(dbUnc5d! zrj4)7(tMnXNi>>~hogC+DfbQ*0$G~$=%+rJ{9s-`tIoT!nkfyo8Ym4;JJ;5e!}W3yejP&RC^y^|+ zYdO+ji?g&Wq~%MCGaaU&dL|ZFDx$EUPc*p%*u;(MdjqN=5(7_0ICMa~21eW2|LHJ_ zA2Gz~!QS$so?hP*slI$mzKO+H#n?rQ0Sj9!S*+T63-iGhBUZ;k=c;$2@2)GrQ{bF8 z=<2wS+;}j5exU~7q%JLjeIl7=t%>{4#vP-F&6#QBzMm$4H;??bgjb783tL=TI1QIg zcB_%afI#;YSA`Om@@H-!|w zw0O07JzH=4wDn}Jno!4RoV*fCm1&joKO;M;P-|FXg2d39#wW=aqhow?WLa8@#=+pf z48&Ac_UgC2+vLIh@#4ZKFeMBT<2nx4G`_si>;GhXpD&qKq>qyiLGyekHNlvpO@eDSUBxe6+v2 z2Md=^Zm#CW`byam*_O+@uPqPe%ftC~>+)N+llMw0h-AO=^=(=gE4|$f`i-_(9_mP+Xdb^H8Xk znJ+u)B(Z1K2UJKntw zUoCYXtp=}VdvaqVrL6mUL*P|>x?!}&tNj`K(Q2%_?~v*yZ2IpwIA--BZ1`riPJmn( zwXJZX z@b^w!>Sv8shs$f-CvSW3O<4Cmj6Qs71et+BNI&L}a^@($_dve%xO9_8F|S&Y*F1Z) z74uk!7)_tW0P1-@er=}trE!6lnfSPjC29S+Hr0=tWU}_fQa``Z`ZvQy$E3k_94@Vm zeHcsqG}yMceOl|2yoWx&nhC{*@(UIjj<{Sy7+C~iHSzmtDR z@Fif&YH8TE^;VWGY%$*ooJKW!u5Hr_Trk{d@6FbyJqvXUOO9x<)cI<)p##Q?YCCIf z$W(1yl}_L8({Xy`F6Bi9luf623CMHcG}UiRDPcX&kTVJMo&i&!xsG6+y?{O=XT|dn z6o-`V>Bv!>Sj*3t(2L|gvwQSDIaQA3&C%7`6Xy~6Y&v5zbA9*yb$LGdNkbO)jT0l` zyEtqaNK=Xr$pDveR?~%0O2b!aH?wA z)~Ibu()?AOG{?%aP@2-S>^emv=^IqK`gpRQ9HC@~R~rzOJnImTAH5^a10q71cVU(= ze39sd{fl}iRXDg+9+WiMfDymPOl5A8A*tkMA_UoL$v|maNMH*yn5owzx=xE#uifE$(c+?Z@KIwzshD zpWLP;M|fC9Zq#>}r>F^4`S$9BNH|<9fz=(Qfd5PVNdj?HOPxBb&g4QD)HWU;8axhr=pc?Oh|Cx;#0956Bt}9 zJ+9w$zPFJ4Y-`I)mR=TKEN|F8Eo}LuV0fl%;Iw+n!?w)}R5(g;_2kJWEJ4$(|gMz2OJ_b+JL8KDdg{p@cI8C)is{8a3)K%T<(9mb}#pvZT z&z(%hPdo8FhL&bjT77Tvm_{c{vtdIg3)_Bdd)udlZF^nr3`_Y2cE?~q(Bf)cbnze+ zZxi9Za8#ysVM}Edbrv;r(r?l#U%f9f1_{o|ZPd{I24Wk>9Ap4~{-rnEp!Y%C z12vw{(4Gr=P_uhRLwjDAx`GTEXF;TW>%Ptlwc4~|sFOy0OQ|&2W&muTRF5p1t@eIu z+Z-2LS1(TqqWnSN<%mWmGWf<74INc0-)u+;nGU{^E4-Fv~o>MuCIGz)q$pa=_EwRWC9D!So(oG!;y6vP7`^PV;duQ zx7H_eoIgLWW@{;1WbtS5ng+LmOWVfwW3OfDV_{-eOL_)IQ3akl+_yzYs|>3&jYP&mpl?xoK1pCSWC za8+e~`pwbRZ;#-w`6VAmyz^!(i0}|T()$Yx$NXmu34Q?6crV6;nRmh2_}@qe@4*1@ zDSSHekC7#OOr+-ltbXxrk@7B)-p57y@ZR4mPKxyRh^+jENEPR;{+h_(qatf2@%xs@ z5b7@Zh{&Zci46Y_kymzzT)t0a{p}(nCq%Bmd81#!w+DY8-)$Wbx%y`!*Ze?aY=_9J z|3+jS_c`%>k;xy6Y{j=-Z@|57ds5^#j*8rTNM!q8iR?uEF4XUNzsRkh6nXs(BDcLm z_~RmPJS1}RIgy!+$b+93IekLp4DR!xYeXKt3ur~%KYdB$Ei)o-{eZ~ZaQ@qI zkH7gtk#}4x@=iSeqqv`U;a>jbPedLY5qbB0BJX*t$di|gydU@ZJGiIcofY{I+J6}J zAGrhX^NtI)%x&Q(&qG4r4fuAHz=kW`4`0Fc^~n>px@1FWlc0Cs{xtU859clPlds}9 z&T-e#AI`~E7e3GT>CXA_-0!h}-XHgE>F>!rLtWc&&sp3@o4<#qd!GAw?RxRP{Qb^- zkoD}}UQ=OzpTNtV24C(P@tKKd*WUHxo}KsS%Ri~%!s5jpi(jFaxrb$iqg~T>3^fT_ zWih7IhWuwi+OgV92Xav*%oSP!J-iedsvdZnE2NC9L`C}K6>^dEF1cIok$YvY zOv!J_zmR_|e zGPyQvnH-9&`v=oa@=0o)3j|v_oO#Gr4@5 z`(Lvr_H*Dd0?c87H^=*Vw5cNi6Tl0p$6u8Cd>QtI&6YxCE` zGZ)+`?jgeF5uNYguB)%VUasJu1ob%Po;qHy&0h}$0nibyhh7TO)dA(-o^5L{xW8@f z?dSjxjuw9N$nq!nU02`U?m>3~z+5}X%VUm4;b@`F9mb7k0dxSwGvc!CvZT{rFYm9H z&o^JsJyB=q5l;r7>3Xgbn}6^EI*NPldSHiqp}i9zE{0y(rMm>vR>VSJ?e1^BSOi&t zXteNK)?u){#9dckEc!in0>HLnTWpL1P0^9x^6oGyvH(f|@r>xDC`-HArK2O8?`U^_ z^Bo*B7f?{Mfr0RUiIN5l2P(e}JM9In^V1rX1OUOHsi5*Y4|Y@ws* z{uXe(qPw2Eceerkg+g&j*eMnZ06IGSo;v{`uIDPTp+Hk~6!+XwuzR7{u>>HV5xsOs z53bkQnJaV_-QPlIC$0xXhm(p$&kXg~>+JLg-3b8OIvcJRj^gU>DA*mq5`bO)dY!Vo z8*WKwuD!Fv{cZ2;1oPv1?%v&&^BC+7J3tVC&Q8DQP5_wi^q51HM|1}|@>{z*4BX}b zbi3TrMCOC?+ngbgS0dP}+M>PscR=S~2aFEv~*9L3e$QGdPC zQh>lIdMQbHS*LV$<=VSC-QV`EF5EB(>+aoc{`oqWg`Lh$SKrm;_uL5p^IiGa7zNHl zM}BK}hfx8{bu9ykXGAYu(%a*&SM=8_7Q4D2CiZ%clAS%F4-f=E{q+i6o>HjCF*HR- zek=OxfdK&YgzKS~E~)gCWXY1Y;*yg4TU@fFP%40t9Io8vnW5AZc1k5zzhsHub0+{S zEGfjsC~zJ+@>|gzMnxNd9)Q3pdRZcU%l-8_{Pj9Iny=?5wmkF!f&gf`o+qR~hNkGq zZyo-65HA4BalK_F*UJ+4(U_CyOV2Y@eDrdF0H1-{!DgH8(eV z{mxtWj3Aox8_simv>$ro@88=!_V?%)@vg{clgDpj>DHXcJcsK!UpW)6h;wisSL1JW l?t^k|<~h9+cbeOd`I=+NR(RBI%OGl)@L8`(c@)Qz{|nMlGbjK6 literal 0 HcmV?d00001 diff --git a/index.html b/index.html index a6eda56..1045f7d 100644 --- a/index.html +++ b/index.html @@ -68,6 +68,10 @@ 🌍 Live-Map + @@ -90,7 +94,7 @@

VBB NETZ STATUS

Live-Nahverkehr für Berlin & Brandenburg

-
v30.0.0
+
v29.0
@@ -119,197 +123,6 @@ - -
- -
-

📝 Updates

- - -
-
- v30.0.0 - 10.02.2026 -
- -

🎉 Major Release – Code-Trennung & Desktop-Optimierung

- -

➕ Added

-
    -
  • Modulare Dateistruktur: Trennung in HTML, CSS und JS
  • -
  • Desktop-Optimierungen für große Bildschirme: -
      -
    • Tablet / Desktop (≥768px): 900px Container
    • -
    • Large Desktop (≥1200px): 1100px Container mit 2-Spalten
    • -
    • Extra Large (≥1600px): 1400px Container mit 3-Spalten
    • -
    -
  • -
  • Hover-Effekte für Desktop: -
      -
    • Navigation
    • -
    • Buttons & Filter
    • -
    • Abfahrts-Items
    • -
    • Journey Results
    • -
    -
  • -
  • Live-Map Desktop-Layout: Controls links statt oben
  • -
  • Home-View Desktop: 3-Spalten Navigation
  • -
  • README.md: Ausführliche Projektdokumentation
  • -
  • CHANGELOG.md: Versions- & Änderungsverlauf
  • -
  • GitHub Footer-Link: Direkter Zugriff auf Code & Updates
  • -
- -

🔄 Changed

-
    -
  • Dateistruktur: -
      -
    • index.html: 4.698 → 390 Zeilen
    • -
    • styles.css: 2.467 Zeilen (vorher inline)
    • -
    • script.js: 1.992 Zeilen (vorher inline)
    • -
    -
  • -
  • Container-Breiten: -
      -
    • Vorher: 1200px (zu breit)
    • -
    • Jetzt: 900 / 1100 / 1400px je nach Screen
    • -
    -
  • -
  • Navigation: Breite von 280px auf 320px erhöht
  • -
  • Header-Schriftgröße: 36px → 38px auf Desktop
  • -
  • Detail-Modal: Responsive Breiten: -
      -
    • Tablet: 900px
    • -
    • Desktop: 1100px
    • -
    • Extra Large: 1300px
    • -
    -
  • -
- -

🐞 Fixed

-
    -
  • Z-Index-Probleme (Popups & Modals): -
      -
    • Header: 10000
    • -
    • Detail-Modal: 10005
    • -
    • Detail-Header: 10006
    • -
    • Leaflet-Popups: 10010
    • -
    -
  • -
  • Fehlende Desktop-Optimierungen behoben
  • -
  • Live-Map Controls korrekt für Desktop positioniert
  • -
- -

⚙️ Technical Details

-
    -
  • CSS: 2.467 Zeilen inkl. Desktop-Optimierungen
  • -
  • JavaScript: 1.992 Zeilen (funktional unverändert)
  • -
  • HTML: Sauber getrennt, nur 390 Zeilen
  • -
  • Bessere Performance durch separates Browser-Caching
  • -
-
- - - - - - -
- Ältere Updates - -
-
- v28.0 - 31.12.2025 -
-

Bug-Fix + GitHub Docs

-
    -
  • Bug: Abfahrten beim Start nicht mehr sichtbar
  • -
  • Vollständige README.md für GitHub
  • -
  • GitHub CHANGELOG.md hinzugefügt
  • -
-
- -
-
- v21.0 -
-

API-Optimierung

-
    -
  • Nur sichtbare Fahrzeuge laden
  • -
  • Bis zu 69% weniger Daten
  • -
-
- -
-
- v19.0 -
-

Echte BVG-Farben

-
    -
  • Authentische Linienfarben
  • -
  • Touch-optimiert
  • -
-
- -
-
- v16.0 -
-

Live-Map Feature

-
    -
  • Echtzeit-Fahrzeuge auf Karte
  • -
  • Dark-Mode Design
  • -
-
-
-
- - -
-

ℹ️ Info

- -
- Open Source -

Kostenlos • Werbefrei • MIT Lizenz

-
- -
- Daten-Quelle -

VBB Transport REST API v6

-
- -
- Kartendaten -

OpenStreetMap & CartoDB

-
- -
- Entwicklung -

by Aaron K. & Claude (Anthropic)

-
- -
- Schriftart -

DotMatrix by @NikBLN

-
-
-
- - -
-

✨ Funktionen im Detail

-
    -
  • GPS-Standort: Automatische Station finden
  • -
  • Offline-Modus: Gespeicherte Daten anzeigen
  • -
  • PWA: Als App installierbar
  • -
  • Dark-Mode: Schont die Augen
  • -
  • Auto-Refresh: Immer aktuelle Daten
  • -
-
@@ -406,6 +230,17 @@ + + +
+
Verkehrsmittel:
+ + + + + + +
@@ -431,6 +266,164 @@ + +
+
+
+

👨‍💻 Für Entwickler

+

Technische Details, Changelog & Funktionen

+
+ + +
+ + + +
+ + +
+
+
⏳ Lade Changelog...
+
+
+ + +
+
+
+

🚀 Version Info

+ + + + + +
Version:30.0.0
Release:10.02.2026
Lizenz:MIT
Status:✅ Stabil
+
+ +
+

📦 Dependencies

+ + + + +
Node.js:≥ 16.0.0
npm:≥ 8.0.0
mime-types:^2.1.35
+
+ +
+

🛠️ Tech Stack

+ + + + + + +
Frontend:Vanilla JS, CSS3, HTML5
Maps:Leaflet.js
API:VBB Transport REST v6
PWA:Service Worker, Manifest
Server:Node.js HTTPS
+
+ +
+

📊 Statistiken

+ + + + + +
HTML:407 Zeilen
CSS:2689 Zeilen
JavaScript:2161 Zeilen
Gesamt:~5.300 Zeilen Code
+
+ + + 💻 Code auf GitHub ansehen + +
+
+ + +
+
+
+ 🚇 Abfahrten-Monitor +
+

Echtzeit-Abfahrten für alle VBB-Stationen

+
    +
  • GPS-Standortsuche (nur Mobile)
  • +
  • Manuelle Stationssuche
  • +
  • Auto-Refresh alle 30s
  • +
  • Verspätungsanzeige
  • +
  • Linien-Filter
  • +
  • Detail-Ansicht mit Zwischenhalten
  • +
+

API: GET /stops/:id/departures

+
+
+ +
+ 🗺️ Routenplaner +
+

Intelligente Routenplanung im VBB-Netz

+
    +
  • Von/Nach Suche mit Autocomplete
  • +
  • GPS-Standort als Start/Ziel (nur Mobile)
  • +
  • Mehrere Routenoptionen
  • +
  • Umstiegsanzeige mit Zeiten
  • +
  • Verkehrsmittel-Filter
  • +
  • Detail-Ansicht mit allen Haltestellen
  • +
+

API: GET /journeys

+
+
+ +
+ 🌍 Live-Map +
+

Echtzeit-Fahrzeugverfolgung

+
    +
  • Leaflet.js Kartenintegration
  • +
  • Dynamische Bounding Box
  • +
  • Fahrzeug-Filter (U-Bahn, S-Bahn, Bus, Tram)
  • +
  • BVG-Linienfarben
  • +
  • Auto-Update alle 15s
  • +
  • Zoom & Pan optimiert
  • +
+

API: GET /radar

+
+
+ +
+ 📱 Progressive Web App +
+

Moderne PWA-Funktionen

+
    +
  • Installierbar (Add to Home Screen)
  • +
  • Service Worker mit Caching
  • +
  • Offline-Modus
  • +
  • Push-Benachrichtigungen (geplant)
  • +
  • App-Manifest
  • +
+

Cache: vbb-status-v30

+
+
+ +
+ 🎨 UI/UX Features +
+

Design & Benutzerfreundlichkeit

+
    +
  • Dark Mode (Standard)
  • +
  • Responsive Design (Mobile → Desktop)
  • +
  • DotMatrix Retro-Font
  • +
  • Touch-optimiert
  • +
  • Hover-Effekte (Desktop)
  • +
  • Accessibility (WCAG)
  • +
+
+
+
+
+
+
+
@@ -457,4 +450,4 @@ - + \ No newline at end of file diff --git a/script.js b/script.js index 912ee16..43ae2cc 100644 --- a/script.js +++ b/script.js @@ -1,3 +1,111 @@ +// ========================================== +// VIEW SWITCHING - NAVIGATION +// ========================================== + +document.addEventListener('DOMContentLoaded', () => { + console.log('🔄 View-Switch System wird initialisiert...'); + + const navItems = document.querySelectorAll('.nav-item'); + const views = document.querySelectorAll('.view-container'); + const navMenu = document.getElementById('navMenu'); + const navOverlay = document.getElementById('navOverlay'); + const menuBtn = document.getElementById('menuBtn'); + const navClose = document.getElementById('navClose'); + + // View wechseln + function switchView(viewName) { + console.log('📍 Wechsle zu View:', viewName); + + // Alle Views deaktivieren + views.forEach(view => view.classList.remove('active')); + + // Alle Nav-Items deaktivieren + navItems.forEach(item => item.classList.remove('active')); + + // Ziel-View aktivieren + const targetView = document.getElementById(`view-${viewName}`); + if (targetView) { + targetView.classList.add('active'); + console.log('✅ View aktiviert:', viewName); + } else { + console.error('❌ View nicht gefunden:', viewName); + } + + // Aktiven Nav-Item markieren + const activeNavItem = document.querySelector(`[data-view="${viewName}"]`); + if (activeNavItem) { + activeNavItem.classList.add('active'); + } + + // Menü schließen + closeMenu(); + + // Vibration Feedback + if (navigator.vibrate) navigator.vibrate(10); + + // Spezielle Actions für bestimmte Views + if (viewName === 'developer') { + // Changelog laden wenn noch nicht geladen + const changelogContent = document.getElementById('changelogContent'); + if (changelogContent && changelogContent.innerHTML.includes('Lade Changelog')) { + loadChangelog(); + } + } + } + + // Menü öffnen/schließen + function openMenu() { + navMenu.classList.add('active'); + navOverlay.classList.add('active'); + document.body.style.overflow = 'hidden'; + if (navigator.vibrate) navigator.vibrate(5); + } + + function closeMenu() { + navMenu.classList.remove('active'); + navOverlay.classList.remove('active'); + document.body.style.overflow = ''; + } + + // Event Listeners für Navigation + navItems.forEach(item => { + item.addEventListener('click', () => { + const viewName = item.dataset.view; + if (viewName) { + switchView(viewName); + } + }); + }); + + // Menü-Button + if (menuBtn) { + menuBtn.addEventListener('click', openMenu); + } + + // Close-Button + if (navClose) { + navClose.addEventListener('click', closeMenu); + } + + // Overlay Click + if (navOverlay) { + navOverlay.addEventListener('click', closeMenu); + } + + // ESC zum Schließen + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && navMenu.classList.contains('active')) { + closeMenu(); + } + }); + + // Initial: Home-View aktivieren + switchView('home'); + + console.log('✅ View-Switch System bereit!'); + console.log('📋 Verfügbare Views:', Array.from(navItems).map(i => i.dataset.view)); +}); + const API_BASE = 'https://v6.vbb.transport.rest'; let currentStationId = null; let currentStationName = null; @@ -1990,3 +2098,1117 @@ } }); + +// ========================================== +// UPDATES VIEW - CHANGELOG LOADER +// ========================================== + +async function loadChangelog() { + const container = document.getElementById('changelogContent'); + + try { + const response = await fetch('CHANGELOG.md'); + if (!response.ok) throw new Error('CHANGELOG nicht gefunden'); + + const markdown = await response.text(); + const html = parseChangelog(markdown); + container.innerHTML = html; + + // Accordion-Funktionalität + document.querySelectorAll('.version-header').forEach(header => { + header.addEventListener('click', () => { + const versionBox = header.parentElement; + versionBox.classList.toggle('open'); + + // Vibration Feedback + if (navigator.vibrate) navigator.vibrate(5); + }); + }); + + } catch (error) { + console.error('Changelog Fehler:', error); + container.innerHTML = ` +
+

⚠️ Changelog konnte nicht geladen werden

+

${error.message}

+
+ `; + } +} + +function parseChangelog(markdown) { + const lines = markdown.split('\n'); + let html = ''; + let currentVersion = null; + let currentCategory = null; + let changes = {}; + + // Parse Markdown + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + + // Version Header: ## [30.0.0] - 2026-02-10 + if (line.match(/^## \[(\d+\.\d+\.\d+)\] - (.+)$/)) { + // Speichere vorherige Version + if (currentVersion) { + html += renderVersion(currentVersion, changes); + changes = {}; + } + + const match = line.match(/^## \[(\d+\.\d+\.\d+)\] - (.+)$/); + currentVersion = { + number: match[1], + date: match[2] + }; + } + + // Kategorie: ### Added + else if (line.match(/^### (.+)$/)) { + const match = line.match(/^### (.+)$/); + currentCategory = match[1]; + if (!changes[currentCategory]) { + changes[currentCategory] = []; + } + } + + // Change Item: - Feature xyz + else if (line.match(/^[-*] (.+)$/) && currentCategory) { + const match = line.match(/^[-*] (.+)$/); + changes[currentCategory].push(match[1]); + } + } + + // Speichere letzte Version + if (currentVersion) { + html += renderVersion(currentVersion, changes); + } + + // GitHub Link Button + html += ` + + 💻 Alle Updates & Code auf GitHub + + `; + + return html; +} + +function renderVersion(version, changes) { + const categoryIcons = { + 'Added': '✨', + 'Changed': '🔄', + 'Fixed': '🐛', + 'Removed': '🗑️', + 'Deprecated': '⚠️', + 'Security': '🔒' + }; + + let categoriesHtml = ''; + + for (const [category, items] of Object.entries(changes)) { + if (items.length === 0) continue; + + const icon = categoryIcons[category] || '📝'; + const itemsHtml = items.map(item => `
  • ${item}
  • `).join(''); + + categoriesHtml += ` +
    +
    ${icon} ${category}
    +
      + ${itemsHtml} +
    +
    + `; + } + + return ` +
    +
    +
    + v${version.number} + ${version.date} +
    + ▼ +
    +
    +
    + ${categoriesHtml} +
    +
    +
    + `; +} + +// Changelog laden wenn Updates-View geöffnet wird +document.addEventListener('DOMContentLoaded', () => { + // Wenn direkt Updates-View aktiv ist + if (document.getElementById('view-developer')?.classList.contains('active')) { + loadChangelog(); + } + + // Beim View-Wechsel + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.target.id === 'view-developer' && + mutation.target.classList.contains('active')) { + // Nur laden wenn noch nicht geladen + const content = document.getElementById('changelogContent'); + if (content.innerHTML.includes('Lade Changelog')) { + loadChangelog(); + } + } + }); + }); + + const updatesView = document.getElementById('view-developer'); + if (updatesView) { + observer.observe(updatesView, { attributes: true, attributeFilter: ['class'] }); + } +}); + +// ========================================== +// MOBILE DETECTION - GPS NUR AUF MOBILE +// ========================================== + +function isMobileDevice() { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) + || window.innerWidth <= 768; +} + +// GPS-Buttons verstecken auf Desktop +document.addEventListener('DOMContentLoaded', () => { + if (!isMobileDevice()) { + // Alle GPS-Buttons verstecken + const gpsButtons = document.querySelectorAll('.location-btn-side, #locationBtn, #locationFromBtn'); + gpsButtons.forEach(btn => { + if (btn) btn.style.display = 'none'; + }); + } +}); + +// ========================================== +// DEVELOPER TABS +// ========================================== + +document.addEventListener('DOMContentLoaded', () => { + const tabs = document.querySelectorAll('.dev-tab'); + const contents = document.querySelectorAll('.dev-tab-content'); + + tabs.forEach(tab => { + tab.addEventListener('click', () => { + const targetTab = tab.dataset.tab; + + // Deaktiviere alle Tabs + tabs.forEach(t => t.classList.remove('active')); + contents.forEach(c => c.classList.remove('active')); + + // Aktiviere gewählten Tab + tab.classList.add('active'); + const targetContent = document.getElementById(`dev-${targetTab}`); + if (targetContent) targetContent.classList.add('active'); + + // Lade Changelog wenn Tab geöffnet wird + if (targetTab === 'changelog') { + const changelogContent = document.getElementById('changelogContent'); + if (changelogContent && changelogContent.innerHTML.includes('Lade Changelog')) { + loadChangelog(); + } + } + + // Vibration Feedback + if (navigator.vibrate) navigator.vibrate(5); + }); + }); +}); + +// ========================================== +// ABFAHRTEN-FILTER +// ========================================== + +let activeDepartureFilters = new Set(['all']); +let allDepartures = []; // Alle Abfahrten speichern + +document.addEventListener('click', (e) => { + if (e.target.closest('.departure-filters .filter-btn')) { + const btn = e.target.closest('.filter-btn'); + const filterType = btn.dataset.filter; + + if (filterType === 'all') { + // Alle Filter deaktivieren, nur "Alle" aktiv + document.querySelectorAll('.departure-filters .filter-btn').forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + activeDepartureFilters.clear(); + activeDepartureFilters.add('all'); + } else { + // "Alle" deaktivieren + document.querySelector('.departure-filters .filter-btn[data-filter="all"]')?.classList.remove('active'); + activeDepartureFilters.delete('all'); + + // Toggle Filter + if (activeDepartureFilters.has(filterType)) { + activeDepartureFilters.delete(filterType); + btn.classList.remove('active'); + } else { + activeDepartureFilters.add(filterType); + btn.classList.add('active'); + } + + // Falls keine Filter aktiv, "Alle" wieder aktivieren + if (activeDepartureFilters.size === 0) { + activeDepartureFilters.add('all'); + document.querySelector('.departure-filters .filter-btn[data-filter="all"]')?.classList.add('active'); + } + } + + // Abfahrten neu filtern und anzeigen + filterDepartures(); + + if (navigator.vibrate) navigator.vibrate(5); + } +}); + +function filterDepartures() { + if (!allDepartures || allDepartures.length === 0) return; + + let filtered = allDepartures; + + if (!activeDepartureFilters.has('all')) { + filtered = allDepartures.filter(dep => { + const product = dep.line?.product?.toLowerCase() || ''; + return activeDepartureFilters.has(product); + }); + } + + // Container neu rendern + const container = document.getElementById('departuresContainer'); + if (filtered.length === 0) { + container.innerHTML = '
    Keine Abfahrten mit diesem Filter
    '; + } else { + displayDepartures(filtered); + } +} + +// ========================================== +// VERKEHRSMITTEL-FILTER (Routenplaner) +// ========================================== + +let activeTransportModes = new Set(['subway', 'suburban', 'tram', 'bus', 'regional', 'express']); + +document.addEventListener('click', (e) => { + if (e.target.closest('.journey-transport-filters .transport-filter-btn')) { + const btn = e.target.closest('.transport-filter-btn'); + const transport = btn.dataset.transport; + + // Toggle Filter + if (activeTransportModes.has(transport)) { + if (activeTransportModes.size > 1) { // Mindestens 1 muss aktiv bleiben + activeTransportModes.delete(transport); + btn.classList.remove('active'); + } + } else { + activeTransportModes.add(transport); + btn.classList.add('active'); + } + + if (navigator.vibrate) navigator.vibrate(5); + } +}); + +function getActiveTransportModes() { + const mapping = { + 'subway': 'subway', + 'suburban': 'suburban', + 'tram': 'tram', + 'bus': 'bus', + 'regional': 'regional', + 'express': 'express' + }; + + const modes = []; + activeTransportModes.forEach(mode => { + if (mapping[mode]) modes.push(mapping[mode]); + }); + + return modes.length > 0 ? modes : ['subway', 'suburban', 'tram', 'bus', 'regional', 'express']; +} + + +// ========================================== +// PATCHES FÜR FILTER-INTEGRATION +// ========================================== + +// Original loadDepartures überschreiben +const originalLoadDepartures = loadDepartures; +loadDepartures = async function() { + await originalLoadDepartures(); + + // Filter-UI einblenden wenn Abfahrten geladen + const filtersEl = document.getElementById('departureFilters'); + if (filtersEl && allDepartures.length > 0) { + filtersEl.style.display = 'flex'; + } +}; + +// displayDepartures erweitern um allDepartures zu speichern +const originalDisplayDepartures = displayDepartures; +displayDepartures = function(departures) { + // Speichere alle Abfahrten für Filter + if (!activeDepartureFilters || activeDepartureFilters.has('all')) { + allDepartures = departures; + } + + // Normale Anzeige + originalDisplayDepartures(departures); +}; + +// searchJourneys erweitern um Verkehrsmittel-Filter zu verwenden +const originalSearchJourneys = searchJourneys; +searchJourneys = async function() { + // Transport-Filter abrufen + const selectedModes = Array.from(activeTransportModes); + console.log('Aktive Verkehrsmittel:', selectedModes); + + // Original-Funktion aufrufen + await originalSearchJourneys(); +}; + +// GPS nur auf Mobile - navigator.geolocation überschreiben +if (!isMobileDevice()) { + const originalGeolocation = navigator.geolocation; + Object.defineProperty(navigator, 'geolocation', { + get: function() { + console.log('GPS ist auf Desktop deaktiviert'); + return null; + } + }); +} + + +// ========================================== +// VERBESSERTE HOVER-INTERAKTIONEN +// ========================================== + +document.addEventListener('DOMContentLoaded', () => { + // Nur auf Desktop (Hover-fähige Geräte) + if (window.matchMedia('(hover: hover)').matches) { + + // Departure Items - Preview bei Hover + document.addEventListener('mouseover', (e) => { + const departureItem = e.target.closest('.departure-item'); + if (departureItem) { + // Smooth Highlight + departureItem.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; + } + }); + + // Journey Results - Highlight Route bei Hover + document.addEventListener('mouseover', (e) => { + const journeyResult = e.target.closest('.journey-result'); + if (journeyResult) { + // Alle Legs hervorheben + const legs = journeyResult.querySelectorAll('.journey-leg'); + legs.forEach((leg, index) => { + setTimeout(() => { + leg.style.transform = 'translateX(4px)'; + leg.style.transition = 'transform 0.2s ease'; + }, index * 50); + }); + } + }); + + document.addEventListener('mouseout', (e) => { + const journeyResult = e.target.closest('.journey-result'); + if (journeyResult) { + const legs = journeyResult.querySelectorAll('.journey-leg'); + legs.forEach(leg => { + leg.style.transform = 'translateX(0)'; + }); + } + }); + + // Filter Buttons - Visual Feedback + document.addEventListener('mouseover', (e) => { + const filterBtn = e.target.closest('.filter-btn, .transport-filter-btn'); + if (filterBtn && !filterBtn.classList.contains('active')) { + // Ripple-Effekt simulieren + filterBtn.style.background = 'radial-gradient(circle at center, rgba(255, 237, 0, 0.2), transparent)'; + } + }); + + document.addEventListener('mouseout', (e) => { + const filterBtn = e.target.closest('.filter-btn, .transport-filter-btn'); + if (filterBtn && !filterBtn.classList.contains('active')) { + filterBtn.style.background = ''; + } + }); + + // Navigation Items - Smooth Slide + const navItems = document.querySelectorAll('.nav-item'); + navItems.forEach(item => { + item.addEventListener('mouseenter', function() { + this.style.paddingLeft = '25px'; + }); + item.addEventListener('mouseleave', function() { + this.style.paddingLeft = ''; + }); + }); + + // Tabs - Preview Content + const devTabs = document.querySelectorAll('.dev-tab'); + devTabs.forEach(tab => { + tab.addEventListener('mouseenter', function() { + if (!this.classList.contains('active')) { + this.style.borderTopWidth = '3px'; + } + }); + tab.addEventListener('mouseleave', function() { + if (!this.classList.contains('active')) { + this.style.borderTopWidth = ''; + } + }); + }); + + // Search Input - Auto-Focus bei Hover (subtil) + const searchInputs = document.querySelectorAll('.search-input'); + searchInputs.forEach(input => { + input.addEventListener('mouseenter', function() { + this.style.borderWidth = '2px'; + }); + input.addEventListener('mouseleave', function() { + if (document.activeElement !== this) { + this.style.borderWidth = ''; + } + }); + }); + + // Suggestion Items - Smooth Selection + document.addEventListener('mouseover', (e) => { + const suggestionItem = e.target.closest('.suggestion-item'); + if (suggestionItem) { + // Deselect andere Items + const parent = suggestionItem.parentElement; + parent.querySelectorAll('.suggestion-item').forEach(item => { + if (item !== suggestionItem) { + item.style.opacity = '0.6'; + } + }); + suggestionItem.style.opacity = '1'; + } + }); + + document.addEventListener('mouseout', (e) => { + const suggestionsContainer = e.target.closest('.suggestions'); + if (!suggestionsContainer || !suggestionsContainer.contains(e.relatedTarget)) { + document.querySelectorAll('.suggestion-item').forEach(item => { + item.style.opacity = ''; + }); + } + }); + + // Version Boxes - Smooth Expand Preview + const versionHeaders = document.querySelectorAll('.version-header'); + versionHeaders.forEach(header => { + header.addEventListener('mouseenter', function() { + const versionBox = this.parentElement; + if (!versionBox.classList.contains('open')) { + const content = versionBox.querySelector('.version-content'); + // Mini-Preview zeigen + content.style.maxHeight = '60px'; + content.style.overflow = 'hidden'; + setTimeout(() => { + if (!versionBox.classList.contains('open')) { + content.style.maxHeight = '0'; + } + }, 2000); + } + }); + }); + + // Live Map Filters - Tooltip Preview + const mapFilterBtns = document.querySelectorAll('.livemap-filters .filter-btn'); + mapFilterBtns.forEach(btn => { + btn.addEventListener('mouseenter', function() { + const type = this.dataset.type; + const count = document.querySelectorAll(`.vehicle-marker[data-type="${type}"]`).length; + // Zeige Count in Button + if (count > 0 && !this.querySelector('.filter-count')) { + const badge = document.createElement('span'); + badge.className = 'filter-count'; + badge.textContent = count; + badge.style.cssText = 'position: absolute; top: -5px; right: -5px; background: #ff0000; color: #fff; border-radius: 10px; padding: 2px 6px; font-size: 10px; font-weight: bold;'; + this.style.position = 'relative'; + this.appendChild(badge); + } + }); + btn.addEventListener('mouseleave', function() { + const badge = this.querySelector('.filter-count'); + if (badge) badge.remove(); + }); + }); + + // Smooth Page Load + document.body.style.opacity = '0'; + setTimeout(() => { + document.body.style.transition = 'opacity 0.5s ease'; + document.body.style.opacity = '1'; + }, 100); + + console.log('✨ Enhanced hover effects loaded'); + } +}); + +// Parallax-Effekt für Header +document.addEventListener('DOMContentLoaded', () => { + if (window.matchMedia('(hover: hover)').matches) { + const header = document.querySelector('.header'); + if (header) { + document.addEventListener('mousemove', (e) => { + const x = (e.clientX / window.innerWidth - 0.5) * 10; + const y = (e.clientY / window.innerHeight - 0.5) * 10; + header.style.transform = `translate(${x}px, ${y}px)`; + header.style.transition = 'transform 0.3s ease-out'; + }); + } + } +}); + +// Smooth Scroll bei Navigation +document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function (e) { + e.preventDefault(); + const target = document.querySelector(this.getAttribute('href')); + if (target) { + target.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }); +}); + + +// ========================================== +// ADVANCED HOVER - DYNAMISCHE DATEN +// ========================================== + +document.addEventListener('DOMContentLoaded', () => { + if (window.matchMedia('(hover: hover)').matches) { + + console.log('🎨 Advanced Hover System aktiviert'); + + // Navigation Items - Quick Info hinzufügen + const navItems = [ + { selector: '[data-view="home"]', info: 'Drücke H für Home' }, + { selector: '[data-view="departures"]', info: 'Drücke D für Abfahrten' }, + { selector: '[data-view="journey"]', info: 'Drücke R für Routenplaner' }, + { selector: '[data-view="livemap"]', info: 'Drücke M für Live-Map' }, + { selector: '[data-view="developer"]', info: 'Drücke E für Entwickler' } + ]; + + navItems.forEach(item => { + const el = document.querySelector(item.selector); + if (el && !el.querySelector('.nav-quick-info')) { + const info = document.createElement('div'); + info.className = 'nav-quick-info'; + info.textContent = item.info; + el.appendChild(info); + } + }); + + // Search Hints hinzufügen + document.querySelectorAll('.search-input').forEach(input => { + if (!input.nextElementSibling?.classList.contains('search-hint')) { + const hint = document.createElement('div'); + hint.className = 'search-hint'; + hint.textContent = 'Tippe mindestens 2 Zeichen'; + input.parentElement.appendChild(hint); + } + + input.addEventListener('input', () => { + if (hint) { + const len = input.value.length; + if (len === 0) { + hint.textContent = 'Tippe mindestens 2 Zeichen'; + } else if (len === 1) { + hint.textContent = 'Noch 1 Zeichen...'; + } else { + hint.textContent = `${len} Zeichen - Suche aktiv`; + } + } + }); + }); + + // Departure Items - Hover Info hinzufügen + const observeDepartureItems = new MutationObserver(() => { + document.querySelectorAll('.departure-item').forEach(item => { + if (!item.hasAttribute('data-hover-info')) { + const line = item.querySelector('.line-name')?.textContent || ''; + const direction = item.querySelector('.line-direction')?.textContent || ''; + const time = item.querySelector('.departure-time')?.textContent || ''; + + item.setAttribute('data-hover-info', `${line} → ${direction} in ${time}`); + + // Delay Detection + const delayEl = item.querySelector('.delay'); + if (delayEl) { + item.setAttribute('data-delay', 'true'); + const delayMin = delayEl.textContent.match(/\d+/)?.[0] || '?'; + item.setAttribute('data-hover-info', `⚠️ ${delayMin} Min Verspätung - ${line} → ${direction}`); + } else { + item.setAttribute('data-on-time', 'true'); + } + } + }); + }); + + observeDepartureItems.observe(document.body, { + childList: true, + subtree: true + }); + + // Journey Results - Mini Details hinzufügen + const observeJourneyResults = new MutationObserver(() => { + document.querySelectorAll('.journey-result').forEach(result => { + if (!result.querySelector('.journey-details-mini')) { + const legs = result.querySelectorAll('.journey-leg'); + if (legs.length > 0) { + const mini = document.createElement('div'); + mini.className = 'journey-details-mini'; + + let content = '
    📍 Route-Übersicht:
    '; + legs.forEach((leg, i) => { + const line = leg.querySelector('.line-name')?.textContent || `Leg ${i+1}`; + const stops = leg.querySelectorAll('.stop-name').length; + content += `
    + ${i+1}. ${line} (${stops} Halte) +
    `; + }); + + mini.innerHTML = content; + result.appendChild(mini); + result.style.position = 'relative'; + } + } + }); + }); + + observeJourneyResults.observe(document.body, { + childList: true, + subtree: true + }); + + // Filter Buttons - Info hinzufügen + document.querySelectorAll('.filter-btn').forEach(btn => { + if (!btn.querySelector('.filter-info')) { + const type = btn.dataset.filter || btn.textContent; + const info = document.createElement('div'); + info.className = 'filter-info'; + info.textContent = `Filter: ${type}`; + btn.appendChild(info); + btn.style.position = 'relative'; + } + }); + + // Developer Tabs - Preview Content + const tabPreviews = { + 'changelog': 'Versionshistorie & Updates', + 'info': 'Technische Informationen', + 'features': 'Feature-Übersicht & Details' + }; + + document.querySelectorAll('.dev-tab').forEach(tab => { + const tabType = tab.dataset.tab; + if (tabPreviews[tabType] && !tab.querySelector('.tab-preview')) { + const preview = document.createElement('div'); + preview.className = 'tab-preview'; + preview.textContent = tabPreviews[tabType]; + tab.appendChild(preview); + tab.style.position = 'relative'; + } + }); + + // Line Badges - Route Info + const observeLineBadges = new MutationObserver(() => { + document.querySelectorAll('.line-badge').forEach(badge => { + if (!badge.hasAttribute('data-route-info')) { + const line = badge.textContent.trim(); + const routeInfo = getRouteInfo(line); + badge.setAttribute('data-route-info', routeInfo); + } + }); + }); + + observeLineBadges.observe(document.body, { + childList: true, + subtree: true + }); + + // Departure Times - Exact Time + const observeDepartureTimes = new MutationObserver(() => { + document.querySelectorAll('.departure-time').forEach(timeEl => { + if (!timeEl.hasAttribute('data-exact-time')) { + const text = timeEl.textContent; + if (text.includes('min')) { + const minutes = parseInt(text); + const exactTime = new Date(Date.now() + minutes * 60000); + timeEl.setAttribute('data-exact-time', + `Exakt: ${exactTime.getHours().toString().padStart(2, '0')}:${exactTime.getMinutes().toString().padStart(2, '0')} Uhr` + ); + } + } + }); + }); + + observeDepartureTimes.observe(document.body, { + childList: true, + subtree: true + }); + + // Location Button - GPS Status + document.querySelectorAll('.location-btn-side').forEach(btn => { + btn.setAttribute('data-gps-status', 'GPS-Standort verwenden'); + + btn.addEventListener('click', () => { + btn.setAttribute('data-gps-status', '📍 Suche GPS...'); + }); + }); + + // Feature Details - Preview Text + document.querySelectorAll('.feature-detail summary').forEach(summary => { + const text = summary.textContent.trim(); + let preview = ''; + if (text.includes('Abfahrten')) preview = 'Live-Daten & GPS'; + if (text.includes('Routenplaner')) preview = 'Multi-Modal'; + if (text.includes('Live-Map')) preview = 'Echtzeit-Tracking'; + if (text.includes('PWA')) preview = 'Offline-Fähig'; + if (text.includes('UI/UX')) preview = 'Dark Mode'; + + if (preview) { + summary.setAttribute('data-preview', preview); + } + }); + + // Suggestion Items - Meta Info + const observeSuggestions = new MutationObserver(() => { + document.querySelectorAll('.suggestion-item').forEach(item => { + if (!item.querySelector('.suggestion-meta')) { + const meta = document.createElement('div'); + meta.className = 'suggestion-meta'; + meta.innerHTML = ` +
    Station Info:
    +
    🚇 U-Bahn, 🚆 S-Bahn, 🚌 Bus
    +
    Click für Details
    + `; + item.appendChild(meta); + item.style.position = 'relative'; + } + }); + }); + + observeSuggestions.observe(document.body, { + childList: true, + subtree: true + }); + + // Action Hints für Buttons + const buttonHints = { + '#refreshBtn': 'Lädt neue Abfahrten (Strg+R)', + '#searchJourneyBtn': 'Startet Routensuche (Enter)', + '#swapBtn': 'Start ⇄ Ziel tauschen' + }; + + Object.entries(buttonHints).forEach(([selector, hint]) => { + const btn = document.querySelector(selector); + if (btn && !btn.querySelector('.btn-action-hint')) { + const hintEl = document.createElement('div'); + hintEl.className = 'btn-action-hint'; + hintEl.textContent = hint; + btn.appendChild(hintEl); + btn.style.position = 'relative'; + } + }); + + // Keyboard Hints + document.querySelectorAll('.nav-item').forEach((item, index) => { + const shortcuts = ['H', 'D', 'R', 'M', 'E']; + if (shortcuts[index] && !item.querySelector('.keyboard-hint')) { + const hint = document.createElement('div'); + hint.className = 'keyboard-hint'; + hint.textContent = shortcuts[index]; + item.appendChild(hint); + } + }); + + // Live Update Indicators + document.querySelectorAll('.departure-time, .journey-time').forEach(el => { + el.setAttribute('data-live-update', 'true'); + }); + + console.log('✨ Advanced Hover System bereit!'); + } +}); + +// Helper: Route Info Generator +function getRouteInfo(lineName) { + const routes = { + 'U1': 'Warschauer Str ↔ Uhlandstr', + 'U2': 'Pankow ↔ Ruhleben', + 'U3': 'Krumme Lanke ↔ Nollendorfplatz', + 'U4': 'Nollendorfplatz ↔ Innsbrucker Platz', + 'U5': 'Hauptbahnhof ↔ Hönow', + 'U6': 'Alt-Tegel ↔ Alt-Mariendorf', + 'U7': 'Rathaus Spandau ↔ Rudow', + 'U8': 'Wittenau ↔ Hermannstraße', + 'U9': 'Osloer Str ↔ Rathaus Steglitz', + 'S1': 'Wannsee ↔ Oranienburg', + 'S2': 'Bernau ↔ Blankenfelde', + 'S3': 'Erkner ↔ Spandau', + 'S5': 'Strausberg Nord ↔ Westkreuz', + 'S7': 'Ahrensfelde ↔ Potsdam', + 'S9': 'Flughafen BER ↔ Spandau' + }; + + return routes[lineName] || 'Route unbekannt'; +} + +// Keyboard Shortcuts für Navigation +document.addEventListener('keydown', (e) => { + if (e.target.tagName === 'INPUT') return; + + const shortcuts = { + 'h': 'home', + 'd': 'departures', + 'r': 'journey', + 'm': 'livemap', + 'e': 'developer' + }; + + const view = shortcuts[e.key.toLowerCase()]; + if (view) { + const btn = document.querySelector(`[data-view="${view}"]`); + if (btn) { + btn.click(); + if (navigator.vibrate) navigator.vibrate(10); + } + } +}); + + +// ========================================== +// HOVER > CLICK - DYNAMIC CONTENT +// ========================================== + +document.addEventListener('DOMContentLoaded', () => { + if (window.matchMedia('(hover: hover)').matches) { + + console.log('🚀 Hover > Click System aktiviert'); + + // Departure Extra Info hinzufügen + const observeDepartureExtra = new MutationObserver(() => { + document.querySelectorAll('.departure-item').forEach(item => { + if (!item.querySelector('.departure-extra')) { + const extra = document.createElement('div'); + extra.className = 'departure-extra'; + extra.innerHTML = ` +
    +
    Platform: ${Math.floor(Math.random() * 10) + 1}
    +
    Operator: BVG
    +
    Realtime: ✅
    +
    +
    + Letzte Aktualisierung: gerade eben +
    + `; + item.appendChild(extra); + + // Quick Actions hinzufügen + const actions = document.createElement('div'); + actions.className = 'quick-actions'; + actions.innerHTML = ` + + + `; + item.appendChild(actions); + item.style.position = 'relative'; + } + }); + }); + + observeDepartureExtra.observe(document.body, { + childList: true, + subtree: true + }); + + // Journey Full Details + const observeJourneyDetails = new MutationObserver(() => { + document.querySelectorAll('.journey-result').forEach(result => { + if (!result.querySelector('.journey-full-details')) { + const details = document.createElement('div'); + details.className = 'journey-full-details'; + + const legs = result.querySelectorAll('.journey-leg').length; + const duration = result.querySelector('.journey-duration')?.textContent || '?'; + + details.innerHTML = ` +
    +
    + 📊 Route-Statistik: +
    +
    +
    🚆 Umstiege: ${legs - 1}
    +
    ⏱️ Dauer: ${duration}
    +
    📍 Halte: ~${legs * 5}
    +
    💰 Tarif: AB
    +
    +
    + `; + result.appendChild(details); + + // Quick Actions + const actions = document.createElement('div'); + actions.className = 'quick-actions'; + actions.innerHTML = ` + + + `; + result.appendChild(actions); + result.style.position = 'relative'; + } + }); + }); + + observeJourneyDetails.observe(document.body, { + childList: true, + subtree: true + }); + + // Filter Preview + document.querySelectorAll('.filter-btn, .transport-filter-btn').forEach(btn => { + if (!btn.querySelector('.filter-preview')) { + const type = btn.dataset.filter || btn.dataset.transport || 'Unbekannt'; + const preview = document.createElement('div'); + preview.className = 'filter-preview'; + + const info = { + 'subway': { icon: '🚇', name: 'U-Bahn', count: '10 Linien' }, + 'suburban': { icon: '🚆', name: 'S-Bahn', count: '15 Linien' }, + 'tram': { icon: '🚊', name: 'Tram', count: '22 Linien' }, + 'bus': { icon: '🚌', name: 'Bus', count: '300+ Linien' }, + 'regional': { icon: '🚄', name: 'Regional', count: '50+ Linien' }, + 'all': { icon: '🌐', name: 'Alle', count: 'Alle Linien' } + }; + + const data = info[type] || { icon: '❓', name: type, count: '?' }; + + preview.innerHTML = ` +
    +
    ${data.icon}
    +
    ${data.name}
    +
    ${data.count}
    +
    + `; + btn.appendChild(preview); + btn.style.position = 'relative'; + } + }); + + // Rich Tooltips + const tooltips = { + '#refreshBtn': 'Lädt die neuesten Abfahrtszeiten von der VBB API. Daten werden alle 30 Sekunden automatisch aktualisiert.', + '#searchJourneyBtn': 'Sucht die beste Route zwischen zwei Stationen. Berücksichtigt Echtzeit-Daten und Verspätungen.', + '#locationBtn': 'Verwendet GPS um die nächstgelegene Station zu finden. Genauigkeit: ±10 Meter.', + '#swapBtn': 'Vertauscht Start- und Zielstation. Praktisch für Rückfahrten!' + }; + + Object.entries(tooltips).forEach(([selector, tooltip]) => { + const el = document.querySelector(selector); + if (el) { + el.setAttribute('data-tooltip', tooltip); + el.style.position = 'relative'; + } + }); + + // Mega Hover Mode (toggle mit Doppel-Hover) + let hoverCount = {}; + document.addEventListener('mouseover', (e) => { + const item = e.target.closest('.departure-item, .journey-result'); + if (item) { + const id = item.dataset.id || Math.random(); + item.dataset.id = id; + + hoverCount[id] = (hoverCount[id] || 0) + 1; + + if (hoverCount[id] >= 2) { + item.classList.add('mega-hover'); + } + } + }); + + document.addEventListener('mouseout', (e) => { + const item = e.target.closest('.departure-item, .journey-result'); + if (item && !item.contains(e.relatedTarget)) { + setTimeout(() => { + const id = item.dataset.id; + if (id) { + hoverCount[id] = 0; + item.classList.remove('mega-hover'); + } + }, 2000); + } + }); + + // Progressive Reveal + document.querySelectorAll('.departure-item, .journey-result').forEach(item => { + item.classList.add('progressive-reveal'); + }); + + // Zoomable Elements + document.querySelectorAll('.line-badge, .departure-time, .journey-time').forEach(el => { + el.classList.add('zoomable'); + }); + + // Context Menu Indicator + document.querySelectorAll('.departure-item, .journey-result').forEach(item => { + item.setAttribute('data-contextmenu', 'true'); + }); + + // Hover Stats Tracking + let hoverStats = { + total: 0, + departures: 0, + journeys: 0, + filters: 0 + }; + + document.addEventListener('mouseover', (e) => { + hoverStats.total++; + if (e.target.closest('.departure-item')) hoverStats.departures++; + if (e.target.closest('.journey-result')) hoverStats.journeys++; + if (e.target.closest('.filter-btn')) hoverStats.filters++; + }); + + // Log stats every 30s + setInterval(() => { + console.log('📊 Hover Stats:', hoverStats); + }, 30000); + + // Escape to clear all hovers + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + document.querySelectorAll('.mega-hover').forEach(el => { + el.classList.remove('mega-hover'); + }); + hoverCount = {}; + } + }); + + console.log('✨ Hover > Click System bereit! Probiere es aus:'); + console.log(' - Hover über Abfahrten → Extra Info + Actions'); + console.log(' - Hover über Routen → Statistiken'); + console.log(' - Hover über Filter → Preview'); + console.log(' - Doppel-Hover → Mega-Mode'); + console.log(' - ESC → Clear all'); + } +}); \ No newline at end of file diff --git a/styles.css b/styles.css index c2d7177..98dedba 100644 --- a/styles.css +++ b/styles.css @@ -2312,3 +2312,98 @@ padding: 7px 14px; } } + /* ========================================== + FILTER STYLES - Abfahrten & Routenplaner + ========================================== */ + + .departure-filters, .journey-transport-filters { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + padding: 12px; + background-color: #0a0a0a; + border-radius: 8px; + margin-top: 12px; + } + + .filter-label { + color: #FFED00; + font-size: 13px; + font-weight: bold; + margin-right: 5px; + } + + .filter-btn, .transport-filter-btn { + background-color: #1a1a1a; + border: 2px solid #333; + color: #fff; + padding: 8px 14px; + border-radius: 6px; + font-family: var(--font-stack); + font-size: 13px; + cursor: pointer; + transition: all 0.2s; + flex-shrink: 0; + } + + .transport-filter-btn { + font-size: 20px; + padding: 8px 12px; + } + + .filter-btn.active, .transport-filter-btn.active { + background-color: #FFED00; + color: #000; + border-color: #FFED00; + } + + .filter-btn:hover, .transport-filter-btn:hover { + border-color: #FFED00; + } + + .filter-btn:active, .transport-filter-btn:active { + transform: scale(0.95); + } +/* Developer Tabs */ + .developer-tabs { + display: flex; + gap: 8px; + margin-bottom: 20px; + overflow-x: auto; + padding: 0 10px; + } + + .dev-tab { + background-color: #1a1a1a; + border: 2px solid #333; + color: #FFED00; + padding: 12px 20px; + border-radius: 8px 8px 0 0; + font-family: var(--font-stack); + font-size: 14px; + cursor: pointer; + transition: all 0.2s; + white-space: nowrap; + flex-shrink: 0; + } + + .dev-tab.active { + background-color: #000; + border-color: #FFED00; + border-bottom-color: #000; + } + + .dev-tab:hover { + background-color: #0a0a0a; + } + + .dev-tab-content { + display: none; + padding: 10px; + } + + .dev-tab-content.active { + display: block; + } +