From b0ff3395254d15fac55d2f9b7bfb6700b70a2b52 Mon Sep 17 00:00:00 2001 From: Anahita-Mahmoudi Date: Sat, 30 May 2026 14:20:20 +0330 Subject: [PATCH] git add . git commit -m "feat: improve wheel UI with gold pointer and center circle sync" --- backend/.env | 3 +- backend/__pycache__/config.cpython-313.pyc | Bin 716 -> 958 bytes backend/__pycache__/main.cpython-313.pyc | Bin 669 -> 939 bytes backend/__pycache__/schemas.cpython-313.pyc | Bin 913 -> 1151 bytes ...808a86947_add_prize_column.cpython-313.pyc | Bin 0 -> 997 bytes .../versions/b55808a86947_add_prize_column.py | 28 ++ backend/api/__pycache__/admin.cpython-313.pyc | Bin 3116 -> 4101 bytes backend/api/__pycache__/user.cpython-313.pyc | Bin 1613 -> 2509 bytes backend/api/admin.py | 34 +- backend/api/user.py | 26 +- .../app/db/__pycache__/models.cpython-313.pyc | Bin 672 -> 711 bytes backend/app/db/models.py | 5 + backend/config.py | 10 +- .../core/__pycache__/logger.cpython-313.pyc | Bin 2548 -> 2548 bytes backend/logs/app.log | 288 ++++++++++++++++ backend/logs/error.log | 40 +++ backend/main.py | 20 +- backend/schemas.py | 9 + .../services/__pycache__/crud.cpython-313.pyc | Bin 1665 -> 2212 bytes backend/services/crud.py | 56 +-- backend/{wheel.db => spinwheel.db} | Bin 20480 -> 12288 bytes frontend/admin.html | 88 ++++- frontend/assets/css/style.css | 322 ++++++++++++++++-- frontend/assets/js/admin.js | 98 +++++- frontend/assets/js/api.js | 34 +- frontend/assets/js/wheel.js | 306 ++++++++++++++++- frontend/index.html | 14 +- 27 files changed, 1241 insertions(+), 140 deletions(-) create mode 100644 backend/alembic/versions/__pycache__/b55808a86947_add_prize_column.cpython-313.pyc create mode 100644 backend/alembic/versions/b55808a86947_add_prize_column.py rename backend/{wheel.db => spinwheel.db} (56%) diff --git a/backend/.env b/backend/.env index 7841a24..e8403d6 100644 --- a/backend/.env +++ b/backend/.env @@ -1,4 +1,3 @@ -DATABASE_URL=sqlite:///./wheel.db - +DATABASE_URL=sqlite:///./spinwheel.db LOG_ENABLED=true LOG_LEVEL=DEBUG \ No newline at end of file diff --git a/backend/__pycache__/config.cpython-313.pyc b/backend/__pycache__/config.cpython-313.pyc index 3a0cad92c3ce9a5ee6a6fa64f9ccf7ae09400953..ac76f951acad4b23501fdeb5b42e3285f607802e 100644 GIT binary patch literal 958 zcmY*X%}*0S6rcTM=!bl3DJm6XOf~jagT$ysiUFFEFsPSJCfjzYtIKYk-I`!Lpa&)K z!i9uC0sj)ugbPf%G``yQnO{-6Iwe3L`>giqP&5%*<4#G5wJ$49m0zn06Q9 zV%}h8%u{O+c=LiEU^M0vaXdvn);$Qi6Gb(yN<@++MXH&~rzP#{Izuf5nOZ2Vm*z{X z%wMimunF69?QmTtE2}Sfxmud9lozDFR9#$^gGg1%ugaB6J;Ce7t8*Q;%RMnw=u8s^ z(}jsIC5QUe3(o_8!)=}=;m$|B;P@Zbe~@Z^KvK?qd(+`uCOF>=8f_2l6zA{Tw%0ou z;(WsuVdS~K;|Fj|;XY8FBZpf-tL@p5!)se{B00f%7}=3q=XMkcx7LoFkaIB%g*&KQ zPM7B_)^h#a>o<<$Sv9-<&hZ;oJr-DKZAr7MRa}8+)ElM&zLMjy$sKw;HGM=gA_I)b zB4FNXJ}pMj8ATXF7)QVv5)?pcVI(B9Vf%i7;sPHv;lxV476hJ1VQ8H28d{jOTVxU~ zvk&-(;ZuNbRD z{HLo%?wkO0b7R^n{1R<#HEbWAELJQ>`YsQ9GcEMK07);n%Niwp-2J^L;uZwskc2sa hKboQ_rzCqy?wy-d$?bePC$Rm!N>bzB=RVU|$$zycw*|AeMs2ddSIKN)b}+MI{GE(Zj;9O}24sIvd|?3KcJ2 zROq>Xga`i`FEStqo^n$X5O2=g-H4sTH}B1S?|t9=m^T_LV8@F;{>f)M|Da?!yegRA zwZSC_5bO~=WXN`43wA*`Z4hq6x^BrsCvtY7TNR-@;-NRAEMz7)h;<^8JWtg~4=PGA z@hYiAB0wep9l_AO1rd)t;oP>ALeRB>yz7(|SQ8%BN42nS{AK4ESuw$b-qYSePaMi; zPaaN|Au!Gb@mDRVa@nbs`xaK(yT~% znhmF8iUTQMPvdd5)0FZ!MV*gRm8i^a`?lOJjT6(7Qs;4=4rH9?m>x~@L`#X+t;(D% znwy7vk(i`v=lRPd8AnI);8miA(V%eCnVg$iX$-GfHks1|@8Rpl_Jxn@HpWc?>evr` z3@B+3tP(UW7+2?LtjSoZjLc+;JQQVXM55ZV)dofM8w#L1)+F%(zOT2w@YZk6y!HYt zlyqYDLvGe4=fhahDRuSoNVP>1k3wk6Yx=^GY)x{R@JaA(0i*8cc* R0e1Se3c=>Z-n;U|e*vW+p`QQ% diff --git a/backend/__pycache__/main.cpython-313.pyc b/backend/__pycache__/main.cpython-313.pyc index 5524bfe5ccc32d28770e92cd60b657bbe90099eb..8048de56923949746a95a9238e4659d097c655c1 100644 GIT binary patch literal 939 zcmZ{i&ubGw6vy9We`J$pqiwLQ6q~5zkRrQjMWnR|Dk@Fz2U)?(LYQWz&FW@%ncYzB zN%7!KdJ%f`sQ*ptrDY*dksiE73Z9+WW|LEQ*_qG#zPInkFlV}+2mU<$clFJF2H>|a z(nlM?nTi0uf&~_8LIW{W5u9me8WNKdmYQ-yVM@X>XO*c5E6r>}V_F4lbOY{;(y<)> zTW#hWQ>>6gsZE`8kw@iP<+S)@w~JQR(pEFvueK@^J*C#olqS2aSh-b{WUNlqlx)e$ zTT^DnDp{3i9 z3wKo$lb+)}ua)BL79R@-4wLwp@!ZHVdgJMgj?Y3S3vUqzfk8HnkcOe-draXXQ-wz# zd3rvfZpgAileiY=cxodl#>DN43kkQ+4<*)wSDZ^cCDv3fI$p=^6Pnr@9|IE{;W*D{ z#1U=tfq6HCv?qQ3fHPhI;%krBY%6fQ%IhwruDyvnZ>dM@9(Fta=QRemU N8qfR_mf0e2=|6>F^w|Ia delta 400 zcmZ3@K9`m6GcPX}0}!kh70NooIFV0+X&u8v4b^%!1%_aTU}kT2FV-Rs1%?<#B?eQF zIv^MXZx+m^4+G6AKD%@g=6D#OG$Fq~xTQCl;mN;>*lS&M8evjfWUJ`4*F5 zodnQvj6htR10+5$Gcq#XWstthVDywh=PrZRM>Ym_zUIsZ<_~NPj4am~gf21&-Cz;A zAuKjQ^@f-fga&awa5FFpe`H`|WcnZkS2@9E1;+)D9>W_9%n;2JtQV96g)TAZ7l{Ea F1OWCeSt|el diff --git a/backend/__pycache__/schemas.cpython-313.pyc b/backend/__pycache__/schemas.cpython-313.pyc index 069f2abe526d95d19731591155b430499c7eb153..b605276d6510164284c681d315fdc70372f0e732 100644 GIT binary patch literal 1151 zcmb_bO>fgc5Z(1xoR2m@sc3`L77&6U)C=kXgg8(kAc!7n5En};H`$~H#|~o$spm-L zz@?Y|i2ezFN3{|sPPrjDh$}PeL?oyO4y@$2GjG;A<2U1cX{o|+J@~r*{w`stWhVQ( zAcIVfEet3-3$sWn`%@4Ni_}-iy*2~=#wvNbF5(yJqHmy;zd-nx?&*$N)JHurHcpJQW6%|tqvVyqrI}GR z>g~Qv2dKRqFJn*{Vdqcn3q{~a>y@wJwpfH`faRHB_pGL^R^~6=-c90e*c;P#y(y$< zI#5Lqs3)*QpcB`-ko4#J4Ob|nmC>S*V#+D5gAH_uO-bfscHY?dQZKZ-fy6XV`Jy~< znGRecs1xY(7o>s)MG2_7^g03#kXs!+pH0!I^~>V+=7b>>x#{#CD&P)|Izf!%3-sF+ zqo{kW@BHhN+{eWa^8{|;VG=pLkMP6foPT2*6UX57ll2M1ZBm)H9lm+;@F&AfXa5Z> Ch2cT~ delta 538 zcmZvYO-lk%6o&8B`SNoLDL=pug0R`=A}}{DA}bf!elQ(nARRe#CEDadutgiiU(w%a zAGDN#et^3mBWPpi=pu;*?lW`eyf5!LXQ@rp=tm?HWcZYBm-aK8L=(I?$sbE?#ZV{r z2Rt`@;pzRFAC$Ia$X@VNSe-Ihbz#l@=<;M?iyP~SRZ;$Zp4c%>SJZ{wFzZ5qeccz9 zYnq@<8~iX%23JKquvsWNs=yY0gFHR+C3y*R@`VK3V>KzoYI4DcH7ct$1+=zmXkM7I zFDkI9|xVYInqDsm!CkmZiwB&I$bH3^s!Nc2_`@WD&y3@J|-Rs7jzw++76jMGFm2 za5=-um&E1lf+5zqB&-^er}gfd^)7B0Xq_MvVyue%Gm2L1IL-czRf05XI372Lc=vP8 a-&kfLajuV(n85XgBJ=F{=#$}VmG%QxfO6LW diff --git a/backend/alembic/versions/__pycache__/b55808a86947_add_prize_column.cpython-313.pyc b/backend/alembic/versions/__pycache__/b55808a86947_add_prize_column.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f01da5173370cb00fcbbd94a3f2d153fa20be3f GIT binary patch literal 997 zcmah|&ubG=5Pth3$!42PTD6KrbfJhwNl4Oc+61u&Tf`Q6a4B9EmY3aUns&3B&L-6q zgdB6Mhl1YfO>Z9kGg^8n&x;@)ycMf>^}S6hRV+Suvorh6&V2I?d!lJ7V&~0=tLtSE zq0c-xJ@UvodC#0(bQ75fkSUnrvalqAxFmt3^H^Gzmtqi$e0ezz2{Q(Ym9$hdev5r+ zCSc5xSS(t}7?0($#GedTB3y>lIe2n}YvxC+=8->}z(^>Ta5Zod9 z0y8zeR47$)1tV9g>cwhprc^5z^Od4eF=kZtD;o%9MXH!&vrBx3(AcWa8f6742JID^ zI@^pYFycW|XWLQD4W9Y-Z&0fP_MJ7`!*$|?RCP(4_-<$izQfy{9)UR$yC-~MyQm># z#qG>$y8+lGdg!c?7S6A{L!j{-b`gEJShLJ_)7PJ@5#m{O>};^Y7WPQ1-gK-j0$ilf zI!oGSYTA*S_E61yyGIk4LwK9j6J~b_2(@X}r^8Gd6&016&)L7)=PAB~`;s4|`mn z9u-3j3I0+FJ3uqGjeS4p;0~9;wjs$&@IM8{xZ%@AJpQJs+-n7H*CY4g8cX4e346>O zOM)PL6XSyXGmV7lFX-||bmdq<6F2(GVEPbUKPt}mwLy2se06(o=5_AvrTxj=K_-8Q u=8uZTzY*goU+Sxa#hvM$=X=u27klphg*ykSyN9UsJuV8#A1X7EJih<{%lIJx literal 0 HcmV?d00001 diff --git a/backend/alembic/versions/b55808a86947_add_prize_column.py b/backend/alembic/versions/b55808a86947_add_prize_column.py new file mode 100644 index 0000000..623134a --- /dev/null +++ b/backend/alembic/versions/b55808a86947_add_prize_column.py @@ -0,0 +1,28 @@ +"""add prize column + +Revision ID: b55808a86947 +Revises: ab09ab5070f7 +Create Date: 2026-05-29 19:32:41.615653 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'b55808a86947' +down_revision: Union[str, Sequence[str], None] = 'ab09ab5070f7' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + pass + + +def downgrade() -> None: + """Downgrade schema.""" + pass diff --git a/backend/api/__pycache__/admin.cpython-313.pyc b/backend/api/__pycache__/admin.cpython-313.pyc index 80a2500c6909a6bb18a0f06ded775ab8690b323a..d8926f41b5fc54b4f4a2be9ca7f28c34d9852b95 100644 GIT binary patch delta 1322 zcmah}U1%d!6u$G9`Aa6hEos_qCu!X_-R+jTt!txgcWT$tbuE%1h%}-hnUsW@#@mU= zve;FSZJ#P$AAGF7n1zB{7TGKal700h?iN}?5Wz+4i=;$wsW0A}WN0gb_u;$e-0z$- zch31{cKn~JgAIp60(gD*S6B9X5rAKbV=bX=G=AS5yhE+WmFR>;o$R_N&g+gzI!=c` z+%-Y9m~l$?#{If0Zi^0|#9EfF#JO>s-Vp#Z!APt3#I|F z!_OJ{`#(}e+7XQ9j1Tih(lk=4feP%aqL73cGm}iLpfdZu%em>S1YRm>rOOrn%${%+ z&2m@iX8_&hhtLk^3&lXxW?kcCpI};cj$RVbsBjJ)au>aJi|0Y27l0gtxh>3Hvt5~0 zPhSJVRj4MRkuWp4Wi?|O??u$G07?6*a`W7Hhn!Y9Gh?n8RqlhtN&)TIUh7ZcklUax z**K-MKZW$z6<7~UOz;+{f2{=y5*w~Ca;O8pi8^qw$tAUOW6b2O(M zulaF_Pc%hygR)!FCYf?~mp0Cn$2o2Ceu00R8aj3@;AgA3RZ~r`7ILYGdiVvN(y?;( z_%1pYrX6SA3q{1&sUX@AgElfmIE=m(KWcUO)Lqb&G)N~IlxKX@4OHZZ=G7P#WpTp4 z7C!y2*0&>bOpF<7XQV;^vNPEU&lKm`U^ za#@M(39?I;q(RhcaVNWOi?Is0Oj{x*HFS_;H8xG9!4%U6(o~pVg#9=L4iI<_fOi2r zkIkE{b{6uf1eRt*y?LgSO@%bMQwovV3Tfpu6#}u&0PG{+BX9u&%8MQA91Ie_n}C9$ z%D0~c1K3;RFsNifCy5xbV4jelW56P_D_CTn&V5hlu_v_s{5|%8v@4b7ez^Yd`V-Im z2LGG0vu5{fEp9GW&W#k8i*HrBuk6`htaFY{{GQXSwY+w-+kDOMAlT zfwOZ%dG=KHHvq*cSd}%|*8o_TH5~iDqP?Vzf%2Gdwx4;#b0SU&};?RVH6B;Rx!J1M&>^$uDQ^B8BGUpJR2}Nh$RZpoKeuj{_BZ zZ}HacK!tkwNC4izm*>&w5$~mx1Ba{5;QbfLy)X6rB=yue?A2{Rbp1*A1_S7+BN|(9 H8W;Zu4No#R delta 395 zcmZowSR8`D{9qQ&|L;Rn=fh(LZh#FksFucLR`}+ec z6D!}ZVhNxjO^(ctYzzn4xE%GE4+=;+YBC?xWOdZrEX)zhs8J>02{c+EFTX?~Ex$A` z#Y!P_3L^tU6(6dw=HwZib|AMs=JaJ$m~67fK6rS1juGfF-#Bu&ZyfHywN&{*@4G9FC76b`Ok(Y==ETxXU4ojW2JG&0x zO9PiCMJ*{60@OoO^#E0S=%rN-y}+@D%2gsYtP-geZ6$6_odarn=$mymF@dV>lXvEu zpZ9k5z3;to+2?a3kiKsM7r)~WdQ1nlz}S-VK8(;UBqJFQp+QWrL@@)HA&iAtz${@c z>=<$ka)h%mH^dVj_VPo{L4gR?oO8%E=q7Fp3qzhkFY!t!j>G8C#_NbL?hPZ^bqL-{ zg^04d1rdMT7wf3ZRJXDxUK{6Qoz-&{Oph;n4>NHljN*RV+CeP);@m+-7F*Co$3?vP z);h2sdESb=sre6fYPQ*_pWq_3PpF{?HeJ!Pr(@Z?p%552UsrO9meK+GPMte<_Qd6+ zk~7q-M%QFT*8vU#b|gtY1-*A;?o2kB$iUb=p%`PC?8JmZOh;Z)ogfyngHa+w< zk=YSOMBU7m9R)H}W1>tN+YyR5h6}+F@U)axjN}C+C0SnSl?r?X6*@*V<#J9*8nC7i zlHJ&;Y7_R>h?CGSGhH-Lmb%H?d{nh`)?_n^X1X?cZ3;=E8Y*QkYdPEGDq2|4shcH9 zA|mu8U^mSGbn5hJRC4f~xKb0mE#4IGkj0wt47VbPH$1bRV#D4qyYJ_}=)J%1t8_7V zbV=x6_6KLYPggv_GGagr_W5W1#g@K>=t6g~>DZERd|B}QS>j;v54|4jUpssRed~^P zvJd#?Xo!6f;s6)8{W?(e8A{M9Vo~BvAjtCxlUKvLImVV6}U5(eaKj#61|d}T@KF7e1$zueGvTm4X7ZVr9YcDD_*8pRUJ z)woNDbGe_ci^$ttMwk;JV}9A5P(2H^3v99J$dYihDxsR-tk_WS|yJUg^q`NkXfy>D1{&LN^7dRa0tG z(JM|D6S-VhYP?IY+>@1i#*@qvrK?hc2raKBPpsS4ii>rTm@E`4(~(hi!}Oj8QpGmz z0ITSjj$BqZEU$l#%3uKgZBR495x61p8Ks{bfFZpi^t;fMSd8&1+OdjSS5e2WDEt_S ztEhPu?OsLUpIw3Lt<(G?SKZvnMHisoc@Ec=0*DdbZMoVqb87zF{oOzA?f(IOyN*q_ z6!Gy#Z9C^re*DH7a$<3A_d4y$EMlU#!~iTcP}SC}tuyBras8vl_B&G_y|adzu{d*n Qopy9@!x{sK?1$6+4bF!D=Kufz delta 870 zcmZ`%PfyfP5P$EruiJlHmNv+;bytGuS_I?(2NFZng9i`9Y$ce`xCd!)$pYqe2}hQT z8b~CYM%k~xN6>@cfJDQ=rilj=H1XDT`2fz_f)WxZ?e91J&Age;v`5yFZgjI*6@dkU zvie?V8F`#*l^3=>Jc4Gp4#qTBE+D3RTE$M4i|8tz;YpR!MJ@FykgLuTkBlHspTVg* zja|*7)5O&aXjNRrf4k(M{`Ipe_s^J|1JpF+l^Lgor}`ogEU5`MyO%|krz5GF&R zm_Dy>1pZ<8jNB`Yf$$48(TOs!S86^;M3GvNk=vCM2`O6mkt`Sjr0}~t3GgnwCft59 ziWuJ(6wCm{%8K8p)*Aj=z;t*H_=v;I0A%JlT;%}^pova%NYCxh?asAFj=IX)B|dh=1zeHN^?wX1pYM{nbdpTrQRGPT;Rk7I zJC?R;>+3#qNK9(WF>TZub>E@sBx>-owqD(O;rn&QJF`58Apn>)%l$!sfB+1q6UHJx z!uTI?i-#Toz=$d`%>C+awOu*R4mThE)cQ{hJIdS9j8*PXp-)Yaps0zCl>W%fA2=;1 z8n6$%mIpygO$4e-38GYDd}WRmF3U?9iEV5)d=`F_$8PX`ho~3rEjpI(u5B!D*8MqF zh7kV(K|35*3fn(Wxg#Of+OwO|Z%M{hl1D^-UDzo^q8?d;?abHw_!qdWiG(7e!;2Bp zQIS<>-wf|`1QC}ax$l&7SAkF>gO}uZ4SUCavv`aLzMDg7u8E~yE^t>s_yxYKiDzy zOx9go3G&M;sbrOFRAG8%USYYJcvAyX^a%R^l zh92Z%Xxtd=LkvBpP-t_@vHw8t6%JTz?Ob~5O<;#y``)Z(S2d+ja_PX%d-LABH?!~e z-kY;rE<<3P{(9}f8-)2IWh&ib?#$o7gQb+3yAeuaN$Gr$~@GAZ?HaR-bgIi!T#=xR%vki_&xmz^Nb zr3FI?w1=CNZ7~(Zfl_r{Lt<#`Bn(z27 z4YWs9cavK9mtvp?T%Uan(;-=k{&%t8ls=K`3dEpyf7h<8h<`6&l>X)axjxx7>mh&h zpiB@$UNB^qgB`37CIfN<2!zVEgM`(n)f_)aGP=sBw+>1?G2R`hJSJ#SfWa_11Knb@ z>Qf#rHUJV#Oz%UOn&d>!AJCR=G^g8Y;@f-M_Z}B_K0|hPdv<3YnYrz`$No!opsnYd z^DK)FlX%FoV_SHR7|8Pgo&)oCDHA7FDIwg)v@A_QR*JBf(y>NxBIW98S$)4NtbRbq z;)ELkv{-Z_-vC@**v3?N;lN#v<$^1#;tsGwogfUsHPQ^D8)BANbzGmaKz8gvTXmS{ z$4D{s$okPl3qc%2L4-TI0!t6Ci$Dq_H#h7E$zjmpwLKH23wd8?>4hH$ej55==;!;b z>mMEI#bbS{rB5B{x0=(hl6re^crO!9smy+QH@&B~fywM<4vH^RS7WTLsK()t2;vi1 z{7)d3%W?$qb%HgB{oukV%RNw$5(fFg=mA?6P$pw2*%0h!S8>8F6C`ZvQh*jgG?X6v zIBHl1F=pY`u=++ogI+wy;Qx$Sh}6NvULLUuL5qs=pWF8i9P@TSiNMsQvLpg+Lvvgs zOHuulFy$|daZ;8<&_zJCo`7Q61_@TJ+l?CZwe<$Hcj$YV@PWpgn1`t+GC$+Nqp9ceU&r?sug2CFedEyz_jHYV+r$yx(o^LQEa2PROUcmu_$`@DlPEIx*4 z^4mNo+;iA&Q@6l))1`M9#tYvO&xfg_NRsqB$(@q%Q!?67C8==m-Z=r%NodlggZmu< vlucM;7kx@?F&KZ zD=YpUewg%X`(wApOMFGjp*5aL6@HmirNUx|(w=>m8{r?cM0gxgPU=`G=?l?(@||K!WmHMzS8uYDZ}+dxc$VnC@zE7>NDx z3!r+9$zK`B%gC@gGwqp!2RjRgdcLdYKk82F?sv_Ot<1OZm6YW)Iud@6t~b;DMVzOC5cHnshK6Ixy4|DY4R36cb>AuoYGVUPrndXch|{p`4l8T z#&Pi5G4OxkzskRje>%|Ebbc>>JD|~O{PkQMEDVwyH}~D_xYZ`j&ca~MbYsTQ@y4oK>u>ikoZwR- zvam4NG9t;}*nG1cE-xp`43>@tTZYhoV>3{H%guHpStc~?D8`F$F(O>U1@_=22L3<% r-}pc9zv6$we~hsFoRy3CNKXF1{S_>27X@toqV(T!Z#K^=4EnX znQX`}&B!|0mtRe?fSFxfT%57dz9cazCpEJqHMbZ{uuSgdcc1)@U#PxOlaXCqTbr?o z6RadLCp9-IGdaF2wWv5VKM#$|b`puxon)fS&unyV1z8075X z7^Gorq^aQNAEMwF>f@uMz{Tn8?-v{r#k%-{>|}+2AWvV%phyL8*GL5o zBm*_MG;NvK!~?Y%88Y)yQY(rJbAWz{Pb@9T2lM06+!JqzCcq2~K@LzT@`4Bf5W%on zO<@iH#0F0`E)Y*(v!cRA5Lb+s{{aIV|3n7<^ZZBnH}fyzpUD57zZWQ7&R;Lc&dk6M z#Hu&e+*p3Q=f + - پنل ادمین + + + + پنل مدیریت + - -

مدیریت آیتم‌ها

-
- - + + +
+ +
+

مدیریت گردونه شانس

+

مدیریت آیتم‌ها و جوایز گردونه

+
+ + +
+ + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + +
#عنوان جایزهمقدارعملیات
+ +
+
-
    - + + \ No newline at end of file diff --git a/frontend/assets/css/style.css b/frontend/assets/css/style.css index b183966..5f262d2 100644 --- a/frontend/assets/css/style.css +++ b/frontend/assets/css/style.css @@ -1,61 +1,325 @@ +/* ========================= + ADMIN PANEL +========================= */ + +.admin-container { + width: 100%; + max-width: 1200px; + padding: 30px; +} + +.admin-header { + text-align: center; + margin-bottom: 30px; +} + +.admin-header h1 { + color: #FFD700; + font-size: 38px; + text-shadow: 0 0 15px rgba(255,215,0,0.5); +} + +.admin-header p { + color: #f5d6a0; + margin-top: 10px; + font-size: 18px; +} + +.add-section { + background: rgba(0,0,0,0.25); + backdrop-filter: blur(8px); + + border: 1px solid rgba(255,255,255,0.1); + + border-radius: 20px; + + padding: 20px; + + display: flex; + gap: 15px; + + margin-bottom: 30px; + + box-shadow: + 0 10px 30px rgba(0,0,0,0.4), + inset 0 0 20px rgba(255,255,255,0.03); +} + +.add-section input { + flex: 1; + + padding: 14px; + + border: none; + + border-radius: 12px; + + background: rgba(255,255,255,0.08); + + color: white; + + font-size: 16px; + + outline: none; +} + +.add-section input::placeholder { + color: rgba(255,255,255,0.5); +} + +#add-btn { + padding: 14px 28px; + + background: linear-gradient( + 135deg, + #FFD700, + #D4AC0D + ); + + color: #4a0f0f; + + border: none; + + border-radius: 14px; + + font-weight: bold; + + cursor: pointer; + + transition: 0.2s; + + box-shadow: + 0 4px 20px rgba(255,215,0,0.3); +} + +#add-btn:hover { + transform: translateY(-2px); + + box-shadow: + 0 8px 25px rgba(255,215,0,0.45); +} + +.table-wrapper { + background: rgba(0,0,0,0.25); + + backdrop-filter: blur(10px); + + border-radius: 22px; + + overflow: hidden; + + border: 1px solid rgba(255,255,255,0.08); + + box-shadow: + 0 10px 40px rgba(0,0,0,0.45); +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: + linear-gradient( + 135deg, + #FFD700, + #D4AC0D + ); + + color: #4a0f0f; +} + +th { + padding: 18px; + font-size: 17px; +} + +td { + padding: 18px; + text-align: center; + color: white; +} + +tbody tr { + border-bottom: + 1px solid rgba(255,255,255,0.06); + + transition: 0.2s; +} + +tbody tr:hover { + background: + rgba(255,255,255,0.05); +} + +.action-buttons { + display: flex; + justify-content: center; + gap: 10px; +} + +.edit-btn, +.delete-btn { + border: none; + + padding: 10px 16px; + + border-radius: 10px; + + cursor: pointer; + + font-weight: bold; + + transition: 0.2s; +} + +.edit-btn { + background: + linear-gradient( + 135deg, + #9B59B6, + #7D3C98 + ); + + color: white; +} + +.delete-btn { + background: + linear-gradient( + 135deg, + #E74C3C, + #922B21 + ); + + color: white; +} + +.edit-btn:hover, +.delete-btn:hover { + transform: scale(1.05); +} + +@media (max-width: 768px) { + + .add-section { + flex-direction: column; + } + + table { + font-size: 14px; + } + + th, + td { + padding: 12px; + } +} + + +/* ========================= + WHEEL PAGE +========================= */ + * { - box-sizing: border-box; margin: 0; padding: 0; + box-sizing: border-box; } body { + min-height: 100vh; + background: linear-gradient(135deg, #8B0000 0%, #4a0f0f 50%, #2d0000 100%); + display: flex; + justify-content: center; + align-items: center; font-family: Tahoma, sans-serif; - background: #f5f5f5; +} + +.page { display: flex; flex-direction: column; align-items: center; - padding: 40px 20px; - gap: 20px; + gap: 24px; + padding: 30px; } -h1 { - color: #2c3e50; - margin-bottom: 10px; +.page h1 { + color: #FFD700; + font-size: 36px; + text-shadow: 0 0 20px rgba(255, 215, 0, 0.5); + letter-spacing: 2px; } -canvas { - border-radius: 50%; - box-shadow: 0 4px 20px rgba(0,0,0,0.2); +.wheel-wrapper { + position: relative; + width: 500px; + height: 500px; } -button { - padding: 12px 30px; - font-size: 18px; +.wheel-wrapper canvas { + display: block; + width: 100%; + height: 100%; +} + +#spin-btn { + padding: 16px 48px; + font-size: 20px; + font-weight: bold; font-family: Tahoma, sans-serif; - background: #e74c3c; - color: white; + background: linear-gradient(135deg, #FFD700, #D4AC0D); + color: #4a0f0f; border: none; - border-radius: 8px; + border-radius: 50px; cursor: pointer; + box-shadow: 0 6px 25px rgba(255, 215, 0, 0.4); + transition: 0.2s; } -button:hover { - background: #c0392b; +#spin-btn:hover { + transform: translateY(-3px); + box-shadow: 0 10px 30px rgba(255, 215, 0, 0.55); } -button:disabled { - background: #aaa; +#spin-btn:disabled { + opacity: 0.5; cursor: not-allowed; + transform: none; } #result { - font-size: 24px; + min-height: 50px; + text-align: center; +} + +.winner-text { + font-size: 28px; font-weight: bold; - color: #2c3e50; + color: #FFD700; + text-shadow: 0 0 20px rgba(255, 215, 0, 0.7); + animation: pop 0.4s ease; } -/* ادمین */ -#add-section { - display: flex; - gap: 10px; - margin-bottom: 20px; +@keyframes pop { + 0% { transform: scale(0.5); opacity: 0; } + 70% { transform: scale(1.1); } + 100% { transform: scale(1); opacity: 1; } } -input { - padding: 10px; \ No newline at end of file +@media (max-width: 550px) { + .wheel-wrapper { + width: 340px; + height: 340px; + } + + .page h1 { + font-size: 26px; + } + + #spin-btn { + font-size: 16px; + padding: 12px 36px; + } +} \ No newline at end of file diff --git a/frontend/assets/js/admin.js b/frontend/assets/js/admin.js index 2058645..c81efca 100644 --- a/frontend/assets/js/admin.js +++ b/frontend/assets/js/admin.js @@ -1,33 +1,103 @@ -import { adminGetItems, createItem, deleteItem } from './api.js'; +import { + adminGetItems, + createItem, + deleteItem, + updateItem // 👈 باید اینو هم به api.js اضافه کنی +} from './api.js'; -const list = document.getElementById('items-list'); -const input = document.getElementById('new-item'); +const tableBody = document.getElementById('items-table-body'); +const titleInput = document.getElementById('title-input'); +const valueInput = document.getElementById('value-input'); const addBtn = document.getElementById('add-btn'); +let editId = null; // 👈 حالت ویرایش + +// -------------------- Render -------------------- async function renderItems() { const items = await adminGetItems(); - list.innerHTML = ''; - items.forEach(item => { - const li = document.createElement('li'); - li.innerHTML = ` - ${item.title} - + + tableBody.innerHTML = ''; + + items.forEach((item, index) => { + const row = document.createElement('tr'); + + row.innerHTML = ` + ${index + 1} + ${item.title} + ${item.value} + + + + + + + `; - list.appendChild(li); + + tableBody.appendChild(row); }); } +// -------------------- Delete -------------------- window.removeItem = async (id) => { await deleteItem(id); renderItems(); }; +// -------------------- Edit -------------------- +window.editItem = (id, title, value) => { + titleInput.value = title; + valueInput.value = value; + + editId = id; + + addBtn.textContent = 'آپدیت ✏️'; +}; + +// -------------------- Add / Update -------------------- addBtn.onclick = async () => { - const title = input.value.trim(); - if (!title) return; - await createItem(title); - input.value = ''; + const title = titleInput.value.trim(); + const value = valueInput.value.trim(); + + if (!title || !value) { + alert('همه فیلدها را پر کن'); + return; + } + + // حالت ویرایش + if (editId) { + await updateItem(editId, { + title, + value: parseInt(value) + }); + + editId = null; + addBtn.textContent = 'افزودن'; + } + + // حالت افزودن + else { + await createItem({ + title, + value: parseInt(value) + }); + } + + titleInput.value = ''; + valueInput.value = ''; + renderItems(); }; +// -------------------- Init -------------------- renderItems(); \ No newline at end of file diff --git a/frontend/assets/js/api.js b/frontend/assets/js/api.js index a55795f..17958bc 100644 --- a/frontend/assets/js/api.js +++ b/frontend/assets/js/api.js @@ -1,20 +1,40 @@ const BASE = 'http://127.0.0.1:8000'; export const getItems = () => - fetch(`${BASE}/user/items`).then(r => r.json()); + fetch(`${BASE}/user/items`) + .then(r => r.json()); export const spinWheel = () => - fetch(`${BASE}/user/spin`, { method: 'POST' }).then(r => r.json()); + fetch(`${BASE}/user/spin`, { + method: 'POST' + }).then(r => r.json()); export const adminGetItems = () => - fetch(`${BASE}/admin/items`).then(r => r.json()); + fetch(`${BASE}/admin/items`) + .then(r => r.json()); -export const createItem = (title) => +export const createItem = (data) => fetch(`${BASE}/admin/items`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ title }) + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }).then(r => r.json()); + +export const updateItem = (id, data) => + fetch(`${BASE}/admin/items/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) }).then(r => r.json()); export const deleteItem = (id) => - fetch(`${BASE}/admin/items/${id}`, { method: 'DELETE' }).then(r => r.json()); \ No newline at end of file + fetch(`${BASE}/admin/items/${id}`, { + method: 'DELETE' + }).then(r => { + if (r.status === 204) return {}; + return r.json(); + }); \ No newline at end of file diff --git a/frontend/assets/js/wheel.js b/frontend/assets/js/wheel.js index 83cc99b..e4ca45c 100644 --- a/frontend/assets/js/wheel.js +++ b/frontend/assets/js/wheel.js @@ -4,43 +4,309 @@ const canvas = document.getElementById('wheel'); const ctx = canvas.getContext('2d'); const btn = document.getElementById('spin-btn'); const result = document.getElementById('result'); -const colors = ['#e74c3c','#3498db','#2ecc71','#f39c12','#9b59b6','#1abc9c']; + +const DARK = { edge: '#6D0025', main: '#cb183d', highlight: '#E8005E', text: '#fff' }; +const LIGHT = { edge: '#D4889A', main: '#FAD4DC', highlight: '#FFF0F3', text: '#7B0030' }; let items = []; let angle = 0; let spinning = false; +let lampOn = true; async function init() { items = await getItems(); - drawWheel(angle); + draw(angle); + setInterval(() => { + lampOn = !lampOn; + draw(angle); + }, 600); } -function drawWheel(rotation) { +function getTheme(i, total) { + if (total % 2 === 0) { + return i % 2 === 0 ? DARK : LIGHT; + } else { + if (i === total - 1) return DARK; + return i % 2 === 0 ? DARK : LIGHT; + } +} + +function drawSegment(cx, cy, r, start, end, theme) { + ctx.beginPath(); + ctx.moveTo(cx, cy); + ctx.arc(cx, cy, r, start, end); + ctx.closePath(); + ctx.fillStyle = theme.edge; + ctx.fill(); + + const mid = (start + end) / 2; + const gx = cx + r * 0.5 * Math.cos(mid); + const gy = cy + r * 0.5 * Math.sin(mid); + const grad = ctx.createRadialGradient(gx, gy, 0, cx, cy, r); + grad.addColorStop(0, theme.highlight); + grad.addColorStop(0.45, theme.main); + grad.addColorStop(1, theme.edge); + + ctx.beginPath(); + ctx.moveTo(cx, cy); + ctx.arc(cx, cy, r - 2, start, end); + ctx.closePath(); + ctx.fillStyle = grad; + ctx.fill(); + + const hGrad = ctx.createRadialGradient(gx, gy, 0, gx, gy, r * 0.35); + hGrad.addColorStop(0, 'rgba(255,255,255,0.28)'); + hGrad.addColorStop(1, 'rgba(255,255,255,0)'); + ctx.beginPath(); + ctx.moveTo(cx, cy); + ctx.arc(cx, cy, r - 2, start, end); + ctx.closePath(); + ctx.fillStyle = hGrad; + ctx.fill(); +} + +function draw(rotation) { if (items.length === 0) return; const cx = canvas.width / 2; const cy = canvas.height / 2; - const r = cx - 10; + const r = cx - 40; const slice = (2 * Math.PI) / items.length; + const total = items.length; ctx.clearRect(0, 0, canvas.width, canvas.height); + + // ═══ سایه کلی ═══ + ctx.save(); + ctx.shadowColor = 'rgba(0,0,0,0.6)'; + ctx.shadowBlur = 30; + ctx.shadowOffsetX = 5; + ctx.shadowOffsetY = 8; + ctx.beginPath(); + ctx.arc(cx, cy, r + 40, 0, Math.PI * 2); + ctx.fillStyle = '#1A0008'; + ctx.fill(); + ctx.restore(); + + // ═══ حلقه بیرونی تیره ═══ + const rimGrad = ctx.createRadialGradient(cx - 28, cy - 28, r, cx, cy, r + 40); + rimGrad.addColorStop(0, '#A0003A'); + rimGrad.addColorStop(0.3, '#6D0025'); + rimGrad.addColorStop(0.7, '#3D0015'); + rimGrad.addColorStop(1, '#1A0008'); + ctx.beginPath(); + ctx.arc(cx, cy, r + 40, 0, Math.PI * 2); + ctx.fillStyle = rimGrad; + ctx.fill(); + + // ═══ بخش‌های گردونه ═══ items.forEach((item, i) => { const start = rotation + i * slice; const end = start + slice; + drawSegment(cx, cy, r, start, end, getTheme(i, total)); + }); + + // ═══ خطوط طلایی ═══ + items.forEach((_, i) => { + const lineAngle = rotation + i * slice; + const lGrad = ctx.createLinearGradient( + cx, cy, + cx + r * Math.cos(lineAngle), + cy + r * Math.sin(lineAngle) + ); + lGrad.addColorStop(0, 'rgba(700,550,0,5)'); + lGrad.addColorStop(0.4, '#a2922e'); + lGrad.addColorStop(1, '#c8b43b'); ctx.beginPath(); ctx.moveTo(cx, cy); - ctx.arc(cx, cy, r, start, end); - ctx.fillStyle = colors[i % colors.length]; - ctx.fill(); + ctx.lineTo(cx + r * Math.cos(lineAngle), cy + r * Math.sin(lineAngle)); + ctx.strokeStyle = lGrad; + ctx.lineWidth = 5; ctx.stroke(); + }); + + // ═══ متن آیتم‌ها ═══ + items.forEach((item, i) => { + const start = rotation + i * slice; + const theme = getTheme(i, total); ctx.save(); ctx.translate(cx, cy); ctx.rotate(start + slice / 2); ctx.textAlign = 'right'; - ctx.fillStyle = '#fff'; - ctx.font = 'bold 16px Tahoma'; - ctx.fillText(item.title, r - 10, 5); + ctx.fillStyle = theme.text; + ctx.font = 'bold 12px Tahoma'; + ctx.shadowColor = 'rgba(0,0,0,0.6)'; + ctx.shadowBlur = 4; + const text = item.title; + const maxWidth = r - 52; + if (ctx.measureText(text).width > maxWidth) { + const mid = Math.ceil(text.length / 2); + ctx.fillText(text.slice(0, mid), r - 20, -7); + ctx.fillText(text.slice(mid), r - 20, 9); + } else { + ctx.fillText(text, r - 20, 5); + } ctx.restore(); }); + + // ═══ حلقه طلایی داخلی ═══ + ctx.beginPath(); + ctx.arc(cx, cy, r + 2, 0, Math.PI * 2); + ctx.strokeStyle = '#dfd498'; + ctx.lineWidth = 4.5; + ctx.shadowColor = 'rgba(255,215,0,0.9)'; + ctx.shadowBlur = 10; + ctx.stroke(); + ctx.shadowBlur = 0; + + // ═══ لامپ‌ها ═══ + const lampCount = 24; + const lampR = r + 23; + for (let i = 0; i < lampCount; i++) { + const lampAngle = (i / lampCount) * Math.PI * 2; + const lx = cx + lampR * Math.cos(lampAngle); + const ly = cy + lampR * Math.sin(lampAngle); + const isOn = lampOn ? i % 2 === 0 : i % 2 !== 0; + + if (isOn) { + const glowGrad = ctx.createRadialGradient(lx, ly, 0, lx, ly, 14); + glowGrad.addColorStop(0, 'rgba(255,220,80,0.55)'); + glowGrad.addColorStop(1, 'rgba(255,220,80,0)'); + ctx.beginPath(); + ctx.arc(lx, ly, 14, 0, Math.PI * 2); + ctx.fillStyle = glowGrad; + ctx.fill(); + } + + const lGrad = ctx.createRadialGradient(lx - 2.5, ly - 2.5, 0.5, lx, ly, 7.5); + if (isOn) { + lGrad.addColorStop(0, '#FFFFF5'); + lGrad.addColorStop(0.2, '#FFE566'); + lGrad.addColorStop(0.6, '#FFB300'); + lGrad.addColorStop(1, '#E65100'); + ctx.shadowColor = 'rgba(255,180,0,1)'; + ctx.shadowBlur = 14; + } else { + lGrad.addColorStop(0, '#B8954A'); + lGrad.addColorStop(1, '#4A3010'); + } + + ctx.beginPath(); + ctx.arc(lx, ly, 7.5, 0, Math.PI * 2); + ctx.fillStyle = lGrad; + ctx.fill(); + ctx.shadowBlur = 0; + + ctx.beginPath(); + ctx.arc(lx, ly, 7.5, 0, Math.PI * 2); + ctx.strokeStyle = isOn ? '#C8960C' : '#6A4A18'; + ctx.lineWidth = 1.2; + ctx.stroke(); + + ctx.beginPath(); + ctx.arc(lx - 2.5, ly - 2.5, 2.5, 0, Math.PI * 2); + ctx.fillStyle = isOn ? 'rgba(255,255,255,0.6)' : 'rgba(255,255,255,0.2)'; + ctx.fill(); + } + + // ═══ حلقه طلایی بیرونی ═══ + ctx.beginPath(); + ctx.arc(cx, cy, r + 35, 0, Math.PI * 2); + ctx.strokeStyle = '#C8960C'; + ctx.lineWidth = 4.5; + ctx.shadowColor = 'rgba(150,100,12,0.5)'; + ctx.shadowBlur = 6; + ctx.stroke(); + ctx.shadowBlur = 0; + + // ═══ دایره مرکزی — هماهنگ با نشانگر ═══ + const cGrad = ctx.createRadialGradient(cx - 8, cy - 8, 1, cx, cy, 22); + cGrad.addColorStop(0, '#FFFFF0'); + cGrad.addColorStop(0.15, '#FFE566'); + cGrad.addColorStop(0.45, '#FFD700'); + cGrad.addColorStop(0.75, '#ff962a'); + cGrad.addColorStop(1, '#b37900'); + + ctx.beginPath(); + ctx.arc(cx, cy, 22, 0, Math.PI * 2); + ctx.fillStyle = cGrad; + ctx.shadowColor = 'rgba(0,0,0,0.6)'; + ctx.shadowBlur = 14; + ctx.fill(); + ctx.shadowBlur = 0; + + ctx.beginPath(); + ctx.arc(cx, cy, 22, 0, Math.PI * 2); + ctx.strokeStyle = '#8B6914'; + ctx.lineWidth = 2; + ctx.stroke(); + + // بازتاب روی دایره مرکزی + ctx.beginPath(); + ctx.arc(cx - 6, cy - 6, 7, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(255,255,255,0.3)'; + ctx.fill(); + + // ═══ نشانگر ═══ + drawPointer(cx); +} + +function drawPointer(cx) { + // سایه + ctx.save(); + ctx.shadowColor = 'rgba(0,0,0,0.4)'; + ctx.shadowBlur = 8; + ctx.shadowOffsetX = 3; + ctx.shadowOffsetY = 4; + ctx.beginPath(); + ctx.arc(cx, 20, 17, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(0,0,0,0.1)'; + ctx.fill(); + ctx.restore(); + + // دایره طلایی — همرنگ مرکز + const pGrad = ctx.createRadialGradient(cx - 6, 14, 1, cx, 20, 18); + pGrad.addColorStop(0, '#FFFFF0'); + pGrad.addColorStop(0.15, '#FFE566'); + pGrad.addColorStop(0.45, '#FFD700'); + pGrad.addColorStop(0.75, '#ff962a'); + pGrad.addColorStop(1, '#b37900'); + + ctx.beginPath(); + ctx.arc(cx, 20, 17, 0, Math.PI * 2); + ctx.fillStyle = pGrad; + ctx.shadowColor = 'rgba(200,150,0,0.8)'; + ctx.shadowBlur = 12; + ctx.fill(); + ctx.shadowBlur = 0; + ctx.strokeStyle = '#8B6914'; + ctx.lineWidth = 2; + ctx.stroke(); + + // بازتاب + ctx.beginPath(); + ctx.arc(cx - 5, 13, 6, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(255,255,255,0.35)'; + ctx.fill(); + + // مثلث — همرنگ + const triGrad = ctx.createLinearGradient(cx, 34, cx, 62); + triGrad.addColorStop(0, '#FFE566'); + triGrad.addColorStop(0.35, '#FFD700'); + triGrad.addColorStop(1, '#b37900'); + + ctx.beginPath(); + ctx.moveTo(cx - 13, 35); + ctx.lineTo(cx + 13, 35); + ctx.lineTo(cx, 62); + ctx.closePath(); + ctx.fillStyle = triGrad; + ctx.shadowColor = 'rgba(0,0,0,0.5)'; + ctx.shadowBlur = 8; + ctx.fill(); + ctx.shadowBlur = 0; + ctx.strokeStyle = '#8B6914'; + ctx.lineWidth = 1.5; + ctx.stroke(); } btn.onclick = async () => { @@ -53,26 +319,30 @@ btn.onclick = async () => { const winner = data.winner; const winnerIdx = items.findIndex(i => i.id === winner.id); const slice = (2 * Math.PI) / items.length; - const targetAngle = angle + (Math.PI * 2 * 5) + - (Math.PI * 2 - (winnerIdx * slice + slice / 2)); - const duration = 4000; - const start = performance.now(); + const targetAngle = + angle + + Math.PI * 2 * 8 + + (-Math.PI / 2 - (winnerIdx * slice + slice / 2) - angle % (Math.PI * 2) + Math.PI * 2 * 3); + + const duration = 5500; + const startTime = performance.now(); const from = angle; function animate(now) { - const elapsed = now - start; + const elapsed = now - startTime; const t = Math.min(elapsed / duration, 1); - const ease = 1 - Math.pow(1 - t, 3); + const ease = 1 - Math.pow(1 - t, 4); angle = from + (targetAngle - from) * ease; - drawWheel(angle); + draw(angle); if (t < 1) { requestAnimationFrame(animate); } else { spinning = false; btn.disabled = false; - result.textContent = `🎉 ${winner.title}`; + draw(angle); + result.innerHTML = `🎉 ${winner.title} 🎉`; } } diff --git a/frontend/index.html b/frontend/index.html index 0713603..a2039d2 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -6,12 +6,14 @@ -

    گردونه شانس

    - -
    - -

    - +
    +

    گردونه شانس

    +
    + +
    + +
    +
    \ No newline at end of file