Crée l'application web mobile ChessCubing Arena
This commit is contained in:
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
.git
|
||||
.codex
|
||||
WhatsApp Video 2026-04-11 at 20.38.50.mp4
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.codex
|
||||
WhatsApp Video 2026-04-11 at 20.38.50.mp4
|
||||
93
ChessCubing_Time_Reglement_Officiel_V1-1.pdf
Normal file
93
ChessCubing_Time_Reglement_Officiel_V1-1.pdf
Normal file
@@ -0,0 +1,93 @@
|
||||
%PDF-1.4
|
||||
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Symbol /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 9 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 8 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Author (\(anonymous\)) /CreationDate (D:20260204130259+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260204130259+00'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Count 2 /Kids [ 4 0 R 5 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1359
|
||||
>>
|
||||
stream
|
||||
Gatn&>Bed\&BE]".HWAPDHogm(\b&*UmlFgCq"E]`/?;V-u2J9/nt%6mmmXTY,H=MkC>:mju9'Gr#q0]MhLi)!B;^j_[-?1TtCGi-TRE(0J*fQ%`itXO=%ks('I+[+<e_.^ut3"I#r1=U-g=J+Ln);\0-KG!L3G__WG@CGoC"8$&@DM)+hT5:!]^R-2`13E]Q4OT"TdI)PDHg7p2Dtj".%_2M[L2VO*X5n&M=B?_BMET>Kr614`=[I.f0TjI[T0RM:%U2N@Y7i9Mg6da='c)l3AVABDW6'?F+%m/uBs3W-_$&PBnN!],]t:SlUJCd9t!$+FC.\C9pZEk([aDmeLABM+%!bicNkj'p4)oRane06k4Mc3*\u5a)>bC*>+TE`dW?l/+=u,H+/<f-hM")m7;?)`)JT+d_0#J/\kk6DG;9.b1mI`!S#t@.iD^Ms`2MB@=!Dcn]GALBRmOqR+k2<sG[i<`1g6iD'+7`gO^J2pe^p_%/E,`KF@M1k*9c_d]BUBDj"5NGlo3YM\+8@e+&Z6jCX0Lch:oB5HUkO2N6PUg1N+Tm<ce5@_+"/8mL;!Fm=U\8g`J/Bb;/\\!KJK6s<j.B4fidG(nc_&*ZJlUKgo2/AR5^`;3F=p*_&^T2*^`,4Z?4pRS;_kY'p(6enZB6'\V%!hj+jJN<S1E-s5nCegO)!5le[Hjo]A$/3Bd&]b,Fr<-OE>YF2&N8*%2NKQS"CmJFj84p^?$O+7F?<I=Z4=pmI!P(6aiF?f`Xf,p5W6gL.)UM*LD7:"^46,sl7]@Mqlrg3VBRo2U]ArN:gVS(NhTN5WK8!nX1:=o..7C_mVUNK/eMWljBI?0Ufr&Z?[NGB]lNcO55Q\UF]n=/q*l4iRC&U?[MBSc/UFnLWN.OH@K"[f`L?JDeaF-<SdXlr&[k0(QR?u8kT`+%hJ61)9d@jXQofmTFE&ID/@-86$qUKs]X'dB[m_3d62G#;G3dNso$;HbdGp0aS3E4$MjUA1Mgc?V-q?+L+A?%p7:6S1/Q4n1*f]1c[*`*JP:,s-XOVdbb+Pac8%aT4M8l59VnWtMLd(P)TZn+Z[+\O2Bc`Lhd_8aF@(fDI&b[Z!1O8(LU!11q6K-oRS:>.Qs+9*bclY+3K/Mm3GfUg7V7BiQo%)"6L[);`NNcVUfP)h*h0:O?-f^ir\]M>l[rLCCA%AA/K::Q7ZGWm*KJOM>4B]#80!Fb+V<j9=8[Z2Cb96spVj7YF?_:,\6Ctuj)W`^QNN%)7,>>!3AaTp\g4k:blTQH8=bUVFLEm!N@Xa7<_g(Z3Lh!(<U1mJ(@M9++K#nNNoMH!#s6s\4'SMGYd_mQM@=R!:ej%N)MuR$'R>-@o!<T2a8H~>endstream
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1031
|
||||
>>
|
||||
stream
|
||||
Gb!#[9i'e'&A@g>b]-dO&1:1Ep').#.rj4,HL/UFVSss?K0QC(^Yc<6!\/nm)Qd&&W\fb7SnHp(Eu:]p1&kI%!*=5$^L=o5R'2$7P:=^oiHd\5R'=Uib:G*CXC<k6=i.2EU3SScqI-e'cjK,R;4?@$$VpYP(N-c:705]bJ_()E$O44/6\A4i-(_pM*p-fMi\.n:U#slke3#1OE)Utqo([lce]JdmaSAQ=4*^HYn7i_d&)bE9GhqA#*7Q/cR$Oj36M>r`J3kLY;\uY6:<?hR]6e8>.Th'9>/$/Jn)%7.;Zb-?d#4)5KQd>_Hg_>RQ,goA1aIP3/-b;9pi21Un^6;kn`.juQ,^>tPlY@L?NOd?f3;_>U*`?=[2N0Q5dK'mWW/]MiKY4PhrM2dMXjJV+5Vs3dSr/oe1lh(@!ArEh(b5pZgE`S+5\/e^C:mriF`*-D4ns[E3c6MFR#d<C(C>u=U\_>*3G/uhklF-4:MknriS+Oi9)-4UK7OYB,[#-s3?VN#!`tn9UFor]RZp*/si\BF!HCUJ9F*#A'clGD5j8**M>n>cl03J+u9Rs'0k3Wik9m]NPU`l#V-\0C2hI\l5eVpd!;>@qG?Ep0`$aWM0=lpH(rjG'=H"nM_9KCS_,kD]$:aBJs36EDZY*E=&GA3s&NAAT@@'<)V)h^877Cfrp@h)*BJs'bt03N8!hdiBr#%_*ld7lZ7Nnqd$00;pU"jl:,X8paH&O5?6jJ7H;BPO,6Fl!O#k@n;XcrEh9e`8Ji%1gnR-d^ggE[bc(e%9B2ekieTY+W%<^GG#0a),^rn%m)q+IM[\RA-FWZ-:*gYsfXB*j>V!*e&=)Pr[[(IbD%?g(cFY_ul.7gu7&/N`n"7@r`0gX3Mg:8_94Wo/C"f+3&@dq]K"Bl;h-sIXkd!(j'q0KiE2+o0fWQ&5Kf.H]l2D9"SmlRT\r,?0_d3Q^9l<C#Z3oH;f@KW:HB^"t2V/]Z-^)m=nV7/2:??Ai":@uf`?M.ZhCL<NM!1coIh[Z3$D.&,29`>%X"8bL~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 11
|
||||
0000000000 65535 f
|
||||
0000000073 00000 n
|
||||
0000000114 00000 n
|
||||
0000000221 00000 n
|
||||
0000000298 00000 n
|
||||
0000000501 00000 n
|
||||
0000000705 00000 n
|
||||
0000000773 00000 n
|
||||
0000001056 00000 n
|
||||
0000001121 00000 n
|
||||
0000002571 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<64150b42f508c76f69b388aa96285995><64150b42f508c76f69b388aa96285995>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 7 0 R
|
||||
/Root 6 0 R
|
||||
/Size 11
|
||||
>>
|
||||
startxref
|
||||
3694
|
||||
%%EOF
|
||||
124
ChessCubing_Twice_Reglement_Officiel_V2-1.pdf
Normal file
124
ChessCubing_Twice_Reglement_Officiel_V2-1.pdf
Normal file
@@ -0,0 +1,124 @@
|
||||
%PDF-1.4
|
||||
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 7 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 12 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 11 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Contents 13 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 11 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 11 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 11 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Author (\(anonymous\)) /CreationDate (D:20260216162615+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260216162615+00'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Count 3 /Kids [ 5 0 R 6 0 R 8 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1279
|
||||
>>
|
||||
stream
|
||||
Gat%">Bf'b&:Vs/Qq,TRFY29SZYp-rgRLXZ^!Q-S01*aQ`4+sIDZ>,tA-.T\TK/NP5gml(qW=7b1PkS=_GaJ8f5p`SL&p<g07]lr$M,-/k8-o1Cj.YX+\Pt&kMmO6E$Gi3Apo6=D!qiC.s/a]jlD;?E]!R&86Ge(2SGU:cf*3>S(l'PF9W,u7kjaKqX*%uYNss?:GDhGFu$aaq#eijU$,kQo,7X*c6D0eJco0q0%2!=Y)Ln*%GuFIG$6u68;9fhWaVs[??lVp&Um$H>GIGa*qRcN1bk'd%S^kWPT@3X;j@Q[GlYu2phY%(FfSnE=DE.!;4^*0)@iP730N7qYk@W,^b@)<*C1kGJZrdk5f!YgNO!`eQb$M@r.gTZQZ_5%-lQ"j#I:=4!pI#?"`40@)SK%UMADq`P6tK%S5uj\i]3Qp)^R)W;HTul]F#IM?m"AVjs_As*C!r)*3>BYFsAPe29Sn$Kkn'358YOhcShVDPZ:u`MAREaJd$h.,+'g(:d2\&c?ST$Yb?Ftb=4[!Y[0r7SM"`c`t4$Y1[tkliEHI\R7cMc#>keE6$QY6=rY;b/Fe0YeLN%i%I0iB^"$=e.D.aRJEQe2?AJ;jr,Wna@O^SB;]fHjbNpoE.)*H]ShGiY)WMFb#o@][<=),XH#6@ACWNh15)pgU'_-0kAa+Jg/Qe=tiJo6a"4`qL!i+iL,SLg#/d&_;Pd%'go98oK!6HGD=TXLI)RAn[:n96lHZg'Rr_\egdBis4Bqb6#+K,T/"jIY''KPP`hpI!Qe8)EXV.o2*oOB&o:'YEKKAAt6aRd5fccoJEQ<K0=kGBZ7`71Hi?5-enJ:TCIm>O(TTTldl>!k&W03Z8?"I,R0Zjj^T/Hn%dPp^^t%5LYWHMgTSXLfLe*T(c89X6Jn*='Z@LTR/V)#GE"lY9'EW2/!sN%_m!j89Wa;TTCkCqEk!Yi%b>=T7)u)HG6<0pR$G8RHa)"nb;/)e7Am%jt?J67KA5p7,$"Ql_-]-%+KjdNj71'=u&*E]<Y2Ck^9X%lUot"``Oa0Pse8`EX@e<n-hZ@pI*[/fJ*]`?hIW5YFmbH`LP2rHBc6r=gS/o'SOdEmn^&.4Z1:#nQ`Q+9()+F18'Z+]"91q@J]pp7_5:3K;tk]fm-N[-R[g7*G/K>GIf"itnKjorH3u]Okd]?>td&Uq9C>@;#['&2^of')Uain]R(bY&C!\;:EI!)k:]NN&gcriR&&'r2hFgLRi8RIV+B1/gH;t(!!HrgibW(:Q_-%f?>l*QOC=u(CsTor;~>endstream
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1179
|
||||
>>
|
||||
stream
|
||||
Gat%!bAQ&q&DcM"B'EV)W[,@?]B`2YU-Mm=M:tDW%5"hCHRVkO,O"<7L3cs"S[=(N)&DC`c5@r?j-,.?;$$T]_s:S08IG8P8/BSSnlsc/=-$ESCUq_u[9Q1([`Qku&'uWeUYs2*$LYAtTU5f"iA!iQr#_<>ok$Eq-610VR\jE-60dHZp_T)E#0RCV8oA!%rk1&4Gu\(IfFLjT$>O1Gd(W!@oTJmF'=/YcJ)Ht1;B2+R_JnGUiO_(S8'dZP]h<=Vo@bo<U4:ddHNW&WIr0&c"M6T'3@8*e.L(`RpY5)e/Y6M%P7Ql^]ml[Zj2i06p(!_bFQ\Q1D5THYMU+eR=5?ir#V673f=MaE$Q*IY`G?:*:8m^$6P[o]Z-:eHic5XSAD&&!csNWa8,mXc2o=XcWJ'7R[BTkYO8r[rE^3%/Oi]kU_+_9*SKupTh#D&=ZTru`3A0f&&sSF[0%r/"O2f8UQb]^*2!1YLNm-4_%.r=c:V<?M7LQ"i;q6^@k>8JiJ[s.\.)B,BQ<I^8SaCPJ1fOH)I,rj6j<h0-A!,u7Hpcf<2tfQr(/ELndm3OLCDNCbYK?%VC-IaFhlp]5_-'Ped:KRb8+G^-fL:9HThDij4^HpoZn,ZJddAu%J`D&GlA0$i"t[;7:4YD-TPcb<?dVrMU%F=ompj<kJ.o:C8M.$HV'q]E>)f7M8/G<qeaa#*%5B/^%;@(d`t/lO<A>eu*@t-RBIU0^8^oASVi7_bMNcF[LM3jDST`dc]<d;\dUN\d@sBu=,Dh<f-8fbs@Ze1k_FgJg7j`rUHN2!<j4@eu0I=V@C//A5\JUU3WsZmBZKu]$`=niU9](-,H#DN8S'>?ur!e(\#juE\7oH3S4o0eIQ2p8a?denRQRJD:BsbHVd3b`Sp$)UTPO&f\i!h:u61Em!+O?hKaG?4K>0uqPEUb0L]5@''L@OYiK1M/W?::j9MYgq/eb]cXZ@0#b_`Pc+a)(X@_L`?5g9H9l_G[U]6tbhHa>!%Og;8:prIUM@hQY^Zj>lg,RjK$qQh;-e'@]kKbGF#k0ta1o[Q%rpC]"]`U)6I5($X/@7V\AR?6=;\NC9UCO%tm8n!j3I#VaZVd=aF9BN_*[af;>dI\)3a%.fNaDJk:[i1qmcXW:[jXe_SAq"gMMq#VAXOPPdN*MO='2IWlh,!WU6$+sZlEr~>endstream
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 784
|
||||
>>
|
||||
stream
|
||||
Gat=(9lJc?%)(h*n<aZ;CWW4%D<onS/d6XH7BtNFKdAaFeFi!cP-^tSdVKp4(l"[$7m9,"5tVZGmoBI:DrBcL!TVNh!m?cIRPikXMuA5J:0'5O3DXn5Wp.O_@0WtMZsTU@(j?>pbDo71;\9D8plVB_*6V=`%B@7]+@^%(+$N!^%*dtNr\16!=%/9ZcCh+2d/KQ&Yr#:4imq%rBh70+Q4V]7[ngK%0kLEVOUa9P_;0q:Ccm,$5RfgZMlkAI>#=3Y.^,#&DN+'0*#jR=W),SNH9sNkWo,d'*cUKD%/AshY<a'+10m!R?Fnu32)qk+]sKMl:1t"u_VQ4ZNYeR=KV^a-[Qi7u:M5uqn1]g",Yh'aGl$Pd5A2og\(pJI4<Wu13TC`Uqu/nkIr\;)r[29(<#!^([5h,lnki7p8c=[1p^-eaP?F?i^!tD;n?S7&c,iCX-0aM"nuOf;1FJOemm^WA"$MJK%S%g&JlF4UdRiCEmDH<U/[<D7CS.g?C+A3)2Ve_r)1CqYkZsuZL.M]RPYRYq_2E-#On;^n39U&dXi'47;f\2;2r%Q,]c$5K22*>qB8;TRRV_PRr`W7+Z:rDb119beRPcCls(/g8[C:T8oa[F_Z:.%EJB%T9N$L8_2u;,$ES<5Z8?X$@mCi^#2d'J7GT&fpXBT@b^U<#s0;@^K*>`jJZ]*Jb%_niA%#GNrf'!@5TT:]B6Ob@FGBBM%j0TG._a:%[0_D*9<Bpu1BHG(2,B?CS\8ZC7_lBMo^VsH4^VMu)HAhUaXm9Ec$OgpZ!!~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 15
|
||||
0000000000 65535 f
|
||||
0000000073 00000 n
|
||||
0000000134 00000 n
|
||||
0000000241 00000 n
|
||||
0000000353 00000 n
|
||||
0000000468 00000 n
|
||||
0000000673 00000 n
|
||||
0000000878 00000 n
|
||||
0000000955 00000 n
|
||||
0000001160 00000 n
|
||||
0000001229 00000 n
|
||||
0000001513 00000 n
|
||||
0000001585 00000 n
|
||||
0000002956 00000 n
|
||||
0000004227 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<b2d808ce539c534b92369a705967def5><b2d808ce539c534b92369a705967def5>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 10 0 R
|
||||
/Root 9 0 R
|
||||
/Size 15
|
||||
>>
|
||||
startxref
|
||||
5102
|
||||
%%EOF
|
||||
4
Dockerfile
Normal file
4
Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM nginx:1.27-alpine
|
||||
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY . /usr/share/nginx/html
|
||||
33
README.md
Normal file
33
README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# ChessCubing Arena
|
||||
|
||||
Application web mobile-first pour téléphone et tablette, pensée comme application officielle de suivi de match pour `ChessCubing Twice` et `ChessCubing Time`.
|
||||
|
||||
## Ce que fait cette première version
|
||||
|
||||
- configure une rencontre `Twice` ou `Time`
|
||||
- gère les blocks de 180 secondes et le temps par coup de 20 secondes
|
||||
- suit les quotas `FAST`, `FREEZE` et `MASTERS`
|
||||
- orchestre la phase cube avec désignation du cube, capture des temps et préparation du block suivant
|
||||
- applique la logique du double coup V2 en `Twice`
|
||||
- applique les ajustements `bloc -` et `bloc +` en `Time` avec plafond de 120 s pris en compte
|
||||
- conserve un historique local dans le navigateur
|
||||
|
||||
## Hypothèse de produit
|
||||
|
||||
Cette version est volontairement construite comme une **application d'arbitrage et de direction de match** autour d'un vrai échiquier physique, et non comme un moteur d'échecs complet. C'est le choix le plus fidèle aux règlements fournis et le plus réaliste pour une utilisation immédiate en club, en démonstration ou en tournoi.
|
||||
|
||||
## Démarrage avec Docker
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
L'application est ensuite disponible sur `http://localhost:8080`.
|
||||
|
||||
## Fichiers clés
|
||||
|
||||
- `index.html` : structure de l'interface
|
||||
- `styles.css` : design mobile/tablette
|
||||
- `app.js` : logique de match et arbitrage
|
||||
- `docker-compose.yml` + `Dockerfile` : exécution locale
|
||||
7
docker-compose.yml
Normal file
7
docker-compose.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
container_name: chesscubing-web
|
||||
ports:
|
||||
- "8080:80"
|
||||
restart: unless-stopped
|
||||
410
index.html
Normal file
410
index.html
Normal file
@@ -0,0 +1,410 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Application officielle de suivi de match pour ChessCubing Twice et ChessCubing Time."
|
||||
/>
|
||||
<title>ChessCubing Arena</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="ambient ambient-left"></div>
|
||||
<div class="ambient ambient-right"></div>
|
||||
<div class="layout">
|
||||
<header class="hero">
|
||||
<div class="hero-copy">
|
||||
<p class="eyebrow">Application officielle de match</p>
|
||||
<h1>ChessCubing Arena</h1>
|
||||
<p class="lead">
|
||||
Une web app pensée pour l'arbitrage sur téléphone et tablette,
|
||||
directement dérivée des règlements de
|
||||
<strong>ChessCubing Twice</strong> et
|
||||
<strong>ChessCubing Time</strong>.
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<button class="button primary" data-scroll-target="setupPanel">
|
||||
Configurer une rencontre
|
||||
</button>
|
||||
<button class="button secondary" data-scroll-target="rulesPanel">
|
||||
Voir la synthèse du règlement
|
||||
</button>
|
||||
</div>
|
||||
<div class="hero-pills">
|
||||
<span>Mobile-first</span>
|
||||
<span>Twice + Time</span>
|
||||
<span>Arbitrage en direct</span>
|
||||
<span>Fonctionne hors build</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="hero-preview">
|
||||
<div class="preview-card">
|
||||
<p class="preview-label">Ce que l'application gère</p>
|
||||
<ul class="preview-list">
|
||||
<li>Blocks de 180 s et quotas FAST / FREEZE / MASTERS</li>
|
||||
<li>Temps par coup de 20 s</li>
|
||||
<li>Phase cube, égalité, gagnant et block suivant</li>
|
||||
<li>Mode Time avec chronos cumulés et blocs - / +</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="preview-banner">
|
||||
<span class="mini-chip">Block 1</span>
|
||||
<strong id="heroModeHint">Twice ou Time, selon la configuration</strong>
|
||||
<p>
|
||||
L'app sert de chef d'orchestre pendant la partie sans imposer un
|
||||
échiquier numérique.
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
</header>
|
||||
|
||||
<main class="workspace">
|
||||
<section class="panel" id="setupPanel">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Préparer la rencontre</p>
|
||||
<h2>Configuration de match</h2>
|
||||
</div>
|
||||
<p class="section-copy">
|
||||
Lancement rapide pour club, démo ou tournoi. Les choix pilotent le
|
||||
tableau d'arbitrage en direct.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form id="setupForm" class="setup-form">
|
||||
<label class="field span-2">
|
||||
<span>Nom de la rencontre</span>
|
||||
<input
|
||||
name="matchLabel"
|
||||
type="text"
|
||||
maxlength="80"
|
||||
placeholder="Open ChessCubing de Paris"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<fieldset class="field span-2">
|
||||
<legend>Mode officiel</legend>
|
||||
<div class="option-grid mode-grid">
|
||||
<label class="option-card">
|
||||
<input type="radio" name="mode" value="twice" checked />
|
||||
<strong>ChessCubing Twice</strong>
|
||||
<span>
|
||||
Le gagnant du cube commence le block suivant et peut obtenir
|
||||
un double coup selon la règle V2.
|
||||
</span>
|
||||
</label>
|
||||
<label class="option-card">
|
||||
<input type="radio" name="mode" value="time" />
|
||||
<strong>ChessCubing Time</strong>
|
||||
<span>
|
||||
Même structure de blocks, avec deux chronos cumulés et un
|
||||
impact cube en bloc - / +.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="field span-2">
|
||||
<legend>Cadence du block</legend>
|
||||
<div class="option-grid preset-grid">
|
||||
<label class="option-card">
|
||||
<input type="radio" name="preset" value="fast" checked />
|
||||
<strong>FAST</strong>
|
||||
<span>6 coups par joueur et par block</span>
|
||||
</label>
|
||||
<label class="option-card">
|
||||
<input type="radio" name="preset" value="freeze" />
|
||||
<strong>FREEZE</strong>
|
||||
<span>8 coups par joueur et par block</span>
|
||||
</label>
|
||||
<label class="option-card">
|
||||
<input type="radio" name="preset" value="masters" />
|
||||
<strong>MASTERS</strong>
|
||||
<span>10 coups par joueur et par block</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<label class="field">
|
||||
<span>Joueur blanc</span>
|
||||
<input
|
||||
name="whiteName"
|
||||
type="text"
|
||||
maxlength="40"
|
||||
placeholder="Blanc"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span>Joueur noir</span>
|
||||
<input
|
||||
name="blackName"
|
||||
type="text"
|
||||
maxlength="40"
|
||||
placeholder="Noir"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span>Arbitre</span>
|
||||
<input
|
||||
name="arbiterName"
|
||||
type="text"
|
||||
maxlength="40"
|
||||
placeholder="Arbitre principal"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span>Club / événement</span>
|
||||
<input
|
||||
name="eventName"
|
||||
type="text"
|
||||
maxlength="60"
|
||||
placeholder="Club local, tournoi, démonstration"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field span-2">
|
||||
<span>Notes de mise en place</span>
|
||||
<textarea
|
||||
name="notes"
|
||||
rows="3"
|
||||
placeholder="Tirage au sort effectué, app lancée, cubes vérifiés..."
|
||||
></textarea>
|
||||
</label>
|
||||
|
||||
<div class="setup-summary span-2" id="setupSummary"></div>
|
||||
|
||||
<div class="setup-actions span-2">
|
||||
<button class="button primary" type="submit">
|
||||
Lancer le match
|
||||
</button>
|
||||
<button class="button ghost" type="button" id="loadDemoButton">
|
||||
Charger une démo
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="panel live-panel hidden" id="livePanel">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Direction de match</p>
|
||||
<h2 id="matchTitle">Rencontre en direct</h2>
|
||||
</div>
|
||||
<div class="status-badge" id="phaseBadge">Prêt</div>
|
||||
</div>
|
||||
|
||||
<div class="live-grid">
|
||||
<article class="panel inset score-panel">
|
||||
<div class="score-head">
|
||||
<div>
|
||||
<p class="micro-label" id="blockLabel">Block 1</p>
|
||||
<h3 id="modeLabel">ChessCubing Twice</h3>
|
||||
<p class="section-copy" id="blockMeta">
|
||||
Les Blancs commencent le block 1.
|
||||
</p>
|
||||
</div>
|
||||
<button class="button ghost small" id="resetMatchButton">
|
||||
Réinitialiser
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="timer-grid">
|
||||
<div class="timer-card emphasized">
|
||||
<span>Chrono du block</span>
|
||||
<strong id="blockTimer">03:00</strong>
|
||||
<p id="blockStatusText">En attente du démarrage.</p>
|
||||
</div>
|
||||
<div class="timer-card">
|
||||
<span>Temps par coup</span>
|
||||
<strong id="moveTimer">00:20</strong>
|
||||
<p id="turnLabel">Trait : Blanc</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="player-grid">
|
||||
<section class="player-card white-seat" id="whiteCard">
|
||||
<div class="player-name-row">
|
||||
<span class="player-color">Blanc</span>
|
||||
<strong id="whiteNameDisplay">Blanc</strong>
|
||||
</div>
|
||||
<p id="whiteMoveCount">0 / 6 coups</p>
|
||||
<p id="whiteClockLabel" class="muted"></p>
|
||||
<p id="whiteCubeLabel" class="muted"></p>
|
||||
</section>
|
||||
|
||||
<section class="player-card black-seat" id="blackCard">
|
||||
<div class="player-name-row">
|
||||
<span class="player-color">Noir</span>
|
||||
<strong id="blackNameDisplay">Noir</strong>
|
||||
</div>
|
||||
<p id="blackMoveCount">0 / 6 coups</p>
|
||||
<p id="blackClockLabel" class="muted"></p>
|
||||
<p id="blackCubeLabel" class="muted"></p>
|
||||
</section>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel inset">
|
||||
<h3>Commandes d'arbitrage</h3>
|
||||
<div class="action-grid">
|
||||
<button class="button primary" id="startPauseButton">
|
||||
Démarrer le block
|
||||
</button>
|
||||
<button class="button secondary" id="confirmBlockButton">
|
||||
Clore le block
|
||||
</button>
|
||||
<button class="button secondary" id="moveActionButton">
|
||||
Coup compté
|
||||
</button>
|
||||
<button class="button secondary" id="reliefMoveButton">
|
||||
Coup hors quota
|
||||
</button>
|
||||
<button class="button secondary" id="timeoutMoveButton">
|
||||
Dépassement 20 s
|
||||
</button>
|
||||
<button class="button ghost" id="switchTurnButton">
|
||||
Corriger le trait
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="notice-card" id="contextNotice"></div>
|
||||
<div class="double-card" id="doubleCard"></div>
|
||||
|
||||
<div class="result-grid">
|
||||
<button class="button ghost" id="whiteWinButton">
|
||||
Blanc gagne
|
||||
</button>
|
||||
<button class="button ghost" id="blackWinButton">
|
||||
Noir gagne
|
||||
</button>
|
||||
<button class="button ghost danger" id="drawStopButton">
|
||||
Abandon / arrêt
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel inset">
|
||||
<h3>Phase cube</h3>
|
||||
<div class="cube-head">
|
||||
<div>
|
||||
<span class="micro-label">Cube désigné</span>
|
||||
<strong id="cubeNumber">-</strong>
|
||||
</div>
|
||||
<button class="button secondary" id="startCubeButton">
|
||||
Démarrer la phase cube
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="cube-clock">
|
||||
<strong id="cubeElapsed">00:00.0</strong>
|
||||
<p id="cubeStatusText">
|
||||
La phase cube se déclenche à la fin du block.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="capture-grid">
|
||||
<button class="button capture" id="captureWhiteCubeButton">
|
||||
Arrêt Blanc
|
||||
</button>
|
||||
<button class="button capture" id="captureBlackCubeButton">
|
||||
Arrêt Noir
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="cube-results">
|
||||
<div>
|
||||
<span>Blanc</span>
|
||||
<strong id="whiteCubeResult">--</strong>
|
||||
<small id="whiteCubeCap"></small>
|
||||
</div>
|
||||
<div>
|
||||
<span>Noir</span>
|
||||
<strong id="blackCubeResult">--</strong>
|
||||
<small id="blackCubeCap"></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-grid compact">
|
||||
<button class="button primary" id="applyCubeButton">
|
||||
Appliquer et préparer le block suivant
|
||||
</button>
|
||||
<button class="button ghost" id="redoCubeButton">
|
||||
Rejouer la phase cube
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel inset">
|
||||
<h3>Historique</h3>
|
||||
<ul class="history-list" id="historyList"></ul>
|
||||
</article>
|
||||
|
||||
<article class="panel inset" id="rulesPanel">
|
||||
<h3>Synthèse règlementaire</h3>
|
||||
<div class="rules-grid">
|
||||
<div class="rule-card">
|
||||
<span class="micro-label">Twice</span>
|
||||
<strong>Blocks et cube</strong>
|
||||
<p>
|
||||
Block de 180 s, 20 s max par coup, phase cube obligatoire
|
||||
entre chaque block, gagnant du cube au départ suivant.
|
||||
</p>
|
||||
</div>
|
||||
<div class="rule-card">
|
||||
<span class="micro-label">Twice</span>
|
||||
<strong>Double coup V2</strong>
|
||||
<p>
|
||||
Accordé si le gagnant du cube n'a pas joué le dernier coup
|
||||
du block précédent. Premier coup gratuit sans échec,
|
||||
deuxième coup compté.
|
||||
</p>
|
||||
</div>
|
||||
<div class="rule-card">
|
||||
<span class="micro-label">Time</span>
|
||||
<strong>Impact cube</strong>
|
||||
<p>
|
||||
Block impair : le temps cube est retiré de son propre
|
||||
chrono. Block pair : il est ajouté au chrono adverse, avec
|
||||
plafond de 120 s pris en compte.
|
||||
</p>
|
||||
</div>
|
||||
<div class="rule-card">
|
||||
<span class="micro-label">Arbitrage</span>
|
||||
<strong>Vérifications clés</strong>
|
||||
<p>
|
||||
Huit cubes, caches numérotés, mélanges identiques,
|
||||
application lancée, variante choisie, tirage au sort fait,
|
||||
aucun coup pendant la phase cube.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p>
|
||||
Sources intégrées :
|
||||
<a href="ChessCubing_Twice_Reglement_Officiel_V2-1.pdf" target="_blank"
|
||||
>règlement ChessCubing Twice</a
|
||||
>
|
||||
et
|
||||
<a href="ChessCubing_Time_Reglement_Officiel_V1-1.pdf" target="_blank"
|
||||
>règlement ChessCubing Time</a
|
||||
>.
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script type="module" src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
11
nginx.conf
Normal file
11
nginx.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
646
styles.css
Normal file
646
styles.css
Normal file
@@ -0,0 +1,646 @@
|
||||
:root {
|
||||
--bg: #07151f;
|
||||
--bg-soft: rgba(12, 31, 42, 0.84);
|
||||
--panel: rgba(10, 26, 37, 0.86);
|
||||
--panel-border: rgba(255, 255, 255, 0.08);
|
||||
--panel-highlight: rgba(255, 193, 124, 0.22);
|
||||
--text: #eef5f2;
|
||||
--muted: #97adb0;
|
||||
--warm: #ffb86c;
|
||||
--warm-strong: #ff8f3c;
|
||||
--cool: #5de2d8;
|
||||
--cool-strong: #23bdb0;
|
||||
--danger: #ff6b6b;
|
||||
--shadow: 0 24px 70px rgba(0, 0, 0, 0.35);
|
||||
--radius: 26px;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-family: "Avenir Next", "Segoe UI", sans-serif;
|
||||
color: var(--text);
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(255, 184, 108, 0.15), transparent 30%),
|
||||
radial-gradient(circle at bottom right, rgba(93, 226, 216, 0.16), transparent 26%),
|
||||
linear-gradient(160deg, #030c12 0%, #07151f 46%, #0a2331 100%);
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
|
||||
background-size: 42px 42px;
|
||||
mask-image: radial-gradient(circle at center, black 48%, transparent 100%);
|
||||
}
|
||||
|
||||
.ambient {
|
||||
position: fixed;
|
||||
width: 28rem;
|
||||
height: 28rem;
|
||||
border-radius: 50%;
|
||||
filter: blur(70px);
|
||||
opacity: 0.22;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ambient-left {
|
||||
top: -10rem;
|
||||
left: -8rem;
|
||||
background: var(--warm-strong);
|
||||
}
|
||||
|
||||
.ambient-right {
|
||||
right: -10rem;
|
||||
bottom: -8rem;
|
||||
background: var(--cool-strong);
|
||||
}
|
||||
|
||||
.layout {
|
||||
position: relative;
|
||||
width: min(1200px, calc(100% - 2rem));
|
||||
margin: 0 auto;
|
||||
padding: 1.2rem 0 2.4rem;
|
||||
}
|
||||
|
||||
.hero,
|
||||
.panel,
|
||||
.footer {
|
||||
animation: rise 0.7s ease both;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.95fr);
|
||||
gap: 1.2rem;
|
||||
align-items: stretch;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.hero-copy,
|
||||
.hero-preview,
|
||||
.panel,
|
||||
.panel.inset {
|
||||
border: 1px solid var(--panel-border);
|
||||
border-radius: calc(var(--radius) + 4px);
|
||||
background: var(--panel);
|
||||
backdrop-filter: blur(18px);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.hero-preview {
|
||||
padding: 1.4rem;
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.eyebrow,
|
||||
.micro-label {
|
||||
margin: 0 0 0.4rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.18em;
|
||||
color: var(--cool);
|
||||
font-size: 0.76rem;
|
||||
}
|
||||
|
||||
.micro-label {
|
||||
color: var(--warm);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
strong {
|
||||
font-family: "Baskerville", "Georgia", serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(2.8rem, 7vw, 4.8rem);
|
||||
line-height: 0.96;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.lead,
|
||||
.section-copy,
|
||||
.preview-banner p,
|
||||
.rule-card p,
|
||||
.preview-list,
|
||||
.footer p {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.hero-actions,
|
||||
.setup-actions,
|
||||
.action-grid,
|
||||
.result-grid,
|
||||
.capture-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
margin: 1.6rem 0 1rem;
|
||||
}
|
||||
|
||||
.hero-pills {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.65rem;
|
||||
}
|
||||
|
||||
.hero-pills span,
|
||||
.mini-chip,
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.5rem 0.85rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.preview-card,
|
||||
.preview-banner,
|
||||
.notice-card,
|
||||
.double-card,
|
||||
.rule-card,
|
||||
.setup-summary,
|
||||
.timer-card,
|
||||
.player-card,
|
||||
.cube-results > div {
|
||||
padding: 1rem;
|
||||
border-radius: 22px;
|
||||
background:
|
||||
linear-gradient(160deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
|
||||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||||
}
|
||||
|
||||
.preview-list {
|
||||
margin: 0.8rem 0 0;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.preview-list li + li {
|
||||
margin-top: 0.45rem;
|
||||
}
|
||||
|
||||
.workspace {
|
||||
display: grid;
|
||||
gap: 1.2rem;
|
||||
}
|
||||
|
||||
.panel {
|
||||
padding: 1.4rem;
|
||||
}
|
||||
|
||||
.panel.inset {
|
||||
padding: 1.15rem;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.setup-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.span-2 {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
legend,
|
||||
.field > span {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 0.95rem 1rem;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 6rem;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus {
|
||||
outline: 2px solid rgba(93, 226, 216, 0.35);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.option-grid {
|
||||
display: grid;
|
||||
gap: 0.85rem;
|
||||
}
|
||||
|
||||
.mode-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.preset-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.option-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
min-height: 9rem;
|
||||
padding: 1rem 1rem 1rem 3rem;
|
||||
border-radius: 22px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
cursor: pointer;
|
||||
transition: transform 180ms ease, border-color 180ms ease, background 180ms ease;
|
||||
}
|
||||
|
||||
.option-card:hover {
|
||||
transform: translateY(-3px);
|
||||
border-color: rgba(255, 184, 108, 0.35);
|
||||
}
|
||||
|
||||
.option-card input {
|
||||
position: absolute;
|
||||
top: 1.2rem;
|
||||
left: 1rem;
|
||||
width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
}
|
||||
|
||||
.option-card:has(input:checked) {
|
||||
background:
|
||||
linear-gradient(160deg, rgba(255, 184, 108, 0.18), rgba(93, 226, 216, 0.08));
|
||||
border-color: rgba(255, 184, 108, 0.55);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 184, 108, 0.14);
|
||||
}
|
||||
|
||||
.button {
|
||||
appearance: none;
|
||||
border: 0;
|
||||
border-radius: 16px;
|
||||
padding: 0.95rem 1.1rem;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
color: var(--text);
|
||||
transition: transform 160ms ease, filter 160ms ease, background 160ms ease;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-2px);
|
||||
filter: brightness(1.04);
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.button.primary {
|
||||
background: linear-gradient(135deg, var(--warm-strong), var(--warm));
|
||||
color: #1d140a;
|
||||
}
|
||||
|
||||
.button.secondary {
|
||||
background: linear-gradient(135deg, rgba(93, 226, 216, 0.18), rgba(93, 226, 216, 0.08));
|
||||
border: 1px solid rgba(93, 226, 216, 0.25);
|
||||
}
|
||||
|
||||
.button.ghost {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.button.capture {
|
||||
flex: 1 1 12rem;
|
||||
background: linear-gradient(135deg, rgba(255, 184, 108, 0.16), rgba(255, 184, 108, 0.08));
|
||||
border: 1px solid rgba(255, 184, 108, 0.25);
|
||||
}
|
||||
|
||||
.button.danger {
|
||||
border-color: rgba(255, 107, 107, 0.25);
|
||||
color: #ffd7d7;
|
||||
}
|
||||
|
||||
.button.small {
|
||||
padding: 0.7rem 0.95rem;
|
||||
}
|
||||
|
||||
.setup-summary {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.live-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 0.95fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.live-grid > .panel.inset:nth-child(4),
|
||||
.live-grid > .panel.inset:nth-child(5) {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.score-panel {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.score-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.timer-grid,
|
||||
.player-grid,
|
||||
.cube-results,
|
||||
.rules-grid {
|
||||
display: grid;
|
||||
gap: 0.85rem;
|
||||
}
|
||||
|
||||
.timer-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
margin-bottom: 0.85rem;
|
||||
}
|
||||
|
||||
.timer-card strong,
|
||||
.cube-clock strong {
|
||||
display: block;
|
||||
margin: 0.4rem 0;
|
||||
font-size: clamp(2rem, 6vw, 3.6rem);
|
||||
line-height: 0.95;
|
||||
}
|
||||
|
||||
.timer-card.emphasized {
|
||||
background:
|
||||
linear-gradient(160deg, rgba(255, 184, 108, 0.22), rgba(255, 255, 255, 0.03));
|
||||
}
|
||||
|
||||
.player-grid,
|
||||
.cube-results,
|
||||
.rules-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.player-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.player-card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: auto -10% -50% auto;
|
||||
width: 9rem;
|
||||
height: 9rem;
|
||||
border-radius: 50%;
|
||||
opacity: 0.18;
|
||||
}
|
||||
|
||||
.white-seat::after {
|
||||
background: var(--warm);
|
||||
}
|
||||
|
||||
.black-seat::after {
|
||||
background: var(--cool);
|
||||
}
|
||||
|
||||
.player-card.active {
|
||||
border-color: var(--panel-highlight);
|
||||
box-shadow: 0 0 0 1px rgba(255, 184, 108, 0.2), inset 0 0 0 1px rgba(255, 184, 108, 0.14);
|
||||
animation: pulse 1.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.player-name-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.player-color {
|
||||
padding: 0.25rem 0.55rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.82rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.notice-card,
|
||||
.double-card {
|
||||
margin-top: 1rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.double-card strong {
|
||||
display: block;
|
||||
margin-bottom: 0.35rem;
|
||||
color: var(--warm);
|
||||
}
|
||||
|
||||
.cube-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.cube-clock {
|
||||
padding: 1rem;
|
||||
border-radius: 22px;
|
||||
background:
|
||||
linear-gradient(160deg, rgba(93, 226, 216, 0.18), rgba(255, 255, 255, 0.03));
|
||||
border: 1px solid rgba(93, 226, 216, 0.18);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.cube-results {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.cube-results strong {
|
||||
font-size: 1.35rem;
|
||||
display: block;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
.compact {
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
|
||||
.history-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
max-height: 24rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.history-list li {
|
||||
padding: 0.85rem 0.95rem;
|
||||
border-radius: 18px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.history-list small {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
color: var(--cool);
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem 0 0;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: var(--warm);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes rise {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(14px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 0 0 1px rgba(255, 184, 108, 0.2), inset 0 0 0 1px rgba(255, 184, 108, 0.14);
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: 0 0 0 1px rgba(255, 184, 108, 0.32), 0 0 32px rgba(255, 184, 108, 0.16);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.hero,
|
||||
.live-grid,
|
||||
.mode-grid,
|
||||
.preset-grid,
|
||||
.player-grid,
|
||||
.cube-results,
|
||||
.rules-grid,
|
||||
.timer-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.setup-form,
|
||||
.live-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.section-heading,
|
||||
.score-head,
|
||||
.cube-head {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.layout {
|
||||
width: min(100% - 1rem, 100%);
|
||||
}
|
||||
|
||||
.hero-copy,
|
||||
.hero-preview,
|
||||
.panel {
|
||||
padding: 1rem;
|
||||
border-radius: 22px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.65rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hero-actions,
|
||||
.setup-actions,
|
||||
.action-grid,
|
||||
.result-grid,
|
||||
.capture-grid {
|
||||
display: grid;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user