From e2a71ab0961060d2c15a91c32f0f5b64a6b4b9cf Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 11 Apr 2026 01:14:33 +0200 Subject: [PATCH] =?UTF-8?q?Hashtags=20eingef=C3=BChrt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + .../de/oaa/xxx/config/SecurityConfig.class | Bin 10993 -> 10974 bytes .../xxx/feed/FeedController$FeedPage.class | Bin 1722 -> 1722 bytes .../xxx/feed/FeedController$VoteRequest.class | Bin 1401 -> 1401 bytes bin/main/de/oaa/xxx/feed/FeedController.class | Bin 23971 -> 27674 bytes ...itragController$CreateBeitragRequest.class | Bin 2386 -> 2386 bytes .../GruppenbeitragController$PostsPage.class | Bin 1821 -> 1821 bytes ...uppenbeitragController$ReportRequest.class | Bin 1461 -> 1461 bytes ...GruppenbeitragController$VoteRequest.class | Bin 1471 -> 1471 bytes .../xxx/gruppe/GruppenbeitragController.class | Bin 22181 -> 22580 bytes .../oaa/xxx/hashtag/HashtagController.class | Bin 0 -> 1967 bytes .../de/oaa/xxx/hashtag/HashtagEntity.class | Bin 0 -> 1208 bytes .../oaa/xxx/hashtag/HashtagRepository.class | Bin 0 -> 819 bytes .../de/oaa/xxx/hashtag/HashtagService.class | Bin 0 -> 7546 bytes .../oaa/xxx/hashtag/PostHashtagEntity.class | Bin 0 -> 2157 bytes ...stHashtagRepository$HashtagFrequency.class | Bin 0 -> 356 bytes .../xxx/hashtag/PostHashtagRepository.class | Bin 0 -> 1462 bytes bin/main/static/community/feed.html | 89 ++++- bin/main/static/community/freunde.html | 2 +- bin/main/static/community/gruppe.html | 10 +- bin/main/static/community/gruppen.html | 120 +----- bin/main/static/community/nachrichten.html | 2 +- .../static/community/personen-suchen.html | 206 ---------- bin/main/static/games/common/einladungen.html | 371 ++++++++++++++++++ bin/main/static/games/vanilla/neuvanilla.html | 15 +- bin/main/static/js/hashtag.js | 217 ++++++++++ bin/main/static/js/mobile-nav.js | 6 +- bin/main/static/js/nav.js | 2 +- bin/main/static/js/section-nav.js | 9 +- bin/main/static/js/topbar.js | 44 ++- bin/main/static/search.html | 245 +++++++++++- bin/main/static/userhome.html | 59 ++- docker-compose.yml | 2 +- .../de/oaa/xxx/config/SecurityConfig.java | 2 +- .../java/de/oaa/xxx/feed/FeedController.java | 61 ++- .../xxx/gruppe/GruppenbeitragController.java | 8 +- .../de/oaa/xxx/hashtag/HashtagController.java | 35 ++ .../de/oaa/xxx/hashtag/HashtagEntity.java | 21 + .../de/oaa/xxx/hashtag/HashtagRepository.java | 15 + .../de/oaa/xxx/hashtag/HashtagService.java | 100 +++++ .../de/oaa/xxx/hashtag/PostHashtagEntity.java | 35 ++ .../xxx/hashtag/PostHashtagRepository.java | 29 ++ src/main/resources/static/community/feed.html | 89 ++++- .../resources/static/community/freunde.html | 2 +- .../resources/static/community/gruppe.html | 10 +- .../resources/static/community/gruppen.html | 120 +----- .../static/community/nachrichten.html | 2 +- .../static/community/personen-suchen.html | 206 ---------- .../static/games/common/einladungen.html | 371 ++++++++++++++++++ .../static/games/vanilla/neuvanilla.html | 15 +- src/main/resources/static/js/hashtag.js | 217 ++++++++++ src/main/resources/static/js/mobile-nav.js | 6 +- src/main/resources/static/js/nav.js | 2 +- src/main/resources/static/js/section-nav.js | 9 +- src/main/resources/static/js/topbar.js | 44 ++- src/main/resources/static/search.html | 245 +++++++++++- src/main/resources/static/userhome.html | 59 ++- 57 files changed, 2365 insertions(+), 740 deletions(-) create mode 100644 bin/main/de/oaa/xxx/hashtag/HashtagController.class create mode 100644 bin/main/de/oaa/xxx/hashtag/HashtagEntity.class create mode 100644 bin/main/de/oaa/xxx/hashtag/HashtagRepository.class create mode 100644 bin/main/de/oaa/xxx/hashtag/HashtagService.class create mode 100644 bin/main/de/oaa/xxx/hashtag/PostHashtagEntity.class create mode 100644 bin/main/de/oaa/xxx/hashtag/PostHashtagRepository$HashtagFrequency.class create mode 100644 bin/main/de/oaa/xxx/hashtag/PostHashtagRepository.class delete mode 100644 bin/main/static/community/personen-suchen.html create mode 100644 bin/main/static/games/common/einladungen.html create mode 100644 bin/main/static/js/hashtag.js create mode 100644 src/main/java/de/oaa/xxx/hashtag/HashtagController.java create mode 100644 src/main/java/de/oaa/xxx/hashtag/HashtagEntity.java create mode 100644 src/main/java/de/oaa/xxx/hashtag/HashtagRepository.java create mode 100644 src/main/java/de/oaa/xxx/hashtag/HashtagService.java create mode 100644 src/main/java/de/oaa/xxx/hashtag/PostHashtagEntity.java create mode 100644 src/main/java/de/oaa/xxx/hashtag/PostHashtagRepository.java delete mode 100644 src/main/resources/static/community/personen-suchen.html create mode 100644 src/main/resources/static/games/common/einladungen.html create mode 100644 src/main/resources/static/js/hashtag.js diff --git a/.gitignore b/.gitignore index da56994..2d6aca8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ # Ignore Gradle build output directory build .aider* + +# Secrets – niemals einchecken +.env diff --git a/bin/main/de/oaa/xxx/config/SecurityConfig.class b/bin/main/de/oaa/xxx/config/SecurityConfig.class index 740f6aab7cf1d90f65397cf31d30015efebe6dc9..dee7a62405a935d8c2a9d0a6947a095b7f499a2c 100644 GIT binary patch delta 352 zcmewudM|W?f{?VhesX?pZfRa-Nu_>TQEF*kN~&H)Np21Y!=}jzLJEvqCN~PnGH#nZ zUr2^=$K*Xi(nh-&8AOmZrKYApb?jke5Y$gh$<54zkWk@$lR1So7!OP~5>{qBG&xLI zi}A?hMqyRPW0RK)>oA^}d<7_bYVu#8?3u}mB1(+sCcB8JGhUdSBci}~X>zZKgv=F2 z22uT##FEUsbp52%;?m@d)FP^At bK)-`|&lnka^fMBRGfEQEi#PX)CW`_9E--cS delta 378 zcmcZ?`Z08af{?PjesX?pZfRa-Nu_>4YEf~1UTU6hacOc!YMx$3Np21Y!=}k{LNbh7 zCR+ELMY$&Y7cx-Zvuqxw;$xXsKjHf2A7uH}rGx-Kk_T1z@ z!b*%6Cd-ScGhUkPE}|fHg^@v6KP9mwGcO%VK@Gh&xlu&Q_y!|`5KKBJGdr~yDtim$ zMyR&j#1f#7VKR3npA(T|xyQ&LKl!4Fu(obWYDr>d4piF%RBxx2rRJ5uG(MWFC#op* egpq+;9}*mS`dV5X49^%DIP^hbu(?_^Srh=V`hKeb diff --git a/bin/main/de/oaa/xxx/feed/FeedController$FeedPage.class b/bin/main/de/oaa/xxx/feed/FeedController$FeedPage.class index 61b7dd59c49d74b11373c90c7eee8199e54c1926..f1930d3e37a5afeea66ee9168a9c22c2ffa1a7ad 100644 GIT binary patch delta 20 bcmdnRyNh>&0V`wCWJ6XX#-hzBtYM4*Lv;nn delta 20 bcmdnRyNh>&0V`wHWJ6XX#;naLtYM4*LgfXx diff --git a/bin/main/de/oaa/xxx/feed/FeedController$VoteRequest.class b/bin/main/de/oaa/xxx/feed/FeedController$VoteRequest.class index 8bf8cb7117f83f2cd8f5a8af89f3f6916a60f6b7..616c5776f61b0a216ca853e94fc0f83c387894f1 100644 GIT binary patch delta 14 Vcmey#^^3k|2_A;H}huZCB**w`~UqgnLF>@d+xbs zKj+@}eEHwoo+P4L{R@i}(>_hHszfweb^7V2S2f3CO;xk-XL_P7l}xm>#F7>jFqN*3 zu8CH)MB7$YEo@jFYfLeP<}I8(d*))MvU!Q*%Bqf*=EGK3%}cCY8B5mSqCi@d=twP& zwI@2_sYG%uQ`NlOsgmitX4dQh05ik`GcSHh%n$0&zJSUe1aLz=a0}a0@kE;+;Ng7% zm^;jm;j%>P>tmQb2;c@cC*!fUrjAwdc0YDm%k4-s#-lA&e%b{L0E6~5i>H#&m42ub`vf&_Ab{@C+1df5I;Lr^}v`Cxm+Jhu8B9snD!1F z;NF(rEzsw5?|uEfT_lA_ng0D(MLSldqASxwM*4@iH>>7kb;2}7lN(#&ZSm9;COvx0 zGNyv*iKbYX#?p9;#zFRSE!V7QqnIk24x}ok;+3(KW6_xQ8$B;$1E;q{J34B{hsb5HHoG2b9`2w zJp2ezsx(8{ye}W**4!rLvrvGMu-uiF%prd1!g-O#yn+~JVqC7{~WKof{ zYHTW|u(Xb}sf2849c5EL>MyNYn+8y+w5HoMkjkVr)22Z*SX#4fDyJdRsxj*4OJNLf zDXx#NY>TEklQ2P-|G%1dvY+{eM&A1BQnA(IWu_pK6=&X2aYgBE~UxF*`tiKT|B5P7BmXhilTX`cLt zvn`Gp-b_n0t*r?|IGSx*Nvq(~lCh3N%bM8ywRKHQV_km{$UEanI#cnMs-;WoW&p}+ zYO&}PrUQ&a%2{#%S8ZygHY~9N)at|%CylOiz0vN+juOnLb;eto5UH0?J0ZkBmFdtv zPVFCVQwKqq3mXKQthvufV9jgjbc;@78kw6Ec!J_&1BTc(t)(+W|6-?OzQLF$sfUxl9* zW*yw%*3$+UnT}XWz>Dk;a#7Fp6f?h$Zn5cB>05%nwVkk!$+{+C+wC@e z$6;?W-YOmMwCOJTE~tTyOAO1)gQ#JE-ec27x|gXR`ew+9X7o{FSfYTnQYK$x^b3G zwNUqAE|mgCjagPgZ_(Qp{e)?JuHYwoD=ygdQ+fx>=!mX?uh`$u2&cjU7n`v$?j<3{ zdp5lL)h+Ldam_YIm z)26TJ-w;Z0gQ;9xX9ih!xW(}7V|{!HGNn$YaeF$mYRZjs>R@!d5p7o0UwoI^wh<%U}kr*NrG0YGouU%PX02o(K0rJwzG2hs{Gd z0<0Pkejup#v(e*OV8j;?{?u5!7u-ySYco%8j2RANxycx!nf?4$JPRjTtn2F$EEY?= zWI}2Lqt3ztA~s;%YNEQ*+_huA)Y^6k3kY5A6QeXjn4_TGsOtfFCcFfrPwIVJ|vqsm| z^+=(<2YyVa{#O9yCm4ceCJ7rQKNq2}XcCRX0!1q*nV1b9oyjGtT2aK2Qe;qc95mb& zEt6yj@7s`@i3k7@*#D+ja})upw$;qn0_58xmpDU!B*VYCHbtH}(npT_2TUIJZgjlv2{@`KNZ z=ASDYSD9y-&BvOEFO`r5TfE#eac0)qWHP!|ZiM+bJ{|=VJ^?1nKjC!QuQ3a#k+^6Z zpJ?+*d@_)WHbH;0LTJz8t|;LKZnC%$GVWu(8!r2pKf9-W0UM?d>5459Fm0^_50V0m z@FQlDT>s(?L(AZkRtbM#?e_H|!yfohi2W-04_VvG+-h?hBN8ZvZgzIeh^|EfJZ$t_ zcY=V(1~DR^YIBm&@o)!EFa4-%t8Hsahh(+QDAg=XA~Bw}HhpnMtOL}k@h`~W@7ThC z>GD=8yy&!fjhlqdY;8}iE#cGo42vO?hxdik>;~Sp3k@W0=^K++L&l-jKWjR_mePN{vBH8Ygd*T$R1LL zeaTfNd@)~Y@g=AUm|(y_n4V~DN8JsDe48(0B;Li1<_f9=ptc{0eC++a%fNvhxR@`u z`3hbKW1EbvK`1OPacGv$A=_Z{&3udG&7BP*qf#otJs)+Ad z1%d$g8RlF0J7R9`5NNreOcJ=7@3Q&365Oc-sw>6L9%fw=)Xg6-nYnR0>Zbm9%lNNsOpo#j-yqaBU* zwpx8K4($zwVPtOfEm-%HeAFasqTHZJm6N)Kc{@L0@#9E#f+C!zXewHT5>PbWRwc2N z8{MG3z)y-mi*S97aj00l#ZTG%G(Ur!+p)xTZlPgGvNINJdh18`X%5*_>!B|0Uq(HO z8ZjvJbG*ai=aDJw4u3Pi<`={_hb4i>(Fic9j0(lhJ;yKG{6k~eA!G|ov8u2`=75Ur z7Hi!RyklaHbJuU2R=rcC|1Zhhh5t*Szn;_;Rk_7FRPy-uHvfS?6|dwJ z{ualYL9S5&h~~NDGO~D8nE%XwwfHZ{X)>&LqX9RtPb87={f++)Zln_RF?jcVvf_{2 z-X?qbADcgCRL`tNCp$or>6gpAF)nt7Y3W+nKj1(zSYhfiD&l!aL};Rf9beh}Z{7uI zfV(wumsI)u%S0W>T(2onthA+Y>Z~jS&%0}@0#zs}voWICn>-#W3Wd2*zR1PpfY}0c zrO)w|VHHxvwhBwB*5e7vmN@)z7Qc;~^bu4_^|#dkRSL{qUj)yXRbTYdB%jZ9$|=}_ zEu&&pW~)JJFl>A4}u|qbY9Nu#rnv35fI~y5IQD}@`YR1&Q2ye+f8&iz&E+z zCRuSy3;v=nViA9VqDPs+ph-S3dC_0b10@6-?i&gW0)xwN3I`}#VsdIHu#94qCcjYE zL)a5kImLdpUj_^x9K`-_z`BbuR%2Nr-ekOTsj~rJb!NQj>?%3%g{VB<1d-)VY`&@q zwwkCWi4RGnW+gh?pnKJ@8bN+3U?HRC#G=U4JJK83su~;F3Ur?EpFs$#Bh^usnu5b? zKKqh?s>N1QRW0<*;gq;KiLe}wjqHK6zT6qx606m8q)W2zT(Aa*Mw!mbALc`$8B^pY zXU=gevtj+;IU{n-|7R3-FF-;nF?Tr8^OY(LtE1I?OU+}d>^rG!wLtdf+|8i6CTz8+ z{<1CeQ8E2VEw)v?S^}mlS-f;6N^g!<7?Jiisj$bTmf7l9+5DAw$G4e_oiBd7M6FOK zSnBxfN~&R?uaxG=O!k>MTYW>Fh&rR=i5weV*M#6sf_N#ZO~_|a#)szFQ_l$)7jd$! zqLQK%V^`O41(rfhYfLWb;3ND;1q4CIo65pswrW1?Y(qvbe6}%fDNX!d9j_+g9hure$GMtf3P`8|Qa0yVGbyYZ7i_CX`%!lu7dxOh~jl_xr^= zd{T%2J$5>Z5-4>E^C0WqPbBzxDD=o~SkWT`=FJa}b09dvVvPUv|75(gKpzx;!Ngnr zZn4OSt--{zjUBBq+INGkZWMX$??9Ng*3mz)b~lu$4eAC< z-P|_|m3?}+NZktElamf7Em~N=q(t4W?y%H%0=KPv<}TpWsjco*cQGC4n1>}%RPIs| zoWt5VKkAxtbLv_t!XM^W_MT|qBZ_M|Zda`m?8l1KM&#~(jTTd!KHso1*79suN8>7^ z!1v+Qf%+cGeLYU;Pm!EC{a560%xZPNtsamVr?@TQmW@zDN*~mRe)^-pJmJjVDNEBa ztsi0auzJ)|k059CS@KR;EOS1;-Z_nLtH;!4$Qkyb4Y0VSD!6%U0XGGcYn69Ibj>^|#a$*$*3~5zTBTii7`wt)7yjIiW;yW=ls*B=#9w zJ*%FBPBbD;OnCwwc+hOF^xq-==R^_E7j5;DdKvpe?#Mt{LlLk?E@xcSn7N|1uh{BU zg`+%DQ1u@-t3c@LqljC~&GbMrZmZXA^@ePg?c?{0uB7`;f|OB-dK23->Mb03-fcn| zm?6&cb}-Ax=2e#ZDfZZW=ozz@QzsVIzH6)Zyu(ckuyb9cK7jBpMH%`OoVQI$mk(|A zk#rdd0xU`gP63pfiL}zp&LWrDQ56p`24^a`o4?`i+d1>ZHSq zrOB4CdRzStTMX*=%mZCcWa#eGtsYeVKks2QHVbN>uXr(`Gr2+6m>cJJ!0yG|6?238 zX2!0_Ux{S3jIHI6vugrL_KR6sgE^2~t*eW-_u`xRz#-@MT^0*xaw5}Cr z=Xh3=CRgX6nB{C;p%%HxdO%`^dzyQXypu)FE(TLWmK3{iT(|4=$=T`fq2b1ZyOCbR zxnHO&E%jygLBQQlCFA~StAB~b5~*}mR*cqHYL}({4Iki}+Xd+JZ`t@~yG&b?*sW66 z`uJgMWos=F|4_#mr0=Ce{vurnZ((XN#^C}}ZP~iWiyEAXfha=R{+2EYTzMX%8Dm>U z{ZMyQLv()}V$cH+?D};tT^&lB62GDGg0Y$+jgw{}2NfaZDv)`q6&sDpC$zl~J;RXN+)XC9GqHQP^oGd8S8>q<+H z$xh>Xhhpoop77M;DN}LH0%p9e50vZT(-y}XV{zex$hquMKKu`1!g0u)^x6+DC~rtF!-s5; zqxH>ms46AmIWg0mlxZHpv^<|08LR_q zy}3ki`6Usz4if2j?L1Z zAZg3Wm4jl>KsRmarKoo2V-(aGl`(y+t&j6eh8HGC#q2$~dAzMpkb}lj3iE}psGjH( zZGDn>z5%Z1OOMq{bQGbUhGjj#&rF9~hI{@HtZuFefTZZ-iNRs!FjoE&TmhSHjISqN z`2iL9quzyqKv5G}*yuSJrc5rlZ#n#3u1s>B!pbc{B;&uHs2 z^;t-3@pz?oiRl$rq>Ko;BOTy#qHRqrv5rym5{Xkf+ufqMU($S%k~L8368`Rezv)cb z`&iel`|+KQeW`lvq}B`w>C;N-9kJf$*?K>{KQ5eWPR{!TX*Vu8FKj5)7q~Dlw)MgK z5WxIqsXoNL_HA2F(nk!iG)~RU&C-Y<6BMKhrk^N+2psCM=26d5eT6&0)wZtIHJIQk zro~wk1lie~q#(=6^)S)3GLd>)-)QTb&3brfSxGT^@f-Y`dHBJ znOw;;w97%vS2_)lyyx60UbJ7xHs9=n*>s|M zM;L+V8%$HPD40zE*Vg(6Wt}oLj;K`s1leF+TU#t?-t`beL{em^Z_e?T$U5D#aB%g` zS$s>QR|UI@Daj>3zHu;Bef-FmA{9KwNLA+(Hv7$8M%cmE$&3b>;ZYG`TWTu$iV*MhZ96 z)XGR`Gfk_ESet1^Wu#~`&8mzPZ>Bkwk?>}kTNx?YO!F!u_GVg88R@r~7F9<2Z>GhS zkpY`&NoAyTGcC*DGuMz~5f#wMcojhu#A?6`1{(1ifhN2xAcj{0G~-PoE9qcbMH4Ab zN8*X%>2wO!Q41}^1KLZejgF@TMz)(Z-S4b`j-%yR3D#m5Bjjk1R~=x5(h}wf0~_R3 z2bf!0!XP;U-yp9#FiaIp24lQj6E+Eh8{}07fFvzpl`y+OUUdMN(h_zF;~V5v2Y@au zVVMX)gS_eh^GHkBR!S?-t}QiF2uJ}TL!^r^Vh}#%`0RmC1fRX|*&CmI@fnWKNPPCg zhe3i9fc!VmR&*krgzx^idNMxI}+-M~b?+1n6EA z;_{FH@805LQeVgva0fj; z%dyK+Jda*M*YQDgJs*M8qn2*uqv|jsL_z+Lc_)t*8s2Lw-)NG_iR~S{E(7_XVQU{in{#`1+s{u?D@CXnJUV&#6ig@kfVH`i+F=Gi!zRj>`LLVKaYA0(R9h1>bK<&nZIyrMc32}KIJ`5TUE zOp|t0x?$WlUYRw@f;J82)m_|DU1U@W0^7~)nyQNt5T+=WXMQ2>TP??B;JX(74h=xn!?lt0vgx_OO#9B}Nyv z^0gECmD}#GJ858~q}(p=*UjrS-aH_tpt^r~zw~VjmZv~Z7_gmhT^{LQK41&qzLoFX z1hN(wow)$EmoU(xdub?MJ}?Y2xj!^vEIkM>@(@kNwJG#4yvQR|Pn)4TUEtgn_>--a z#P1ZIU_KYm_g_d)LW7=wh&>H$cm|`M29zE6`vU!ho~L){Mfx+o|B7eyKgT2bA$o=P zqgQzny=HXhPKUi|-I)z}5GM_md-s7HMd}RIY#)70?tsx_md^14*mHBh(ww z3IU@x`Chav;PoQk$KQiDS_hJTAJ>Y3(+0jDtuQcI#t)!X0(_3)2Qh~YtZMlowE6+J z5xfa^`vb!P{4hU)zmI}aa{$+w^|ADG09SMuK6fV# zg?jFSX@cG|v!|}xEOP$6j0Vv>{N07M_3ycpGk`&}X=v`Gf%rQWl|#(aPxC|&P&CL< z&#k*r&+f0Ko&a2cl@p!Ni+WDlYuHxa*3CaqP(-*TepUn>`exMQ#VnNz8F7UJ>gJc2 za%HyC#1ijd#&;pJ??YxkfXu#+pzcG&Xdh86UV1PK?K$*`kuBjFaPwq~PVi(4EmyYC za%GE_y0SGI4;j6}uY${SXt0rmkdc5PkVWU-kJ9(Hz&;BmIoW*^mjFhF9WO(3QiNDw&3Kn~j*)Q8zah`~QuPlLsh zjg5r5`5jm)YaS3@bR<41l}N8UWKK1B%lFa6twbQ)rl3xQ#!`VrJMd(-=cz zD=iI^RFKQmStgGA6eRp3MPMo_prB*uZ!kiChcEjKO!$(H!Rr%Nz%{R;od|5dB9vxO z?PR>EM!^je+;o9q_(kwYphcSDoA@o53v|FqIWa2|+HNwem(X^TVSNE+zSpq65OZI} zAM?+l))V2TKYN|EMX%ohk@S?jLm5n z(sD+Mk#I&z%Ne{knZf*jDZD^KFYBhzv5q~CHOZ168u{u!TX8?AC4*IFK7I^X?iYQ zGz)Cv(|Z(INaQjI-|xg(yMDGzWC=bFbp_+c&}EufiHz_5W7?lpgf09pNHiwhiKwAuBdvr#m5ksi6{-#+f)017qApdko<@q+$WOal z^;0lq64n-PQ^-lWRQX0aaFE(7)4Kw_XSluhmflvRI8wMx!6u`(%rTs4!>;QAAuAkE zBNPqWd)0(+d3f)WC8$75EO9_dxzQ^8k~pBaJY0?#ArlAKM)Bg75;B{G7b z9}%er-;wtRXWgQDV~yH%slz1cR)=>hYz}V1jU{8pcd5x+)ifs!2M{Mk90F8`C&OJI zNke!F5~-;)2}zwyS?NsXi6zylLm_VBHEN9rA|;|4uD~^IEOap`C-EBUraywX6JMzr zYG!)TvjP@9+bnvpq(6?Q$5>y%L8UO4B$JiB1ys)B#nCD97latakASlFPBcusO z(@>sIqj&)w&I{=%K86tYzRM_qY!pj`rJ4vb5U$&!(mc_Y=g+=!xrLQZhHlZIJp$X9fTB>ZYf(N`c1zV~*6 zRwKsgOkRCENPpGk5Lc7Qt1EiaUsOVztYGWz^jGrvToJ|vBf>_y*P-eIDMiuk+H}zk zGP11f0|?F4M%ud7_3)?=cWA{;rd5sBded4kNZo=Tw?P<1BNo83tM8gSQXf-yD0NTP>I!!VR9I~lZH!HDtTbBO4}$!cugo|R19rBz9l9u8~XxE zPGUb7gRYypRF@PA9Ls46!h9*FxXdg^(jckkd*$OYvj{c_z^sMLe1&Z6p^{g9IdiGs z!w*TOvJB`f*lTQj9)Rqz{0@Oqo=4oBByspYV3| z_HyY{_5rgXJAMa+;aT26rDc$M-~IJ7Da*>-UFu_WeizN9_3Q`R zSg8=oE|03e;+h5f^C11xgkAM?pZc5FtF)k+d+P6)%enWMYmq*r!ddJAy)_7y~}c8(G|T9y6=lm1wO-FT?t1@G-qkaw_p&7H$h7t zhL$`6EqN4L0{hP>NMb`|CE9Up;I(l#Oz<|C;O%(B?c>mrCs6MD0o{(@ckoltj;HbB z(`V>OeiqvC6g|&7=q0>L<8^+K-sYE(yu3`m#p^RZL*d8bSGk0Lgabvd;WdS?^ALW6 z_dtKqnr6rjSm$XC@1;lRk)kzt4`2nnJhTR0-qV^d(pvM&wATFC)tWdxqDLWyD8}a8 z14e(s)Is+e{V7T7kGTi^@$Nl_LZTS@uS9>IH-$q>ec{uevob2QZ!fo@MbG1(qebbz zqDfMV3SKtg_T|UP=Vy% zFW%yVRH!FtlwC`eoMLeY*T9e>t0k*)93tlL1TD7)L!;CSXCHFIWjELaS^@6MYL9&0`PLZf{C*9T*(-B&;iI2^*o?ARvHTp z;`lWV$d2#QlXH>93(kP-7}^8Y3MCzyiphm^4wAKLBx{X`6~MiX(TnskE|MjZD<5L% zx!5cOra02$Vj2}41DN64^%6u{8U;Ts-z$JQqBFg$TVs!Ii#|Cc;0`7?QYqg=dm}EY zz;{NphNm$Zt{bsQ%FtaL?{TN|i&k?6q_F~UCK*{M)GL9!=q}Cy^?>VY;Ma;zo8uz& z>TaC~#Ptc_`Xq3D0=Pce6Rx@)(-i7cK@6pnre=T_PvZXuq8r;WbnLeAe zK1ZLgFEp)-^dlS^x{*Gzgp}(u|Hm!T~efoQ* z^?m)Ie#o>o=|}Zu)9TXQdb?>ou79APGOefev-)|{+M!?4FPqj6^^f#xruDjhOTVpu z3Oo-%ws0$slf6Sj)zSFIb`4L)3IF$|M)dpoXZj;l`rB!>{y0>us`35vP_h05<3iXG JzEvIle*lO(AH4to literal 23971 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 BQHcNm diff --git a/bin/main/de/oaa/xxx/gruppe/GruppenbeitragController$CreateBeitragRequest.class b/bin/main/de/oaa/xxx/gruppe/GruppenbeitragController$CreateBeitragRequest.class index 18d9ea8649efdd412123cd1392fcb7880e0b3113..b1e2d5c9abb887be4984759d3556719873af9c6a 100644 GIT binary patch delta 38 mcmca4bV+E#BQ{3o$&cBLfTS2Zi1cK)0+KBta`S%n$&3K`1q;Uj delta 38 mcmca4bV+E#BQ{3+$&cBLfTS2Zi1cK)0+KBta`S%n$&3K^oeQ%7 diff --git a/bin/main/de/oaa/xxx/gruppe/GruppenbeitragController$PostsPage.class b/bin/main/de/oaa/xxx/gruppe/GruppenbeitragController$PostsPage.class index 97f995f13fa1951ed4a511f3857c954b8a8e68b6..a51d49b1b9720c3c20c6cfb90308a171366b6ead 100644 GIT binary patch delta 20 ccmbQsHt<8 delta 20 ccmbQsHRR910 diff --git a/bin/main/de/oaa/xxx/gruppe/GruppenbeitragController$ReportRequest.class b/bin/main/de/oaa/xxx/gruppe/GruppenbeitragController$ReportRequest.class index 03932cb2af515f5918210321cc74abf1deb0ee08..342011a1570467185099a64b2190660b36b014c1 100644 GIT binary patch delta 14 VcmdnWy_I`IJqx4T<^~p1MgS&|1ZV&N delta 14 VcmdnWy_I`IJqx4L<^~p1MgS&w1Y`gJ diff --git a/bin/main/de/oaa/xxx/gruppe/GruppenbeitragController$VoteRequest.class b/bin/main/de/oaa/xxx/gruppe/GruppenbeitragController$VoteRequest.class index f24197625f94f28dc3ef85d73fe148ae717b406b..5f4eb52cbe17d44c58168b4d70ed02e257a22409 100644 GIT binary patch delta 14 Vcmdnby`OtSCkvzN<}MafMgS-f1djj! delta 14 Vcmdnby`OtSCkvzF<}MafMgS-H1d9Lw diff --git a/bin/main/de/oaa/xxx/gruppe/GruppenbeitragController.class b/bin/main/de/oaa/xxx/gruppe/GruppenbeitragController.class index 6106767d2a1df90a4ff24c8112280815b743e1ce..d243124f01ba176b58fd22603b2087eb7d2da4c7 100644 GIT binary patch literal 22580 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ⅈV(>({bj-+qEoWRb5 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 Mt(VAWJzfug09ot@{Qv*} literal 0 HcmV?d00001 diff --git a/bin/main/de/oaa/xxx/hashtag/HashtagEntity.class b/bin/main/de/oaa/xxx/hashtag/HashtagEntity.class new file mode 100644 index 0000000000000000000000000000000000000000..8d3287496d4802775b0156210291f87ab412fdcf GIT binary patch literal 1208 zcma)4+iuf95S`7nF@}VsDJ?DZ#wAIiey0ejDz`|XAWHMlw@tiCwsqEEudDD^Ac2s0 z-~;$5#97;jCIqFj?{PDNicDy7+N7vWAEkF@lheqXFSl{c`t%chFYk;aM~`))@Ftdd@mX)gma3I1fy!wFP`T}2T~0XTTqeb zarDZ99x*CJN`8o9kHPbV8t9>kVc^1Jsic0wDBEau0ej%P!lEoK*;J((qjEdX@nF)YFoS9ZZpRCw?9*qB{r z@PR$NB0LM;0&FRcfNJ_)hbcqe-Q-^EQ8IrzES;ijBo>g zgH0$~#@|j7XoYUV!?3i5jBUugXk9y9b8s5BF3leXpnZC?)PF5Lsq>4_cz zJY%M8?R=&6X>L|L(o&LM!fM59e3q0V(J$b4z!KhJFf!Gkw16|t1vfFGIEeHRY2Y-M+%D8O_ zU=+i_!|r5vW?tT#c^{u|?*Pz*0}BcSjyy&sB2AKn1{en#eR`9}2NubgYpEs{6baOa z_>43};nQ|+$okrXGJ#X6d>TiJ3xA++#9pL&q8@6b!w9LGLD0Jh=YRxi11`LaNz1el zXf&KTxT{kz`#Ap^YaY^eq`4Fr+61aD_XX;)GHuR^ZD-DMkyk=i0|s z((7O7?hA z{uOY=F_a_420S59iKf)dwp=+@eRj=5GxozD{y0guN8oi~i@8PYi_@% literal 0 HcmV?d00001 diff --git a/bin/main/de/oaa/xxx/hashtag/HashtagService.class b/bin/main/de/oaa/xxx/hashtag/HashtagService.class new file mode 100644 index 0000000000000000000000000000000000000000..0dd6f18e4d8c55bd1eac51f3fdf3283406bff5ca GIT binary patch literal 7546 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 cfEqVMN0c_YwT=j9@!dn*`u#u z`7|DU03XV-C)r8@O7P-gveTWOp6>7adVc=;{sRC?u$qS)fi<7gkTH68c1GJQZfn+} z^)S{uy}iSuFcvygX&%N2ES#`YMgt~Ww03mDJ)MV10xv?8B)yFP)HwQBcef=qBvA}ua!drQwV4GEP+Y0?OtVIsE1J!Fy#vAVOz~CofaKVtD(n&3e&uSRams=OA^hgrRlD4b@P`ewYBZk z_ASF|R%%Zkn8kX(V>`A<=P8b4_$6&^zOi1#hB51{Q)||(tq6_h^lB;sS;+V&Icue5s=*b;)@rSN(elJRmz{~-U^*-+$ z;WXo`MPLlq1ipo1__F0;3UW|DD+kjsi{A-60p`)W26Gr_T!kFW<7c7kz798FJUej{ zZlQk$!*1ic6T@A&hmN@%T8ll;653e~v%QaN93J3r4jyJ%9>Fr&(-?2zTA3`slWr8+ FzX8Ng!}I_E literal 0 HcmV?d00001 diff --git a/bin/main/static/community/feed.html b/bin/main/static/community/feed.html index a7d0de5..efe43d2 100644 --- a/bin/main/static/community/feed.html +++ b/bin/main/static/community/feed.html @@ -68,6 +68,11 @@ .empty-hint { color:var(--color-muted); font-size:0.9rem; margin-top:0.5rem; } .sentinel { height:1px; } + .hashtag-banner { display:flex; align-items:center; gap:0.75rem; margin-bottom:1.25rem; padding:0.65rem 1rem; background:var(--color-card); border:1px solid var(--color-secondary); border-radius:8px; } + .hashtag-banner-tag { font-size:1.05rem; font-weight:700; color:var(--color-primary); } + .hashtag-banner-back { margin-left:auto; font-size:0.82rem; color:var(--color-muted); text-decoration:none; } + .hashtag-banner-back:hover { color:var(--color-primary); } + /* Lightbox */ .lightbox { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.88); z-index:300; align-items:center; justify-content:center; } .lightbox.open { display:flex; } @@ -92,7 +97,13 @@
-
+ + + +
@@ -140,6 +151,13 @@
+ + +
@@ -165,37 +183,68 @@ - + + - + + @@ -157,7 +128,7 @@ function esc(s) { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; } - function gruppeCard(g, showJoin = false) { + function gruppeCard(g) { const img = g.bild ? `
` : `
👥
`; @@ -165,16 +136,6 @@ ? `${g.myRole === 'ADMIN' ? 'Admin' : 'Mitglied'}` : ''; const privBadge = g.isPrivate ? ' 🔒' : ''; - let actions = ''; - if (showJoin && !g.myRole) { - if (g.myRequestStatus === 'AUSSTEHEND') { - actions = ``; - } else if (g.isPrivate) { - actions = ``; - } else { - actions = ``; - } - } return `
${img} @@ -183,7 +144,6 @@
${g.memberCount} Mitglied${g.memberCount !== 1 ? 'er' : ''} · ${g.postCount} Beiträge
${g.beschreibung ? `
${esc(g.beschreibung.substring(0,80))}${g.beschreibung.length>80?'…':''}
` : ''}
- ${actions ? `
${actions}
` : ''}
`; } @@ -223,37 +183,6 @@ })); } - async function doSearch() { - const q = document.getElementById('searchInput').value.trim(); - if (!q) return; - try { - const res = await fetch('/gruppen/search?q=' + encodeURIComponent(q)); - if (!res.ok) return; - const data = await res.json(); - const grid = document.getElementById('discoverGrid'); - const hint = document.getElementById('discoverHint'); - grid.innerHTML = ''; - if (data.length === 0) { hint.textContent = 'Keine Gruppen gefunden.'; hint.style.display = ''; return; } - hint.style.display = 'none'; - data.forEach(g => grid.insertAdjacentHTML('beforeend', gruppeCard(g, true))); - } catch(e) { console.error(e); } - } - - async function joinGruppe(gruppeId, btn) { - btn.disabled = true; - btn.textContent = '…'; - try { - const res = await fetch('/gruppen/' + gruppeId + '/join', { method: 'POST', headers:{'Content-Type':'application/json'}, body:'{}' }); - if (res.ok || res.status === 201) { - btn.textContent = 'Beigetreten ✓'; - setTimeout(loadMine, 500); - } else { - btn.disabled = false; - btn.textContent = 'Beitreten'; - } - } catch(e) { btn.disabled = false; btn.textContent = 'Beitreten'; } - } - async function loadRequests() { try { const reqRes = await fetch('/gruppen/requests/mine'); @@ -333,42 +262,6 @@ } catch(e) { showCreateError('Fehler: ' + e.message); } } - // ── Join dialog ── - - let pendingJoinGruppeId = null; - function openJoinDialog(gruppeId, name) { - pendingJoinGruppeId = gruppeId; - document.getElementById('joinDialogGroupName').textContent = name; - document.getElementById('joinNachricht').value = ''; - document.getElementById('joinDialog').classList.add('visible'); - } - function closeJoinDialog() { document.getElementById('joinDialog').classList.remove('visible'); pendingJoinGruppeId = null; } - - async function sendJoinRequest() { - if (!pendingJoinGruppeId) return; - document.getElementById('joinError').style.display = 'none'; - const nachricht = document.getElementById('joinNachricht').value.trim() || null; - try { - const res = await fetch('/gruppen/' + pendingJoinGruppeId + '/join', { - method: 'POST', - headers: {'Content-Type':'application/json'}, - body: JSON.stringify({ nachricht }) - }); - if (res.ok || res.status === 201) { - closeJoinDialog(); - doSearch(); - } else { - const el = document.getElementById('joinError'); - el.textContent = 'Fehler beim Senden der Anfrage.'; - el.style.display = 'block'; - } - } catch(e) { - const el = document.getElementById('joinError'); - el.textContent = 'Fehler: ' + e.message; - el.style.display = 'block'; - } - } - // ── Image preview ── function previewBild(input, previewId, dataId) { @@ -401,9 +294,6 @@ document.getElementById('createDialog').addEventListener('click', e => { if (e.target === document.getElementById('createDialog')) closeCreateDialog(); }); - document.getElementById('joinDialog').addEventListener('click', e => { - if (e.target === document.getElementById('joinDialog')) closeJoinDialog(); - }); loadMine(); diff --git a/bin/main/static/community/nachrichten.html b/bin/main/static/community/nachrichten.html index a691a84..eae7222 100644 --- a/bin/main/static/community/nachrichten.html +++ b/bin/main/static/community/nachrichten.html @@ -360,7 +360,7 @@ const list = document.getElementById('convList'); list.innerHTML = ''; if (convs.length === 0) { - list.innerHTML = '
  • Noch keine Nachrichten. Personen suchen
  • '; + list.innerHTML = '
  • Noch keine Nachrichten. Personen suchen
  • '; return; } convs.forEach(c => { diff --git a/bin/main/static/community/personen-suchen.html b/bin/main/static/community/personen-suchen.html deleted file mode 100644 index 1cf76a7..0000000 --- a/bin/main/static/community/personen-suchen.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - Personen suchen – xXx Sphere - - - - - - -
    -
    -

    Personen suchen

    - - - -
      -

      Gib mindestens 2 Zeichen ein, um zu suchen.

      -
      -
      - - - - - - - diff --git a/bin/main/static/games/common/einladungen.html b/bin/main/static/games/common/einladungen.html new file mode 100644 index 0000000..f198d67 --- /dev/null +++ b/bin/main/static/games/common/einladungen.html @@ -0,0 +1,371 @@ + + + + + + + Einladungen – xXx Sphere + + + + + +
      +
      +

      Einladungen

      + +
      + + +
      + + +
      +
      Wird geladen…
      + + + + + + + + +
      + + + +
      +
      + + + + + + diff --git a/bin/main/static/games/vanilla/neuvanilla.html b/bin/main/static/games/vanilla/neuvanilla.html index 559bed4..27a9699 100644 --- a/bin/main/static/games/vanilla/neuvanilla.html +++ b/bin/main/static/games/vanilla/neuvanilla.html @@ -1213,6 +1213,7 @@ const id = addPlayer(p.name, i === 0, i === 0, false, false); if (id) { restorePlayer(id, p); if (p.userId) userIdToInfo[p.userId] = { playerId: id, name: p.name }; } }); + Object.entries(userIdToInfo).forEach(([userId, info]) => { pruefeChastityConstraint(info.playerId, userId); }); await ladeEinladungenAusDb(userIdToInfo); restoredFromSetup = true; } else { @@ -1273,14 +1274,15 @@ if (draft.gruppenJson) { sessionStorage.setItem('vanilla-session-gruppen', draft.gruppenJson); savedGruppen = new Set(JSON.parse(draft.gruppenJson)); } } } - const selfGeschlecht = user?.geschlecht || null; - const selfWerkzeuge = selfGeschlecht ? (WERKZEUGE_DEFAULTS[selfGeschlecht] || []) : []; + const defaults = await fetch('/user/me/bdsm-defaults').then(r => r.ok ? r.json() : {}).catch(() => ({})); + const selfGeschlecht = defaults.geschlecht || user?.geschlecht || null; + const selfWerkzeuge = defaults.werkzeuge?.length ? defaults.werkzeuge : (selfGeschlecht ? (WERKZEUGE_DEFAULTS[selfGeschlecht] || []) : []); const selfId = addPlayer(user ? user.name : '', true, true, !!selfGeschlecht, false); if (selfId) { if (user?.profilePicture) { playerProfilePics[selfId] = user.profilePicture; updatePlayerHeader(selfId, user.name); } if (playerIds.length < MAX_PLAYERS) addPlayer(); - restorePlayer(selfId, { geschlecht: selfGeschlecht, spieltMit: [], rollen: [], werkzeuge: selfWerkzeuge }); - pruefeChastityConstraint(selfId, myUserId); + restorePlayer(selfId, { geschlecht: selfGeschlecht, spieltMit: defaults.spieltMit || [], rollen: defaults.rollen || [], werkzeuge: selfWerkzeuge }); + if (myUserId) pruefeChastityConstraint(selfId, myUserId); } await ladeEinladungenAusDb(null); } @@ -1324,8 +1326,9 @@ if (user?.profilePicture) { playerProfilePics[guestOwnPlayerId] = user.profilePicture; updatePlayerHeader(guestOwnPlayerId, user?.name || ''); } - restorePlayer(guestOwnPlayerId, { geschlecht: user?.geschlecht || null, spieltMit: [], rollen: [], werkzeuge: [] }); - pruefeChastityConstraint(guestOwnPlayerId, myUserId); + const defaults = await fetch('/user/me/bdsm-defaults').then(r => r.ok ? r.json() : {}).catch(() => ({})); + restorePlayer(guestOwnPlayerId, { geschlecht: defaults.geschlecht || user?.geschlecht || null, spieltMit: defaults.spieltMit || [], rollen: defaults.rollen || [], werkzeuge: defaults.werkzeuge || [] }); + if (myUserId) pruefeChastityConstraint(guestOwnPlayerId, myUserId); document.getElementById('acc-grundeinstellungen-btn').classList.add('is-open'); document.getElementById('acc-grundeinstellungen-body').classList.add('is-open'); diff --git a/bin/main/static/js/hashtag.js b/bin/main/static/js/hashtag.js new file mode 100644 index 0000000..567df05 --- /dev/null +++ b/bin/main/static/js/hashtag.js @@ -0,0 +1,217 @@ +(function () { + 'use strict'; + + // ── Styles ────────────────────────────────────────────────────────────── + const style = document.createElement('style'); + style.textContent = ` + .post-hashtag { + color: var(--color-primary); + text-decoration: none; + font-weight: 600; + } + .post-hashtag:hover { text-decoration: underline; } + + .hashtag-dropdown { + position: fixed; + z-index: 600; + background: var(--color-card); + border: 1px solid var(--color-secondary); + border-radius: 8px; + min-width: 170px; + max-width: 260px; + box-shadow: 0 4px 20px rgba(0,0,0,0.45); + overflow: hidden; + } + .hashtag-dropdown-item { + padding: 0.45rem 0.9rem; + cursor: pointer; + font-size: 0.88rem; + color: var(--color-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .hashtag-dropdown-item:hover, + .hashtag-dropdown-item.active { + background: var(--color-secondary); + color: var(--color-primary); + } + .hashtag-dropdown-create { + font-style: italic; + border-top: 1px solid var(--color-secondary); + color: var(--color-primary); + } + .hashtag-dropdown-hint { + padding: 0.45rem 0.9rem; + font-size: 0.82rem; + color: var(--color-text); + opacity: 0.6; + font-style: italic; + } + `; + document.head.appendChild(style); + + // ── Escape helper (works even if shared.js not yet loaded) ─────────────── + function esc(s) { + return String(s ?? '') + .replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + } + + // ── renderTextWithHashtags ──────────────────────────────────────────────── + window.renderTextWithHashtags = function (text) { + if (!text) return ''; + const PATTERN = /#([\wäöüÄÖÜß]{1,100})/g; + const parts = []; + let lastIndex = 0; + let match; + while ((match = PATTERN.exec(text)) !== null) { + parts.push(esc(text.slice(lastIndex, match.index))); + const tag = match[1].toLowerCase(); + parts.push( + `#${esc(match[1])}` + ); + lastIndex = match.index + match[0].length; + } + parts.push(esc(text.slice(lastIndex))); + return parts.join(''); + }; + + // ── attachHashtagAutocomplete ───────────────────────────────────────────── + window.attachHashtagAutocomplete = function (textarea) { + let dropdownEl = null; + let suggestions = []; + let selectedIdx = -1; + let debounce = null; + + // Returns { prefix, hashStart } if cursor is inside a #word, else null + function getHashAtCursor() { + const pos = textarea.selectionStart; + const text = textarea.value; + let i = pos - 1; + while (i >= 0 && /[\wäöüÄÖÜß]/.test(text[i])) i--; + if (i >= 0 && text[i] === '#') { + return { prefix: text.slice(i + 1, pos), hashStart: i }; + } + if (i === -1 && text.length > 0 && text[0] === '#' && pos > 0) { + return { prefix: text.slice(1, pos), hashStart: 0 }; + } + return null; + } + + function positionDropdown() { + if (!dropdownEl) return; + const r = textarea.getBoundingClientRect(); + dropdownEl.style.top = (r.bottom + window.scrollY + 2) + 'px'; + dropdownEl.style.left = (r.left + window.scrollX) + 'px'; + } + + function highlight() { + if (!dropdownEl) return; + dropdownEl.querySelectorAll('.hashtag-dropdown-item').forEach((el, i) => { + el.classList.toggle('active', i === selectedIdx); + }); + } + + function removeDropdown() { + dropdownEl?.remove(); + dropdownEl = null; + suggestions = []; + selectedIdx = -1; + } + + function showDropdown(items, prefix) { + removeDropdown(); + const lPrefix = prefix ? prefix.toLowerCase() : ''; + const hasCreate = lPrefix.length > 0 && !items.some(t => t === lPrefix); + + if (!items.length && !hasCreate) { + if (prefix !== undefined && prefix.length === 0) { + // Nur "#" getippt, noch keine populären Tags + dropdownEl = document.createElement('div'); + dropdownEl.className = 'hashtag-dropdown'; + const hint = document.createElement('div'); + hint.className = 'hashtag-dropdown-hint'; + hint.textContent = 'Tippe weiter um einen Hashtag zu erstellen'; + dropdownEl.appendChild(hint); + document.body.appendChild(dropdownEl); + positionDropdown(); + } + return; + } + + suggestions = hasCreate ? [...items, lPrefix] : [...items]; + + dropdownEl = document.createElement('div'); + dropdownEl.className = 'hashtag-dropdown'; + + suggestions.forEach((tag, i) => { + const isCreate = hasCreate && i === suggestions.length - 1; + const item = document.createElement('div'); + item.className = 'hashtag-dropdown-item' + (isCreate ? ' hashtag-dropdown-create' : ''); + item.textContent = (isCreate ? '+ #' : '#') + tag; + item.addEventListener('mousedown', e => { e.preventDefault(); insertTag(tag); }); + item.addEventListener('mouseover', () => { selectedIdx = i; highlight(); }); + dropdownEl.appendChild(item); + }); + + document.body.appendChild(dropdownEl); + positionDropdown(); + } + + function insertTag(tag) { + const info = getHashAtCursor(); + if (!info) { removeDropdown(); return; } + const v = textarea.value; + const cursor = textarea.selectionStart; + const before = v.slice(0, info.hashStart); + const after = v.slice(cursor); + const inserted = '#' + tag + ' '; + textarea.value = before + inserted + after; + const newPos = before.length + inserted.length; + textarea.setSelectionRange(newPos, newPos); + textarea.focus(); + textarea.dispatchEvent(new Event('input', { bubbles: true })); + removeDropdown(); + } + + textarea.addEventListener('input', () => { + clearTimeout(debounce); + const info = getHashAtCursor(); + if (!info) { removeDropdown(); return; } + debounce = setTimeout(async () => { + try { + const url = info.prefix.length === 0 + ? '/hashtags/popular?limit=6' + : '/hashtags/suggest?q=' + encodeURIComponent(info.prefix) + '&limit=6'; + const res = await fetch(url); + if (res.ok) showDropdown(await res.json(), info.prefix); + else showDropdown([], info.prefix); + } catch { removeDropdown(); } + }, 150); + }); + + textarea.addEventListener('keydown', e => { + if (!dropdownEl) return; + if (e.key === 'ArrowDown') { + e.preventDefault(); + selectedIdx = Math.min(selectedIdx + 1, suggestions.length - 1); + highlight(); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + selectedIdx = Math.max(selectedIdx - 1, 0); + highlight(); + } else if ((e.key === 'Enter' || e.key === 'Tab') && selectedIdx >= 0) { + e.preventDefault(); + insertTag(suggestions[selectedIdx]); + } else if (e.key === 'Escape') { + removeDropdown(); + } + }); + + textarea.addEventListener('blur', () => setTimeout(removeDropdown, 150)); + window.addEventListener('scroll', positionDropdown, { passive: true }); + window.addEventListener('resize', positionDropdown, { passive: true }); + }; +})(); diff --git a/bin/main/static/js/mobile-nav.js b/bin/main/static/js/mobile-nav.js index 6fab66a..6e97bd7 100644 --- a/bin/main/static/js/mobile-nav.js +++ b/bin/main/static/js/mobile-nav.js @@ -19,7 +19,7 @@ box-shadow: 0 2px 12px rgba(0,0,0,0.4); z-index: 500; align-items: center; - padding: 0 0.25rem; + padding: 0 4px 0 0.25rem; } .mobile-topbar-logo { position: absolute; @@ -43,9 +43,9 @@ background: none; border: none; color: var(--color-text); - font-size: 1.725rem; + font-size: 1.3rem; line-height: 1; - padding: 0.75rem 0.675rem; + padding: 0.55rem 0.6rem; cursor: pointer; display: flex; align-items: center; diff --git a/bin/main/static/js/nav.js b/bin/main/static/js/nav.js index 70fa159..1a44fb6 100644 --- a/bin/main/static/js/nav.js +++ b/bin/main/static/js/nav.js @@ -301,7 +301,7 @@ '/community/gruppen.html', '/community/gruppe.html', '/community/locations.html', '/community/location-detail.html', '/community/events.html', '/community/event-detail.html', - '/community/abonnements.html', '/community/personen-suchen.html', + '/community/abonnements.html', '/community/benutzer.html', ])} ${column('colDating', 'Dating', col3Html, ['/dating/'])} diff --git a/bin/main/static/js/section-nav.js b/bin/main/static/js/section-nav.js index 4328936..41f1367 100644 --- a/bin/main/static/js/section-nav.js +++ b/bin/main/static/js/section-nav.js @@ -5,12 +5,19 @@ // ── Bereichs-Definitionen ──────────────────────────────────────────────── const SECTIONS = { + common: { + prefixes: ['/games/common/'], + items: [ + { href: '/games/vanilla/neuvanilla.html', icon: 'VANILLA', label: 'Vanilla Game' }, + { href: '/games/bdsm/neubdsm.html', icon: 'BDSM', label: 'BDSM Game' }, + { href: '/games/chastity/neulock.html', icon: 'CHASTITY', label: 'Chastity Game' }, + ], + }, social: { prefixes: ['/community/'], exclude: [ '/community/nachrichten.html', '/community/benachrichtigungen.html', - '/community/einladungen.html', ], items: [ { href: '/community/feed.html', icon: 'FEED', label: 'Feed' }, diff --git a/bin/main/static/js/topbar.js b/bin/main/static/js/topbar.js index 7b88b4b..66d866d 100644 --- a/bin/main/static/js/topbar.js +++ b/bin/main/static/js/topbar.js @@ -168,18 +168,26 @@ async function doSearch(q, overlay) { try { - const res = await fetch('/search?q=' + encodeURIComponent(q) + '&limit=3'); - if (!res.ok) { overlay.innerHTML = '
      Fehler bei der Suche.
      '; return; } - const data = await res.json(); - const { users = [], locations = [], events = [] } = data; + const tagQuery = q.startsWith('#') ? q.slice(1) : q; + const [searchRes, gruppenRes, hashtagRes] = await Promise.all([ + fetch('/search?q=' + encodeURIComponent(q) + '&limit=3'), + fetch('/gruppen/search?q=' + encodeURIComponent(q)), + fetch('/hashtags/suggest?q=' + encodeURIComponent(tagQuery) + '&limit=4') + ]); - if (!users.length && !locations.length && !events.length) { - overlay.innerHTML = '
      Keine Ergebnisse.
      '; - return; - } + const data = searchRes.ok ? await searchRes.json() : {}; + const gruppen = gruppenRes.ok ? await gruppenRes.json() : []; + const hashtags = hashtagRes.ok ? await hashtagRes.json() : []; + + const { users = [], locations = [], events = [] } = data; + const gruppenSlice = gruppen.slice(0, 3); let html = ''; + if (!users.length && !locations.length && !events.length && !gruppenSlice.length && !hashtags.length) { + html += '
      Keine Ergebnisse.
      '; + } + if (users.length) { html += `
      Personen
      `; html += users.map(u => { @@ -213,6 +221,26 @@ }).join(''); } + if (gruppenSlice.length) { + html += `
      Gruppen
      `; + html += gruppenSlice.map(g => { + const av = g.bild + ? `` + : `👥`; + return ` + ${av}${esc(g.name)}`; + }).join(''); + } + + if (hashtags.length) { + html += `
      Hashtags
      `; + html += `
      `; + html += hashtags.map(tag => + `#${esc(tag)}` + ).join(''); + html += `
      `; + } + html += `Alle Ergebnisse anzeigen →`; overlay.innerHTML = html; } catch (e) { diff --git a/bin/main/static/search.html b/bin/main/static/search.html index 5d193a1..5e3c309 100644 --- a/bin/main/static/search.html +++ b/bin/main/static/search.html @@ -163,6 +163,33 @@ transition: border-color 0.15s, color 0.15s; } .search-load-more:hover { border-color: var(--color-primary); color: var(--color-primary); background: none; } + + .hashtag-chip { + display: inline-flex; + align-items: center; + gap: 0.3rem; + padding: 0.4rem 0.85rem; + background: var(--color-card); + border: 1px solid var(--color-secondary); + border-radius: 20px; + color: var(--color-primary); + font-weight: 600; + font-size: 0.9rem; + text-decoration: none; + transition: border-color 0.15s, background 0.15s; + } + .hashtag-chip:hover { border-color: var(--color-primary); background: var(--color-secondary); } + + /* Dialog (Gruppen-Beitritt) */ + .dialog-backdrop { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.6); z-index:200; align-items:center; justify-content:center; } + .dialog-backdrop.visible { display:flex; } + .dialog { background:var(--color-card); border:1px solid var(--color-secondary); border-radius:12px; padding:1.75rem; width:100%; max-width:420px; box-shadow:0 8px 32px rgba(0,0,0,0.6); max-height:90vh; overflow-y:auto; } + .dialog h3 { color:var(--color-primary); font-size:1.1rem; margin-bottom:1.25rem; } + .dialog label { display:block; font-size:0.8rem; color:#aaa; margin-bottom:0.3rem; margin-top:1rem; } + .dialog textarea { width:100%; padding:0.6rem 0.85rem; border:1px solid var(--color-secondary); border-radius:6px; background:var(--color-secondary); color:var(--color-text); font-size:0.95rem; outline:none; transition:border-color 0.2s; resize:vertical; min-height:80px; box-sizing:border-box; } + .dialog textarea:focus { border-color:var(--color-primary); } + .dialog-actions { display:flex; justify-content:flex-end; gap:0.75rem; margin-top:1.5rem; } + .dialog-actions button { flex:none; margin:0; padding:0.55rem 1.1rem; font-size:0.9rem; width:auto; } @@ -173,7 +200,7 @@
      -
      @@ -188,6 +215,12 @@ + +
      @@ -207,6 +240,31 @@
      + +
      + +
      +
      + +
      + +
      +
      + + + + +
      +
      +

      Beitrittsanfrage senden

      +

      + + + +
      + + +
      @@ -279,10 +337,12 @@ document.getElementById('grid' + cap(t)).innerHTML = ''; document.getElementById('more' + cap(t)).style.display = 'none'; }); - // Alle drei Typen parallel laden + // Alle Typen parallel laden loadChunk('users'); loadChunk('locations'); loadChunk('events'); + loadGruppen(q); + loadHashtags(q); } function clearAll() { @@ -295,6 +355,12 @@ document.getElementById('count' + cap(t)).textContent = '0'; document.getElementById('loading' + cap(t)).style.display = 'none'; }); + document.getElementById('gridGruppen').innerHTML = ''; + document.getElementById('countGruppen').textContent = '0'; + document.getElementById('loadingGruppen').style.display = 'none'; + document.getElementById('gridHashtags').innerHTML = ''; + document.getElementById('countHashtags').textContent = '0'; + document.getElementById('loadingHashtags').style.display = 'none'; const url = new URL(window.location); url.searchParams.delete('q'); history.replaceState(null, '', url); @@ -374,14 +440,185 @@ }); } + // ── Gruppen-Suche ── + + async function loadGruppen(q) { + const loadingEl = document.getElementById('loadingGruppen'); + const grid = document.getElementById('gridGruppen'); + loadingEl.style.display = ''; + grid.innerHTML = ''; + try { + const res = await fetch('/gruppen/search?q=' + encodeURIComponent(q)); + if (!res.ok) throw new Error(); + const data = await res.json(); + document.getElementById('countGruppen').textContent = data.length; + if (!data.length) { + grid.innerHTML = '
      Keine Ergebnisse.
      '; + return; + } + data.forEach(g => grid.appendChild(buildGruppeCard(g))); + } catch (e) { + // ignore + } finally { + loadingEl.style.display = 'none'; + } + } + + function buildGruppeCard(g) { + const card = document.createElement('div'); + card.className = 'search-card'; + card.style.cssText = 'justify-content:space-between; cursor:pointer;'; + card.addEventListener('click', () => { location.href = '/community/gruppe.html?gruppeId=' + g.gruppeId; }); + + const av = g.bild + ? `
      ` + : `
      👥
      `; + const privBadge = g.isPrivate ? ' 🔒' : ''; + const sub = g.memberCount + ' Mitglied' + (g.memberCount !== 1 ? 'er' : ''); + + const info = document.createElement('div'); + info.style.cssText = 'display:flex; align-items:center; gap:0.75rem; min-width:0; flex:1;'; + info.innerHTML = `${av}
      ${esc(g.name)}${privBadge}
      ${esc(sub)}
      `; + card.appendChild(info); + + if (!g.myRole) { + const btn = document.createElement('button'); + btn.style.cssText = 'font-size:0.78rem; padding:0.3rem 0.65rem; width:auto; margin:0; white-space:nowrap; flex-shrink:0; margin-left:0.5rem;'; + if (g.myRequestStatus === 'AUSSTEHEND') { + btn.disabled = true; + btn.style.opacity = '0.6'; + btn.textContent = 'Anfrage ausstehend'; + } else if (g.isPrivate) { + btn.textContent = 'Anfrage senden'; + btn.addEventListener('click', e => { e.stopPropagation(); openSearchJoinDialog(g.gruppeId, g.name); }); + } else { + btn.textContent = 'Beitreten'; + btn.addEventListener('click', e => { e.stopPropagation(); joinGruppeSearch(g.gruppeId, btn); }); + } + card.appendChild(btn); + } + + return card; + } + + // ── Hashtag-Suche ── + + async function loadHashtags(q) { + const loadingEl = document.getElementById('loadingHashtags'); + const grid = document.getElementById('gridHashtags'); + loadingEl.style.display = ''; + grid.innerHTML = ''; + try { + const raw = q.startsWith('#') ? q.slice(1) : q; + const res = await fetch('/hashtags/suggest?q=' + encodeURIComponent(raw) + '&limit=20'); + if (!res.ok) throw new Error(); + const tags = await res.json(); + document.getElementById('countHashtags').textContent = tags.length; + if (!tags.length) { + grid.innerHTML = '
      Keine Hashtags gefunden.
      '; + return; + } + tags.forEach(tag => { + const a = document.createElement('a'); + a.className = 'hashtag-chip'; + a.href = '/community/feed.html?tag=' + encodeURIComponent(tag); + a.textContent = '#' + tag; + grid.appendChild(a); + }); + } catch (e) { + // ignore + } finally { + loadingEl.style.display = 'none'; + } + } + + async function joinGruppeSearch(gruppeId, btn) { + btn.disabled = true; + btn.textContent = '…'; + try { + const res = await fetch('/gruppen/' + gruppeId + '/join', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{}' + }); + if (res.ok || res.status === 201) { + btn.textContent = 'Beigetreten ✓'; + } else { + btn.disabled = false; + btn.textContent = 'Beitreten'; + } + } catch (e) { + btn.disabled = false; + btn.textContent = 'Beitreten'; + } + } + + // ── Join-Dialog für Gruppen ── + + let searchJoinGruppeId = null; + + function openSearchJoinDialog(gruppeId, name) { + searchJoinGruppeId = gruppeId; + document.getElementById('searchJoinGroupName').textContent = name; + document.getElementById('searchJoinNachricht').value = ''; + document.getElementById('searchJoinError').style.display = 'none'; + document.getElementById('searchJoinDialog').classList.add('visible'); + } + + function closeSearchJoinDialog() { + document.getElementById('searchJoinDialog').classList.remove('visible'); + searchJoinGruppeId = null; + } + + async function sendSearchJoinRequest() { + if (!searchJoinGruppeId) return; + document.getElementById('searchJoinError').style.display = 'none'; + const nachricht = document.getElementById('searchJoinNachricht').value.trim() || null; + try { + const res = await fetch('/gruppen/' + searchJoinGruppeId + '/join', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ nachricht }) + }); + if (res.ok || res.status === 201) { + closeSearchJoinDialog(); + if (currentQuery.length >= 2) loadGruppen(currentQuery); + } else { + const el = document.getElementById('searchJoinError'); + el.textContent = 'Fehler beim Senden der Anfrage.'; + el.style.display = 'block'; + } + } catch (e) { + const el = document.getElementById('searchJoinError'); + el.textContent = 'Fehler: ' + e.message; + el.style.display = 'block'; + } + } + + document.getElementById('searchJoinCancelBtn').addEventListener('click', closeSearchJoinDialog); + document.getElementById('searchJoinSendBtn').addEventListener('click', sendSearchJoinRequest); + document.getElementById('searchJoinDialog').addEventListener('click', e => { + if (e.target === document.getElementById('searchJoinDialog')) closeSearchJoinDialog(); + }); + function cap(s) { return s.charAt(0).toUpperCase() + s.slice(1); } // ── URL-Parameter beim Start auslesen ── - const initQ = new URLSearchParams(window.location.search).get('q') || ''; + const params = new URLSearchParams(window.location.search); + const initQ = params.get('q') || ''; + const initTab = params.get('tab') || ''; + + if (initTab) { + const tabBtn = document.querySelector(`.search-tab-btn[data-tab="${initTab}"]`); + if (tabBtn) tabBtn.click(); + } + if (initQ.length >= 2) { input.value = initQ; - // Warten bis icons.js geladen ist setTimeout(() => startSearch(initQ), 100); + } else if (initTab === 'hashtags') { + // Populäre Tags zeigen wenn kein Suchbegriff + setTimeout(() => loadHashtags(''), 100); } })(); diff --git a/bin/main/static/userhome.html b/bin/main/static/userhome.html index fa94bba..fe813f9 100644 --- a/bin/main/static/userhome.html +++ b/bin/main/static/userhome.html @@ -243,6 +243,33 @@ .lb-comment-compose button { width:auto; margin:0; padding:0.35rem 0.75rem; font-size:0.8rem; } @media (max-width:650px) { .lb-layout { flex-direction:column; height:95vh; } .lb-post-side { border-right:none; border-bottom:1px solid var(--color-secondary); max-height:55vh; } .lb-comments-panel { width:100%; } } + /* ── Spiel starten ── */ + .start-game-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 0.75rem; + } + .start-game-card { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.6rem; + background: var(--color-card); + border: 1px solid var(--color-secondary); + border-radius: 12px; + padding: 1.25rem 1rem; + text-decoration: none; + color: var(--color-text); + transition: border-color 0.15s, background 0.15s; + text-align: center; + } + .start-game-card:hover { + border-color: var(--color-primary); + background: rgba(var(--color-primary-rgb,233,69,96),0.06); + } + .start-game-icon { font-size: 2rem; line-height: 1; } + .start-game-title { font-size: 0.9rem; font-weight: 600; } + /* ── Neue Mitglieder ── */ .new-members-strip { display: flex; @@ -306,6 +333,25 @@
      + + + `; } @@ -223,37 +183,6 @@ })); } - async function doSearch() { - const q = document.getElementById('searchInput').value.trim(); - if (!q) return; - try { - const res = await fetch('/gruppen/search?q=' + encodeURIComponent(q)); - if (!res.ok) return; - const data = await res.json(); - const grid = document.getElementById('discoverGrid'); - const hint = document.getElementById('discoverHint'); - grid.innerHTML = ''; - if (data.length === 0) { hint.textContent = 'Keine Gruppen gefunden.'; hint.style.display = ''; return; } - hint.style.display = 'none'; - data.forEach(g => grid.insertAdjacentHTML('beforeend', gruppeCard(g, true))); - } catch(e) { console.error(e); } - } - - async function joinGruppe(gruppeId, btn) { - btn.disabled = true; - btn.textContent = '…'; - try { - const res = await fetch('/gruppen/' + gruppeId + '/join', { method: 'POST', headers:{'Content-Type':'application/json'}, body:'{}' }); - if (res.ok || res.status === 201) { - btn.textContent = 'Beigetreten ✓'; - setTimeout(loadMine, 500); - } else { - btn.disabled = false; - btn.textContent = 'Beitreten'; - } - } catch(e) { btn.disabled = false; btn.textContent = 'Beitreten'; } - } - async function loadRequests() { try { const reqRes = await fetch('/gruppen/requests/mine'); @@ -333,42 +262,6 @@ } catch(e) { showCreateError('Fehler: ' + e.message); } } - // ── Join dialog ── - - let pendingJoinGruppeId = null; - function openJoinDialog(gruppeId, name) { - pendingJoinGruppeId = gruppeId; - document.getElementById('joinDialogGroupName').textContent = name; - document.getElementById('joinNachricht').value = ''; - document.getElementById('joinDialog').classList.add('visible'); - } - function closeJoinDialog() { document.getElementById('joinDialog').classList.remove('visible'); pendingJoinGruppeId = null; } - - async function sendJoinRequest() { - if (!pendingJoinGruppeId) return; - document.getElementById('joinError').style.display = 'none'; - const nachricht = document.getElementById('joinNachricht').value.trim() || null; - try { - const res = await fetch('/gruppen/' + pendingJoinGruppeId + '/join', { - method: 'POST', - headers: {'Content-Type':'application/json'}, - body: JSON.stringify({ nachricht }) - }); - if (res.ok || res.status === 201) { - closeJoinDialog(); - doSearch(); - } else { - const el = document.getElementById('joinError'); - el.textContent = 'Fehler beim Senden der Anfrage.'; - el.style.display = 'block'; - } - } catch(e) { - const el = document.getElementById('joinError'); - el.textContent = 'Fehler: ' + e.message; - el.style.display = 'block'; - } - } - // ── Image preview ── function previewBild(input, previewId, dataId) { @@ -401,9 +294,6 @@ document.getElementById('createDialog').addEventListener('click', e => { if (e.target === document.getElementById('createDialog')) closeCreateDialog(); }); - document.getElementById('joinDialog').addEventListener('click', e => { - if (e.target === document.getElementById('joinDialog')) closeJoinDialog(); - }); loadMine(); diff --git a/src/main/resources/static/community/nachrichten.html b/src/main/resources/static/community/nachrichten.html index a691a84..eae7222 100644 --- a/src/main/resources/static/community/nachrichten.html +++ b/src/main/resources/static/community/nachrichten.html @@ -360,7 +360,7 @@ const list = document.getElementById('convList'); list.innerHTML = ''; if (convs.length === 0) { - list.innerHTML = '
    • Noch keine Nachrichten. Personen suchen
    • '; + list.innerHTML = '
    • Noch keine Nachrichten. Personen suchen
    • '; return; } convs.forEach(c => { diff --git a/src/main/resources/static/community/personen-suchen.html b/src/main/resources/static/community/personen-suchen.html deleted file mode 100644 index 1cf76a7..0000000 --- a/src/main/resources/static/community/personen-suchen.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - Personen suchen – xXx Sphere - - - - - - -
      -
      -

      Personen suchen

      - - - -
        -

        Gib mindestens 2 Zeichen ein, um zu suchen.

        -
        -
        - - - - - - - diff --git a/src/main/resources/static/games/common/einladungen.html b/src/main/resources/static/games/common/einladungen.html new file mode 100644 index 0000000..f198d67 --- /dev/null +++ b/src/main/resources/static/games/common/einladungen.html @@ -0,0 +1,371 @@ + + + + + + + Einladungen – xXx Sphere + + + + + +
        +
        +

        Einladungen

        + +
        + + +
        + + +
        +
        Wird geladen…
        + + + + + + + + +
        + + + +
        +
        + + + + + + diff --git a/src/main/resources/static/games/vanilla/neuvanilla.html b/src/main/resources/static/games/vanilla/neuvanilla.html index 559bed4..27a9699 100644 --- a/src/main/resources/static/games/vanilla/neuvanilla.html +++ b/src/main/resources/static/games/vanilla/neuvanilla.html @@ -1213,6 +1213,7 @@ const id = addPlayer(p.name, i === 0, i === 0, false, false); if (id) { restorePlayer(id, p); if (p.userId) userIdToInfo[p.userId] = { playerId: id, name: p.name }; } }); + Object.entries(userIdToInfo).forEach(([userId, info]) => { pruefeChastityConstraint(info.playerId, userId); }); await ladeEinladungenAusDb(userIdToInfo); restoredFromSetup = true; } else { @@ -1273,14 +1274,15 @@ if (draft.gruppenJson) { sessionStorage.setItem('vanilla-session-gruppen', draft.gruppenJson); savedGruppen = new Set(JSON.parse(draft.gruppenJson)); } } } - const selfGeschlecht = user?.geschlecht || null; - const selfWerkzeuge = selfGeschlecht ? (WERKZEUGE_DEFAULTS[selfGeschlecht] || []) : []; + const defaults = await fetch('/user/me/bdsm-defaults').then(r => r.ok ? r.json() : {}).catch(() => ({})); + const selfGeschlecht = defaults.geschlecht || user?.geschlecht || null; + const selfWerkzeuge = defaults.werkzeuge?.length ? defaults.werkzeuge : (selfGeschlecht ? (WERKZEUGE_DEFAULTS[selfGeschlecht] || []) : []); const selfId = addPlayer(user ? user.name : '', true, true, !!selfGeschlecht, false); if (selfId) { if (user?.profilePicture) { playerProfilePics[selfId] = user.profilePicture; updatePlayerHeader(selfId, user.name); } if (playerIds.length < MAX_PLAYERS) addPlayer(); - restorePlayer(selfId, { geschlecht: selfGeschlecht, spieltMit: [], rollen: [], werkzeuge: selfWerkzeuge }); - pruefeChastityConstraint(selfId, myUserId); + restorePlayer(selfId, { geschlecht: selfGeschlecht, spieltMit: defaults.spieltMit || [], rollen: defaults.rollen || [], werkzeuge: selfWerkzeuge }); + if (myUserId) pruefeChastityConstraint(selfId, myUserId); } await ladeEinladungenAusDb(null); } @@ -1324,8 +1326,9 @@ if (user?.profilePicture) { playerProfilePics[guestOwnPlayerId] = user.profilePicture; updatePlayerHeader(guestOwnPlayerId, user?.name || ''); } - restorePlayer(guestOwnPlayerId, { geschlecht: user?.geschlecht || null, spieltMit: [], rollen: [], werkzeuge: [] }); - pruefeChastityConstraint(guestOwnPlayerId, myUserId); + const defaults = await fetch('/user/me/bdsm-defaults').then(r => r.ok ? r.json() : {}).catch(() => ({})); + restorePlayer(guestOwnPlayerId, { geschlecht: defaults.geschlecht || user?.geschlecht || null, spieltMit: defaults.spieltMit || [], rollen: defaults.rollen || [], werkzeuge: defaults.werkzeuge || [] }); + if (myUserId) pruefeChastityConstraint(guestOwnPlayerId, myUserId); document.getElementById('acc-grundeinstellungen-btn').classList.add('is-open'); document.getElementById('acc-grundeinstellungen-body').classList.add('is-open'); diff --git a/src/main/resources/static/js/hashtag.js b/src/main/resources/static/js/hashtag.js new file mode 100644 index 0000000..567df05 --- /dev/null +++ b/src/main/resources/static/js/hashtag.js @@ -0,0 +1,217 @@ +(function () { + 'use strict'; + + // ── Styles ────────────────────────────────────────────────────────────── + const style = document.createElement('style'); + style.textContent = ` + .post-hashtag { + color: var(--color-primary); + text-decoration: none; + font-weight: 600; + } + .post-hashtag:hover { text-decoration: underline; } + + .hashtag-dropdown { + position: fixed; + z-index: 600; + background: var(--color-card); + border: 1px solid var(--color-secondary); + border-radius: 8px; + min-width: 170px; + max-width: 260px; + box-shadow: 0 4px 20px rgba(0,0,0,0.45); + overflow: hidden; + } + .hashtag-dropdown-item { + padding: 0.45rem 0.9rem; + cursor: pointer; + font-size: 0.88rem; + color: var(--color-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .hashtag-dropdown-item:hover, + .hashtag-dropdown-item.active { + background: var(--color-secondary); + color: var(--color-primary); + } + .hashtag-dropdown-create { + font-style: italic; + border-top: 1px solid var(--color-secondary); + color: var(--color-primary); + } + .hashtag-dropdown-hint { + padding: 0.45rem 0.9rem; + font-size: 0.82rem; + color: var(--color-text); + opacity: 0.6; + font-style: italic; + } + `; + document.head.appendChild(style); + + // ── Escape helper (works even if shared.js not yet loaded) ─────────────── + function esc(s) { + return String(s ?? '') + .replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + } + + // ── renderTextWithHashtags ──────────────────────────────────────────────── + window.renderTextWithHashtags = function (text) { + if (!text) return ''; + const PATTERN = /#([\wäöüÄÖÜß]{1,100})/g; + const parts = []; + let lastIndex = 0; + let match; + while ((match = PATTERN.exec(text)) !== null) { + parts.push(esc(text.slice(lastIndex, match.index))); + const tag = match[1].toLowerCase(); + parts.push( + `#${esc(match[1])}` + ); + lastIndex = match.index + match[0].length; + } + parts.push(esc(text.slice(lastIndex))); + return parts.join(''); + }; + + // ── attachHashtagAutocomplete ───────────────────────────────────────────── + window.attachHashtagAutocomplete = function (textarea) { + let dropdownEl = null; + let suggestions = []; + let selectedIdx = -1; + let debounce = null; + + // Returns { prefix, hashStart } if cursor is inside a #word, else null + function getHashAtCursor() { + const pos = textarea.selectionStart; + const text = textarea.value; + let i = pos - 1; + while (i >= 0 && /[\wäöüÄÖÜß]/.test(text[i])) i--; + if (i >= 0 && text[i] === '#') { + return { prefix: text.slice(i + 1, pos), hashStart: i }; + } + if (i === -1 && text.length > 0 && text[0] === '#' && pos > 0) { + return { prefix: text.slice(1, pos), hashStart: 0 }; + } + return null; + } + + function positionDropdown() { + if (!dropdownEl) return; + const r = textarea.getBoundingClientRect(); + dropdownEl.style.top = (r.bottom + window.scrollY + 2) + 'px'; + dropdownEl.style.left = (r.left + window.scrollX) + 'px'; + } + + function highlight() { + if (!dropdownEl) return; + dropdownEl.querySelectorAll('.hashtag-dropdown-item').forEach((el, i) => { + el.classList.toggle('active', i === selectedIdx); + }); + } + + function removeDropdown() { + dropdownEl?.remove(); + dropdownEl = null; + suggestions = []; + selectedIdx = -1; + } + + function showDropdown(items, prefix) { + removeDropdown(); + const lPrefix = prefix ? prefix.toLowerCase() : ''; + const hasCreate = lPrefix.length > 0 && !items.some(t => t === lPrefix); + + if (!items.length && !hasCreate) { + if (prefix !== undefined && prefix.length === 0) { + // Nur "#" getippt, noch keine populären Tags + dropdownEl = document.createElement('div'); + dropdownEl.className = 'hashtag-dropdown'; + const hint = document.createElement('div'); + hint.className = 'hashtag-dropdown-hint'; + hint.textContent = 'Tippe weiter um einen Hashtag zu erstellen'; + dropdownEl.appendChild(hint); + document.body.appendChild(dropdownEl); + positionDropdown(); + } + return; + } + + suggestions = hasCreate ? [...items, lPrefix] : [...items]; + + dropdownEl = document.createElement('div'); + dropdownEl.className = 'hashtag-dropdown'; + + suggestions.forEach((tag, i) => { + const isCreate = hasCreate && i === suggestions.length - 1; + const item = document.createElement('div'); + item.className = 'hashtag-dropdown-item' + (isCreate ? ' hashtag-dropdown-create' : ''); + item.textContent = (isCreate ? '+ #' : '#') + tag; + item.addEventListener('mousedown', e => { e.preventDefault(); insertTag(tag); }); + item.addEventListener('mouseover', () => { selectedIdx = i; highlight(); }); + dropdownEl.appendChild(item); + }); + + document.body.appendChild(dropdownEl); + positionDropdown(); + } + + function insertTag(tag) { + const info = getHashAtCursor(); + if (!info) { removeDropdown(); return; } + const v = textarea.value; + const cursor = textarea.selectionStart; + const before = v.slice(0, info.hashStart); + const after = v.slice(cursor); + const inserted = '#' + tag + ' '; + textarea.value = before + inserted + after; + const newPos = before.length + inserted.length; + textarea.setSelectionRange(newPos, newPos); + textarea.focus(); + textarea.dispatchEvent(new Event('input', { bubbles: true })); + removeDropdown(); + } + + textarea.addEventListener('input', () => { + clearTimeout(debounce); + const info = getHashAtCursor(); + if (!info) { removeDropdown(); return; } + debounce = setTimeout(async () => { + try { + const url = info.prefix.length === 0 + ? '/hashtags/popular?limit=6' + : '/hashtags/suggest?q=' + encodeURIComponent(info.prefix) + '&limit=6'; + const res = await fetch(url); + if (res.ok) showDropdown(await res.json(), info.prefix); + else showDropdown([], info.prefix); + } catch { removeDropdown(); } + }, 150); + }); + + textarea.addEventListener('keydown', e => { + if (!dropdownEl) return; + if (e.key === 'ArrowDown') { + e.preventDefault(); + selectedIdx = Math.min(selectedIdx + 1, suggestions.length - 1); + highlight(); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + selectedIdx = Math.max(selectedIdx - 1, 0); + highlight(); + } else if ((e.key === 'Enter' || e.key === 'Tab') && selectedIdx >= 0) { + e.preventDefault(); + insertTag(suggestions[selectedIdx]); + } else if (e.key === 'Escape') { + removeDropdown(); + } + }); + + textarea.addEventListener('blur', () => setTimeout(removeDropdown, 150)); + window.addEventListener('scroll', positionDropdown, { passive: true }); + window.addEventListener('resize', positionDropdown, { passive: true }); + }; +})(); diff --git a/src/main/resources/static/js/mobile-nav.js b/src/main/resources/static/js/mobile-nav.js index 6fab66a..6e97bd7 100644 --- a/src/main/resources/static/js/mobile-nav.js +++ b/src/main/resources/static/js/mobile-nav.js @@ -19,7 +19,7 @@ box-shadow: 0 2px 12px rgba(0,0,0,0.4); z-index: 500; align-items: center; - padding: 0 0.25rem; + padding: 0 4px 0 0.25rem; } .mobile-topbar-logo { position: absolute; @@ -43,9 +43,9 @@ background: none; border: none; color: var(--color-text); - font-size: 1.725rem; + font-size: 1.3rem; line-height: 1; - padding: 0.75rem 0.675rem; + padding: 0.55rem 0.6rem; cursor: pointer; display: flex; align-items: center; diff --git a/src/main/resources/static/js/nav.js b/src/main/resources/static/js/nav.js index 70fa159..1a44fb6 100644 --- a/src/main/resources/static/js/nav.js +++ b/src/main/resources/static/js/nav.js @@ -301,7 +301,7 @@ '/community/gruppen.html', '/community/gruppe.html', '/community/locations.html', '/community/location-detail.html', '/community/events.html', '/community/event-detail.html', - '/community/abonnements.html', '/community/personen-suchen.html', + '/community/abonnements.html', '/community/benutzer.html', ])} ${column('colDating', 'Dating', col3Html, ['/dating/'])} diff --git a/src/main/resources/static/js/section-nav.js b/src/main/resources/static/js/section-nav.js index 4328936..41f1367 100644 --- a/src/main/resources/static/js/section-nav.js +++ b/src/main/resources/static/js/section-nav.js @@ -5,12 +5,19 @@ // ── Bereichs-Definitionen ──────────────────────────────────────────────── const SECTIONS = { + common: { + prefixes: ['/games/common/'], + items: [ + { href: '/games/vanilla/neuvanilla.html', icon: 'VANILLA', label: 'Vanilla Game' }, + { href: '/games/bdsm/neubdsm.html', icon: 'BDSM', label: 'BDSM Game' }, + { href: '/games/chastity/neulock.html', icon: 'CHASTITY', label: 'Chastity Game' }, + ], + }, social: { prefixes: ['/community/'], exclude: [ '/community/nachrichten.html', '/community/benachrichtigungen.html', - '/community/einladungen.html', ], items: [ { href: '/community/feed.html', icon: 'FEED', label: 'Feed' }, diff --git a/src/main/resources/static/js/topbar.js b/src/main/resources/static/js/topbar.js index 7b88b4b..66d866d 100644 --- a/src/main/resources/static/js/topbar.js +++ b/src/main/resources/static/js/topbar.js @@ -168,18 +168,26 @@ async function doSearch(q, overlay) { try { - const res = await fetch('/search?q=' + encodeURIComponent(q) + '&limit=3'); - if (!res.ok) { overlay.innerHTML = '
        Fehler bei der Suche.
        '; return; } - const data = await res.json(); - const { users = [], locations = [], events = [] } = data; + const tagQuery = q.startsWith('#') ? q.slice(1) : q; + const [searchRes, gruppenRes, hashtagRes] = await Promise.all([ + fetch('/search?q=' + encodeURIComponent(q) + '&limit=3'), + fetch('/gruppen/search?q=' + encodeURIComponent(q)), + fetch('/hashtags/suggest?q=' + encodeURIComponent(tagQuery) + '&limit=4') + ]); - if (!users.length && !locations.length && !events.length) { - overlay.innerHTML = '
        Keine Ergebnisse.
        '; - return; - } + const data = searchRes.ok ? await searchRes.json() : {}; + const gruppen = gruppenRes.ok ? await gruppenRes.json() : []; + const hashtags = hashtagRes.ok ? await hashtagRes.json() : []; + + const { users = [], locations = [], events = [] } = data; + const gruppenSlice = gruppen.slice(0, 3); let html = ''; + if (!users.length && !locations.length && !events.length && !gruppenSlice.length && !hashtags.length) { + html += '
        Keine Ergebnisse.
        '; + } + if (users.length) { html += `
        Personen
        `; html += users.map(u => { @@ -213,6 +221,26 @@ }).join(''); } + if (gruppenSlice.length) { + html += `
        Gruppen
        `; + html += gruppenSlice.map(g => { + const av = g.bild + ? `` + : `👥`; + return ` + ${av}${esc(g.name)}`; + }).join(''); + } + + if (hashtags.length) { + html += `
        Hashtags
        `; + html += `
        `; + html += hashtags.map(tag => + `#${esc(tag)}` + ).join(''); + html += `
        `; + } + html += `Alle Ergebnisse anzeigen →`; overlay.innerHTML = html; } catch (e) { diff --git a/src/main/resources/static/search.html b/src/main/resources/static/search.html index 5d193a1..5e3c309 100644 --- a/src/main/resources/static/search.html +++ b/src/main/resources/static/search.html @@ -163,6 +163,33 @@ transition: border-color 0.15s, color 0.15s; } .search-load-more:hover { border-color: var(--color-primary); color: var(--color-primary); background: none; } + + .hashtag-chip { + display: inline-flex; + align-items: center; + gap: 0.3rem; + padding: 0.4rem 0.85rem; + background: var(--color-card); + border: 1px solid var(--color-secondary); + border-radius: 20px; + color: var(--color-primary); + font-weight: 600; + font-size: 0.9rem; + text-decoration: none; + transition: border-color 0.15s, background 0.15s; + } + .hashtag-chip:hover { border-color: var(--color-primary); background: var(--color-secondary); } + + /* Dialog (Gruppen-Beitritt) */ + .dialog-backdrop { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.6); z-index:200; align-items:center; justify-content:center; } + .dialog-backdrop.visible { display:flex; } + .dialog { background:var(--color-card); border:1px solid var(--color-secondary); border-radius:12px; padding:1.75rem; width:100%; max-width:420px; box-shadow:0 8px 32px rgba(0,0,0,0.6); max-height:90vh; overflow-y:auto; } + .dialog h3 { color:var(--color-primary); font-size:1.1rem; margin-bottom:1.25rem; } + .dialog label { display:block; font-size:0.8rem; color:#aaa; margin-bottom:0.3rem; margin-top:1rem; } + .dialog textarea { width:100%; padding:0.6rem 0.85rem; border:1px solid var(--color-secondary); border-radius:6px; background:var(--color-secondary); color:var(--color-text); font-size:0.95rem; outline:none; transition:border-color 0.2s; resize:vertical; min-height:80px; box-sizing:border-box; } + .dialog textarea:focus { border-color:var(--color-primary); } + .dialog-actions { display:flex; justify-content:flex-end; gap:0.75rem; margin-top:1.5rem; } + .dialog-actions button { flex:none; margin:0; padding:0.55rem 1.1rem; font-size:0.9rem; width:auto; } @@ -173,7 +200,7 @@
        -
        @@ -188,6 +215,12 @@ + +
        @@ -207,6 +240,31 @@
        + +
        + +
        +
        + +
        + +
        +
        + + + + +
        +
        +

        Beitrittsanfrage senden

        +

        + + + +
        + + +
        @@ -279,10 +337,12 @@ document.getElementById('grid' + cap(t)).innerHTML = ''; document.getElementById('more' + cap(t)).style.display = 'none'; }); - // Alle drei Typen parallel laden + // Alle Typen parallel laden loadChunk('users'); loadChunk('locations'); loadChunk('events'); + loadGruppen(q); + loadHashtags(q); } function clearAll() { @@ -295,6 +355,12 @@ document.getElementById('count' + cap(t)).textContent = '0'; document.getElementById('loading' + cap(t)).style.display = 'none'; }); + document.getElementById('gridGruppen').innerHTML = ''; + document.getElementById('countGruppen').textContent = '0'; + document.getElementById('loadingGruppen').style.display = 'none'; + document.getElementById('gridHashtags').innerHTML = ''; + document.getElementById('countHashtags').textContent = '0'; + document.getElementById('loadingHashtags').style.display = 'none'; const url = new URL(window.location); url.searchParams.delete('q'); history.replaceState(null, '', url); @@ -374,14 +440,185 @@ }); } + // ── Gruppen-Suche ── + + async function loadGruppen(q) { + const loadingEl = document.getElementById('loadingGruppen'); + const grid = document.getElementById('gridGruppen'); + loadingEl.style.display = ''; + grid.innerHTML = ''; + try { + const res = await fetch('/gruppen/search?q=' + encodeURIComponent(q)); + if (!res.ok) throw new Error(); + const data = await res.json(); + document.getElementById('countGruppen').textContent = data.length; + if (!data.length) { + grid.innerHTML = '
        Keine Ergebnisse.
        '; + return; + } + data.forEach(g => grid.appendChild(buildGruppeCard(g))); + } catch (e) { + // ignore + } finally { + loadingEl.style.display = 'none'; + } + } + + function buildGruppeCard(g) { + const card = document.createElement('div'); + card.className = 'search-card'; + card.style.cssText = 'justify-content:space-between; cursor:pointer;'; + card.addEventListener('click', () => { location.href = '/community/gruppe.html?gruppeId=' + g.gruppeId; }); + + const av = g.bild + ? `
        ` + : `
        👥
        `; + const privBadge = g.isPrivate ? ' 🔒' : ''; + const sub = g.memberCount + ' Mitglied' + (g.memberCount !== 1 ? 'er' : ''); + + const info = document.createElement('div'); + info.style.cssText = 'display:flex; align-items:center; gap:0.75rem; min-width:0; flex:1;'; + info.innerHTML = `${av}
        ${esc(g.name)}${privBadge}
        ${esc(sub)}
        `; + card.appendChild(info); + + if (!g.myRole) { + const btn = document.createElement('button'); + btn.style.cssText = 'font-size:0.78rem; padding:0.3rem 0.65rem; width:auto; margin:0; white-space:nowrap; flex-shrink:0; margin-left:0.5rem;'; + if (g.myRequestStatus === 'AUSSTEHEND') { + btn.disabled = true; + btn.style.opacity = '0.6'; + btn.textContent = 'Anfrage ausstehend'; + } else if (g.isPrivate) { + btn.textContent = 'Anfrage senden'; + btn.addEventListener('click', e => { e.stopPropagation(); openSearchJoinDialog(g.gruppeId, g.name); }); + } else { + btn.textContent = 'Beitreten'; + btn.addEventListener('click', e => { e.stopPropagation(); joinGruppeSearch(g.gruppeId, btn); }); + } + card.appendChild(btn); + } + + return card; + } + + // ── Hashtag-Suche ── + + async function loadHashtags(q) { + const loadingEl = document.getElementById('loadingHashtags'); + const grid = document.getElementById('gridHashtags'); + loadingEl.style.display = ''; + grid.innerHTML = ''; + try { + const raw = q.startsWith('#') ? q.slice(1) : q; + const res = await fetch('/hashtags/suggest?q=' + encodeURIComponent(raw) + '&limit=20'); + if (!res.ok) throw new Error(); + const tags = await res.json(); + document.getElementById('countHashtags').textContent = tags.length; + if (!tags.length) { + grid.innerHTML = '
        Keine Hashtags gefunden.
        '; + return; + } + tags.forEach(tag => { + const a = document.createElement('a'); + a.className = 'hashtag-chip'; + a.href = '/community/feed.html?tag=' + encodeURIComponent(tag); + a.textContent = '#' + tag; + grid.appendChild(a); + }); + } catch (e) { + // ignore + } finally { + loadingEl.style.display = 'none'; + } + } + + async function joinGruppeSearch(gruppeId, btn) { + btn.disabled = true; + btn.textContent = '…'; + try { + const res = await fetch('/gruppen/' + gruppeId + '/join', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: '{}' + }); + if (res.ok || res.status === 201) { + btn.textContent = 'Beigetreten ✓'; + } else { + btn.disabled = false; + btn.textContent = 'Beitreten'; + } + } catch (e) { + btn.disabled = false; + btn.textContent = 'Beitreten'; + } + } + + // ── Join-Dialog für Gruppen ── + + let searchJoinGruppeId = null; + + function openSearchJoinDialog(gruppeId, name) { + searchJoinGruppeId = gruppeId; + document.getElementById('searchJoinGroupName').textContent = name; + document.getElementById('searchJoinNachricht').value = ''; + document.getElementById('searchJoinError').style.display = 'none'; + document.getElementById('searchJoinDialog').classList.add('visible'); + } + + function closeSearchJoinDialog() { + document.getElementById('searchJoinDialog').classList.remove('visible'); + searchJoinGruppeId = null; + } + + async function sendSearchJoinRequest() { + if (!searchJoinGruppeId) return; + document.getElementById('searchJoinError').style.display = 'none'; + const nachricht = document.getElementById('searchJoinNachricht').value.trim() || null; + try { + const res = await fetch('/gruppen/' + searchJoinGruppeId + '/join', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ nachricht }) + }); + if (res.ok || res.status === 201) { + closeSearchJoinDialog(); + if (currentQuery.length >= 2) loadGruppen(currentQuery); + } else { + const el = document.getElementById('searchJoinError'); + el.textContent = 'Fehler beim Senden der Anfrage.'; + el.style.display = 'block'; + } + } catch (e) { + const el = document.getElementById('searchJoinError'); + el.textContent = 'Fehler: ' + e.message; + el.style.display = 'block'; + } + } + + document.getElementById('searchJoinCancelBtn').addEventListener('click', closeSearchJoinDialog); + document.getElementById('searchJoinSendBtn').addEventListener('click', sendSearchJoinRequest); + document.getElementById('searchJoinDialog').addEventListener('click', e => { + if (e.target === document.getElementById('searchJoinDialog')) closeSearchJoinDialog(); + }); + function cap(s) { return s.charAt(0).toUpperCase() + s.slice(1); } // ── URL-Parameter beim Start auslesen ── - const initQ = new URLSearchParams(window.location.search).get('q') || ''; + const params = new URLSearchParams(window.location.search); + const initQ = params.get('q') || ''; + const initTab = params.get('tab') || ''; + + if (initTab) { + const tabBtn = document.querySelector(`.search-tab-btn[data-tab="${initTab}"]`); + if (tabBtn) tabBtn.click(); + } + if (initQ.length >= 2) { input.value = initQ; - // Warten bis icons.js geladen ist setTimeout(() => startSearch(initQ), 100); + } else if (initTab === 'hashtags') { + // Populäre Tags zeigen wenn kein Suchbegriff + setTimeout(() => loadHashtags(''), 100); } })(); diff --git a/src/main/resources/static/userhome.html b/src/main/resources/static/userhome.html index fa94bba..fe813f9 100644 --- a/src/main/resources/static/userhome.html +++ b/src/main/resources/static/userhome.html @@ -243,6 +243,33 @@ .lb-comment-compose button { width:auto; margin:0; padding:0.35rem 0.75rem; font-size:0.8rem; } @media (max-width:650px) { .lb-layout { flex-direction:column; height:95vh; } .lb-post-side { border-right:none; border-bottom:1px solid var(--color-secondary); max-height:55vh; } .lb-comments-panel { width:100%; } } + /* ── Spiel starten ── */ + .start-game-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 0.75rem; + } + .start-game-card { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.6rem; + background: var(--color-card); + border: 1px solid var(--color-secondary); + border-radius: 12px; + padding: 1.25rem 1rem; + text-decoration: none; + color: var(--color-text); + transition: border-color 0.15s, background 0.15s; + text-align: center; + } + .start-game-card:hover { + border-color: var(--color-primary); + background: rgba(var(--color-primary-rgb,233,69,96),0.06); + } + .start-game-icon { font-size: 2rem; line-height: 1; } + .start-game-title { font-size: 0.9rem; font-weight: 600; } + /* ── Neue Mitglieder ── */ .new-members-strip { display: flex; @@ -306,6 +333,25 @@
        + + +