cvq##Lvin04i@Cf-!c}3Rs4Npc-wj;C5yFjH5 RvfCYy~hmQE>_b z(Km?F mn^n78eCc@gRt5w zkUg=ivvW{3W9@+Uyp{vV;8Vl-5RjzcS^M_7LvcsuQ$-uKV?%O(*9#fyQBe`nM!F)? zRp0t_0r8%=?pA!t@Y-Mu!3h_VVhDTN{Qd6})pM(VJ|}`=!x#mx6~_RwfLY3a{X%~? zdRqu+9X~-yB{Lqtn8x0C4S{fqnp20lu$_01OM|YX`H^_Y&A5?TLs49J zfAINcp%mjKtxzB1PQpqgV(H*Fs zuBk$c*$yLZ;vPA1|8^SK*7U22!9fR&C*i_G=d86g>Cx#@IgNrb*WP^NQG0&s4(l~n zjh5tM{U%|^tY93AN4+P^$+~#-H{~e I)~uvXtIUn1slCyqG(i@cht zoAkNv`MTp3IK|o`0s8(MKmh*R6=(mMOZmI|pVd$F)=I-Y00AKq0om58wBQ PaE*l0ktoao@yq1rc4oN;lHh__p|9#NnmX@c;a zVuC2be> R-SXCZb@Max_7nP2 zsIVhwZ-HLPyH=_rqxWY%sdj>$N~bgZuBLm%&Er;8FzKrewhXr_8yPIWT)mvJY}- z(Wsn&Y)+(38oFpFwKaK E0!v-=4hrfN_a?Nc39Yg2x2ImH|D(0UQk3F6in)5xK0G{c z1Zzu|u5)h9uLn|a2DzH9#;rGO(dmb{EYN@Z&mDT>uIii!`7-j1s%*A$$a~2SfH25r z0_sF7mBn24tH6N8T`=U}b1Q8R*){_*llx!o%<}H{aDGkU_RB;k_^G042WN`KOX-&= z|7h@EM~((O_uv0*+K$v6e%C9=sv6UDQi*$!V`PJ G#xt$fBNn*{>Kx_yXqZ-*vumu_Cjl;u%HccM ze;jaCg!vA#N&p3^xiB(v%?oAM0AT%zX24yTV=?damxKcKMG^R$K*EPP&Kb&{ng;2p z9>|$v^gmqt%iY>juMW#z<#3oeIDT(XL&q;f)fg^ZB_#alJ`??mB1`#LsEm6vEb?7? z_~~v+<6*_3yoQ3D`sslOVs>fO*l1c6BXl@JlSI}e%T|nWF8JkEGcaF$6HJxzpZU)C zCP68qR)2(vMRNz0QW)wCQVLJl@CsB7G#bs~jdB7q&U6A->ce06j`3f>c_zZM4I xr-bmV1hiwA zhPihQD|JZ#Q}nZj2)`q6^BYOuP(_BF0_BwR-Q+UTPjLIJehh@|iHOmViSBsK&i0f6 zp jKNfK5|px zne!r>A&$cocoM!gyupdV98-3ya^hC{0I0f H>Q(4a z#M0zyqXOT7#5(EwYbhVS?ISv^(2zk+-|QfLA|?S%ca_TD^b_W$tL{*;zXQ787iR8O zyQ}d70oHt)^F$#AfNKU8UQq|%J>wviP-onnb4UM^cE_wj^ubUOpVd;+o?>}>xx~RA ztfT2we#no3t7Fr IdO|@Jdh3PNB1tj z%@AKCg=%5IkV11T9f7Y#;(>s)t n+2ssy8zo~KV*rfU!Q>!N5qf1^_WrZV zftQC_;lVes9`P5Xtk)wxqbcl8eK!KFlM%Yl@&AcU6*_Ximc*pCEh&=sezuzPFJt7U z9E0UUDTYCJlU(I_8=awR5)>3rGTjlSs<8_UEn0w {h6t@)3XZN%bhsCRzP8Txb{BW0? zWl3Y={u%|#hL6!J&aym7ew$~B&5^iAOl-ItpQG1fytI3!y^l~`(NRG{cyLLv6v}E^ zv3G}swVPBht02ePiE9{zn98kKJZd=ydvuZH#i^A;iCyBEZ*_!Hu@7_v0rw$mY3_>b z|3msy5bvDq=K>pV+G$?8CU}dM6joj%1-MX5KM9tbhGu|`6`cm5f#J~#cG|4U!W+R% z4v|2UV;}k4nRxO&%dthfjeo|-Ux@<2I030sai#%=(MJX|Dq 0iabx}9jFkeQcAp+1l6 z=TS}J|D{%t1`1i80Bn+59Wx~rIM7<)A2ofT@B@oFe;L^ok!GdmE# g}DEe zL_x5v30aZA*{bGxJcrd3GcFK%M99)w4PEvYDF|Zy&jEZ05~?3`M1wjo!+3~%<6YRj zW_lionI`37_`yA_)QSU;1;@kQ?k|m-QXp>>8Pbjmt|%G?py`?Op8_Uq7#xzvk?caC zFUq~&nHA)CP8J~$hMLl9eae`q^nkLRY$= I| z7xgp0aZnFkbEC}@QO{~Dt)fP^Ap8yw2v+e1AkY*}xOWV;?4yP~UF~xwHH;0xG?i`- zeislR`nnEkQ{DHg$i^Om*Ui2=EKwOOlh>m|Y%mqQb-Yr~?gH$t{=)$kYv*?QL!uet z_HO9@k3VjMlJpaS18ici=b8=lHLHk5Gpaw$GIE4fU7cwpAd8!1F=Oin%tBSc7DWyv z-=(3Fj{B)5b@Ci!@>U5doY6r-EfZYU!R;3#e7@-bGR{5v#xoB`B?4i7!1gb(ZCX=o z3O|$b)2h9SHvefe@GbzzF;Si~k74z^>YLeA=XkDs?^?VR`f7-#*wg(*3e9^auclDy zC-TJGvxYe~7Uo14yFHEUaT6cee@EsoljN|SQuwgMZMOHDZS*68-HJgjUD*G0E# =6|J*E2b#67fbHgGib#;5ny6~Tj0aNT;8jQyT~#XO%U@8e&-M_Fl)SQjE=via~t z2!HD0=eyf_4l06Rnt=lLGU~QRP7cU#n5#OIaR!KMJ#ODB<^5V0GsBv|@m4bl&MhY` zv~lebo6|fHGHph*JQoY(DHzZyyt3^}hq&vqLHDZ@SY~GM<3VauI`afvO@uT6N=5K@ zw*kRE=}50&b>64{zuFN|xZFeLY20|&ZJw?ALl`tT8`(WHs^$*QQWSt&>$5`ixdtal z#P-SbueJA+2R$vVp%d1M1$~ROD(dAp-o~h-`0JtnIp5RsCCT=GVjh9#kBFQzDD+Kx zJIV<&xisWZ{|?eU89$5`g?N@vi|LkOm H{s=XI$RUfRjEVxKA8% zZDr2rR~}tI<+=5%^}ZbH$MeOoX-Q&vVs0K<|H9zL59Ji wtSaDd`{scUp~(f z0sw{|m|h_R_ez j0Ga7I&kubKMde-5gBEB(d0Rvg%^$nZe&L;cG^{;Pw=a55aTl0sud8}KcPphM zGZogBdw-B>yu!&BjeUW 7yJUit~9 zni_={N<-up3Blx{rfD8X4eJH>5w8$SV#>U=&n)2h+woHa*ON{<3CCBRgxW?6tNzc> zca6);bw|^dE3gYege_W@`j}UVSzv137~qvuW2=^VY8Bv`BZ+Y8#cSF$a72a~p}+F= zBmM_^Xz9n>N8usgnO|!iWnV_VBa`$%;Sn$Wh+g z*F^~Hvb(Nu{$JCXYf4A^Xom?x&nz{~)DDV~SeqKGge&Zp#3|T$cN;JU%y4!1T#Lvf zR)dbe1k3Pz-cZ(QIjE-@?klFI7K0mIm%B|Q>53Rk(s_yp>y=hM7|pNpNj&kP50J5L zoe$e3CV*_7X7e2r%DXGK1~Y_s37KV^`#1WJ#suDAwD|&1h%CDl_y8Wo8E+uWf(Ci3 zk}7bLLP0p$^q`#t-oBkYQvf9SV`?L(99W3jQ@iR_ZdEB3wZS6;y%PxJl<8)L{JAx4 zVFrEzu^)ns&|GuEziNg817Hy`sBPR}#q$`CL>*r>0BgsBxYUX8R3`XSW<%h)R(a(g z2iwzBaH#jIhFbGpIZ;GPS(~Nxx|WLy@SJbK@JhN@9fy!|KypG%>W)v7Xe2f=1(&{b zs$Wb6=YY~Y8U+s6?>q8M#lEf4Y!WvCbo{-s>xtQHlGLvPNFdhh|MLUHHhQBzJzk4s z)-1)C QMvx)UBm^{Jk7?71s)`*00p3({wh89Uy%Pu vXwz5U?X^ z{U`L2fO!4_Zil8*I9!328)tNeRB7rp?|lDx#V273m+Qx){ODpZ<{=`TO}YEu@~(=^ zY*ed6C~;+PU-{l@g+2Ny+I;(u&@R0q>qs%FIB(w@fyGIerDZ|YuKo~3ZXdRKZ^qLB z@2_bwZ)#-`ak?N*5l*6aISWfm#Os7;Ji)DXEu6V40Hf-jCE86!nvv+X2KbKYc4Grf zb!q7z-h&)WiA|D^F*`@1#_HseG34EEhKUxd=bmq;o}dm-EFMJMAppbTo~Cq}Vaf=e zO^He6vF$k%v#@(~ZTcH?;V%0@^pe1qlRS{74k+XS4>^~@Pjw9#{LNJ^FrS%pbS8!) zRU&kT0=_o?jU}QW@De3_-g97g9g(LkA;Mp=lFZrq`y!ks#j;Zg|vcLM3_ z^%09}2V}#1eLf>;eb*9`a)nS1T-*S6ghaW;=qx33%H>cu-91r1C_rg9S3E28+t;Mb ztSu`kXpXplymQas Rj>bek~O;V;742LqT~9eh_Tq-2Yjv)Fh10v+n4ik}<6 z#pZvElir?ILq0+g6w^ESNh0grVk}u+DskNA{I 4YQcO zH@{Jd(X)F^8HO%HKFW)2{5$oIiB0^DC4r)z>QfjmF2`m|X1MuJX^EKMCBKU &)z`g_`dT1H8Bt8Dg&Qh@d 6?Yyi^EM$o;;@gwZmTt1$+F{W| z;v;1cnxS9YDoYq9)2_A9Sq2qQJQ^N9&mEL{G4}{s+!fG4 Cv!7daX&N=W4Y${H@uL|#+FuUljV1Cu`=QAtYb78i4g=KDI4YVH<<^4tD~Ra6a4 zO_6}52u1bSRTStCyXZF}WACPKM _Ie%!0W*Axn7=>y;kyg3WMI4CZWl6&=y>AT%y|Dh@~sD z h6;>+IU`iJZ8>E|;7DBOecAo5&;*e5nst}->U2@l0R z`^o$A+Is3O{&U!Q+6Cx=#MC!_wL&9#E+TOHjX+l;G2sE4hIM^Z9CA4{dbi7a0j2 e4d}RGo)a*K+C@}1lei6~YN0=-?a!TFzvBuut@a>C-byGt*eH2VS@*2;3iEPk z5<0_>y`@RNYGV?j_E`Hg4s=^Or6fi!bAM$B!Fclbt^XKe&7!>V&m%#-oYq ;hZ|AYhWom~UxTR=7*VeJCibcR8qg|~nn+&AfFZM#Ft zbw$xkZ;rCzSUBDjFUsp1Vtzm$8#iU(qHPE-F`SN65Ui4(XN0W ErMSqFL@n1>V;k3 zhnOb{u!44%5VgM1B5IAvXw+4!49u9#rJm_@!73mHr|OAgZ?qQ1UU@ynEZy )tK^&i4^T0&jN9|=RTU6aJg^uDx;R&by+*i>5o zppRELf8aN8S<52f`MM~Q8F;xVqmjpBudT=jGvhyKL4X$Rjt7eFAnJMqUkz)`)=H2M zVh~4C?txC`y9a5hKd%b9Cbh|FTwxLYGzf9}=rDPT@^4W3=!YoSnglF2q5fgEriq_y z-_)D7)nT@8&*6FEzBRwnd)w~1=#Jj`#(+0j-Sec`xT*wk7-&ov)5Xt*`7|P>X9nK* zazJBfy>j%ViS>re7sy*PP>0U9aK7AAY}PwKJgj84RRpnV9bsQ5sSrjg*TdC7V5Vdm zEsZ&UGmbOqLX1a(^od)_IS(jpZzfR=EW$&?d{O11!dqto-zMurSEgu#>gb8 lOhuU|z&g-Le wsO0iHEpf#qOJc9ZO?lC3!1O5d^XAiBRb3OT>q#KRkmVpu)WMITAk4lVH{+RVaDs zv8W#FiyDA(N7f|y?}(|7^3~g(4>TxrAz1#llLy6XAaFvTW3@-NYRed{Dha;d0@ItC zc%h&-qVaUuNExw4!4#TbCZ=^ ^hz zokzK;MHwpiO%;kU-^{uZ-4GsQABB(xZ-azo)jRRhMv3Q?LpojUuBt;^Z@ID~HoP~Z z5{#}nnU!xB9ew0M5jEkUni4D6=}ahxhGT=-I{hX8n83@1iJki-1K!E3YJL2XuE529 zx|vp8)LMC9Vy{!-o5|Rc%m6E!=^L=LP<8kn5_#z#n6ludrw`)10NjP8Q?L3n)HO>} zHyKOq*#?pH{q@NkSP~xMrk6*0f&g=tl6`|;{vi}@$!=C^Cg}PDao#_2u4fv VXC1I70uMkg5>idXn znBiTf@kuW| LIskSIc{#Ud2uSd~#r6LA5hM#eIsW#~wm WP2Gpg< LQ)zr=FCRKtM!XKxE z+>En7YNs};3Y=Bz&+dhaSvQ`5 i!0|%a}4GmhqGP0ms z8i QtYIHVdKeyrOJqE4F4 z>0yN=KRNK4P^OZB?z^|%R}xb$Z1l(uzp|Xq6ffoYUOUvhb_NUF@-bP0Av&@_-KXA{ zOZ# zPDWVk*vkkS(Ded8o7ED`*j`GF?AIaJ9NQX5vwlDRsbN)??^4QN}>DcrW$YC+nI zQDE8{1M3AVl4@Y9lf%x|&tQ+V>>Y_Qq{S K+=2>{+G}-^?A{j25*uaz(qt zoW0rQg^^fJ4{nL*hrkY@7P$H0=l8ImFr7?&TnJ;YR;4_bj>REh>#{DjqFn|*S-P14 zu{?nc_4WE?ztY8lQ@~roSLQb0Q!3;W6$9`LHheLq;F#=NGorvFj|rYj5nt==(Ue9h zrH;nkcw31ZrGYRcu?eT~r?X34b~X8UISJU*95|8J3$1%9rrPY4^MJVMHC9NW1^_;p z@?UHt9N8df;_yVW1lanPDFoGSV2St8Xyt+(3( xuAQy z-lcuok!@UENmk8J=Z4ntLhR-;a){_$g_gPZF-;6dQDLh>8~;v*&1$9=(?Jn1c1>oD z+AdnT4?3~T5Bo7csZt}ZiTHg*Jme0L5 |rsVTDZ?vnS8F{>TL$R$`LManMMc5*h=X{A{ljoeoW4 zvm%VaI@qqI#|+XQ5teQY)Hl;Wu%lm(0npZcLrm*4WH?#4J{A2GKg+aV+44hxh$2dG zAi<;Nf2#H(K2g--F^RU)s`H_Tbo(7~{g_2Zwo8O))M^ioN3zmc2FUwwK)?FFaL-eb zOWN8tyt}vhvxjIijm`8@(r2m&oZi@H=i6f(NB` zkR@)EW8Mw3g%tdxdq&3}l2XAk2ku`ByhaE?tRp*N)@rSRfS^z(k7c&;AMJ~AhDrD_ z37B^}BM2QxhHetelN)t UX7ByqOyvDGB#4jUS#xs1nV3bqC&7h$1BM^z{B`Wu8 ze+G6E-5=n_PeN@HXXY3+$p8v`g24A3v_ry=>|>!b1KzkaEdYy1YiuJR>iZi~$V*@n zBaX;>&t5Aa7$pE k&kDr2ImUke%_Q2yDFllAS|D_d%?;Rfh0DcwhNz>EEW@nBa=SZEsc>HlP?p z+p&==f68W`B!B>pX$(=;4iP1ov)kF`Y4xRn;8F=|oO9|{it<8qh77MpZaaTZTfwB4 z$9Y{*A)q%y2n27DWqeqvN^6Biq ?UN=FXSQHCS_dzH5uP2)| zKyl;6u@_D?_5C+m?X68TAe}fb+wu<;OM|;*FbA~t$=7i@tE)_c1+!g14C<^XvJxL! z*HG`EApN*Y U }uws=hHFvNQav2y%lP-3QbE1HV*4$G*F6U3wu2GrJC%VzhaCG7V&_T|$Al z5*$N>12XxEc}Opb;^XjxmpSdCe~%Vjq{C(KZdP#si*U>JdCjzkF>7|q12(6KizkO1w7OMKD1${ z`M`Ja!*PO??6O-4&IXDOM-cpodbW20TOeRHQarUwz~QVCQ|x54-wGR=&fSWSRtNEd z^>!(Qzahn^K*GlCd@uOkBh$OT5kusWGc@1#@pTSm<`ANyq9+cq{NZ1E82A8T9$$+g z^fOYfeq04ok`2A-N^45zDmT;&915r1jyA3!=@&PH75!XA7gc}&buoj_hX7DEmBEG& zCy@S%y%3GNA1(g-Q1lg9;42KS4=!@?Jhdnxz9NKVps;7jIz;os7iRu+Sa>RK&1LM| zpFAEK8$DqpeyMM=sJxCr0F7u%W@s#L-zCF7L|ypAM-Jtm1yvR@s_hI~UL?aQ26{5x z@9@~bM(y?lgXjOGw!?6ZQO1QvN9?~%Hny)a#KOLQZ#{20U&8}t=)i!9Chk$r)TPA- zy^q`zlM}zr<4=eQSgbMHBiJSf5ou1?rJB%jw#K+=d9hoe|8&Ya(yov(csBt-`&^fA z4X}5%Wr`MsZ|xxPa<9VCHJ{&JY`_S8W}}*Rf0pSC88M&R61PJ7w4U20sW2>D`u%en zi@1o{pFKp39F cDs|ZoctTfEal!KQ#n=TODq9^ z?F7w0xAobv{^kKf^GJ)p!7n>vBV0|EzSXv)P9YU06k=0eh1~Y_q{|pl!H%_j2g({? zioNVoJq5N#_eQnR7y)Rh4li!SOV9qrnHB(&*A9C3f*voAqT{9%au3zmVzAVfCx5At z;=9@V{s^1SP|{Q8_B^Dbka?;Z<>5HjaKweL>wUss3cm}&a7hN(^Yxv%f5KH-smV~_ z?13iYTQkiT?FZ|c8mOF_9dYW>;XGweyc0FgjN^Gc8gam`Q`2m7$w=vaySc>p6x$;_ zU#&Yz9teB8LiDRpkc9T!JvtH75Md#>d>YWxm9izsvVgq)f-B2& &9DjYanzJx=|HBX^py*z%H=S+OnGEZHk|; z+xxK1keBBPQ^4;20=GK=-SgF5Z7a^!qk@!1zt3B(T`)(IXUK1_1Vri4?%d@o6f+02 zyW2Ez8d)0+TF$Qv>eAs!0HWl_fR~r`J@y1h%?suDTyPsNoDyGWGGm>+Ebx3z^AU}d zZ+9JK!{6sNQC+L|%Z}0|CqEHdr&+8?ih1;pfNM~ty5r9>&eOtpJ w}Z3O3Lm3*Gy`WP$)>b^Y8l_wp1CycJ}In!Y( z!RMU#dl@D>)U&CD(;@YF(4S0#|n&Pf8*NGj^8S2?q#iqN$_+&&T>_LRkTGQ&l@1 zYOg>m-7j$Yl2{*$Y;k_xCbJtfoshgwyYc3I-F6tda+JN_FLtYN_j|flFGcjADT!d? zN*W#SeOlGLzU0&eOm2p c!8T=N$dOl-hACk9yWR|;o z*C71rl+yS&CtAB}Um;WZ=PG(AKfn8gHJ|q { z_^v#=-}0Y_={EgVJXM3L)t)K+;{RBtiDbj^N82I6zp#+%d&PQ@i`9nT U^hqJ}ukT(YMaF*JWjw!f*)95+nm! zRZa;b)vXiP%8=V_Hvzo;J_>xrBXDd$G&UNMZ^Gbz%N }@5wlRi#vC0>WwM3hwXG<6p1qXQ;G_!ohgP*@|)78^8!zHIzK6w~( zn}-d@6|2jL*(hNEXO~21M9OUMD-@O;rm?qw@-obYyzYzki|HYTw59>s)nR)j#l2 zQmT!4r4FUnn)vMaU(q?#`bQLR?+&zk)feWzbT&y^X7lB2=^;rMN=F&fOU;uqKA80; z_7n-PC6`xT^zGgRH0UQpb3wGvQoAFGv5u?ne2B2A!b&%q&uzX*vr7hU6B$3pK}h;1 zkF!g)(V#CpPW%h+ufb!QKN&W$biHiN)|lz=4wM3=*qP$ *WaUvDRiFUG_%~SP>y+9D<1R61Ri@jq6{QuxMfQ(M}^~ zkv42U23wiBs~2v5US5d;OmNx_ ad1C$BtHS~(4$KefLyuO}!_sLs%#CrxfS)s6 z7{Liy(!M-WHhrlp`fBB&<~CunNBHsf6ZpK2G`3f7Y%UzcWLSfMcRVS=N+KtL!N~KY zz> G}iCf%6 zK f#a3-7)Mt7Fp}qveyI(~4_8Uuu-yPEmO0br+HHo2;4ZN4xuT-V7S)Ma zP6FP3nV=RiUC__$I#sJ>;xaM<6qiO2pzk@W*@g!&`%~81uk*aG(nE%|?Olh5CV@gs z?9~u2q|u&`bA8l3 ?(YSx+~@?)iucH+fZ_f2NOj!p zq6?lGMcE+$>o-W yVW869UX~l? HGz#b4l%-*l;V_cT*DZ8H X#Q>cFDu4li0X~sPolGU9BCR0!`T3v{ z31@CO5~Bq?{7`BI708#7xmb!jU!VrykJ}$y?@Ax{argb6@H)GX@n_(4@~fS^zdc{~ z`2YJ>eQ_0K@%XDWXRrU?&kyqAR<+6%msE-M`p#YL=|^{oB&@6I_?JO+87j7tTl4V8 z4`UE_lX_ByR22 (;&!YBuD=rz^^6gTzZ9Hb#2SjDLcET%@t49=+V)g2Cyi04K7HhorF(x zt1!gvn`1+^Gdz@jTYs3^#5uF1v}>Ki56Z}u_tFR$a4Y8%$=D*6LCz?b>mueHZJR%$ z^Y-!hRRp)bVZrMnBT+0e>spTKd+;JmgO&=L&ZO^|<`;0t@s>;+Nx}41!_o>;{g@ax z{>K;mZ85QBgc*6IsoR4rzaQz<+jCU=5uqMzrgb6xdMkF|^d;kp5b>7DVz^>vyEY`G zo3Fwg`ioiCWfKu5$doBWNpH}-D(rx^5ZYxEv^eoBE)!|g7GJ46cwRk`w+8*L5lvt+ zSCK+G^nzx-@TpLpjsi)^?CprzwmYAz)p@-xBb`^fJe~g11d=>wa+EEMbH_tdaf$yt zzOM{36Fr-kdP=06e!zhD`S>fVGm3OCU1}Tliq9ri7_wX*;!z!xT44Y&Qk6}IADPN8 z%HWirkyMK$ydVYT|H{s^KZnS|W}0|4q-TVXxdN2R^~j%hFn}( H;ve1xTFgJU&Jz$*m_^XvACJ;>yq(juPn zjS$~%$w!Wi?*emH_!*YP4ZH~9^f#=+9=Vk)IK87=PO}%SH@S@1pw5av-KD`{rjfL; zZAAt!EUI-MmECt>N6gsc6S(Kdo|*(EqlQAMkL}OURQZo5SDDeGpTDHEOI=P)9O~SH zYA2C=bWNm>0tba66`R @f_oa`^5cO z{_M>`|M!8VMg0nu+}q;yuWH4N 5pGG1YC;f>!fIJ-a`44Rx+&V=UHvZDNSYjPV zu_YI5_7b7_NQQM4ihHfP%#pWk%E+Pti`l2MHJ6A4{*Svke;seD#Dk7}&D^T=A}Byc zqA _AR&59jtW~S=eY&5zYwIgXY0pYb% zE6HO-Z<*;czT kU77aONiKN<(k3*Eyl=tL*#1o=*b)D zM8^^l2{z6R-o^JBAY8 wXS3hh~XSa`H>4yfragHjA$@b$Ny?vZkf %|}fV zp+ X#t>s|n`F`Nw&X)@r%MB&M`H(9RmIPuF(chuApUK2J<;JiEo zTm-8}fB^n)bizx@y &-_FxzLg(`RbSkQ&p2&Dt`0&;4cWx^^<6yKp9X!s zor>c*!v4TCT7-2PZB(^K<1a|Z7deam=J!b9ExuQHQRk9nmqQ77^1sIK1yx+S%qKW6 zi3MF4GE<)1SEth8ba?cCT?OE;TnB6@aoIa^@owqNz=22Bs_+7-ovGyy0DK}*8LKm| zk2;CM6$-K%psBE5lc&QCCtX}zE3MG5M2?K~gAYhJTlwp$W5~UX|8$lc36&8O-jB+c zXwd!#pfk=F!hWhk9h{QQO?T8$#cAvdsJ8NCfDY*-))rB4pZ3UL+*;XcLwOqD0;H3P z^XN`r@D_yks_FPs)M1Y?%alwAmM5&X2se97h7XyMa@UvTOM*w5VIL0JJ|xNBC}#y< zP$zY_I|*Rak^|0iSfLC3hbcA7qT;6Jmd{LiESnTRc) -Mm$iJXMTkipwGvgn_#!M2byx@+UJc)j0y`x>jUOb>gu=--7T5 z{hJS|bzN4A%|!iz-e$2J6&HJjcf~T{Du;>*0h8-g`5Ea0;GFAlg=p?3cvcPyv>{0C zEP)o!rt$gf3=4RR!WZWZm|$0J)SZsb0L|IS7_9aZh0r@ld$%2ts-&K{{G48H@sRZ+ z>(%{CQfi9g0Vy4!vmIJgz#gN< Xq)-1hd-P z8<1|!SN4~t0Vt@U+w@V!zp3ItyZvDbKjVUpTA>? vnMPdNK?cR zNQvc&Qv#9blFoamBztNh><1tZ)`lg)s5C1NmKq8Hw}yT!^wo8j$yY}!ur=~YAzIJs z!<3cYhk&(n5MO4h;gEUxM)2`}%t{fq$|c#H(4%bKjm=f_P<^hN@zG7dvq3F;mci*r z;?(EU80<$XTzg9dZoevOF7khYU>~Vb0w#*{BASyCRBJi!>jnXBUfBiAz%5!+UwI zKb;* sijnSbXZFfH+G))zf}Tsres}*H$)qP(eBqLB%a`tr@tHb4sn$A$YH#E z+lb=^BKZVf@-UIv?2(VAMc{qibk_Bmtrgf9Wxv=iq+W&B;+zFMG&=-KKK6O$Ai@Jl z{oDctqr%-125@vLy`~`+RZ^j)jbq-02{lQbV`;>_+<>KAiffa3Hj)G_JJB9o${LB_ z EH~#t8urH*9_M?8YZR~qm;jVO)AaVB z83VVcjRLCdfi1(V@(lVWrzxL^IsLAIL&G=}p+s1wPQ{ktIncV)G~Y|^J4zfZm+kjO zA@;SgU)NI=5J|5{G<~U}qe3{n6qg9|Kk{jRjC3X#wF$}7ono~21)l2W4^zj3dZN!C zrh2W6B TTAxw}!*F!YLX%TE>a=*(}ImN>R_ZgyG`<`{zL#${O}@BiX(P zFNm6$f9-~2B#2t4c!TJ>gBjxl^^2_vgm&=M8VuuT9)yhFGWxt(+6{?eb8=RxMR}at zR9@Ea^?b2$XFUK21r8?5wG}j?IOJwwH^HB4E@c*>4?x1D^@+w&khX4A50|=EaH5gE zR^-qik!6JB^{5h>yLNM?l{1={j{AZ5bubUo8zuljq7?@P)RQSz7g^bLkM3wFxi0s- z^j^+(g=ECfT+Z+FGD@k0q5!_J3jxP{!x<8SVS4c_T$oE0^)VH~tsDI9gKy7c>{jLG z86B%dHEL6&-~E*Pz`_|>$7?+(5Qjf?-CQ+R9K;pun(}&aM5Hu)pL#vC)5H_E9jrTU z pCg2>Sq_*!;53T)HQZ12y)j60rsK zHW;hHQb0U*Wg@5!Jnl5OoQBN9Ak{rGg~Yny@Na3m|I1J`t>y(@u4(@*qF%`o mz&{93Iv#95oWS-hW4u z_|YVnRkAHd3jlg(LnhB&grSYnaLAo7Cr9{DPzV8G#WYvsVr2>rW y$$rwM%qZN&l<{oShEH!qX<|a6ATx;Z T+IL0TBKR&H$?ERGv%p z?KGcAY}+{vQc8*d2#2#|mGDjCML4YXw(D`0f>1r% =%{`mf(_ zoOSTHI8pG@51}CfwFdlRYE?%=`b3d#O)m~3oLI)KTz0UyGnDXmA~LsfD?Wj(@cId{ z5#yXdLxU$WjrgHAp>~v(2exud%L-JZhz!t!DO1CLo(OJKl9>YC#Jp7$m!W}QWH&*u z&6UZb4%;I_pg%O#3wzdNRmBE}FRs3iR;4I_>?qra8`JKO!9zjF_zaQ4iqUR?Mm(dq zoNn20{J$3PuKw6CGsV#t$nML0$Fi2Km|6#DH3a%7Epqy?4t9)791*KOr^3@5Dv_|n z8XE>sr~pV@$)b5@yc=^~x+U&dqjNsS6T`xij|fg$=qsZ9Y!&iPR#bXkd&ro8?uT0t zp8o{2=>Pyso@!x0L(B%L`b?pmo6~cv)>C&yzbXg()CrToH{zh(#`mo_$A9|>0sf%g z1VI%LygQH0RNN-g$< ;O4;0Z88) z`gllzw9)xQL=~sJ?mL>R;)I^S0x(u1ImAxO@%o<2D$dMFf#9Qqk_S~UzOmiqva3RN z3KfhK=K^PJ;nQU;h6~|Y7~^npm1QNNNs8A4#`%fZPz5uQK$BSqO5QC7Zp->4cviwJ zeH@--Ta-ay2;hYyMUJ$GY2v|-maTI85I+vXa{8R4B+(U$e)f;%bt1(4YcQYeTKB*X z|Bw?|O9|x7ZMC+DO%OUGlr%C8 lj^Cc5E6 z!1%tvWPTB)t0~CG@S#Uqg69k&zrTJvLTnsFiX;5SjVT`Z*PDTM5MdZx-{9&80}Snj ztY7OgDoy-+v;J7Q 3aQ=gU+w$#26S-a!oWE_ZpaxO>%^sL5y*f2=cTf{0eBD#z z_5pKyLTIh1Gd=^zk8^yw6KNY-OIV~DSmrMjbfs>xhX9vkhb;Gh$Y6^#9gWPh4V+^W2L)x>eQvMo+8R1qn-NLk zmwG16IXSN7w`ZLU!&I))@T=F`iUHTj rYDV%rCx8>~mQ1{4htl zM2=46u7OQ6$fZKds7L9M)m^F!8A{jTAw{+}`sI{K16;8+O2?Qp#O@aeW3l?#txdoI zK&@>94)nL|m&kquWc71@xBAZDsz!&}_9IdRLrt^c2C5LXA{ozS%i{&8T%4?vSSl?p zS)duF^FZOsj>ii-HW)9I^k*)22_v0Fv_Z-ch&3$_VTO5zH+}BQc_Z-cbXTTp3$!$V zRgE(U+#(C@i+W?#LDewr!if(g=qGrhrb<&Zj=!(x?6)6@TF<{4tO@Z1oz fz vcO`mY7e_D)STIPky_aBj6( *#)t{oZP7PP56?}jE{Kl{B_%Dnt)359Qm#hcNjk!AwQ$%S~aN{mZlz9S8Xz> zjCKG7tpiCeoNPF^vr2Z=AQAo|=pzgSj6*aNOaA)XO7wq6U5o(0)9}Iz+w+>2d+18x zMV|IbhpW}XnY<*K1`OW>efzqGN|%V15E1}NH1GqEh`59m*K=I?OeO0l9Ff5@rtl%o z**~_YA?lfU0vs3K@7u()CnU{d`+Qfm)*#lz6F|PGUEd+6|MQ?r%$4Be^7C8FkH?mb zuKO(qk5Y>_aBiibr+gg%@vYsEgpkWD8=HhoxXiq=s(aL$JbHy8>Xy|aq_7)_AX)xX zMS0t)@;7;ywGyuX5Me6kS~lM*%che0Xz&3DM9Bq^*l}B)7RS!eoqi;io|zPSBfP0d zuAlHyF_B<5Z6Rft3YDHAkpl7dp5?0@S0b(7PZ6WOSg$y!$KVeDRFDSD6sTvr;`b(t zd=^uYUb3&432IDHdRQ%BU;0@-eGHMwr3KGIsgaQG-cJpds$6X2@U`Qnf}ZD`o>pxD zmHm{M59=30VuhzfrUfm1FFz+OhCc9+yAUEWsG0zz0ph)vdT?5BhWa^}Sc2FoaX2T{ zt27phtr>J0*MM|Mm(5!C6|LLoz4@e &LJr@8i;f$% zD!FNx04E?10#)pnVpXnpB<(O&Jv`KbNwxJ_hhz1|$_FGLwRQ5pG5Eip!!eOeocw|^ z=O1pLy8F#2>4$^R;-;{by0)fvxsDwsHz3L7n$kV4O9yB>=q7;9cg;91sLi0;+xRq{ z7~_cl_M}aycY#M6JCz;hq19LgTLV0OMhIWbq2|YUC9YDeEFEv}F6MpwH;Lci)y2b} zuhz?liP-bH0OI}!x4NFWuGR)2tHwL5ChO(oE6*(}x)EM5gg;yPbVU*yi^smHjJMeJ zzU6gsk&ny~-Rnh8pki4aM~A(e-W2jXbn1#nh2}?G#D1GyRf2nyXw&M~b4x9x+dd&- zrVSgVpg?`ZbZf<2&(tqls`z7Sk>bIm>!6fNIZ8Lqes_!$q32razBfg-NFt2iDsMt5 zOvS@Zh+FF1341wsQhlOLWj*e#BoO{Nr5)_dUuECXcy*6BMABVyZZ=7P-JR&J3^~rH z#5c+~bBL9-Bz{&_y5nTLY|T(qzsiV`inf&%cDs-sTOa8dU0|B>*iXPz*Cd0${=W6u z0jBqbTn<>-LMbmvj6Xo&`e!&jWZr2`ISCt5*B_A5hs}W=)-w3lX_;nnYTqKQexEW0 zm@V=Mah5svDf=zk+mE^dh5{@0osSJ`ERMj9o(4SpiXsP`PP 9qSj7!njb|GKD0r*EKr3Q@;@sd9VyHVSbEb%r*mR8-|2uo%#fGhqrVhfs; z_&s+t97p0UnQZ?k35L`AU>CB6_^JhDF+nUi9kTD;|KfgWd_^ut+}MdSqYYa}{J`m6 zMg{Z_<;VyDp>J|`ZQKj`0BY)+{l)rKXV=)lS@0qmsR`hSMt&Q6SQeFs=}OaI6VAQ} zbi{<}9?k*z^!P)45MGK|wRt6pgAti>n;9ID4(P7KasJ$PHr5*t-eJsu;$ryZa_wL? zR2g8F?V#i)a!eJI;wV}oR>O>{B31A)2Zy1Bp1NA6 M~!V7r+cX}Y+$C4~#bE!Vf%FLZ!*HZC3a`3OEN-0a- zZVkycmDHLjyl|w4yUqu;WY|}&S!An(ww{nf1+zst{Ea n4qCjtyaHsJuz7h0m^KE}jP1r29gPeO;>JK= zA;2JP>=GJmF2LAq?BW`1+{kDYtd + -+ + diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..4b70ef0 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,48 @@ +{ + "name": "TimeKeeper - Indian Exams Countdown", + "short_name": "TimeKeeper", + "description": "Ultimate countdown app for Indian competitive exams", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#000000", + "orientation": "portrait-primary", + "scope": "/", + "lang": "en", + "categories": [ + "education", + "productivity", + "utilities" + ], + "icons": [ + { + "src": "/favicon.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any maskable" + } + ], + "shortcuts": [ + { + "name": "All Exams", + "short_name": "All Exams", + "description": "View all exam countdowns", + "url": "/", + "icons": [ + { + "src": "/favicon.svg", + "sizes": "any", + "type": "image/svg+xml" + } + ] + } + ], + "screenshots": [ + { + "src": "/favicon.svg", + "sizes": "640x480", + "type": "image/svg+xml", + "form_factor": "wide" + } + ] +} \ No newline at end of file diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest deleted file mode 100644 index 3352551..0000000 --- a/public/manifest.webmanifest +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "JeeNeeTards Countdown TimeKeeper for exams like JEE, NEET, BITSAT, MHT-CET", - "short_name": "JeeNeeTards Countdown TimeKeeper", - "description": "JeeNeeTards Countdown TimeKeeper for exams like JEE, NEET, BITSAT, MHT-CET, made for r/jeeneetards", - "icons": [ - { - "src": "/1-min.webp", - "sizes": "192x192", - "type": "image/webp" - }, - { - "src": "/1-min.webp", - "sizes": "512x512", - "type": "image/webp" - } - ], - "start_url": "/", - "background_color": "#1d1f21", - "theme_color": "#2bbc8a", - "display": "standalone" -} \ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..c463c4b --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://exam-timekeeper.pages.dev/sitemap.xml diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..42a32d5 --- /dev/null +++ b/public/sw.js @@ -0,0 +1,190 @@ +// Service Worker for TimeKeeper PiP Widget +// Handles background updates and notifications + +const CACHE_NAME = 'timekeeper-pip-v1'; +const EXAM_DATA_CACHE = 'timekeeper-exam-data-v1'; + +// Install service worker +self.addEventListener('install', (event) => { + console.log('TimeKeeper PiP Service Worker installing...'); + self.skipWaiting(); +}); + +// Activate service worker +self.addEventListener('activate', (event) => { + console.log('TimeKeeper PiP Service Worker activated'); + event.waitUntil(clients.claim()); +}); + +// Handle messages from main app +self.addEventListener('message', (event) => { + const { type, data } = event.data; + + switch (type) { + case 'CACHE_EXAM_DATA': + cacheExamData(data); + break; + case 'GET_CACHED_EXAM_DATA': + getCachedExamData(event.source); + break; + case 'SETUP_BACKGROUND_SYNC': + setupBackgroundSync(data); + break; + default: + console.log('Unknown message type:', type); + } +}); + +// Cache exam data for offline access +async function cacheExamData(examData) { + try { + const cache = await caches.open(EXAM_DATA_CACHE); + await cache.put('current-exam', new Response(JSON.stringify(examData))); + console.log('Exam data cached successfully'); + } catch (error) { + console.error('Failed to cache exam data:', error); + } +} + +// Retrieve cached exam data +async function getCachedExamData(client) { + try { + const cache = await caches.open(EXAM_DATA_CACHE); + const response = await cache.match('current-exam'); + + if (response) { + const examData = await response.json(); + client.postMessage({ + type: 'CACHED_EXAM_DATA', + data: examData + }); + } + } catch (error) { + console.error('Failed to retrieve cached exam data:', error); + } +} + +// Set up background sync for countdown updates +function setupBackgroundSync(examData) { + // Store exam data for background updates + self.currentExam = examData; + + // Start periodic updates + if (self.updateInterval) { + clearInterval(self.updateInterval); + } + + self.updateInterval = setInterval(() => { + updateCountdownInBackground(); + }, 1000); +} + +// Update countdown in background +function updateCountdownInBackground() { + if (!self.currentExam) return; + + const now = new Date().getTime(); + const target = new Date(self.currentExam.date).getTime(); + const distance = target - now; + + if (distance <= 0) { + // Exam has started/passed + notifyExamTime(); + clearInterval(self.updateInterval); + return; + } + + // Calculate time remaining + const days = Math.floor(distance / (1000 * 60 * 60 * 24)); + const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((distance % (1000 * 60)) / 1000); + + // Send updates to all clients + self.clients.matchAll().then(clients => { + clients.forEach(client => { + client.postMessage({ + type: 'COUNTDOWN_UPDATE', + data: { days, hours, minutes, seconds, examName: self.currentExam.name } + }); + }); + }); + + // Check for milestone notifications + checkMilestoneNotifications(days, hours, minutes); +} + +// Check if we should send milestone notifications +function checkMilestoneNotifications(days, hours, minutes) { + const totalMinutes = days * 24 * 60 + hours * 60 + minutes; + + // Send notifications at specific milestones + const milestones = [ + { time: 7 * 24 * 60, message: '7 days until exam!' }, + { time: 24 * 60, message: '24 hours until exam!' }, + { time: 60, message: '1 hour until exam!' }, + { time: 10, message: '10 minutes until exam!' } + ]; + + milestones.forEach(milestone => { + if (totalMinutes === milestone.time) { + showNotification(milestone.message); + } + }); +} + +// Show notification to user +function showNotification(message) { + if ('Notification' in self && Notification.permission === 'granted') { + self.registration.showNotification('TimeKeeper - Exam Alert', { + body: message, + icon: '/favicon.svg', + badge: '/favicon.svg', + tag: 'exam-countdown', + requireInteraction: true, + actions: [ + { + action: 'view', + title: 'View Countdown' + }, + { + action: 'dismiss', + title: 'Dismiss' + } + ] + }); + } +} + +// Notify when exam time arrives +function notifyExamTime() { + if (self.currentExam) { + showNotification(`${self.currentExam.name} exam is starting now!`); + } +} + +// Handle notification clicks +self.addEventListener('notificationclick', (event) => { + event.notification.close(); + + if (event.action === 'view') { + // Open the exam page + event.waitUntil( + clients.openWindow(`/exams/${self.currentExam?.slug || ''}`) + ); + } +}); + +// Handle background sync +self.addEventListener('sync', (event) => { + if (event.tag === 'countdown-sync') { + event.waitUntil(updateCountdownInBackground()); + } +}); + +// Clean up on termination +self.addEventListener('beforeunload', () => { + if (self.updateInterval) { + clearInterval(self.updateInterval); + } +}); diff --git a/src/assets/astro.svg b/src/assets/astro.svg new file mode 100644 index 0000000..8cf8fb0 --- /dev/null +++ b/src/assets/astro.svg @@ -0,0 +1 @@ + diff --git a/src/assets/background.svg b/src/assets/background.svg new file mode 100644 index 0000000..4b2be0a --- /dev/null +++ b/src/assets/background.svg @@ -0,0 +1 @@ + diff --git a/src/components/Footer.astro b/src/components/Footer.astro deleted file mode 100644 index f4a43a9..0000000 --- a/src/components/Footer.astro +++ /dev/null @@ -1,55 +0,0 @@ ---- -const today = new Date(); ---- - - - - - - - - - - diff --git a/src/components/Head.astro b/src/components/Head.astro deleted file mode 100644 index 121db2c..0000000 --- a/src/components/Head.astro +++ /dev/null @@ -1,351 +0,0 @@ - - - - - - JeeNeeTards Countdown/Timer/TimerKeeper for Exams - - - - - - - - - -
--- - - - \ No newline at end of file diff --git a/src/components/PictureInPictureWidget.astro b/src/components/PictureInPictureWidget.astro new file mode 100644 index 0000000..127cbd3 --- /dev/null +++ b/src/components/PictureInPictureWidget.astro @@ -0,0 +1,781 @@ +--- +// Direct Picture-in-Picture Countdown Component +--- + + + + + + + +--Padhle fr, ps you're doing well
-+ + Picture-in-Picture Active + ++ + +++ + diff --git a/src/components/cardx.astro b/src/components/cardx.astro deleted file mode 100644 index fa69c93..0000000 --- a/src/components/cardx.astro +++ /dev/null @@ -1,305 +0,0 @@ - -๏ฟฝ Picture-in-Picture not supported+Use a desktop browser for PiP countdown experience+- -- - diff --git a/src/components/custom-minibar.svelte b/src/components/custom-minibar.svelte deleted file mode 100644 index cf4547e..0000000 --- a/src/components/custom-minibar.svelte +++ /dev/null @@ -1,245 +0,0 @@ - - - {#each Array(Math.ceil(countdowns.length / 3)) as _, rowIndex} --- - ------
----- Jee --JEE? More like Just Endure Everything!
--- - ------
----- NEET --Not Enough Exercise Today! You get it?
--- ------
----- BITSAT --- Students take pillow to BITSAT? To dream about a good score! -
-- {#each countdowns.slice(rowIndex * 3, rowIndex * 3 + 3) as countdown, index} -- {/each} - -- -- {/each} -- ----{countdown.name}
-- {#if countdown.remaining} ---- {:else} - Best of Luck - {/if} -- - {countdown.remaining.days.toString().padStart(2, '0')} - - days --- - {countdown.remaining.hours.toString().padStart(2, '0')} - - hours --- - {countdown.remaining.minutes.toString().padStart(2, '0')} - - min --- - {countdown.remaining.seconds.toString().padStart(2, '0')} - - sec --- -- - {#if showAddForm} - - - {#if availableExams.length > 0} --- {/if} - {/if} - \ No newline at end of file diff --git a/src/components/customcountdown.svelte b/src/components/customcountdown.svelte deleted file mode 100644 index 1d712b2..0000000 --- a/src/components/customcountdown.svelte +++ /dev/null @@ -1,81 +0,0 @@ - diff --git a/src/components/elevenfooter.astro b/src/components/elevenfooter.astro deleted file mode 100644 index 84a13ec..0000000 --- a/src/components/elevenfooter.astro +++ /dev/null @@ -1,45 +0,0 @@ ---- -const today = new Date(); ---- - - - - - - - - - diff --git a/src/components/elevenminibar.svelte b/src/components/elevenminibar.svelte deleted file mode 100644 index 42a2e84..0000000 --- a/src/components/elevenminibar.svelte +++ /dev/null @@ -1,244 +0,0 @@ - - -{#each Array(Math.ceil(countdowns.length / 3)) as _, rowIndex} -Available Exams:
-- {#each availableExams as exam, index} --- - -- {/each} -- {#each countdowns.slice(rowIndex * 3, rowIndex * 3 + 3) as countdown, index} --{/each} - -- -- {/each} -- ----{countdown.name}
-- {#if countdown.remaining} ---- {:else} - Best of Luck - {/if} -- - {countdown.remaining.days.toString().padStart(2, '0')} - - days --- - {countdown.remaining.hours.toString().padStart(2, '0')} - - hours --- - {countdown.remaining.minutes.toString().padStart(2, '0')} - - min --- - {countdown.remaining.seconds.toString().padStart(2, '0')} - - sec --- -- -{#if showAddForm} - - - {#if availableExams.length > 0} --- {/if} -{/if} diff --git a/src/components/eleventimer.svelte b/src/components/eleventimer.svelte deleted file mode 100644 index e4dc1ea..0000000 --- a/src/components/eleventimer.svelte +++ /dev/null @@ -1,80 +0,0 @@ - - -Available Exams:
-- {#each availableExams as exam, index} --- - -- {/each} --\ No newline at end of file diff --git a/src/components/minibar.svelte b/src/components/minibar.svelte deleted file mode 100644 index 9bf03f6..0000000 --- a/src/components/minibar.svelte +++ /dev/null @@ -1,258 +0,0 @@ - - -- {#if !countdownFinished} ---- {:else} -- - {paddedDays} - - days --- - {paddedHours} - - hours --- - {paddedMinutes} - - min --- - {paddedSeconds} - - sec --Best of Luck- {/if} -
- -
- - -
- - - - - - - diff --git a/src/components/timer.astro b/src/components/timer.astro deleted file mode 100644 index 797432a..0000000 --- a/src/components/timer.astro +++ /dev/null @@ -1,110 +0,0 @@ ---- -const today = new Date(); -import "../../src/global.css"; ---- - - - - - diff --git a/src/data/2025.json b/src/data/2025.json deleted file mode 100644 index 3fd91b4..0000000 --- a/src/data/2025.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "year": 2025, - "exams": [ - { - "name": "Jee Mains", - "month": "Apr", - "day": 9, - "color": "rose-300" - }, - { - "name": "Jee Adv", - "month": "May", - "day": 18, - "color": "teal-300" - }, - { - "name": "NEET", - "month": "May", - "day": 4, - "color": "orange-400" - }, - { - "name": "BITSAT", - "month": "May", - "day": 26, - "color": "orange-300" - }, - { - "name": "MHT-CET", - "month": "Apr", - "day": 9, - "color": "amber-300" - }, - { - "name": "WBJEE", - "month": "Apr", - "day": 27, - "color": "lime-400" - } - ] - } diff --git a/src/data/2026.json b/src/data/2026.json deleted file mode 100644 index 616a88c..0000000 --- a/src/data/2026.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "year": 2026, - "exams": [ - { - "name": "Jee Mains", - "month": "Jan", - "day": 24, - "color": "rose-300" - }, - { - "name": "Jee Adv", - "month": "May", - "day": 26, - "color": "teal-300" - }, - { - "name": "NEET", - "month": "May", - "day": 5, - "color": "orange-400" - }, - { - "name": "BITSAT", - "month": "May", - "day": 20, - "color": "orange-300" - }, - { - "name": "MHT-CET", - "month": "Apr", - "day": 22, - "color": "amber-300" - }, - { - "name": "WBJEE", - "month": "Apr", - "day": 28, - "color": "lime-400" - } - ] - } \ No newline at end of file diff --git a/src/data/exams.json b/src/data/exams.json new file mode 100644 index 0000000..442a75e --- /dev/null +++ b/src/data/exams.json @@ -0,0 +1,1032 @@ +{ + "exams": [ + { + "id": "jee-main-2026", + "name": "JEE Main", + "fullName": "Joint Entrance Examination Main", + "sessions": [ + { + "session": "Session 1", + "date": "2026-01-24" + }, + { + "session": "Session 2", + "date": "2026-04-08" + } + ], + "description": "National level engineering entrance exam for admission to NITs, IIITs, and other technical institutes", + "category": "Engineering", + "eligibility": "12th pass with PCM", + "subjects": [ + "Physics", + "Chemistry", + "Mathematics" + ], + "duration": "3 hours", + "conductingBody": "NTA", + "seats": "1,50,000+", + "officialWebsite": "https://jeemain.nta.nic.in/", + "relatedExams": [ + "jee-advanced", + "bitsat", + "wbjee", + "mhtcet" + ], + "slug": "jee-main", + "metaDescription": "JEE Main 2026 countdown timer. Track days, hours, minutes until JEE Main exam. Get exam dates and preparation tips.", + "keywords": [ + "JEE Main 2026", + "JEE Main countdown", + "JEE Main exam date", + "JEE preparation", + "NTA JEE" + ] + }, + { + "id": "jee-advanced-2026", + "name": "JEE Advanced", + "fullName": "Joint Entrance Examination Advanced", + "sessions": [ + { + "session": "Paper 1 & 2", + "date": "2026-05-17" + } + ], + "description": "Premier engineering entrance exam for admission to IITs and ISM Dhanbad", + "category": "Engineering", + "eligibility": "JEE Main qualified", + "subjects": [ + "Physics", + "Chemistry", + "Mathematics" + ], + "duration": "6 hours (2 papers)", + "conductingBody": "IIT (Rotating)", + "seats": "17,000+", + "officialWebsite": "https://jeeadv.ac.in/", + "relatedExams": [ + "jee-main", + "bitsat", + "kvpy" + ], + "slug": "jee-advanced", + "metaDescription": "JEE Advanced 2026 countdown timer. Track time until IIT JEE Advanced exam. Complete exam schedule and preparation guide.", + "keywords": [ + "JEE Advanced 2026", + "IIT JEE", + "JEE Advanced countdown", + "IIT admission", + "JEE Advanced exam date" + ] + }, + { + "id": "neet-ug-2026", + "name": "NEET UG", + "fullName": "National Eligibility cum Entrance Test (Undergraduate)", + "sessions": [ + { + "session": "Single Session", + "date": "2026-05-03" + } + ], + "description": "National medical entrance exam for MBBS, BDS, AYUSH courses", + "category": "Medical", + "eligibility": "12th pass with PCB", + "subjects": [ + "Physics", + "Chemistry", + "Biology" + ], + "duration": "3 hours 20 minutes", + "conductingBody": "NTA", + "seats": "1,08,000+", + "officialWebsite": "https://neet.nta.nic.in/", + "relatedExams": [ + "neet-pg", + "aiims", + "jipmer" + ], + "slug": "neet-ug", + "metaDescription": "NEET UG 2026 countdown timer. Track days until NEET medical entrance exam. Get exam pattern and prep tips.", + "keywords": [ + "NEET 2026", + "NEET UG countdown", + "medical entrance exam", + "MBBS admission", + "NEET exam date" + ] + }, + { + "id": "bitsat-2026", + "name": "BITSAT", + "fullName": "Birla Institute of Technology and Science Admission Test", + "sessions": [ + { + "session": "Session 1", + "date": "2026-05-15" + }, + { + "session": "Session 2", + "date": "2026-05-25" + }, + { + "session": "Session 3", + "date": "2026-06-05" + } + ], + "description": "Computer-based test for admission to BITS Pilani campuses", + "category": "Engineering", + "eligibility": "12th pass with PCM", + "subjects": [ + "Physics", + "Chemistry", + "Mathematics", + "English", + "Logical Reasoning" + ], + "duration": "3 hours", + "conductingBody": "BITS Pilani", + "seats": "4,000+", + "officialWebsite": "https://www.bitsadmission.com/", + "relatedExams": [ + "jee-main", + "viteee", + "comedk" + ], + "slug": "bitsat", + "metaDescription": "BITSAT 2026 countdown timer. Track time until BITS admission test. Complete exam guide and preparation strategy.", + "keywords": [ + "BITSAT 2026", + "BITS Pilani", + "BITSAT countdown", + "BITSAT exam date", + "BITS admission" + ] + }, + { + "id": "cat-2026", + "name": "CAT", + "fullName": "Common Admission Test", + "sessions": [ + { + "session": "Single Session", + "date": "2026-11-29" + } + ], + "description": "Premier MBA entrance exam for IIMs and top B-schools", + "category": "Management", + "eligibility": "Bachelor's degree", + "subjects": [ + "Verbal Ability", + "Data Interpretation", + "Quantitative Ability" + ], + "duration": "2 hours", + "conductingBody": "IIM (Rotating)", + "seats": "5,000+", + "officialWebsite": "https://iimcat.ac.in/", + "relatedExams": [ + "xat", + "snap", + "nmat" + ], + "slug": "cat", + "metaDescription": "CAT 2026 countdown timer. Track days until Common Admission Test. IIM MBA entrance exam dates and preparation guide.", + "keywords": [ + "CAT 2026", + "CAT countdown", + "IIM admission", + "MBA entrance exam", + "CAT exam date" + ] + }, + { + "id": "upsc-cse-2026", + "name": "UPSC CSE", + "fullName": "Union Public Service Commission Civil Services Examination", + "sessions": [ + { + "session": "Prelims", + "date": "2026-06-07" + }, + { + "session": "Mains", + "date": "2026-09-18" + }, + { + "session": "Interview", + "date": "2027-02-15" + } + ], + "description": "Premier civil services exam for IAS, IPS, IFS and other services", + "category": "Civil Services", + "eligibility": "Bachelor's degree", + "subjects": [ + "General Studies", + "Optional Subject", + "Essay" + ], + "duration": "Multi-stage exam", + "conductingBody": "UPSC", + "seats": "900+", + "relatedExams": [ + "ssc-cgl", + "ibps-po", + "sbi-po" + ], + "slug": "upsc-cse", + "metaDescription": "UPSC CSE 2026 countdown timer. Track time until Civil Services Examination. IAS, IPS exam dates and preparation strategy.", + "keywords": [ + "UPSC 2026", + "UPSC CSE countdown", + "IAS exam date", + "Civil Services", + "UPSC prelims" + ] + }, + { + "id": "gate-2026", + "name": "GATE", + "fullName": "Graduate Aptitude Test in Engineering", + "sessions": [ + { + "session": "Session 1", + "date": "2026-02-01" + }, + { + "session": "Session 2", + "date": "2026-02-02" + }, + { + "session": "Session 3", + "date": "2026-02-08" + }, + { + "session": "Session 4", + "date": "2026-02-09" + } + ], + "description": "National exam for M.Tech admission and PSU recruitment", + "category": "Engineering", + "eligibility": "B.Tech/B.E.", + "subjects": [ + "Engineering Mathematics", + "Core Subject", + "General Aptitude" + ], + "duration": "3 hours", + "conductingBody": "IIT/IISc (Rotating)", + "seats": "Variable", + "relatedExams": [ + "ugc-net", + "jee-main" + ], + "slug": "gate", + "metaDescription": "GATE 2026 countdown timer. Track days until Graduate Aptitude Test in Engineering. M.Tech admission and PSU jobs.", + "keywords": [ + "GATE 2026", + "GATE countdown", + "M.Tech admission", + "PSU jobs", + "GATE exam date" + ] + }, + { + "id": "ssc-cgl-2026", + "name": "SSC CGL", + "fullName": "Staff Selection Commission Combined Graduate Level", + "sessions": [ + { + "session": "Tier 1", + "date": "2026-07-20" + }, + { + "session": "Tier 2", + "date": "2026-10-15" + } + ], + "description": "Central government jobs exam for graduate level positions", + "category": "Government Jobs", + "eligibility": "Bachelor's degree", + "subjects": [ + "General Intelligence", + "General Awareness", + "Quantitative Aptitude", + "English" + ], + "duration": "Multi-tier exam", + "conductingBody": "SSC", + "seats": "8,000+", + "relatedExams": [ + "ssc-chsl", + "ibps-clerk", + "railway-ntpc" + ], + "slug": "ssc-cgl", + "metaDescription": "SSC CGL 2026 countdown timer. Track time until Staff Selection Commission exam. Central government jobs preparation.", + "keywords": [ + "SSC CGL 2026", + "SSC countdown", + "government jobs", + "SSC CGL exam date", + "central government" + ] + }, + { + "id": "neet-pg-2026", + "name": "NEET PG", + "fullName": "National Eligibility cum Entrance Test (Postgraduate)", + "sessions": [ + { + "session": "Single Session", + "date": "2026-03-15" + } + ], + "description": "Medical entrance exam for MD/MS/Diploma courses", + "category": "Medical", + "eligibility": "MBBS degree", + "subjects": [ + "Pre-clinical", + "Para-clinical", + "Clinical subjects" + ], + "duration": "3 hours 30 minutes", + "conductingBody": "NBE", + "seats": "50,000+", + "relatedExams": [ + "neet-ug", + "fmge", + "inicet" + ], + "slug": "neet-pg", + "metaDescription": "NEET PG 2026 countdown timer. Track days until NEET postgraduate medical entrance exam. MD/MS admission guide.", + "keywords": [ + "NEET PG 2026", + "NEET PG countdown", + "MD MS admission", + "medical PG entrance", + "NBE exam" + ] + }, + { + "id": "clat-2026", + "name": "CLAT", + "fullName": "Common Law Admission Test", + "sessions": [ + { + "session": "Single Session", + "date": "2026-05-10" + } + ], + "description": "National level law entrance exam for NLUs", + "category": "Law", + "eligibility": "12th pass for UG, LLB for PG", + "subjects": [ + "English", + "General Knowledge", + "Legal Reasoning", + "Logical Reasoning", + "Mathematics" + ], + "duration": "2 hours", + "conductingBody": "Consortium of NLUs", + "seats": "2,500+", + "relatedExams": [ + "ailet", + "lsat-india" + ], + "slug": "clat", + "metaDescription": "CLAT 2026 countdown timer. Track time until Common Law Admission Test. NLU law admission exam dates and prep guide.", + "keywords": [ + "CLAT 2026", + "CLAT countdown", + "law entrance exam", + "NLU admission", + "legal studies" + ] + }, + { + "id": "wbjee-2026", + "name": "WBJEE", + "fullName": "West Bengal Joint Entrance Examination", + "sessions": [ + { + "session": "Single Session", + "date": "2026-04-27" + } + ], + "description": "State level engineering and medical entrance exam for West Bengal", + "category": "Engineering", + "eligibility": "12th pass with PCM/PCB", + "subjects": [ + "Physics", + "Chemistry", + "Mathematics/Biology" + ], + "duration": "2 hours per paper", + "conductingBody": "WBJEEB", + "seats": "60,000+", + "relatedExams": [ + "jee-main", + "bitsat", + "kcet" + ], + "slug": "wbjee", + "metaDescription": "WBJEE 2026 countdown timer. Track days until West Bengal Joint Entrance Examination. State engineering admission.", + "keywords": [ + "WBJEE 2026", + "WBJEE countdown", + "West Bengal engineering", + "state entrance exam", + "WBJEEB" + ] + }, + { + "id": "mhtcet-2026", + "name": "MHTCET", + "fullName": "Maharashtra Health and Technical Common Entrance Test", + "sessions": [ + { + "session": "PCM Session", + "date": "2026-05-05" + }, + { + "session": "PCB Session", + "date": "2026-05-12" + } + ], + "description": "State level exam for engineering and medical courses in Maharashtra", + "category": "Engineering", + "eligibility": "12th pass with PCM/PCB", + "subjects": [ + "Physics", + "Chemistry", + "Mathematics/Biology" + ], + "duration": "90 minutes per paper", + "conductingBody": "State CET Cell, Maharashtra", + "seats": "1,50,000+", + "relatedExams": [ + "jee-main", + "neet-ug", + "kcet" + ], + "slug": "mhtcet", + "metaDescription": "MHTCET 2026 countdown timer. Track time until Maharashtra CET. State engineering and medical admission exam.", + "keywords": [ + "MHTCET 2026", + "Maharashtra CET", + "MHTCET countdown", + "state entrance exam", + "Maharashtra admission" + ] + }, + { + "id": "kcet-2026", + "name": "KCET", + "fullName": "Karnataka Common Entrance Test", + "sessions": [ + { + "session": "Single Session", + "date": "2026-04-18" + } + ], + "description": "State level entrance exam for Karnataka engineering colleges", + "category": "Engineering", + "eligibility": "12th pass with PCM", + "subjects": [ + "Physics", + "Chemistry", + "Mathematics", + "Biology" + ], + "duration": "80 minutes per paper", + "conductingBody": "KEA", + "seats": "80,000+", + "relatedExams": [ + "comedk", + "jee-main", + "viteee" + ], + "slug": "kcet", + "metaDescription": "KCET 2026 countdown timer. Track days until Karnataka Common Entrance Test. State engineering admission guide.", + "keywords": [ + "KCET 2026", + "Karnataka CET", + "KCET countdown", + "Karnataka engineering", + "KEA exam" + ] + }, + { + "id": "comedk-2026", + "name": "COMEDK UGET", + "fullName": "Consortium of Medical, Engineering and Dental Colleges of Karnataka", + "sessions": [ + { + "session": "Single Session", + "date": "2026-05-08" + } + ], + "description": "Entrance exam for private engineering colleges in Karnataka", + "category": "Engineering", + "eligibility": "12th pass with PCM", + "subjects": [ + "Physics", + "Chemistry", + "Mathematics" + ], + "duration": "3 hours", + "conductingBody": "COMEDK", + "seats": "25,000+", + "relatedExams": [ + "kcet", + "viteee", + "bitsat" + ], + "slug": "comedk", + "metaDescription": "COMEDK UGET 2026 countdown timer. Track time until Karnataka private engineering admission exam.", + "keywords": [ + "COMEDK 2026", + "Karnataka private engineering", + "COMEDK countdown", + "UGET exam" + ] + }, + { + "id": "viteee-2026", + "name": "VITEEE", + "fullName": "VIT Engineering Entrance Examination", + "sessions": [ + { + "session": "Single Session", + "date": "2026-04-12" + } + ], + "description": "Entrance exam for VIT campuses across India", + "category": "Engineering", + "eligibility": "12th pass with PCM", + "subjects": [ + "Physics", + "Chemistry", + "Mathematics", + "English" + ], + "duration": "2.5 hours", + "conductingBody": "VIT University", + "seats": "10,000+", + "relatedExams": [ + "bitsat", + "comedk", + "srmjeee" + ], + "slug": "viteee", + "metaDescription": "VITEEE 2026 countdown timer. Track days until VIT Engineering Entrance Examination. VIT admission guide.", + "keywords": [ + "VITEEE 2026", + "VIT entrance exam", + "VITEEE countdown", + "VIT admission", + "VIT Vellore" + ] + }, + { + "id": "xat-2026", + "name": "XAT", + "fullName": "Xavier Aptitude Test", + "sessions": [ + { + "session": "Single Session", + "date": "2026-01-05" + } + ], + "description": "MBA entrance exam for XLRI and other Xavier schools", + "category": "Management", + "eligibility": "Bachelor's degree", + "subjects": [ + "Verbal Ability", + "Decision Making", + "Quantitative Ability", + "Data Interpretation" + ], + "duration": "3 hours", + "conductingBody": "XLRI Jamshedpur", + "seats": "2,000+", + "relatedExams": [ + "cat", + "snap", + "iift" + ], + "slug": "xat", + "metaDescription": "XAT 2026 countdown timer. Track time until Xavier Aptitude Test. XLRI MBA admission exam dates and preparation.", + "keywords": [ + "XAT 2026", + "Xavier Aptitude Test", + "XAT countdown", + "XLRI admission", + "MBA entrance" + ] + }, + { + "id": "snap-2026", + "name": "SNAP", + "fullName": "Symbiosis National Aptitude Test", + "sessions": [ + { + "session": "Single Session", + "date": "2026-12-20" + } + ], + "description": "MBA entrance exam for Symbiosis International University", + "category": "Management", + "eligibility": "Bachelor's degree", + "subjects": [ + "General English", + "Quantitative Ability", + "Current Affairs", + "Analytical Reasoning" + ], + "duration": "1 hour", + "conductingBody": "Symbiosis International University", + "seats": "1,500+", + "relatedExams": [ + "cat", + "xat", + "mat" + ], + "slug": "snap", + "metaDescription": "SNAP 2026 countdown timer. Track days until Symbiosis National Aptitude Test. MBA admission guide.", + "keywords": [ + "SNAP 2026", + "Symbiosis MBA", + "SNAP countdown", + "SIU admission", + "management entrance" + ] + }, + { + "id": "nmat-2026", + "name": "NMAT", + "fullName": "NMIMS Management Aptitude Test", + "sessions": [ + { + "session": "Session 1", + "date": "2026-10-04" + }, + { + "session": "Session 2", + "date": "2026-11-15" + }, + { + "session": "Session 3", + "date": "2026-12-10" + } + ], + "description": "MBA entrance exam for NMIMS and other B-schools", + "category": "Management", + "eligibility": "Bachelor's degree", + "subjects": [ + "Language Skills", + "Quantitative Skills", + "Logical Reasoning" + ], + "duration": "2 hours", + "conductingBody": "GMAC", + "seats": "3,000+", + "relatedExams": [ + "cat", + "snap", + "cmat" + ], + "slug": "nmat", + "metaDescription": "NMAT 2026 countdown timer. Track time until NMIMS Management Aptitude Test. MBA admission exam guide.", + "keywords": [ + "NMAT 2026", + "NMIMS MBA", + "NMAT countdown", + "GMAC exam", + "business school" + ] + }, + { + "id": "ibps-po-2026", + "name": "IBPS PO", + "fullName": "Institute of Banking Personnel Selection Probationary Officer", + "sessions": [ + { + "session": "Prelims", + "date": "2026-10-15" + }, + { + "session": "Mains", + "date": "2026-11-28" + } + ], + "description": "Banking exam for Probationary Officer positions in public sector banks", + "category": "Banking", + "eligibility": "Bachelor's degree", + "subjects": [ + "Reasoning", + "Quantitative Aptitude", + "English", + "General Awareness", + "Computer" + ], + "duration": "Multi-phase exam", + "conductingBody": "IBPS", + "seats": "4,000+", + "relatedExams": [ + "sbi-po", + "ibps-clerk", + "rbi-grade-b" + ], + "slug": "ibps-po", + "metaDescription": "IBPS PO 2026 countdown timer. Track days until banking PO exam. Public sector bank officer recruitment.", + "keywords": [ + "IBPS PO 2026", + "banking exam", + "IBPS countdown", + "bank PO", + "probationary officer" + ] + }, + { + "id": "sbi-po-2026", + "name": "SBI PO", + "fullName": "State Bank of India Probationary Officer", + "sessions": [ + { + "session": "Prelims", + "date": "2026-04-30" + }, + { + "session": "Mains", + "date": "2026-06-15" + } + ], + "description": "Premier banking exam for SBI Probationary Officer recruitment", + "category": "Banking", + "eligibility": "Bachelor's degree", + "subjects": [ + "Reasoning", + "Quantitative Aptitude", + "English", + "General Awareness", + "Data Analysis" + ], + "duration": "Multi-phase exam", + "conductingBody": "SBI", + "seats": "2,000+", + "relatedExams": [ + "ibps-po", + "sbi-clerk", + "rbi-assistant" + ], + "slug": "sbi-po", + "metaDescription": "SBI PO 2026 countdown timer. Track time until State Bank of India PO exam. Banking career preparation guide.", + "keywords": [ + "SBI PO 2026", + "SBI probationary officer", + "SBI countdown", + "banking exam", + "state bank" + ] + }, + { + "id": "railway-ntpc-2026", + "name": "Railway NTPC", + "fullName": "Railway Non-Technical Popular Categories", + "sessions": [ + { + "session": "CBT 1", + "date": "2026-03-01" + }, + { + "session": "CBT 2", + "date": "2026-05-20" + } + ], + "description": "Railway recruitment exam for non-technical posts", + "category": "Railway", + "eligibility": "12th pass to Graduate", + "subjects": [ + "General Awareness", + "Mathematics", + "General Intelligence", + "General Science" + ], + "duration": "90 minutes", + "conductingBody": "Railway Recruitment Board", + "seats": "35,000+", + "relatedExams": [ + "railway-group-d", + "ssc-cgl", + "ssc-chsl" + ], + "slug": "railway-ntpc", + "metaDescription": "Railway NTPC 2026 countdown timer. Track days until Railway non-technical exam. RRB NTPC preparation guide.", + "keywords": [ + "Railway NTPC 2026", + "RRB NTPC countdown", + "railway jobs", + "NTPC exam", + "railway recruitment" + ] + }, + { + "id": "ugc-net-2026", + "name": "UGC NET", + "fullName": "University Grants Commission National Eligibility Test", + "sessions": [ + { + "session": "June Session", + "date": "2026-06-25" + }, + { + "session": "December Session", + "date": "2026-12-15" + } + ], + "description": "National exam for Assistant Professor and JRF eligibility", + "category": "Teaching", + "eligibility": "Master's degree", + "subjects": [ + "Paper I (Teaching Aptitude)", + "Paper II (Subject Specific)" + ], + "duration": "3 hours", + "conductingBody": "NTA", + "seats": "Variable", + "relatedExams": [ + "csir-net", + "gate", + "set" + ], + "slug": "ugc-net", + "metaDescription": "UGC NET 2026 countdown timer. Track time until NET exam for Assistant Professor eligibility. Teaching career guide.", + "keywords": [ + "UGC NET 2026", + "NET countdown", + "assistant professor", + "JRF exam", + "teaching eligibility" + ] + }, + { + "id": "afcat-2026", + "name": "AFCAT", + "fullName": "Air Force Common Admission Test", + "sessions": [ + { + "session": "AFCAT 1", + "date": "2026-02-21" + }, + { + "session": "AFCAT 2", + "date": "2026-08-28" + } + ], + "description": "Indian Air Force officer recruitment exam", + "category": "Defence", + "eligibility": "Bachelor's degree", + "subjects": [ + "General Awareness", + "Verbal Ability", + "Numerical Ability", + "Reasoning" + ], + "duration": "2 hours", + "conductingBody": "Indian Air Force", + "seats": "300+", + "relatedExams": [ + "nda", + "cds", + "inet" + ], + "slug": "afcat", + "metaDescription": "AFCAT 2026 countdown timer. Track days until Air Force Common Admission Test. IAF officer recruitment guide.", + "keywords": [ + "AFCAT 2026", + "Air Force exam", + "AFCAT countdown", + "IAF officer", + "defence recruitment" + ] + }, + { + "id": "nda-2026", + "name": "NDA", + "fullName": "National Defence Academy", + "sessions": [ + { + "session": "NDA 1", + "date": "2026-04-19" + }, + { + "session": "NDA 2", + "date": "2026-09-06" + } + ], + "description": "Joint defence services exam for Army, Navy, and Air Force", + "category": "Defence", + "eligibility": "12th pass (unmarried male)", + "subjects": [ + "Mathematics", + "General Ability Test" + ], + "duration": "5 hours (2 papers)", + "conductingBody": "UPSC", + "seats": "400+", + "relatedExams": [ + "cds", + "afcat", + "inet" + ], + "slug": "nda", + "metaDescription": "NDA 2026 countdown timer. Track time until National Defence Academy exam. Military academy admission guide.", + "keywords": [ + "NDA 2026", + "National Defence Academy", + "NDA countdown", + "military academy", + "defence services" + ] + }, + { + "id": "aiims-2026", + "name": "AIIMS", + "fullName": "All Institute of Medical Sciences", + "sessions": [ + { + "session": "Single Session", + "date": "2026-05-25" + } + ], + "description": "Medical entrance exam for AIIMS institutes (if conducted separately)", + "category": "Medical", + "eligibility": "12th pass with PCB", + "subjects": [ + "Physics", + "Chemistry", + "Biology", + "General Knowledge", + "Logical Thinking" + ], + "duration": "3.5 hours", + "conductingBody": "AIIMS", + "seats": "1,000+", + "relatedExams": [ + "neet-ug", + "jipmer", + "neet-pg" + ], + "slug": "aiims", + "metaDescription": "AIIMS 2026 countdown timer. Track days until AIIMS medical entrance exam. Premier medical institute admission.", + "keywords": [ + "AIIMS 2026", + "AIIMS entrance exam", + "AIIMS countdown", + "medical entrance", + "MBBS admission" + ] + }, + { + "id": "jipmer-2026", + "name": "JIPMER", + "fullName": "Jawaharlal Institute of Postgraduate Medical Education and Research", + "sessions": [ + { + "session": "Single Session", + "date": "2026-06-02" + } + ], + "description": "Medical entrance exam for JIPMER Puducherry and Karaikal", + "category": "Medical", + "eligibility": "12th pass with PCB", + "subjects": [ + "Physics", + "Chemistry", + "Biology", + "English", + "Logical Reasoning" + ], + "duration": "2.5 hours", + "conductingBody": "JIPMER", + "seats": "200+", + "relatedExams": [ + "neet-ug", + "aiims", + "keam-medical" + ], + "slug": "jipmer", + "metaDescription": "JIPMER 2026 countdown timer. Track time until JIPMER medical entrance exam. Puducherry medical college admission.", + "keywords": [ + "JIPMER 2026", + "JIPMER entrance", + "JIPMER countdown", + "Puducherry medical", + "medical entrance" + ] + } + ] +} \ No newline at end of file diff --git a/src/data/exams.ts b/src/data/exams.ts new file mode 100644 index 0000000..12f39e1 --- /dev/null +++ b/src/data/exams.ts @@ -0,0 +1,108 @@ +export const indianExams = [ + { + id: 'jee-main-2026', + name: 'JEE Main', + fullName: 'Joint Entrance Examination Main', + date: '2026-01-24', + description: 'National level engineering entrance exam for admission to NITs, IIITs, and other technical institutes', + category: 'Engineering', + difficulty: 'Hard', + eligibility: '12th pass with PCM', + subjects: ['Physics', 'Chemistry', 'Mathematics'], + duration: '3 hours', + conductingBody: 'NTA', + applicationFee: 'โน650-โน3000', + seats: '1,50,000+', + relatedExams: ['jee-advanced-2026', 'bitsat-2026', 'wbjee-2026', 'mhtcet-2026'], + slug: 'jee-main', + metaDescription: 'JEE Main 2026 countdown timer. Track days, hours, minutes until JEE Main exam. Get exam dates, syllabus, and preparation tips.', + keywords: ['JEE Main 2026', 'JEE Main countdown', 'JEE Main exam date', 'JEE preparation', 'NTA JEE'] + }, + { + id: 'jee-advanced-2026', + name: 'JEE Advanced', + fullName: 'Joint Entrance Examination Advanced', + date: '2026-05-17', + description: 'Premier engineering entrance exam for admission to IITs and ISM Dhanbad', + category: 'Engineering', + difficulty: 'Very Hard', + eligibility: 'JEE Main qualified', + subjects: ['Physics', 'Chemistry', 'Mathematics'], + duration: '6 hours (2 papers)', + conductingBody: 'IIT (Rotating)', + applicationFee: 'โน2800', + seats: '17,000+', + relatedExams: ['jee-main-2026', 'bitsat-2026', 'kvpy-2026'], + slug: 'jee-advanced', + metaDescription: 'JEE Advanced 2026 countdown timer. Track time until IIT JEE Advanced exam. Complete exam schedule and preparation guide.', + keywords: ['JEE Advanced 2026', 'IIT JEE', 'JEE Advanced countdown', 'IIT admission', 'JEE Advanced exam date'] + }, + { + id: 'neet-ug-2026', + name: 'NEET UG', + fullName: 'National Eligibility cum Entrance Test (Undergraduate)', + date: '2026-05-03', + description: 'National medical entrance exam for MBBS, BDS, AYUSH courses', + category: 'Medical', + difficulty: 'Hard', + eligibility: '12th pass with PCB', + subjects: ['Physics', 'Chemistry', 'Biology'], + duration: '3 hours 20 minutes', + conductingBody: 'NTA', + applicationFee: 'โน1600-โน8000', + seats: '1,08,000+', + relatedExams: ['neet-pg-2026', 'aiims-2026', 'jipmer-2026'], + slug: 'neet-ug', + metaDescription: 'NEET UG 2026 countdown timer. Track days until NEET medical entrance exam. Get exam pattern, syllabus, and prep tips.', + keywords: ['NEET 2026', 'NEET UG countdown', 'medical entrance exam', 'MBBS admission', 'NEET exam date'] + }, + { + id: 'bitsat-2026', + name: 'BITSAT', + fullName: 'Birla Institute of Technology and Science Admission Test', + date: '2026-06-15', + description: 'Computer-based test for admission to BITS Pilani campuses', + category: 'Engineering', + difficulty: 'Hard', + eligibility: '12th pass with PCM', + subjects: ['Physics', 'Chemistry', 'Mathematics', 'English', 'Logical Reasoning'], + duration: '3 hours', + conductingBody: 'BITS Pilani', + applicationFee: 'โน3400', + seats: '4,000+', + relatedExams: ['jee-main-2026', 'viteee-2026', 'comedk-2026'], + slug: 'bitsat', + metaDescription: 'BITSAT 2026 countdown timer. Track time until BITS admission test. Complete exam guide and preparation strategy.', + keywords: ['BITSAT 2026', 'BITS Pilani', 'BITSAT countdown', 'BITSAT exam date', 'BITS admission'] + }, + { + id: 'cat-2026', + name: 'CAT', + fullName: 'Common Admission Test', + date: '2026-11-29', + description: 'Premier MBA entrance exam for IIMs and top B-schools', + category: 'Management', + difficulty: 'Very Hard', + eligibility: 'Bachelor\'s degree', + subjects: ['Verbal Ability', 'Data Interpretation', 'Quantitative Ability'], + duration: '2 hours', + conductingBody: 'IIM (Rotating)', + applicationFee: 'โน2300-โน4600', + seats: '5,000+', + relatedExams: ['xat-2026', 'snap-2026', 'nmat-2026'], + slug: 'cat', + metaDescription: 'CAT 2026 countdown timer. Track days until Common Admission Test. IIM MBA entrance exam dates and preparation guide.', + keywords: ['CAT 2026', 'CAT countdown', 'IIM admission', 'MBA entrance exam', 'CAT exam date'] + } + // Add more exams as needed... +]; + +export function getExamBySlug(slug: string) { + return indianExams.find(exam => exam.slug === slug); +} + +export function getRelatedExams(currentExamSlug: string, category: string) { + return indianExams + .filter(exam => exam.slug !== currentExamSlug && exam.category === category) + .slice(0, 4); +} diff --git a/src/env.d.ts b/src/env.d.ts deleted file mode 100644 index f964fe0..0000000 --- a/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -///diff --git a/src/global.css b/src/global.css deleted file mode 100644 index b5c61c9..0000000 --- a/src/global.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index ee7d086..d0b116c 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -1,52 +1,131 @@ --- export interface Props { - title: string; - description: string; + title?: string; + description?: string; + keywords?: string[]; + exam?: any; } -const { title } = Astro.props; -const { description } = Astro.props; -import Head from "../components/Head.astro"; -import Footer from "../components/Footer.astro"; + +const { + title = "TimeKeeper - Indian Exams Countdown 2026", + description = "Ultimate countdown app for Indian competitive exams. Track JEE Mains, JEE Advanced, NEET, BITSAT, MHTCET, WBJEE and 25+ major Indian exams with real-time countdown timers.", + keywords = ["Indian exams 2026", "JEE countdown", "NEET countdown", "exam timer", "competitive exams", "entrance exams"], + exam +} = Astro.props; + +const dynamicTitle = exam ? `${exam.name} 2026 Countdown Timer | ${exam.fullName} Exam Date` : title; +const dynamicDescription = exam ? exam.metaDescription : description; +const dynamicKeywords = exam ? exam.keywords.join(", ") : keywords.join(", "); + +// Import PiP Widget +import PictureInPictureWidget from '../components/PictureInPictureWidget.astro'; --- - + - - - - - - {title} -- Exam Countdown & TimerKeeper for Exams like JEE, NEET, BITSAT 2023/2024/2025 - - - - -- - + + + + + + + + {dynamicTitle} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {exam && ( + + )} + + + + + + ++ + + + + + + + diff --git a/src/pages/custom/countdown.astro b/src/pages/custom/countdown.astro deleted file mode 100644 index 4f8a2ea..0000000 --- a/src/pages/custom/countdown.astro +++ /dev/null @@ -1,64 +0,0 @@ ---- -import Layout from "../../layouts/Layout.astro"; -import Logic from "../../components/customcountdown.svelte"; -import Minibar from "../../components/custom-minibar.svelte"; -import CustomMinibar from "../../components/custom-minibar.svelte"; ---- - - Custom Countdown | JeeNeeTards TimeKeeper - -- - --Editable
- -- --- Days -- -- Hours -- -- Minutes -- -- Seconds ----- -
-- -- \ No newline at end of file diff --git a/src/pages/eleventh/eleventh.astro b/src/pages/eleventh/eleventh.astro deleted file mode 100644 index 1acdf69..0000000 --- a/src/pages/eleventh/eleventh.astro +++ /dev/null @@ -1,135 +0,0 @@ ---- -import Head from "../../components/Head.astro"; -import Elevenfooter from "../../components/elevenfooter.astro"; -import Eleventimer from "../../components/eleventimer.svelte"; -import Elevenminibar from "../../components/elevenminibar.svelte"; -import "../../../src/global.css"; ---- - - - - - - - - - - - -- Start --- - -- -
--- ---- JEE Mains 2026 -
---- --- - - --
- -- - - - diff --git a/src/pages/exams/[slug].astro b/src/pages/exams/[slug].astro new file mode 100644 index 0000000..fb34db0 --- /dev/null +++ b/src/pages/exams/[slug].astro @@ -0,0 +1,639 @@ +--- +import Layout from '../../layouts/Layout.astro'; +import examData from '../../data/exams.json'; + +export async function getStaticPaths() { + const exams = examData.exams; + + return exams.map((exam) => ({ + params: { slug: exam.slug }, + props: { exam }, + })); +} + +interface ExamSession { + session: string; + date: string; +} + +interface ExamData { + id: string; + name: string; + fullName: string; + sessions: ExamSession[]; + description: string; + category: string; + eligibility: string; + subjects: string[]; + duration: string; + conductingBody: string; + seats: string; + officialWebsite?: string; + relatedExams: string[]; + slug: string; + metaDescription: string; + keywords: string[]; +} + +const { exam } = Astro.props as { exam: ExamData }; +const allExams: ExamData[] = examData.exams; + +// Get related exams data +const relatedExamsData = exam.relatedExams + .map(slug => allExams.find(e => e.slug === slug)) + .filter(Boolean) + .slice(0, 4); +--- + + + + + + + + + diff --git a/src/pages/exams/bitsat.astro b/src/pages/exams/bitsat.astro deleted file mode 100644 index e13fc64..0000000 --- a/src/pages/exams/bitsat.astro +++ /dev/null @@ -1,27 +0,0 @@ ---- -import Timer from "../../components/timer.astro"; -import "../../../src/global.css"; -import Minibar from "../../components/minibar.svelte"; -import Layout from "../../layouts/Layout.astro"; ---- - - -+ + + ++ + ++ ++++ + + {exam.sessions.length > 1 && ( +{exam.name}
+{exam.fullName}
+ ++ + {exam.category} + + + + {exam.conductingBody} + ++ +{exam.description}
+++ )} + + ++ {exam.sessions.map((session, index) => ( + + ))} ++++ + +++++ ++++ {exam.sessions[0].session} +
++ {new Date(exam.sessions[0].date).toLocaleDateString('en-IN', { + year: 'numeric', + month: 'long', + day: 'numeric', + weekday: 'long' + })} +
++ + +++ +++ ++ + + {relatedExamsData.length > 0 && ( +++ + +Basic Information
++++ Duration: + {exam.duration} +++ Eligibility: + {exam.eligibility} +++ Total Seats: + {exam.seats} +++ Sessions: + {exam.sessions.length} ++++ + +Subjects
++ {exam.subjects.map((subject) => ( + + {subject} + + ))} ++++Quick Links
++ {exam.officialWebsite && ( + + Official Website โ + + )} + ++++ )} + + +Related Exams
++ {relatedExamsData.map((relatedExam) => ( + ++{relatedExam.name}
+{relatedExam.category}
+View Details โ+ + ))} ++++ ++++Disclaimer
++ Please verify exam dates from {exam.conductingBody}'s official website. + This countdown is for reference only. +
+BITSAT | 2025 | Countdown | TimeKeeper - -- \ No newline at end of file diff --git a/src/pages/exams/jeeadvanced.astro b/src/pages/exams/jeeadvanced.astro deleted file mode 100644 index f6ba319..0000000 --- a/src/pages/exams/jeeadvanced.astro +++ /dev/null @@ -1,26 +0,0 @@ ---- -import Timer from "../../components/timer.astro"; -import "../../../src/global.css"; -import Minibar from "../../components/minibar.svelte"; -import Layout from "../../layouts/Layout.astro"; ---- - - -
-- - -- ---- BITSAT 25 -
- -
-- - JEE Advanced | 2025 | Countdown | TimeKeeper - - -- \ No newline at end of file diff --git a/src/pages/exams/jeemains.astro b/src/pages/exams/jeemains.astro deleted file mode 100644 index f751fef..0000000 --- a/src/pages/exams/jeemains.astro +++ /dev/null @@ -1,28 +0,0 @@ ---- -import Timer from "../../components/timer.astro"; -import Layout from "../../layouts/Layout.astro"; -import "../../../src/global.css"; -import Minibar from "../../components/minibar.svelte"; ---- - - -- -- ---- Jee Adv 25 -
- -
-- - JEE Mains | 2025 | Countdown | TimeKeeper - -- diff --git a/src/pages/exams/mhtcet.astro b/src/pages/exams/mhtcet.astro deleted file mode 100644 index f3f31ff..0000000 --- a/src/pages/exams/mhtcet.astro +++ /dev/null @@ -1,28 +0,0 @@ ---- -import Timer from "../../components/timer.astro"; -import "../../../src/global.css"; -import Minibar from "../../components/minibar.svelte"; -import Layout from "../../layouts/Layout.astro"; ---- - - -- -- ---- Jee Mains
-
-- (01st Apr 25) -
- -
-- - MHT-CET (MHTCET) | 2025 | Countdown | TimeKeeper - - -- \ No newline at end of file diff --git a/src/pages/exams/neet.astro b/src/pages/exams/neet.astro deleted file mode 100644 index 5858710..0000000 --- a/src/pages/exams/neet.astro +++ /dev/null @@ -1,28 +0,0 @@ ---- -import Timer from "../../components/timer.astro"; -import "../../../src/global.css"; -import Minibar from "../../components/minibar.svelte"; -import Layout from "../../layouts/Layout.astro"; ---- - - -
-- - -- ---- MHT-CET 25 -
- -
-- - NEET | 2025 | Countdown | TimeKeeper - -- \ No newline at end of file diff --git a/src/pages/index.astro b/src/pages/index.astro index cc15313..b54e0fd 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,27 +1,373 @@ --- -import Layout from "../layouts/Layout.astro"; -import Cardx from "../components/cardx.astro"; -import "../global.css"; +import Layout from '../layouts/Layout.astro'; +import examData from '../data/exams.json'; + +interface ExamSession { + session: string; + date: string; +} + +interface ExamData { + id: string; + name: string; + fullName: string; + sessions: ExamSession[]; + description: string; + category: string; + eligibility: string; + subjects: string[]; + duration: string; + conductingBody: string; + seats: string; + relatedExams: string[]; + slug: string; + metaDescription: string; + keywords: string[]; +} + +const indianExams: ExamData[] = examData.exams; --- -- -- ---- Neet
-
-- (5th May 25) -
- -
-- - - +- -Welcome to TimeKeeper
-- + diff --git a/src/pages/sitemap.xml.astro b/src/pages/sitemap.xml.astro new file mode 100644 index 0000000..e57b4f4 --- /dev/null +++ b/src/pages/sitemap.xml.astro @@ -0,0 +1,22 @@ +--- +import examData from '../data/exams.json'; + +const exams = examData.exams; +const baseUrl = 'https://exam-timekeeper.pages.dev'; // Replace with your actual domain + +const pages = [ + '', + ...exams.map(exam => `exams/${exam.slug}`) +]; +--- + ++ + + + ++ + ++ - ++ + + ++++ +++ {pages.map(page => ( + diff --git a/svelte.config.js b/svelte.config.js deleted file mode 100644 index cbaee33..0000000 --- a/svelte.config.js +++ /dev/null @@ -1,5 +0,0 @@ -import { vitePreprocess } from '@astrojs/svelte'; - -export default { - preprocess: vitePreprocess(), -}; diff --git a/tailwind.config.cjs b/tailwind.config.cjs deleted file mode 100644 index 0280c54..0000000 --- a/tailwind.config.cjs +++ /dev/null @@ -1,28 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue,svelte}'], - theme: { - extend: {}, - screens: { - 'sm': {'max': '386px'}, - 'md': {'max': '586px'}, - 'lg': {'min': '586px', 'max': '786px'}, - 'xl': {'min': '786px'}, - }, - }, - plugins: [require("daisyui")], - daisyui: { - themes: [ - "halloween", // Default - "cupcake", - "retro", - "coffee", - "forest", - "light", - "valentine", - "nord", - "lemonade", - "pastel" - ], - }, -} diff --git a/tailwind.config.mjs b/tailwind.config.mjs new file mode 100644 index 0000000..7709919 --- /dev/null +++ b/tailwind.config.mjs @@ -0,0 +1,13 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], + theme: { + extend: { + fontFamily: { + 'serif': ['Playfair Display', 'serif'], + 'sans': ['Inter', 'sans-serif'], + }, + }, + }, + plugins: [], +} diff --git a/tsconfig.json b/tsconfig.json index d8a3e74..1ef682e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,10 @@ { - "extends": "astro/tsconfigs/base", + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"], "compilerOptions": { - "jsx": "preserve", - "jsxImportSource": "solid-js" + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "resolveJsonModule": true } -} \ No newline at end of file +} From 485812cc5c162805c9a335a5b31afd0392b41f40 Mon Sep 17 00:00:00 2001 From: Kewonit <108450560+kewonit@users.noreply.github.com.> Date: Sun, 29 Jun 2025 03:04:45 +0530 Subject: [PATCH 02/17] refactor: streamline Picture-in-Picture status handling and cleanup --- src/components/PictureInPictureWidget.astro | 26 +++++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/components/PictureInPictureWidget.astro b/src/components/PictureInPictureWidget.astro index 127cbd3..cf39773 100644 --- a/src/components/PictureInPictureWidget.astro +++ b/src/components/PictureInPictureWidget.astro @@ -412,9 +412,7 @@ class PictureInPictureCountdown { // Start PiP await this.video.requestPictureInPicture(); - // Show status indicator - this.showStatusIndicator(); - + // Status indicator will be shown by the 'enterpictureinpicture' event // Start countdown updates this.startUpdating(); @@ -469,6 +467,7 @@ class PictureInPictureCountdown { await document.exitPictureInPicture(); } + // Always reset state and hide indicator this.isActive = false; this.hideStatusIndicator(); this.stopUpdating(); @@ -483,8 +482,14 @@ class PictureInPictureCountdown { this.stream.getTracks().forEach(track => track.stop()); this.stream = null; } + + console.log('PiP stopped and cleaned up'); } catch (error) { console.error('Failed to stop Picture-in-Picture:', error); + // Even if there's an error, make sure we clean up + this.isActive = false; + this.hideStatusIndicator(); + this.stopUpdating(); } } @@ -690,6 +695,7 @@ class PictureInPictureCountdown { const status = document.getElementById('pip-status'); if (status) { status.classList.remove('active'); + console.log('PiP status indicator hidden'); } } @@ -762,14 +768,24 @@ window.closePiP = async () => { // Handle PiP events document.addEventListener('enterpictureinpicture', () => { - console.log('Entered Picture-in-Picture mode'); + window.pipCountdown.showStatusIndicator(); }); document.addEventListener('leavepictureinpicture', () => { - console.log('Left Picture-in-Picture mode'); window.pipCountdown.hideStatusIndicator(); window.pipCountdown.isActive = false; window.pipCountdown.stopUpdating(); + + // Clean up video stream when PiP is closed + if (window.pipCountdown.video) { + window.pipCountdown.video.srcObject = null; + window.pipCountdown.video = null; + } + + if (window.pipCountdown.stream) { + window.pipCountdown.stream.getTracks().forEach(track => track.stop()); + window.pipCountdown.stream = null; + } }); // Keyboard shortcuts From 559d8674311350d94cec4e1491a50698eb797f7f Mon Sep 17 00:00:00 2001 From: Kewonit <108450560+kewonit@users.noreply.github.com.> Date: Sun, 29 Jun 2025 04:20:23 +0530 Subject: [PATCH 03/17] Enhance UI theming and countdown display for exam pages - Updated styles for countdown components to improve visibility and aesthetics. - Introduced theme-aware classes for consistent styling across the application. - Refactored sidebar and main content layout for better responsiveness. - Added theme toggle options for user customization. - Improved accessibility and readability of exam details and navigation buttons. --- src/components/JsonLD.astro | 42 ++ src/components/PictureInPictureWidget.astro | 8 - src/layouts/Layout.astro | 584 +++++++++++++++++--- src/pages/exams/[slug].astro | 278 ++++++---- src/pages/index.astro | 186 ++++--- 5 files changed, 809 insertions(+), 289 deletions(-) create mode 100644 src/components/JsonLD.astro diff --git a/src/components/JsonLD.astro b/src/components/JsonLD.astro new file mode 100644 index 0000000..2523f99 --- /dev/null +++ b/src/components/JsonLD.astro @@ -0,0 +1,42 @@ +--- +// src/components/JsonLD.astro +import type { Props as LayoutProps } from "../layouts/Layout.astro"; + +interface Props extends LayoutProps {} + +const { exam } = Astro.props; + +let eventSchema = {}; + +if (exam) { + const firstSession = exam.sessions[0]; + if (firstSession) { + eventSchema = { + "@context": "https://schema.org", + "@type": "Event", + "name": `${exam.name} ${new Date(firstSession.date).getFullYear()}`, + "description": `Countdown timer and details for the ${exam.fullName}.`, + "startDate": new Date(firstSession.date).toISOString(), + "eventStatus": "https://schema.org/EventScheduled", + "location": { + "@type": "VirtualLocation", + "url": Astro.url.href + }, + "organizer": { + "@type": "Organization", + "name": exam.conductingBody + }, + "offers": { + "@type": "Offer", + "name": "Exam Registration", + "url": exam.officialWebsite, + "availability": "https://schema.org/OnlineOnly" + }, + "keywords": exam.keywords.join(", ") + }; + } +} +--- +{Object.keys(eventSchema).length > 0 && ( + +)} diff --git a/src/components/PictureInPictureWidget.astro b/src/components/PictureInPictureWidget.astro index cf39773..0e27044 100644 --- a/src/components/PictureInPictureWidget.astro +++ b/src/components/PictureInPictureWidget.astro @@ -681,13 +681,6 @@ class PictureInPictureCountdown { const status = document.getElementById('pip-status'); if (status) { status.classList.add('active'); - const statusText = document.getElementById('pip-status-text'); - if (statusText) { - const examName = this.currentExam.name.length > 12 - ? this.currentExam.name.substring(0, 12) + '...' - : this.currentExam.name; - statusText.textContent = `${examName} โข PiP Active`; - } } } @@ -695,7 +688,6 @@ class PictureInPictureCountdown { const status = document.getElementById('pip-status'); if (status) { status.classList.remove('active'); - console.log('PiP status indicator hidden'); } } diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index d0b116c..eaae6bf 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -1,4 +1,5 @@ --- +import JsonLD from '../components/JsonLD.astro'; export interface Props { title?: string; description?: string; @@ -13,9 +14,15 @@ const { exam } = Astro.props; -const dynamicTitle = exam ? `${exam.name} 2026 Countdown Timer | ${exam.fullName} Exam Date` : title; -const dynamicDescription = exam ? exam.metaDescription : description; -const dynamicKeywords = exam ? exam.keywords.join(", ") : keywords.join(", "); +const year = new Date().getFullYear() + 1; +const defaultTitle = `TimeKeeper - Indian Exams Countdown ${year}`; +const defaultDescription = `Real-time countdown timers for major Indian competitive exams in ${year}. Track JEE, NEET, BITSAT, and more. Get exam dates, time left, and stay prepared.`; +const defaultKeywords = ["exam countdown", "time keeper", "time left", `Indian exams ${year}`, "JEE countdown", "NEET countdown", "exam timer", "competitive exams", "entrance exams"]; + +const dynamicTitle = exam ? `${exam.name} ${year} Countdown: Time Left, Exam Date | TimeKeeper` : defaultTitle; +const dynamicDescription = exam ? `Track the time left for ${exam.name} ${year}. Get the latest exam date, syllabus, and preparation resources for ${exam.fullName}.` : defaultDescription; +const dynamicKeywords = exam ? [...exam.keywords, "countdown", "timer", "time left", "exam date"].join(", ") : defaultKeywords.join(", "); +const canonicalUrl = Astro.url.href; // Import PiP Widget import PictureInPictureWidget from '../components/PictureInPictureWidget.astro'; @@ -24,96 +31,176 @@ import PictureInPictureWidget from '../components/PictureInPictureWidget.astro'; - - - - - - -+ + ))} +{baseUrl}/{page} +{new Date().toISOString().split('T')[0]} +{page === '' ? 'daily' : 'weekly'} +{page === '' ? '1.0' : '0.8'} +{dynamicTitle} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {exam && ( - - )} - - - - + + +{dynamicTitle} + + + + + {/* Open Graph / Facebook */} + + + + + + + {/* Twitter */} + + + + + + +- + + - - - - - -