From b9670a32aa35db430b31d547d4fe75b1d85e3916 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 15 Apr 2026 16:15:01 +0000 Subject: [PATCH] Autosave: 20260415-161502 --- ai/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 404 bytes ai/__pycache__/local_ai_api.cpython-311.pyc | Bin 0 -> 19874 bytes config/__pycache__/settings.cpython-311.pyc | Bin 5552 -> 5569 bytes config/settings.py | 1 + core/__pycache__/admin.cpython-311.pyc | Bin 212 -> 942 bytes core/__pycache__/forms.cpython-311.pyc | Bin 0 -> 3619 bytes core/__pycache__/models.cpython-311.pyc | Bin 209 -> 4571 bytes core/__pycache__/tests.cpython-311.pyc | Bin 0 -> 2699 bytes core/__pycache__/urls.cpython-311.pyc | Bin 347 -> 626 bytes core/__pycache__/views.cpython-311.pyc | Bin 1364 -> 5929 bytes core/admin.py | 17 +- core/forms.py | 71 ++ core/migrations/0001_initial.py | 35 + .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 2423 bytes core/models.py | 79 +- core/templates/base.html | 70 +- core/templates/core/_brief_form.html | 74 ++ core/templates/core/brief_detail.html | 93 +++ core/templates/core/brief_form.html | 34 + core/templates/core/brief_list.html | 52 ++ core/templates/core/index.html | 245 +++--- core/tests.py | 37 +- core/urls.py | 5 +- core/views.py | 138 +++- postiz-app | 1 + static/css/custom.css | 724 ++++++++++++++++- staticfiles/css/custom.css | 725 +++++++++++++++++- 27 files changed, 2228 insertions(+), 173 deletions(-) create mode 100644 ai/__pycache__/__init__.cpython-311.pyc create mode 100644 ai/__pycache__/local_ai_api.cpython-311.pyc create mode 100644 core/__pycache__/forms.cpython-311.pyc create mode 100644 core/__pycache__/tests.cpython-311.pyc create mode 100644 core/forms.py create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 core/templates/core/_brief_form.html create mode 100644 core/templates/core/brief_detail.html create mode 100644 core/templates/core/brief_form.html create mode 100644 core/templates/core/brief_list.html create mode 160000 postiz-app diff --git a/ai/__pycache__/__init__.cpython-311.pyc b/ai/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9450fa186aa18cc132580d58f737c5e3d36d0ca GIT binary patch literal 404 zcma)(y-EW?5XbjEl1NCfwp;Ad+*NiW1XK(OHdbD&HGBm10ep{? za+P3ZC!|Z|E~vG`@S7Q!|IENV_4{4q?P2mPUVZ!s#jnLb$@b7EkBFlJ@rcJVQgIQh zq)1d+q^ec4BE*v`G)V8yE58py^n+tD$nu0f(R>i^^yc zX8rYC4%$tJ5N;SDO;3hlgbG4SVH3Z>rU9*hw#N(FdZJyH&y9k-zNxjVb65eZow51S z*xRb4400-RLWCBMkgQzq_Kua|wS*Jf^YU#7tL{apH#v3$#N7tMGxed?w28n)q A4*&oF literal 0 HcmV?d00001 diff --git a/ai/__pycache__/local_ai_api.cpython-311.pyc b/ai/__pycache__/local_ai_api.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e906db1fa6c4f29b7a64b3f917acffc876b79a07 GIT binary patch literal 19874 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 ePYFsOPU&~-F8=#&9ufBbJ86a@tU literal 0 HcmV?d00001 diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index d79d6a79890a58443ee5f65d8d558823a19e9aed..4af15a03d7993009a205d841e4a6983f7e078ff0 100644 GIT binary patch delta 910 zcmah{&1(};5PxrXlcs5Qlif6J(zMx1QPvXgI4}u^fMG*gkod@yE1p zU9S);yg+J5R{ujuSQrczLD3<~43ubzK>Idj?Wkj@W6;4mpp$h%96bgk5WBXiO(D6Z zlqicsH%mYdq&!_s@AUklq*IMwmrTU_lDQsi-p*6s2kGWG^uuB7n%QcxDeHnPi6!?H_ z(lLHk?w3!%ICtd1#R=~sEaMrqcpxL|$(XEun~vr@&MBC#exKy4N+dG_C!3dLd6+%$ z&~v<|WavD9r}V3*SPmB05XcuuZt<0Gi?#(_tL&C7=Z@v@P5&%a`Dg!SpN=n`;>DKT zu^3u8x)&_F*BrNe*LIh3lB1z>Pgz>UO7WZZ_Bs(%j;Lxtnim7-sj>4g@J9BV!aJ~Y zF7!kiR?$W_U)go+wZBBRYpwj7sLo^I(@1nR{F4?&1yKQEPquHl&X~B43z!g46fh}Z z8Ub6ZE!(a$#~3hmh?W6Ar?m z2d-p17*j7CkbnnGPsWoM51usPV0!TA!4pSgV&a?n5hzabesAW@y#Ktnv+q)`QtAs; zbxAlLFYX(~9W_9XUdMHklch3UYHx^5aD%!hOVT0Q?@^(w8@lV#*>gN02dM@se?p?0 zp1M@liqak?9sW&~ma3DyWQ~TgmV_K8DlZ{jyefw`qo=!pix5NgOAUuX*Z{;KfvY$q z+Z=8)Wsr)(24RE^!6*x2^&zw{jIl`h99-V$k|cH>XII1w{rDe0Av}Jq3ny!}<0qYj zDLz8OB+VCTjAkIqEjk*T?red@PbOmC47T$g^K$$vy+vmszw>=IJKS;TD$Jekkfoq- z=C$Vez7i+b_(vtCF0dqAXJbI`NXcSTwlxSpuosA%f3x4UdGPImyyW2gpxYpDKMA1K-`PDUexKf@o@8go7)xPl0`WwHxwNSsgz5I0IlM*^o ULdOn8m$%eock`uJr-~5x1*<5kTmS$7 diff --git a/config/settings.py b/config/settings.py index 291d043..f4bdea9 100644 --- a/config/settings.py +++ b/config/settings.py @@ -23,6 +23,7 @@ DEBUG = os.getenv("DJANGO_DEBUG", "true").lower() == "true" ALLOWED_HOSTS = [ "127.0.0.1", "localhost", + "testserver", os.getenv("HOST_FQDN", ""), ] diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 5e8987a0cd478c226e501b5189550e758662c560..60b731b338a32ed077fbd92c6ebab5f511acbf91 100644 GIT binary patch literal 942 zcmZuvy>1jS5Vm*kkIP*WAV2{`C_$hr$Q4i`Bxn$V01^dgDJ>j3LY0c~UM^BZ#`bu|o|(@xo^S1TlOX;0`b*vq2>Iic3Dr)7 zqYQ;DB8Z@hBs8Lwh(LrYNWv(jBp^W7DibFNw<#ezxW*Kar$kgXiKwDgb#l5Lq7AMw zZR55xQAku1wKt?wKkz3V>Z)8MGClASQM>e7+gyHrmfD;v^~6Y6iBDZom;fKmqq0SS zL{yL{5Ht!!5LHANRYe6Mst8eY*Zz<8*rVZ9HJ7;p*UXBZk})fca;;PgU>jhp)Tx`v zc)ZU0z!IL~X55?sE(Xkk*^n_<7V9F-U9-pp&p|MrcPhqL+e{&I(~xkfSZ4Hw6zDxu zWJ+_vY;MrJKj?&};f*hN<7%pNu$_u)#t1TwX4?Mdq6d{JSj#dnF(6_KOOqFtDc%G1 zvQu{r#!{XDW3I_qqD7&+++yrQ!PUu4OG%qEA#JAkz)h9fic~qitZjjtc$KY4P{O(y zkEl~MI63%Ej_@Jm_+EEaC(tc=n03*GPY@TmHrXqH@lPlwO<=N=4~_piPkzVgG|n0Wf!ro|dh`+6zXX>MfC_@;6- zOcInezTaHKKp%6%M4HR~xcuNT{B<9x40jWy# z$(`M^b5tY2?C{Fw{O;_+PrA5A7e~PqeMd*+eEtRY=9do3%fBT*3`ubNT(|O-{{V$< B2WkKS delta 151 zcmZ3-eudG0IWI340}#~ttjM$n(vLwL7+``jJ_`XE(-~42QW$d>av7r-85vTTf*CZK zUxE~9GTvfMOv%m6^V4LSe2__WvI~=$)h`a4-29Z%oK(9a4xk!FATE{#5+9fu85wUd MC|tmXir9cE0E&Nzv96L*tVRjXdx zKhK?e?m6e4&-=%Lfsg>>ufPAB`cYUA{*IIOkaiz-egF@T1tJiU2^BFbiURQwKl4@m zS-&XwgzEwcd@T@(iYtNk=BIu^cm_XjWF-<@6jD8JaGDYwsi^ac@kaD!vAeoqs`gFI zFcrqGaFrGd-aZf8On1)0{INiVtVo2c&lN0!VBb?82!o$DvVP+KTF3@aDG)4y;Gi@7 zT^V114qb8vb{Xva2^8CtL;cx62@d}UF)R558eHjVOF}b{z_#x%(o@*+43Faum#Vit z1FGPmRDB()9@49d2Xgv<(zxM(I*~pSBK;)1?LSbb{(X869HRICfQAfihq7T3AyM)H ziIF2@h{VY-86h8T_hkpjDEM~led1u>rst`tq(Y8lswSgOsA}bzTF6~2;P_>d=025KoR262VQ+`*d6X5!)!Q;q3>WM7V!h>m5t6F9%#4*%3#t4VXBXG<< z{sBS^;RwPYLIfcSV4t`w7oj?6gssb(QlV%?*(|GuygVL0bEfs=6UVxxUuNd;_3wUyk zu2P=1Kd)-liVP~CEJBNl#Z1?IMW(NUQoHZ>*p(~@leB%|DjLHRuNGauL&Pn9c?WrQ z6nzAtrQ{ivb&t{VeA+(Kz4IQ%=D*$adG{t%_Wo`h%TRVflYL^rN`*3tP1`efRaZ-} zwaN`sc7t86DrQ-Rh?D7B6(S1l2!UNxOBRPc=dPA@9jspl19-^*rY@=sOlXvKixD|b zL8uNP0|rttXxMr>)puY-bceuRfGng2o!(py!yuP)Lb+T;Clrs0Ij zlvQ&kkQsj#c7UPqemYauD>P%}EzPttbd44)IAo@i%b5ZkIfI#Cq^s*r0P7RS;*Jf) z_&KzXuR5P*^y5vb^Sga=&8;Qkc^7O$wTIUi+z-R!QJ*`<`_=?yZyW1RJXiom9nf!4xakmLvllPjF z%dI1!VW0%qbU*w!_@?7lV--vgJD51E}Sh9K;3m!+)X_eVWlu3>Mit1` zGcx2cR|+Q7Z`r_NN4@%p+0JP}FKMdnCVEP8!Ufo*QIaEysW=k!FSY9U8P&Xei7YEj zg(WEc*ErWXbfy3_l%gx#z}uHB^C|dq`dvmjx9!>=AJUxn(OiU0#$|x7gl2T85&h`N zR3m!gRrJ)$=&5>irV*Xll)mjB+_Zi<`|H^)@mH5NFMS&uX~d>#pDOiOz7fmU!ue)o zXfy13@L=ct4*37K&f5^5bZ(izt~1~U3eMlV{~7uWBcIfc&DA3p8LuW4saLwJ-cDs5ExvoVx4^`J6o&%VhHyX{!|6YR zCl^J6o=%XgC|XJPvMA_wBExG-ee>9szJP|{>DR6E``$UPr(NRbffbe)Lj`F0MNw=D jAJ)3RuZ82a?r$r2SrkFHgK=wVJNMRIdv^$hdyIboFhamk literal 0 HcmV?d00001 diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index a251b5fb7c2b603c587f8162d643eb10596d2858..2b700460083e0625aa7fe9c126648eba8837a660 100644 GIT binary patch literal 4571 zcmcH-O-~!!b;e&9FgV880Ybv$g9JATBoQURdwnkilY|6}gGt(FH5!e5ug!$|f|^T+PB7sp^xeF4z&}t+ML5cWejS+9j*`NyCCEzkFI+<)kz@p$W#4{?~h<`9Q?g)8uBo~Lt{;wre)Zk}^- zcRA#K$srHspL!ZEzj1TiJNPW!w(Ze_J*amt(56kGjjIp4QGW+lALHt;AI_oR-ifx;f?wn~@kNeMn=C!p zi$Wc;40Le$&_D-Qm~jm@B@XWmwn-de9MK~jkv5K#9URd%jv*E?Cm~{n-hhAK!Pj}x z!)W*=mmWbQ08imjbm}Gd21xH33|#s&U`IQ!X8?P;13L!TGaXm~uwxzAvq*sWJZldm zHQkWQ%c^cjigKICI2Uu-o}yYTkS-7d7H&GHQ5JK@TpxYcXN8rG6zOKfkG>n%*pkK= zPVAUXyUhEayqiQNH2U1&dQOIdTP_^f?L z3R6M?GNvHO3MTYi+pB0(JQ<>k z00k!iY`|vTj3TL9FbPN{LleP1qJ$8v{f%`?wP@t`2YTAkYLVyDZsY>Lx<8)m^y}m* zHl%Xz)lw0GJ&-6FiI~Usi=rwOuqfI+q6k&0q|kXl6u&P?N}b}@2*N~G^KZEia3uF} za%xj6;Hgrkq#C6u`~qh|ZZh>;BU^e=%HpZ4M(~tV^CpYiwuc@~&cLge;HyUfRGOdK z;MtvTU-{{WTmTBW2q4=~66PG8)6)8Z8tgUqJMJlr&XeNa@(F4}LpBurmWLq1N=~P5 z2xJ_f-6e{;K}4}Ua*Rppv|j+DPMuWYYQgXy%BFvqiP9p42k1!k#H{$!_3 zs9Ts#W?=Gpn;^anl@7VeV8eDRfY%S`76%9b)FrnJb@3+5JG2Z@;bj%;x>&>{3-z;n z;WH|>a81Kz`e^`7{jTow)xqogscLw(8l1C&bEbc;Q#tBJrrbPCT2l(~Tg=JCMQaJT z891B=xtkPE3VIOQ;Uvf$5X(dB)U&L-6I|4UjHVfoc2behy~?L6FgJqSLUK%g@TmE=+1>m|eO1h{>8LX7YjT{nYXt7ks1JEh;Es?&cvu zf=tM2g#xhYljU1UqCJy!SyS0X4FQyTxWcx`N<|h~@r8|r+Y6~BkzV+;6`|MtBvzjZ zIcPK{%k$6xL1eDe@+4@g0;wkG5X@3`D^H;AAmRHGE@55BN~!=}Acnx&5@ma`JeLHy zX$P`VuV?J!q6R51Y%HxN*VY$S#D#c#eJPa^B!e9dTV%H;V{RwNX)cprQqX=8^sCTl zhy{>QXHGwRjMH0L?!h+tEdZTW&o{-F2IRE7iaiD{!T)3MJDzmX5?lya?^_3tR!j!<2x6vf$?2z4op`ErmcbL%7f!M zBjfb-@%;hwS1I#J){LNP1X&RTo3tL9Go$%xG;c-oaA;tZDfO3WGcZvNOjv;ls?_*S z)C!I7Pn)6HYG~F9&4N;OPlXf9T=8=OGO`zwmNou|b4BeeNLhvfBMIphE|Nfgn~_Vjv(DQWl! zX}C`6@;-p$C~Oixp&Wt}Uo^B5F`wOyp2CHrO`?pGZ@u+3WR>o;rQTKgj|(RjTjTSi z|7+*x!Z+l%upKS>x(?vb&GUSX8#P=1YTTIF`giDi!1Gk#&ksLD|1|pEq5t$VY)q;D E0kDCE-2eap delta 143 zcmcbue33C>IWI340}#~ttjM$k(vLwL7+``jJ_`XE(-~42QW$d>av7r-85vTTf*CZK zUxE~9GTvg#%}+_qDfZK3y2Y82m6(^Fua}Zk#0-?2Y|m;g`isK`sGu|_)vkyGD8>lH U#j=wV1f{qyFvtK=5gSkh0P58p%>V!Z diff --git a/core/__pycache__/tests.cpython-311.pyc b/core/__pycache__/tests.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc6813fd078294771f40429b14faf70b321f2824 GIT binary patch literal 2699 zcmbtW&2JM&6rWvtoyB$>7bi3>qO4*_F$EjNQXxQb0kyPJYbAhs$Yr(hOu~l!>g*V> z6(kNEI8~^s6b`MFN;C!0bJahgz1h)Lu~tGw>WQ0CIC1Km-PpC0P$4m%{msmqH*em| zybu2}JRC<*{`~Q8{aXp4fB2wVU>uaq&!AaB7-7LcrZ6W6ye}G}8JUYfKZ2u1)Rg8V z0g31m!qQ!YV?i3rWwWss*{CkC?!PB#>=Jxu!0Dh6EK<+v2Yj7MX(4M#SuUWM`26JPlxhmv_<0b zZQW{D0++&%$<^Ha4PXT=qa|p=vf9trB_h_n7@%Nb4S1gP_y$L}g=MtbyMmqo=qw9M z(e4D?XOnYjZKCf-C2M;sGWKD`!g%$@5(=)eDu*z<%i`~ zrsQQx_vE&m+>poDo87tzLeYSw zq<|1u$|HF1x3v^3a>v0Ejs?FVEaN>m{zDumeoYR#&Kp|M@OI01o$ax}<@xClT8f1& zLaRgFUVpm=gC{)#PHo@gHM01t>?nh>Zi}Gbo*hg;SQeJWrJj!4cYOUSJhGiP@QrlE zPtNlgqE;!<7-__3sBUVsu6+GfE6i5rZJW7_YEB^^_a&w?gZOc$HgD)vwMGp;X@U2s zJjCfBWCT3mIyZAuv!J1VitoJ$KBR(vxb7z?!FqR)V#IW8su^38DPn56p*qyQp<`G) zQggVwsC<7$7VExb+2AKvA*{w0Y{Rf`(gfToHt3aSKQ zfO2D}BOJJi_(S&m5~(uRm#Q{+H67+NX$}Vf@)5e1_cH*#6x_DUJw;IzI?8GL0^bcE zKn-7G0d#A8u)CA*VC37m&j7aEd?S-v&*YvaGxsa4WX?m6Wk2+?rH$%J>zB1{65=~%br|r%H^l&2`_zgBVAZe7h35PUi!p6sV};$l84t@@{}h}HRY*3 zdUNuVmVDlm&o{f3?t?=PM4euKx&t$B8+G11Kkdv)fj31eems0XLw8wYtYUYQi4s1$ zQYS1Q4a_Mt5}s5-OL+%+TPY&6iv59A{3lKYTW58!W$vcOb~laPxesyCrvV1jz}Z&r zl$Sfz%1nEi>8(W2jrSk7s#lfK{k9`5~fa+cXxYPS}#E5iR=%7A~k4YDR(uW06DtFLM2>0OPEBPTm zftNIE(GHR#<-XRVK*({!R2~EE2@JRghkBKGkDFMa>nr(ec)oM=B+&T37}o~qLBx%T%Dyzo@Q-9RL6T literal 0 HcmV?d00001 diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index f705988a223abc7b5af333902925bc196186bbf5..e4a3fccb0323a8f3219b9f1828b4091a46be5120 100644 GIT binary patch literal 626 zcmah^zfapx5WW|m9VbqJcB|Sdb0mbNj1fwPGBI>%Co58A@~&X;kLcMZG!tV+CI|x( z6K&NF{WrQfQU>Iul{&FSVq@xiP8dK)+`IR5_kDNw?)zG=S3%nEi$C5w2JlZM_hv4G z+fx~w0SC@}2$;hd@DkVjQlL3nfDQ(_qX&j#Fem{rj%0BCr*WkL{E&BH1yd%Sn@=(o z(s=br#vlqEW{mRU$cTj&Z|TOylYn6w869a|U@e%~~6i|@PP zZnTjKKNd=Pt{-|djElaas+d`Pms_$*?wpVa`t*e R!|uGIua=H2b;PRN?mzBfregpA delta 215 zcmeywa+|4sIWI340}#~ttjNp)(vLwL7+{4mKHC5p(-~42QW$d>av7r-85vTTQkZj? za+#x;85x)uQW;ZNQkhd&*RU;PW?)zi#1N3q7{!vp9?YQ0@e(AU$#{#UAh9IlB_ouR zA)23?dW)e5WD!U*FEKaOPm^`BFQeS#Dn?%}KA<>AZL!zni;OY?;ujcD&}4olF@X=v PO#Dm@+#p!Q15^tD-T*3w diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2f0989c7137f7930a63ea76fcd63516c4974e834..e41ea7111c2b0e0d9f44ae5d69b4601af318fe51 100644 GIT binary patch literal 5929 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 diff --git a/core/admin.py b/core/admin.py index 8c38f3f..03b388e 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,18 @@ from django.contrib import admin -# Register your models here. +from .models import PostizInstallBrief + + +@admin.register(PostizInstallBrief) +class PostizInstallBriefAdmin(admin.ModelAdmin): + list_display = ( + "title", + "public_url", + "node_version", + "package_manager", + "ready_services_count", + "updated_at", + ) + list_filter = ("node_version", "package_manager", "email_provider", "upload_strategy") + search_fields = ("title", "public_url", "notes") + readonly_fields = ("created_at", "updated_at", "readiness_percent", "status_label") diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..b62bd46 --- /dev/null +++ b/core/forms.py @@ -0,0 +1,71 @@ +from django import forms + +from .models import PostizInstallBrief + + +class PostizInstallBriefForm(forms.ModelForm): + class Meta: + model = PostizInstallBrief + fields = [ + "title", + "public_url", + "node_version", + "package_manager", + "postgres_ready", + "redis_ready", + "temporal_ready", + "email_provider", + "upload_strategy", + "notes", + ] + widgets = { + "title": forms.TextInput( + attrs={ + "class": "form-control form-control-lg", + "placeholder": "Hosted Postiz workspace", + } + ), + "public_url": forms.URLInput( + attrs={ + "class": "form-control form-control-lg", + "placeholder": "https://your-machine.example.com", + } + ), + "node_version": forms.Select(attrs={"class": "form-select"}), + "package_manager": forms.Select(attrs={"class": "form-select"}), + "email_provider": forms.Select(attrs={"class": "form-select"}), + "upload_strategy": forms.Select(attrs={"class": "form-select"}), + "notes": forms.Textarea( + attrs={ + "class": "form-control", + "rows": 5, + "placeholder": "Optional notes about ports, reverse proxy decisions, or missing credentials.", + } + ), + "postgres_ready": forms.CheckboxInput(attrs={"class": "form-check-input"}), + "redis_ready": forms.CheckboxInput(attrs={"class": "form-check-input"}), + "temporal_ready": forms.CheckboxInput(attrs={"class": "form-check-input"}), + } + help_texts = { + "title": "A friendly name for this VM install brief.", + "public_url": "Use the public URL already attached to this machine.", + "node_version": "Postiz development requires Node.js 18 or newer.", + "package_manager": "pnpm is the default workflow in the development guide.", + "postgres_ready": "Check this if PostgreSQL is already available on the VM.", + "redis_ready": "Check this if Redis is already available on the VM.", + "temporal_ready": "Check this if the Temporal stack is already reachable.", + "email_provider": "Pick the first email path you expect to configure.", + "upload_strategy": "Choose how uploaded files should be stored initially.", + } + + def clean_title(self): + title = self.cleaned_data["title"].strip() + if len(title) < 3: + raise forms.ValidationError("Use at least 3 characters so the brief is recognizable.") + return title + + def clean_public_url(self): + public_url = self.cleaned_data["public_url"].strip() + if not public_url.startswith(("http://", "https://")): + raise forms.ValidationError("Include http:// or https:// so the reverse-proxy target is unambiguous.") + return public_url diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..8c6dea2 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 5.2.7 on 2026-04-15 14:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='PostizInstallBrief', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=120)), + ('public_url', models.URLField(help_text='Public URL where the reverse proxy will expose Postiz.')), + ('node_version', models.CharField(choices=[('18 LTS', '18 LTS'), ('20 LTS', '20 LTS'), ('22 Current', '22 Current')], default='20 LTS', max_length=20)), + ('package_manager', models.CharField(choices=[('pnpm', 'pnpm'), ('npm', 'npm')], default='pnpm', max_length=10)), + ('postgres_ready', models.BooleanField(default=False)), + ('redis_ready', models.BooleanField(default=False)), + ('temporal_ready', models.BooleanField(default=False)), + ('email_provider', models.CharField(choices=[('none', 'Skip for now'), ('resend', 'Resend'), ('smtp', 'SMTP / Nodemailer')], default='none', max_length=20)), + ('upload_strategy', models.CharField(choices=[('local', 'Local uploads'), ('r2', 'Cloudflare R2'), ('later', 'Decide later')], default='local', max_length=20)), + ('notes', models.TextField(blank=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + options={ + 'ordering': ['-updated_at'], + }, + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f78480e57c51248bc9fe70b4a562950c38705385 GIT binary patch literal 2423 zcma)8O-vg{6y9C`U>jp>!cSt7cmXG=sT%VW(KbI+F@Y!*At=zavf8c|&tNaCcQ>(vo3$Yjqbh6s=DlyedGp@v zn`eIO=!gjL{QJv4+KZqdyx_*+6KE>0Zi4cYfCMD!LRrjvrLAB_Gc;jaQQr-x3RX5eaz- ziFsd9$onM$2UZ}=#)Bl}1Eg&wbcBdVM1B<54Lp zh!gQR-hproweLn-I2|Yb6a^IfRYaXAzMD9iI_%kXl8eru?xKWxQ12<+K9Aep3`Riy z-LoxwpL-8Z@;x~HC*y(~&O;6tPR-$B3pWK^`Bd(J$BiE4Anp#fZk&x~A(g z-mt*6+=%lkcnp`YZYd7la9GrUyQ1(VaCI`ijPxcr`iVTdxInu3?qH6McxF$-jEs%R zQx!t6;gDW{2*(dRu8*}9N~Tu8Hfuw8S*_>}iNh@GwA8|?TEt3OHDDlcUHz6}l}W$n zlli!Qo;%~f0ly|J1|d5|f^7v(4sB*ca*o681S749v6zF)mPu5-!C^66RyAFL%-1vo z+dwwVZHyd%ABb8}5n_>wg?Jq`=0aX+!5j;j1QOPa zVzvFFV-z2x&+93;tYIC&YcVaaL(XM7SxE6}O;K1xQJ`T~bPRe_Q65)Rov#9`O*1qH zDwIVLw&1+5QP8kWcoSyrQ(j5m=dG6Te#oLZt#})XeTHww!c!%cG}I7OXhVzUOjF0I z(WERw1vb=lo8Vp_P_&3_kOfpg)_XW5cj#@WhJ#c(QFynzd_70IrfWYvcH z!r6jJaQ5x5Kz3wgWK?M;aMju*yt{jXEA}w_3~V2Lp2xbj@7DcdcW`^Q9umT-A0uw) z;(p@N^F$Br8-B9p_6_g(|4!4qLJdOu>{_4g_SrP<>?h9BiKi3p z(8OMa4o%mFrrn`w8lTxuT%_{&)1WJl?@d!VSCezDoTKrn|5*X9#<{C;@O^8R2X|aP zlD_7UCav-zt@E%!Lf9YyNtBO=4Z!3ahmXszB!xq|WbomV$z6a17a##iSm5IAX?N4) zL@hbtCMRh8V>oS^8sAxSQ{#Vi)6`5YHRGmcX#DQ6iM>Ud%GFXiH^nop_s6d6uc9UIDtOi3D6iX` z=5eP$dlS0ZxLA{98chBVf&B - {% block title %}Knowledge Base{% endblock %} - {% if project_description %} - - - - {% endif %} + + {% block title %}Postiz Native Setup Studio{% endblock %} + {% if project_image_url %} {% endif %} - {% load static %} + + + + + + + + {% block head %}{% endblock %} - - {% block content %}{% endblock %} + +
+
+ + +
+
+ {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + {% block content %}{% endblock %} +
+
+ +
+
+ {{ project_name }} · Django {{ django_version }} · Python {{ python_version }} + Last render: {{ current_time|date:"Y-m-d H:i" }} UTC +
+
+ + diff --git a/core/templates/core/_brief_form.html b/core/templates/core/_brief_form.html new file mode 100644 index 0000000..4ba5ad6 --- /dev/null +++ b/core/templates/core/_brief_form.html @@ -0,0 +1,74 @@ +
+ {% csrf_token %} +
+
+ + {{ form.title }} + {% if form.title.help_text %}
{{ form.title.help_text }}
{% endif %} + {% for error in form.title.errors %}
{{ error }}
{% endfor %} +
+
+ + {{ form.public_url }} + {% if form.public_url.help_text %}
{{ form.public_url.help_text }}
{% endif %} + {% for error in form.public_url.errors %}
{{ error }}
{% endfor %} +
+
+ + {{ form.node_version }} +
{{ form.node_version.help_text }}
+
+
+ + {{ form.package_manager }} +
{{ form.package_manager.help_text }}
+
+
+ + {{ form.email_provider }} +
{{ form.email_provider.help_text }}
+
+
+
+
+
+ +
{{ form.postgres_ready.help_text }}
+
+
{{ form.postgres_ready }}
+
+
+
+ +
{{ form.redis_ready.help_text }}
+
+
{{ form.redis_ready }}
+
+
+
+ +
{{ form.temporal_ready.help_text }}
+
+
{{ form.temporal_ready }}
+
+
+
+
+ + {{ form.upload_strategy }} +
{{ form.upload_strategy.help_text }}
+
+
+ + {{ form.notes }} + {% for error in form.notes.errors %}
{{ error }}
{% endfor %} +
+
+ {% if form.non_field_errors %} +
{{ form.non_field_errors }}
+ {% endif %} +
+

Saving a brief generates a live detail page with prerequisite status, env variables, and boot commands.

+ +
+
diff --git a/core/templates/core/brief_detail.html b/core/templates/core/brief_detail.html new file mode 100644 index 0000000..d881f58 --- /dev/null +++ b/core/templates/core/brief_detail.html @@ -0,0 +1,93 @@ +{% extends "base.html" %} + +{% block title %}{{ brief.title }} · Install blueprint{% endblock %} +{% block meta_description %}Review the generated Postiz install blueprint for {{ brief.title }}, including prerequisite readiness, env placeholders, and bootstrap commands.{% endblock %} + +{% block content %} +
+
+
+
Install blueprint
+

{{ brief.title }}

+

{{ brief.next_milestone }}

+
+ {{ brief.status_label }} + {{ brief.public_url }} + {{ brief.node_version }} · {{ brief.package_manager }} +
+
+
+
+
Readiness
+
+
+ {{ brief.readiness_percent }}% + service readiness +
+
+

{{ brief.ready_services_count }} of 3 prerequisite services are marked ready for the initial development boot.

+
+
+
+
+ +
+
+
+
+
+ Prerequisites +

Service checklist

+
+
+
+ {% for item in blueprint.prerequisites %} +
+
+
+

{{ item.title }}

+

{{ item.detail }}

+
+
+ {% endfor %} +
+ {% if brief.notes %} +
+ Notes +

{{ brief.notes }}

+
+ {% endif %} +
+
+
+
+
+
+ Environment +

Suggested .env starting point

+
+
+
+ {% for line in blueprint.env_lines %}{{ line }}{% if not forloop.last %}
{% endif %}{% endfor %}
+
+
+
+
+
+ Bootstrap +

Suggested command path

+
+ Back to briefs +
+
+ {% for command in blueprint.command_plan %} +
+ 0{{ forloop.counter }} + {{ command }} +
+ {% endfor %} +
+
+
+
+{% endblock %} diff --git a/core/templates/core/brief_form.html b/core/templates/core/brief_form.html new file mode 100644 index 0000000..e3ee841 --- /dev/null +++ b/core/templates/core/brief_form.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} + +{% block title %}Create install brief · Postiz Native Setup Studio{% endblock %} +{% block meta_description %}Create a hosted Postiz install brief, capture the public URL, and save prerequisite readiness for this VM.{% endblock %} + +{% block content %} +
+
+
+
Create install brief
+

Capture the first hosted Postiz setup plan.

+

Tell the app which URL, Node target, and prerequisite services you expect. After saving, you will land on a generated detail page with next actions, env placeholders, and command prompts.

+
+
+
+
+ Tracked now + Public URL + service readiness +

Keep one source of truth while the VM is being prepared.

+
+
+ After save + Confirmation + detail blueprint +

Jump directly into the generated install checklist and command sequence.

+
+
+
+
+
+ +
+ {% include "core/_brief_form.html" with submit_label="Generate install blueprint" %} +
+{% endblock %} diff --git a/core/templates/core/brief_list.html b/core/templates/core/brief_list.html new file mode 100644 index 0000000..87074c8 --- /dev/null +++ b/core/templates/core/brief_list.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} + +{% block title %}Install briefs · Postiz Native Setup Studio{% endblock %} +{% block meta_description %}Browse saved Postiz install briefs, compare service readiness, and reopen any setup blueprint for this VM.{% endblock %} + +{% block content %} +
+
+
+
Install briefs
+

Saved setup plans for this hosted environment.

+

Every brief keeps the public URL, infrastructure readiness, and next milestone visible so you can iterate without losing context.

+
+ Create another brief +
+
+ +
+ {% if briefs %} +
+ {% for brief in briefs %} +
+
+
+
+

{{ brief.title }}

+

{{ brief.public_url }}

+
+ {{ brief.status_label }} +
+
+
{{ brief.ready_services_count }}/3 infrastructure services ready
+
    +
  • Node{{ brief.node_version }}
  • +
  • Package manager{{ brief.package_manager }}
  • +
  • Email{{ brief.get_email_provider_display }}
  • +
+

{{ brief.next_milestone }}

+ Open blueprint +
+
+ {% endfor %} +
+ {% else %} +
+

No install briefs yet

+

Start with one brief so the app can generate the first Postiz setup blueprint for this VM.

+ Create the first brief +
+ {% endif %} +
+{% endblock %} diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..03ddcfb 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,112 @@ {% extends "base.html" %} -{% block title %}{{ project_name }}{% endblock %} - -{% block head %} - - - - -{% endblock %} +{% block title %}Postiz Native Setup Studio · Overview{% endblock %} +{% block meta_description %}Design the first native Postiz install plan for this VM, track service readiness, and jump into the next development steps from one polished landing page.{% endblock %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+
+
+
Hosted Postiz development workflow
+

Plan a native Postiz development install without losing sight of the public URL, services, or next commands.

+

This first slice turns the blank starter into a Postiz setup cockpit: a polished landing page, install brief workflow, generated prerequisite summary, and a detail view you can revisit while you wire the VM.

+ +
+
+ {{ brief_stats.total }} + Saved briefs +
+
+ {{ brief_stats.ready }} + Ready to bootstrap +
+
+ 3 + Core services tracked +
+
+
+
+
+
First delivery included
+
+
+ Runtime + Node 18+ +

Choose the Node target and package manager before the repo bootstrap.

+
+
+ Local services + Postgres · Redis · Temporal +

Mark what is already available on the VM and see the gaps instantly.

+
+
+ Public entrypoint + Reverse-proxied URL +

Keep the exposed hostname visible in every install brief and detail page.

+
+
+
-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
- Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC) -
-{% endblock %} \ No newline at end of file + + +
+
+
+
+
+ Workflow +

How the thin slice works

+
+
+
+ {% for step in setup_steps %} +
+ 0{{ forloop.counter }} +

{{ step.title }}

+

{{ step.description }}

+
+ {% endfor %} +
+
+
+
+
+
+
+ Recent activity +

Install brief snapshot

+
+ See all +
+ {% if recent_briefs %} + + {% else %} +
+

No install briefs yet

+

Create the first brief to generate a hosted Postiz setup blueprint for this machine.

+ Start the first brief +
+ {% endif %} +
+
+
+{% endblock %} diff --git a/core/tests.py b/core/tests.py index 7ce503c..9df2d93 100644 --- a/core/tests.py +++ b/core/tests.py @@ -1,3 +1,38 @@ from django.test import TestCase +from django.urls import reverse -# Create your tests here. +from .models import PostizInstallBrief + + +class PostizPagesTests(TestCase): + def test_home_page_loads(self): + response = self.client.get(reverse("home")) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Plan a native Postiz development install") + + def test_create_brief_flow(self): + response = self.client.post( + reverse("brief_create"), + { + "title": "Primary VM", + "public_url": "https://postiz.example.com", + "node_version": "20 LTS", + "package_manager": "pnpm", + "postgres_ready": "on", + "redis_ready": "on", + "temporal_ready": "on", + "email_provider": "smtp", + "upload_strategy": "local", + "notes": "Ready to bootstrap.", + }, + follow=True, + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(PostizInstallBrief.objects.count(), 1) + brief = PostizInstallBrief.objects.get() + self.assertContains(response, brief.title) + self.assertContains(response, "Ready for repo bootstrap") + + def test_brief_list_empty_state(self): + response = self.client.get(reverse("brief_list")) + self.assertContains(response, "No install briefs yet") diff --git a/core/urls.py b/core/urls.py index 6299e3d..494f4bf 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,10 @@ from django.urls import path -from .views import home +from .views import brief_create, brief_detail, brief_list, home urlpatterns = [ path("", home, name="home"), + path("briefs/", brief_list, name="brief_list"), + path("briefs/new/", brief_create, name="brief_create"), + path("briefs//", brief_detail, name="brief_detail"), ] diff --git a/core/views.py b/core/views.py index c9aed12..f50933e 100644 --- a/core/views.py +++ b/core/views.py @@ -2,18 +2,44 @@ import os import platform from django import get_version as django_version -from django.shortcuts import render +from django.contrib import messages +from django.db.models import Count, Q +from django.shortcuts import get_object_or_404, redirect, render from django.utils import timezone +from .forms import PostizInstallBriefForm +from .models import PostizInstallBrief -def home(request): - """Render the landing screen with loader and environment details.""" + +SETUP_STEPS = [ + { + "title": "Install the runtime", + "description": "Get Node.js and the selected package manager onto the VM before cloning Postiz.", + }, + { + "title": "Wire prerequisite services", + "description": "Confirm PostgreSQL, Redis, and Temporal are reachable before the first app boot.", + }, + { + "title": "Bootstrap the repository", + "description": "Fill the .env file, install packages, push Prisma schema changes, and start the dev processes.", + }, +] + + +def base_context(request): host_name = request.get_host().lower() agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" now = timezone.now() - - context = { - "project_name": "New Style", + stats = PostizInstallBrief.objects.aggregate( + total=Count("id"), + ready=Count( + "id", + filter=Q(postgres_ready=True, redis_ready=True, temporal_ready=True), + ), + ) + return { + "project_name": "Postiz Native Setup Studio", "agent_brand": agent_brand, "django_version": django_version(), "python_version": platform.python_version(), @@ -21,5 +47,105 @@ def home(request): "host_name": host_name, "project_description": os.getenv("PROJECT_DESCRIPTION", ""), "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), + "brief_stats": stats, + "setup_steps": SETUP_STEPS, } + + +def build_blueprint(brief): + prerequisites = [ + { + "title": "Node runtime", + "ready": True, + "detail": f"Use {brief.node_version} with {brief.package_manager} for dependency management.", + }, + { + "title": "PostgreSQL", + "ready": brief.postgres_ready, + "detail": "Prepare DATABASE_URL before copying .env.example to .env.", + }, + { + "title": "Redis", + "ready": brief.redis_ready, + "detail": "Queue workers expect REDIS_URL on the VM.", + }, + { + "title": "Temporal stack", + "ready": brief.temporal_ready, + "detail": "Point TEMPORAL_ADDRESS at the Temporal service before starting dev workflows.", + }, + ] + + env_lines = [ + 'DATABASE_URL="postgresql://postiz-user:password@localhost:5432/postiz-db-local"', + 'REDIS_URL="redis://localhost:6379"', + 'TEMPORAL_ADDRESS="localhost:7233"', + f'FRONTEND_URL="{brief.public_url}"', + 'NEXT_PUBLIC_BACKEND_URL="http://127.0.0.1:3000"', + 'BACKEND_INTERNAL_URL="http://127.0.0.1:3000"', + ] + + command_plan = [ + "git clone https://github.com/gitroomhq/postiz-app.git", + "pnpm install", + "pnpm run prisma-db-push", + "pnpm run dev", + ] + + return { + "prerequisites": prerequisites, + "env_lines": env_lines, + "command_plan": command_plan, + } + + +def home(request): + context = base_context(request) + context.update( + { + "recent_briefs": PostizInstallBrief.objects.all()[:3], + } + ) return render(request, "core/index.html", context) + + +def brief_create(request): + if request.method == "POST": + form = PostizInstallBriefForm(request.POST) + if form.is_valid(): + brief = form.save() + messages.success(request, "Install brief saved. Review the generated blueprint below.") + return redirect("brief_detail", pk=brief.pk) + else: + form = PostizInstallBriefForm() + + context = base_context(request) + context.update( + { + "form": form, + } + ) + return render(request, "core/brief_form.html", context) + + +def brief_list(request): + context = base_context(request) + context.update( + { + "briefs": PostizInstallBrief.objects.all(), + } + ) + return render(request, "core/brief_list.html", context) + + +def brief_detail(request, pk): + brief = get_object_or_404(PostizInstallBrief, pk=pk) + blueprint = build_blueprint(brief) + context = base_context(request) + context.update( + { + "brief": brief, + "blueprint": blueprint, + } + ) + return render(request, "core/brief_detail.html", context) diff --git a/postiz-app b/postiz-app new file mode 160000 index 0000000..386fc7b --- /dev/null +++ b/postiz-app @@ -0,0 +1 @@ +Subproject commit 386fc7b049737d5047bc83c6c19dd291e22eb28c diff --git a/static/css/custom.css b/static/css/custom.css index 925f6ed..6176d04 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -1,4 +1,722 @@ -/* Custom styles for the application */ -body { - font-family: system-ui, -apple-system, sans-serif; +/* Postiz Native Setup Studio */ +:root { + --bg-main: #081220; + --bg-panel: rgba(9, 19, 36, 0.72); + --bg-panel-strong: rgba(11, 24, 44, 0.92); + --line-soft: rgba(167, 243, 208, 0.14); + --line-strong: rgba(167, 243, 208, 0.22); + --text-main: #f8fafc; + --text-soft: #bfd1e3; + --text-muted: #8ca0b8; + --primary: #0f766e; + --primary-strong: #14b8a6; + --secondary: #132238; + --accent: #f97316; + --accent-soft: #fed7aa; + --success: #34d399; + --danger: #fb7185; + --warning: #fbbf24; + --shadow-soft: 0 30px 80px rgba(3, 10, 20, 0.45); + --radius-xl: 28px; + --radius-lg: 20px; + --radius-md: 16px; + --spacing-section: clamp(3rem, 7vw, 5.5rem); +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body.app-shell { + margin: 0; + min-height: 100vh; + font-family: 'Inter', sans-serif; + color: var(--text-main); + background: + radial-gradient(circle at top left, rgba(20, 184, 166, 0.24), transparent 32%), + radial-gradient(circle at 85% 18%, rgba(249, 115, 22, 0.18), transparent 24%), + linear-gradient(180deg, #0b1322 0%, #08111f 48%, #060d19 100%); + position: relative; + overflow-x: hidden; +} + +body.app-shell::before { + content: ""; + position: fixed; + inset: 0; + background-image: linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px); + background-size: 42px 42px; + mask-image: linear-gradient(180deg, rgba(255, 255, 255, 0.6), transparent 78%); + pointer-events: none; +} + +.bg-orb { + position: fixed; + border-radius: 999px; + filter: blur(24px); + opacity: 0.55; + pointer-events: none; +} + +.bg-orb-one { + width: 300px; + height: 300px; + top: 6rem; + right: -6rem; + background: linear-gradient(135deg, rgba(20, 184, 166, 0.28), rgba(52, 211, 153, 0.14)); +} + +.bg-orb-two { + width: 260px; + height: 260px; + bottom: 8rem; + left: -5rem; + background: linear-gradient(135deg, rgba(249, 115, 22, 0.2), rgba(254, 215, 170, 0.12)); +} + +h1, +.h1, +.display-title, +.page-title, +.section-title, +.brand-title { + font-family: 'Space Grotesk', sans-serif; + letter-spacing: -0.04em; +} + +p, +li, +label, +input, +select, +textarea, +button { + font-family: 'Inter', sans-serif; +} + +.site-header { + position: sticky; + top: 0; + z-index: 20; + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + background: rgba(6, 13, 25, 0.52); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + +.navbar-brand { + color: var(--text-main); + text-decoration: none; +} + +.brand-mark { + width: 48px; + height: 48px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 16px; + background: linear-gradient(135deg, var(--primary), var(--primary-strong)); + box-shadow: 0 14px 30px rgba(15, 118, 110, 0.32); + font-weight: 800; + font-size: 1.2rem; +} + +.brand-title { + display: block; + font-size: 1rem; + font-weight: 700; +} + +.brand-subtitle { + color: var(--text-muted); + font-size: 0.78rem; + letter-spacing: 0.02em; +} + +.nav-link { + color: var(--text-soft) !important; + font-weight: 600; + padding: 0.75rem 1rem !important; + border-radius: 999px; +} + +.nav-link:hover, +.nav-link:focus-visible { + color: #ffffff !important; + background: rgba(255, 255, 255, 0.08); +} + +.page-shell { + position: relative; + z-index: 1; +} + +.hero-section, +.inner-hero { + padding-top: var(--spacing-section); +} + +.py-lg-6 { + padding-top: var(--spacing-section) !important; + padding-bottom: var(--spacing-section) !important; +} + +.eyebrow-chip, +.panel-label, +.section-kicker, +.info-chip { + display: inline-flex; + align-items: center; + gap: 0.5rem; + border-radius: 999px; + padding: 0.55rem 0.9rem; + border: 1px solid var(--line-strong); + background: rgba(167, 243, 208, 0.08); + color: #dffdf3; + font-size: 0.84rem; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.display-title, +.page-title { + font-size: clamp(2.75rem, 5vw, 4.8rem); + line-height: 0.96; + margin: 0; + max-width: 12ch; +} + +.page-title { + font-size: clamp(2.3rem, 4vw, 3.6rem); +} + +.hero-copy { + max-width: 60ch; + color: var(--text-soft); + font-size: 1.08rem; + line-height: 1.8; +} + +.hero-copy.small { + font-size: 0.98rem; + line-height: 1.7; +} + +.btn { + border-radius: 16px; + padding: 0.88rem 1.3rem; + font-weight: 700; + letter-spacing: -0.01em; +} + +.btn-highlight { + color: #081220; + background: linear-gradient(135deg, #5eead4, #facc15); + border: none; + box-shadow: 0 18px 35px rgba(94, 234, 212, 0.22); +} + +.btn-highlight:hover, +.btn-highlight:focus-visible { + color: #081220; + transform: translateY(-1px); + box-shadow: 0 24px 45px rgba(94, 234, 212, 0.28); +} + +.btn-outline-light { + border-color: rgba(255, 255, 255, 0.18); + color: var(--text-main); + background: rgba(255, 255, 255, 0.03); +} + +.btn-outline-light:hover, +.btn-outline-light:focus-visible { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.3); + color: #ffffff; +} + +.glass-panel { + background: linear-gradient(180deg, rgba(10, 20, 38, 0.86), rgba(6, 15, 29, 0.92)); + border: 1px solid rgba(255, 255, 255, 0.08); + box-shadow: var(--shadow-soft); + border-radius: var(--radius-xl); + padding: 2rem; +} + +.hero-panel, +.section-panel, +.brief-card, +.summary-card, +.setup-form { + position: relative; + overflow: hidden; +} + +.hero-panel::after, +.section-panel::after, +.setup-form::after, +.summary-card::after { + content: ""; + position: absolute; + inset: auto -20% -30% auto; + width: 180px; + height: 180px; + background: radial-gradient(circle, rgba(20, 184, 166, 0.18), transparent 68%); + pointer-events: none; +} + +.metric-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1rem; + max-width: 44rem; +} + +.metric-card, +.stack-preview-card, +.roadmap-card, +.brief-preview-card, +.info-rail-card, +.empty-card, +.note-panel, +.checklist-item, +.command-row { + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.04); + border-radius: var(--radius-lg); +} + +.metric-card { + padding: 1.15rem 1.2rem; +} + +.metric-value { + display: block; + font-family: 'Space Grotesk', sans-serif; + font-size: 2rem; + font-weight: 700; +} + +.metric-label, +.progress-copy, +.card-subtitle, +.form-hint, +.helper-copy, +.next-copy, +.text-link, +.card-subtitle, +.stack-preview-card p, +.roadmap-card p, +.brief-preview-card p, +.info-rail-card p, +.checklist-item p, +.note-panel p { + color: var(--text-muted); +} + +.stack-preview, +.info-rail, +.roadmap-grid, +.brief-list-preview, +.checklist-grid, +.command-list { + display: grid; + gap: 1rem; +} + +.stack-preview-card, +.roadmap-card, +.brief-preview-card, +.info-rail-card, +.empty-card, +.note-panel, +.checklist-item, +.command-row { + padding: 1.25rem; +} + +.stack-title, +.roadmap-index, +.command-index { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 3rem; + min-height: 3rem; + border-radius: 14px; + background: rgba(20, 184, 166, 0.12); + color: #d7fff7; + font-weight: 700; +} + +.stack-preview-card strong, +.info-rail-card strong, +.card-title, +.brief-preview-card h3, +.roadmap-card h3, +.checklist-item h3, +.section-title { + display: block; + margin: 0.8rem 0 0.5rem; + color: #ffffff; +} + +.section-heading-wrap { + display: flex; + justify-content: space-between; + align-items: end; + gap: 1rem; +} + +.section-title { + font-size: clamp(1.5rem, 2.5vw, 2.25rem); + margin: 0.5rem 0 0; +} + +.brief-preview-card { + display: block; + text-decoration: none; + color: inherit; + transition: transform 0.2s ease, border-color 0.2s ease; +} + +.brief-preview-card:hover, +.brief-preview-card:focus-visible, +.brief-card:hover, +.brief-card:focus-within { + transform: translateY(-3px); + border-color: rgba(94, 234, 212, 0.3); +} + +.status-pill { + display: inline-flex; + align-items: center; + border-radius: 999px; + padding: 0.45rem 0.8rem; + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.03em; + white-space: nowrap; +} + +.status-success { + background: rgba(52, 211, 153, 0.14); + color: #86efac; +} + +.status-warning { + background: rgba(251, 191, 36, 0.14); + color: #fde68a; +} + +.status-danger { + background: rgba(251, 113, 133, 0.14); + color: #fda4af; +} + +.progress-track { + width: 100%; + height: 10px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + overflow: hidden; +} + +.progress-track span { + display: block; + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, #5eead4, #facc15); +} + +.progress-0 span { + width: 8%; +} + +.progress-1 span { + width: 33%; +} + +.progress-2 span { + width: 67%; +} + +.progress-3 span { + width: 100%; +} + +.info-rail, +.stack-preview, +.brief-list-preview, +.command-list, +.checklist-grid, +.roadmap-grid { + grid-template-columns: 1fr; +} + +.setup-form .form-control, +.setup-form .form-select { + min-height: 3.5rem; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.05); + color: #ffffff; + padding: 0.85rem 1rem; +} + +.setup-form textarea.form-control { + min-height: 11rem; +} + +.setup-form .form-control::placeholder { + color: rgba(191, 209, 227, 0.55); +} + +.setup-form .form-control:focus, +.setup-form .form-select:focus, +.form-check-input:focus, +.btn:focus-visible, +.nav-link:focus-visible, +.text-link:focus-visible { + box-shadow: 0 0 0 0.25rem rgba(94, 234, 212, 0.18); + border-color: rgba(94, 234, 212, 0.45); + outline: none; +} + +.form-label, +.form-check-label { + font-weight: 700; + margin-bottom: 0.55rem; +} + +.form-hint, +.helper-copy { + font-size: 0.92rem; + line-height: 1.6; +} + +.field-error { + color: #fecaca; + font-size: 0.93rem; + margin-top: 0.5rem; +} + +.service-readiness-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1rem; +} + +.service-toggle { + display: flex; + justify-content: space-between; + gap: 1rem; + padding: 1rem 1.1rem; + border-radius: 18px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.form-check-input { + width: 3rem; + height: 1.6rem; + margin-top: 0; + background-color: rgba(255, 255, 255, 0.16); + border-color: rgba(255, 255, 255, 0.22); +} + +.form-check-input:checked { + background-color: var(--primary-strong); + border-color: var(--primary-strong); +} + +.card-title { + font-size: 1.35rem; + margin: 0; +} + +.detail-list { + list-style: none; + padding: 0; + margin: 0; + display: grid; + gap: 0.8rem; +} + +.detail-list li { + display: flex; + justify-content: space-between; + gap: 1rem; + color: var(--text-soft); +} + +.next-copy { + line-height: 1.7; + font-size: 0.98rem; +} + +.readiness-ring { + margin-inline: auto; + width: 220px; + height: 220px; + border-radius: 50%; + display: grid; + place-items: center; +} + +.readiness-ring.progress-0 { + background: conic-gradient(#5eead4 0deg, #5eead4 29deg, rgba(255, 255, 255, 0.08) 29deg); +} + +.readiness-ring.progress-1 { + background: conic-gradient(#5eead4 0deg, #5eead4 120deg, rgba(255, 255, 255, 0.08) 120deg); +} + +.readiness-ring.progress-2 { + background: conic-gradient(#5eead4 0deg, #5eead4 240deg, rgba(255, 255, 255, 0.08) 240deg); +} + +.readiness-ring.progress-3 { + background: conic-gradient(#5eead4 0deg, #5eead4 360deg, rgba(255, 255, 255, 0.08) 360deg); +} + +.readiness-ring-inner { + width: 150px; + height: 150px; + border-radius: 50%; + background: rgba(7, 16, 30, 0.94); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; +} + +.readiness-ring-inner strong { + font-family: 'Space Grotesk', sans-serif; + font-size: 2.4rem; + line-height: 1; +} + +.readiness-ring-inner span { + color: var(--text-muted); + font-size: 0.9rem; + margin-top: 0.4rem; +} + +.checklist-item { + display: flex; + gap: 1rem; + align-items: flex-start; +} + +.checklist-dot { + width: 16px; + height: 16px; + border-radius: 999px; + background: rgba(251, 113, 133, 0.55); + box-shadow: 0 0 0 8px rgba(251, 113, 133, 0.08); + margin-top: 0.45rem; + flex-shrink: 0; +} + +.checklist-item.is-ready .checklist-dot { + background: rgba(52, 211, 153, 0.85); + box-shadow: 0 0 0 8px rgba(52, 211, 153, 0.08); +} + +.code-panel { + padding: 1.25rem; + border-radius: var(--radius-lg); + background: rgba(4, 10, 20, 0.9); + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.code-panel code, +.command-row code { + color: #ccfbf1; + font-size: 0.95rem; + line-height: 1.9; + white-space: pre-wrap; + word-break: break-word; +} + +.command-row { + display: flex; + align-items: center; + gap: 1rem; +} + +.text-link { + color: #bffef1; + font-weight: 700; + text-decoration: none; +} + +.text-link:hover, +.text-link:focus-visible { + color: #ffffff; +} + +.toast-stack { + padding-top: 1.5rem; +} + +.glass-alert { + border-radius: 18px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(11, 24, 44, 0.86); + color: #ffffff; +} + +.site-footer { + position: relative; + z-index: 1; +} + +@media (max-width: 991.98px) { + .metric-row, + .service-readiness-grid { + grid-template-columns: 1fr; + } + + .display-title, + .page-title { + max-width: none; + } +} + +@media (max-width: 767.98px) { + .glass-panel { + padding: 1.4rem; + border-radius: 22px; + } + + .hero-section, + .inner-hero, + .py-lg-6 { + padding-top: 2.5rem !important; + padding-bottom: 2.5rem !important; + } + + .brand-subtitle { + display: none !important; + } + + .section-heading-wrap { + align-items: flex-start; + flex-direction: column; + } + + .command-row { + align-items: flex-start; + flex-direction: column; + } } diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css index 108056f..6176d04 100644 --- a/staticfiles/css/custom.css +++ b/staticfiles/css/custom.css @@ -1,21 +1,722 @@ - +/* Postiz Native Setup Studio */ :root { - --bg-color-start: #6a11cb; - --bg-color-end: #2575fc; - --text-color: #ffffff; - --card-bg-color: rgba(255, 255, 255, 0.01); - --card-border-color: rgba(255, 255, 255, 0.1); + --bg-main: #081220; + --bg-panel: rgba(9, 19, 36, 0.72); + --bg-panel-strong: rgba(11, 24, 44, 0.92); + --line-soft: rgba(167, 243, 208, 0.14); + --line-strong: rgba(167, 243, 208, 0.22); + --text-main: #f8fafc; + --text-soft: #bfd1e3; + --text-muted: #8ca0b8; + --primary: #0f766e; + --primary-strong: #14b8a6; + --secondary: #132238; + --accent: #f97316; + --accent-soft: #fed7aa; + --success: #34d399; + --danger: #fb7185; + --warning: #fbbf24; + --shadow-soft: 0 30px 80px rgba(3, 10, 20, 0.45); + --radius-xl: 28px; + --radius-lg: 20px; + --radius-md: 16px; + --spacing-section: clamp(3rem, 7vw, 5.5rem); } -body { + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body.app-shell { margin: 0; + min-height: 100vh; font-family: 'Inter', sans-serif; - background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end)); - color: var(--text-color); + color: var(--text-main); + background: + radial-gradient(circle at top left, rgba(20, 184, 166, 0.24), transparent 32%), + radial-gradient(circle at 85% 18%, rgba(249, 115, 22, 0.18), transparent 24%), + linear-gradient(180deg, #0b1322 0%, #08111f 48%, #060d19 100%); + position: relative; + overflow-x: hidden; +} + +body.app-shell::before { + content: ""; + position: fixed; + inset: 0; + background-image: linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px); + background-size: 42px 42px; + mask-image: linear-gradient(180deg, rgba(255, 255, 255, 0.6), transparent 78%); + pointer-events: none; +} + +.bg-orb { + position: fixed; + border-radius: 999px; + filter: blur(24px); + opacity: 0.55; + pointer-events: none; +} + +.bg-orb-one { + width: 300px; + height: 300px; + top: 6rem; + right: -6rem; + background: linear-gradient(135deg, rgba(20, 184, 166, 0.28), rgba(52, 211, 153, 0.14)); +} + +.bg-orb-two { + width: 260px; + height: 260px; + bottom: 8rem; + left: -5rem; + background: linear-gradient(135deg, rgba(249, 115, 22, 0.2), rgba(254, 215, 170, 0.12)); +} + +h1, +.h1, +.display-title, +.page-title, +.section-title, +.brand-title { + font-family: 'Space Grotesk', sans-serif; + letter-spacing: -0.04em; +} + +p, +li, +label, +input, +select, +textarea, +button { + font-family: 'Inter', sans-serif; +} + +.site-header { + position: sticky; + top: 0; + z-index: 20; + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + background: rgba(6, 13, 25, 0.52); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + +.navbar-brand { + color: var(--text-main); + text-decoration: none; +} + +.brand-mark { + width: 48px; + height: 48px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 16px; + background: linear-gradient(135deg, var(--primary), var(--primary-strong)); + box-shadow: 0 14px 30px rgba(15, 118, 110, 0.32); + font-weight: 800; + font-size: 1.2rem; +} + +.brand-title { + display: block; + font-size: 1rem; + font-weight: 700; +} + +.brand-subtitle { + color: var(--text-muted); + font-size: 0.78rem; + letter-spacing: 0.02em; +} + +.nav-link { + color: var(--text-soft) !important; + font-weight: 600; + padding: 0.75rem 1rem !important; + border-radius: 999px; +} + +.nav-link:hover, +.nav-link:focus-visible { + color: #ffffff !important; + background: rgba(255, 255, 255, 0.08); +} + +.page-shell { + position: relative; + z-index: 1; +} + +.hero-section, +.inner-hero { + padding-top: var(--spacing-section); +} + +.py-lg-6 { + padding-top: var(--spacing-section) !important; + padding-bottom: var(--spacing-section) !important; +} + +.eyebrow-chip, +.panel-label, +.section-kicker, +.info-chip { + display: inline-flex; + align-items: center; + gap: 0.5rem; + border-radius: 999px; + padding: 0.55rem 0.9rem; + border: 1px solid var(--line-strong); + background: rgba(167, 243, 208, 0.08); + color: #dffdf3; + font-size: 0.84rem; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.display-title, +.page-title { + font-size: clamp(2.75rem, 5vw, 4.8rem); + line-height: 0.96; + margin: 0; + max-width: 12ch; +} + +.page-title { + font-size: clamp(2.3rem, 4vw, 3.6rem); +} + +.hero-copy { + max-width: 60ch; + color: var(--text-soft); + font-size: 1.08rem; + line-height: 1.8; +} + +.hero-copy.small { + font-size: 0.98rem; + line-height: 1.7; +} + +.btn { + border-radius: 16px; + padding: 0.88rem 1.3rem; + font-weight: 700; + letter-spacing: -0.01em; +} + +.btn-highlight { + color: #081220; + background: linear-gradient(135deg, #5eead4, #facc15); + border: none; + box-shadow: 0 18px 35px rgba(94, 234, 212, 0.22); +} + +.btn-highlight:hover, +.btn-highlight:focus-visible { + color: #081220; + transform: translateY(-1px); + box-shadow: 0 24px 45px rgba(94, 234, 212, 0.28); +} + +.btn-outline-light { + border-color: rgba(255, 255, 255, 0.18); + color: var(--text-main); + background: rgba(255, 255, 255, 0.03); +} + +.btn-outline-light:hover, +.btn-outline-light:focus-visible { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.3); + color: #ffffff; +} + +.glass-panel { + background: linear-gradient(180deg, rgba(10, 20, 38, 0.86), rgba(6, 15, 29, 0.92)); + border: 1px solid rgba(255, 255, 255, 0.08); + box-shadow: var(--shadow-soft); + border-radius: var(--radius-xl); + padding: 2rem; +} + +.hero-panel, +.section-panel, +.brief-card, +.summary-card, +.setup-form { + position: relative; + overflow: hidden; +} + +.hero-panel::after, +.section-panel::after, +.setup-form::after, +.summary-card::after { + content: ""; + position: absolute; + inset: auto -20% -30% auto; + width: 180px; + height: 180px; + background: radial-gradient(circle, rgba(20, 184, 166, 0.18), transparent 68%); + pointer-events: none; +} + +.metric-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1rem; + max-width: 44rem; +} + +.metric-card, +.stack-preview-card, +.roadmap-card, +.brief-preview-card, +.info-rail-card, +.empty-card, +.note-panel, +.checklist-item, +.command-row { + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.04); + border-radius: var(--radius-lg); +} + +.metric-card { + padding: 1.15rem 1.2rem; +} + +.metric-value { + display: block; + font-family: 'Space Grotesk', sans-serif; + font-size: 2rem; + font-weight: 700; +} + +.metric-label, +.progress-copy, +.card-subtitle, +.form-hint, +.helper-copy, +.next-copy, +.text-link, +.card-subtitle, +.stack-preview-card p, +.roadmap-card p, +.brief-preview-card p, +.info-rail-card p, +.checklist-item p, +.note-panel p { + color: var(--text-muted); +} + +.stack-preview, +.info-rail, +.roadmap-grid, +.brief-list-preview, +.checklist-grid, +.command-list { + display: grid; + gap: 1rem; +} + +.stack-preview-card, +.roadmap-card, +.brief-preview-card, +.info-rail-card, +.empty-card, +.note-panel, +.checklist-item, +.command-row { + padding: 1.25rem; +} + +.stack-title, +.roadmap-index, +.command-index { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 3rem; + min-height: 3rem; + border-radius: 14px; + background: rgba(20, 184, 166, 0.12); + color: #d7fff7; + font-weight: 700; +} + +.stack-preview-card strong, +.info-rail-card strong, +.card-title, +.brief-preview-card h3, +.roadmap-card h3, +.checklist-item h3, +.section-title { + display: block; + margin: 0.8rem 0 0.5rem; + color: #ffffff; +} + +.section-heading-wrap { display: flex; + justify-content: space-between; + align-items: end; + gap: 1rem; +} + +.section-title { + font-size: clamp(1.5rem, 2.5vw, 2.25rem); + margin: 0.5rem 0 0; +} + +.brief-preview-card { + display: block; + text-decoration: none; + color: inherit; + transition: transform 0.2s ease, border-color 0.2s ease; +} + +.brief-preview-card:hover, +.brief-preview-card:focus-visible, +.brief-card:hover, +.brief-card:focus-within { + transform: translateY(-3px); + border-color: rgba(94, 234, 212, 0.3); +} + +.status-pill { + display: inline-flex; + align-items: center; + border-radius: 999px; + padding: 0.45rem 0.8rem; + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.03em; + white-space: nowrap; +} + +.status-success { + background: rgba(52, 211, 153, 0.14); + color: #86efac; +} + +.status-warning { + background: rgba(251, 191, 36, 0.14); + color: #fde68a; +} + +.status-danger { + background: rgba(251, 113, 133, 0.14); + color: #fda4af; +} + +.progress-track { + width: 100%; + height: 10px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + overflow: hidden; +} + +.progress-track span { + display: block; + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, #5eead4, #facc15); +} + +.progress-0 span { + width: 8%; +} + +.progress-1 span { + width: 33%; +} + +.progress-2 span { + width: 67%; +} + +.progress-3 span { + width: 100%; +} + +.info-rail, +.stack-preview, +.brief-list-preview, +.command-list, +.checklist-grid, +.roadmap-grid { + grid-template-columns: 1fr; +} + +.setup-form .form-control, +.setup-form .form-select { + min-height: 3.5rem; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.05); + color: #ffffff; + padding: 0.85rem 1rem; +} + +.setup-form textarea.form-control { + min-height: 11rem; +} + +.setup-form .form-control::placeholder { + color: rgba(191, 209, 227, 0.55); +} + +.setup-form .form-control:focus, +.setup-form .form-select:focus, +.form-check-input:focus, +.btn:focus-visible, +.nav-link:focus-visible, +.text-link:focus-visible { + box-shadow: 0 0 0 0.25rem rgba(94, 234, 212, 0.18); + border-color: rgba(94, 234, 212, 0.45); + outline: none; +} + +.form-label, +.form-check-label { + font-weight: 700; + margin-bottom: 0.55rem; +} + +.form-hint, +.helper-copy { + font-size: 0.92rem; + line-height: 1.6; +} + +.field-error { + color: #fecaca; + font-size: 0.93rem; + margin-top: 0.5rem; +} + +.service-readiness-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1rem; +} + +.service-toggle { + display: flex; + justify-content: space-between; + gap: 1rem; + padding: 1rem 1.1rem; + border-radius: 18px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.form-check-input { + width: 3rem; + height: 1.6rem; + margin-top: 0; + background-color: rgba(255, 255, 255, 0.16); + border-color: rgba(255, 255, 255, 0.22); +} + +.form-check-input:checked { + background-color: var(--primary-strong); + border-color: var(--primary-strong); +} + +.card-title { + font-size: 1.35rem; + margin: 0; +} + +.detail-list { + list-style: none; + padding: 0; + margin: 0; + display: grid; + gap: 0.8rem; +} + +.detail-list li { + display: flex; + justify-content: space-between; + gap: 1rem; + color: var(--text-soft); +} + +.next-copy { + line-height: 1.7; + font-size: 0.98rem; +} + +.readiness-ring { + margin-inline: auto; + width: 220px; + height: 220px; + border-radius: 50%; + display: grid; + place-items: center; +} + +.readiness-ring.progress-0 { + background: conic-gradient(#5eead4 0deg, #5eead4 29deg, rgba(255, 255, 255, 0.08) 29deg); +} + +.readiness-ring.progress-1 { + background: conic-gradient(#5eead4 0deg, #5eead4 120deg, rgba(255, 255, 255, 0.08) 120deg); +} + +.readiness-ring.progress-2 { + background: conic-gradient(#5eead4 0deg, #5eead4 240deg, rgba(255, 255, 255, 0.08) 240deg); +} + +.readiness-ring.progress-3 { + background: conic-gradient(#5eead4 0deg, #5eead4 360deg, rgba(255, 255, 255, 0.08) 360deg); +} + +.readiness-ring-inner { + width: 150px; + height: 150px; + border-radius: 50%; + background: rgba(7, 16, 30, 0.94); + display: flex; + flex-direction: column; justify-content: center; align-items: center; - min-height: 100vh; text-align: center; - overflow: hidden; - position: relative; +} + +.readiness-ring-inner strong { + font-family: 'Space Grotesk', sans-serif; + font-size: 2.4rem; + line-height: 1; +} + +.readiness-ring-inner span { + color: var(--text-muted); + font-size: 0.9rem; + margin-top: 0.4rem; +} + +.checklist-item { + display: flex; + gap: 1rem; + align-items: flex-start; +} + +.checklist-dot { + width: 16px; + height: 16px; + border-radius: 999px; + background: rgba(251, 113, 133, 0.55); + box-shadow: 0 0 0 8px rgba(251, 113, 133, 0.08); + margin-top: 0.45rem; + flex-shrink: 0; +} + +.checklist-item.is-ready .checklist-dot { + background: rgba(52, 211, 153, 0.85); + box-shadow: 0 0 0 8px rgba(52, 211, 153, 0.08); +} + +.code-panel { + padding: 1.25rem; + border-radius: var(--radius-lg); + background: rgba(4, 10, 20, 0.9); + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.code-panel code, +.command-row code { + color: #ccfbf1; + font-size: 0.95rem; + line-height: 1.9; + white-space: pre-wrap; + word-break: break-word; +} + +.command-row { + display: flex; + align-items: center; + gap: 1rem; +} + +.text-link { + color: #bffef1; + font-weight: 700; + text-decoration: none; +} + +.text-link:hover, +.text-link:focus-visible { + color: #ffffff; +} + +.toast-stack { + padding-top: 1.5rem; +} + +.glass-alert { + border-radius: 18px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(11, 24, 44, 0.86); + color: #ffffff; +} + +.site-footer { + position: relative; + z-index: 1; +} + +@media (max-width: 991.98px) { + .metric-row, + .service-readiness-grid { + grid-template-columns: 1fr; + } + + .display-title, + .page-title { + max-width: none; + } +} + +@media (max-width: 767.98px) { + .glass-panel { + padding: 1.4rem; + border-radius: 22px; + } + + .hero-section, + .inner-hero, + .py-lg-6 { + padding-top: 2.5rem !important; + padding-bottom: 2.5rem !important; + } + + .brand-subtitle { + display: none !important; + } + + .section-heading-wrap { + align-items: flex-start; + flex-direction: column; + } + + .command-row { + align-items: flex-start; + flex-direction: column; + } }