From 0befcc6a2ca5d0342b5f637543846b37660e8e40 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 2 Jul 2026 13:51:11 +0200 Subject: [PATCH] GUI ans Mockup-Design angepasst (GEFIM-Branding) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GEFIM-Farbwelt aus dem Mockup als shadcn-Theme: Violett #5d52a3 als Primärfarbe, Magenta/Hellblau-Akzente, Ink-Text, #f5f6fa-Hintergrund, Verlaufs-Utilities (bg-grad, bg-grad-soft), Karten-Schatten - Fonts Poppins (Überschriften/Buttons) + Open Sans (Text), self-hosted über next/font — kein Google-CDN-Aufruf zur Laufzeit (DSGVO) - Sidebar mit extrahiertem GEFIM-Logo (public/gefim-logo.png), aktive Navigation in Violett wie im Prototyp - Sticky Topbar mit globaler Suche (leitet vorerst auf die Asset-Suche), Nutzer-Info und Verlaufs-Avatar mit Initialen; Logout in die Topbar - Primär-Buttons mit Verlaufshintergrund, Level-Badges als Pillen in Mockup-Farben, Login-Seite im GEFIM-Look - Dashboard im Mockup-Stil: KPI-Karten (Assets, Prozesse, kritische Prozesse, Risiken-Platzhalter) und Letzte-Aktivitäten-Feed aus dem echten Audit-Log Co-Authored-By: Claude Fable 5 --- messages/de.json | 14 ++- messages/en.json | 14 ++- public/gefim-logo.png | Bin 0 -> 40645 bytes src/app/(app)/dashboard/page.tsx | 83 ++++++++++++--- src/app/(app)/layout.tsx | 68 +++++++++--- src/app/globals.css | 173 ++++++++++++++++++------------- src/app/layout.tsx | 17 +-- src/app/login/page.tsx | 62 +++++------ src/components/level-badge.tsx | 2 +- src/components/nav-link.tsx | 6 +- src/components/ui/button.tsx | 5 +- src/components/ui/card.tsx | 2 +- src/lib/levels.ts | 10 +- 13 files changed, 293 insertions(+), 163 deletions(-) create mode 100644 public/gefim-logo.png diff --git a/messages/de.json b/messages/de.json index 7cc1c0a..52c081e 100644 --- a/messages/de.json +++ b/messages/de.json @@ -45,7 +45,17 @@ "welcome": "Willkommen, {name}", "tenant": "Mandant", "roles": "Rollen", - "placeholder": "Die Modul-Dashboards (Risiken, Aufgaben, SoA-Erfüllungsgrad) folgen in den nächsten Iterationen." + "placeholder": "Die Modul-Dashboards (Risiken, Aufgaben, SoA-Erfüllungsgrad) folgen in den nächsten Iterationen.", + "subtitle": "Willkommen zurück, {name} — Status des ISMS für {tenant}.", + "kpiAssets": "Assets im Inventar", + "kpiProcesses": "Prozesse (BIA)", + "kpiCritical": "Kritische Prozesse", + "kpiCriticalHint": "Kritikalität hoch oder sehr hoch", + "kpiRisks": "Offene Risiken", + "kpiRisksHint": "folgt mit dem Risiko-Modul", + "activity": "Letzte Aktivitäten", + "activitySub": "Audit-Log", + "activityEmpty": "Noch keine Aktivitäten." }, "assets": { "title": "Assets & BIA", @@ -140,4 +150,4 @@ "3": "Hoch", "4": "Sehr hoch" } -} +} \ No newline at end of file diff --git a/messages/en.json b/messages/en.json index b8a4020..744a4ad 100644 --- a/messages/en.json +++ b/messages/en.json @@ -45,7 +45,17 @@ "welcome": "Welcome, {name}", "tenant": "Tenant", "roles": "Roles", - "placeholder": "Module dashboards (risks, tasks, SoA coverage) follow in upcoming iterations." + "placeholder": "Module dashboards (risks, tasks, SoA coverage) follow in upcoming iterations.", + "subtitle": "Welcome back, {name} — ISMS status for {tenant}.", + "kpiAssets": "Assets in inventory", + "kpiProcesses": "Processes (BIA)", + "kpiCritical": "Critical processes", + "kpiCriticalHint": "criticality high or very high", + "kpiRisks": "Open risks", + "kpiRisksHint": "coming with the risk module", + "activity": "Recent activity", + "activitySub": "Audit log", + "activityEmpty": "No activity yet." }, "assets": { "title": "Assets & BIA", @@ -140,4 +150,4 @@ "3": "High", "4": "Very high" } -} +} \ No newline at end of file diff --git a/public/gefim-logo.png b/public/gefim-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..92b0e965b722cd14039347aa0cd9bc0e3d2ee925 GIT binary patch literal 40645 zcmce-by(Ejw?8_72!cpRDIp;O(nt*5NH-$g%`m`FLrX|2O2ZJ+-Q6M}f((jugD|6% z;Lv&B=;!-A=iGbF@1E!Wad{qwckEef@3miR?blvw6RoB3l!)Ly0SE*lQdW}J0fBIS zfIwK5_&0$k-@`%{fIm0vWz=OrpxW1j=a#r25P_o!f1&D3vO%x2PCKF`w_TA?ski{=hNESP# z4Gsx4$Rdw~P!|jT5Y&_KRACo{{{)LIE~(mg%)o>ZsS5(_*4f0azfID^b!BYmqcbsW z_00tf%T;Jihu;eSiQS5iwzVC)G1d=Ce1ZcCQS_D%Ax0j37ZGa_6`KRkiUu^6T-`s9 zjf#)GN|mba#lixGe>+e(tM90|589yyHSNWwG|=VR5`|1&@ML_V2TjO^pvuihKCA$? z(Tk8p+1aONW;%0_5<((M%@0m;ZUtkRAJne{ZE2lP-r5*7MgTd59Z7 zB8hR&l%n5T;y%atsKt`7#;#kHYQ;TO?Q11-BtQ2RBy||>bO>`xh?wt-2cKVRMTLT&gJhK=)Dq;lIV1G7 zZm`slHpx(Ry_|h#GEPGzoB!p+fuIe4^g(~j0zc{dBp7zI%?qOQKK@%#*n82IL@Q{;YYqFJu;K82CrSoV|mRZcBW75pAtNXUg*_Am31QL7Bx90(e?Hy|;< z@hH)8VBc!s3B_lk&weNolr>6>A|9}9do5C0Sk*w}ny^|r@5XXgV4l!b1T&p_0RNFq2pK(9qi9o&}Mxb0!)9KKI_tm|~ z(ND`s?yuZjVUlWJGgl=P#BDE<2H2;HJ0>#9)(crCA5S`swG|%7&`ceE)0kQ;Sjf9j zip;exyjzu6UayMoKo061>K&E#miFtuR_m;e-7xy8_=EfFLh)RoSW$5`RW9qdxhfwG zsC-WTufm+7ZiTC)%Nndp6sTLqJ|!^Wndw;k)A$Ljp1Qu#j>}FC zWrPaA1YmArA}Q4fHDeVh1u2EOlv7NGJ%_VWrYf{JSj+XwoyyB6OB(AN`}~T%Mg>*{ zrX0r{1;6b~^SX3EJY4R(oVtiKy>G&8;%egAQrlYH7Mk&z4Q%qM8?C8s3|`J%;hlTe z^KN8C_$kNJch-&HM}B_(dHKyi4Vrc7@$%sIUYzNZ9no5ZEMii$Q7q8I+dV~0Sd_Ru zt>t0cmRsuwTiAz|!ui-Ld4mU26jiJyOYI`{kqb@H=S5`WN$&2nv&e9xxsd z`yP98^O*9?`^@opbw{}yyDXLV^=>i#l$&BJ9i20mUkH{E5rE_(C9YV>QQcY3Fb z_fryPHvW>wKa^|qYs6c)ZV;u@)rqXF(0&sc6XAMCGDN&Ud&>4%+(mo(^-95R&F;o- zG3gyrRc29U!#9M?#EMjv!mYNUDvykRPgAgsF(fps~&q6bx%FuzqMkX4xy4(^hU>H#z}8!>af7m zDbt$7wjq8Frao~)aN;T9G>a>(|}7N~Zvomf4H#lu`+x=m%DGntHajl2CU zD-0a&NZmGl#i?nu*wyQ+uoCkk8H@pg z3UnUwwAD`rZtr-7d%g8q*etGI>biv0mK=LT2P!Lk`lw_1a~(?CReEk`CHMQ~4nMli zlt!7RkOm6IcXN{U(-|j5XZTfU9u+PE+Q>Us17W~>(y@RKjXJ9_io<-FC`iz*sf`BEb z@1Ws}rY?=*s)C(wK|e1SaY*q*H%vD+i8tsuouz4#h(1J;-7!5w9<@&@W)5_!BZ}UB zaC;6vo;iwMT9PF5EVy7ha`;YMB(((|5PdcuC&1%M4^f8vgv2{)}4~5&goq7 z|K6*9Il|G5n%`)ZI67PqyXE8eD`w};!f?yc?;zyG9v;U7vImq>I{tcl&yU=?)X-1in23E#g&TVAa=kD?@Ke7XH4C3W^= zPkF@5o_8Ft7*w0LD_TFrGaEA3IDdAkayD@}^~9|Cbws>GP~wT*Ib=_4+XrtEw&W8O zb;1kIK6`Us`#W*HxM@@-=YfWehT8dEoHWdNT)$T#Krx_WuG$!5Z9Ypj(X2MpR22hT0qYCCMP}D+&mJ*m_zr_(EJBx0@v4%d6*dfH1TwnV3N63 z$nacUi$MC5E`WB!)}d0UvZhohUPBh;1QTB4;D)XP(X3Fz$KQ9#`OE!GwGkEH;c@%UQ0 z@$hl;UXSWeLmTUVw{!FIaQUO$#+t|0#TH`g>Innd^8LH5n*-Dn3Uh$|52FA5`oAau ztgEj6Zyo<_Um%cwtAKebcmu=u#~}Z0YnUF~&6Y>U76$e5u(nn32D-_7JsLMrIS*S) zPpF3;6zcM?xzhSqml^m4x%v1Q*wihp9bKH_ru6a&3`@}Hk81KTqXsGXw=@Z$fmP5aYbb#+l?SD2@ztF^7N zyaW?qA8toS8&MHk5fL!h&XUX0#@3Qc0BrMw%hFcBic3V~37?37t)LC??qA=Rhgy4G zqu~1ce|^FRY7NNvkM8l?S%Yo(togaDt$D4v?4AhlbAhb@X?!-;R)ThdRs#Hjw*Qi* z>EQ^trKQV%mU`VQ8$hD4fQ^V1*ovP^#Fm#A=#?NJ7g*3vh|7}KR!Gq939pTv4gWRr z|A0wU$q@!jGyET4(zSK}=gP&A;SYX9Ev>KpLW0Tq8W6TNO#eJ~{BP{>zta5A_kA2} z0ipi~l>F5WgW7reSbEsX*aNov-vE*4f9yZh)!WwNKaIf6(!&x6O12&_2_`!aD1_lJ zXl&hJb_|}-f9|d!^goJ(S$hAU4f4MO$lsCvcLw<{zVQFiAph9g+QHJ*-WCYpJb%W+ za~5|nzD_&^0>wuu%ggBb zBDOnC6PcC0vriO>enqratqA<467U%D1LNzsto0kEZ&yL`T@USe2n^poTT{`qt&5b4 z9xvmEOtICXnd{?BbjTW2lC;NEoaa>vRQV#8Io}kI`*FGOW~^)yxhd|&KrG8nZIVvUevvw=dny^YQ2r^)tt-n3yDW8&S_yNf zZ#50@2!9;G>QlXeu?^{d8P)so>MZ!=BgW>t^97jBP-wOPeQtzecn#U zU>x4N15P1B-*4Y0L-)u{dQYvIJn@bO4MSjr49__uqBQ%?RP+7Y83p>MB|4 ztT1Ac;4hR4;HP1(wAg$?^iT#UI6w)>bi%Y8W+1(TLPe!#dVIQ?5S=8C$97Hg5~ z#ZP{G)Ku_3rgl;FA1mmyeQ{nk{@6YL-Y7gciokK>QCuFagM1oJ2)xED0_$xK5y_rBpTL<{s(S(TDktxl;ffl|q6p-^-|-ao*gX$|?_{P=hQ@cdd`Ah8fZ>Pv@w< z#Zmn;Wl_VT7md{wjR&Ju-jSp$a(d;z+vAwOD5UMxs<>QCR zf%*uji>4W@=1wio-6d?byYDYEgLjFGP7R*^wV|@y3$xH9h6g>mZGMzZy&Z#m4q@Qt zR~ege(3xsw#D_YocDwUeg-x4zXpR?MCb!O$<;wSn@ryo*9tNMBNeNGMTm7{fK5iQ> zzinXD%Nrugl1ABDk-W|6#VRUiO@T2muX#VI;?nK8Rq>g??;`rU)n*va=w-T)Pu-`Z z!O$uxg1`2nr6>P!-$uI`E9CWBw<#Vs#eh)~PU9_1d@Yxf<5G6Mr2C>$|6!%bmQk6A zCd8nLqja0*D~wc~_}L+L>k?U6C4(8ZE+~gP%tG|CGspDD>)U_*-ORpiXjYOr%Y_sU#|(T6GA@IwK@40TRU5l%W~+{L%k$##2IB9Y zHU}TsquM^m{IS?zmq!h@sp6+d630zBfrO?zdp!0q2E95l-%QkU)>F!%!#xR93_+KxXo+GbiBWG@$t$wPs-`9 z8McV=SiU4Tu8n@tO6o{rz;k+?0*&`!LoO6Gpto9P^>V=GGj3eSWcb)BEr>q!Db4q{ z7eZ*lrhxK{S?)tak;dfNs;*ahPVKATANy6sz(nwB`j}^sM0XhgO~I$ltbbQF8{!%N z7u>p2IWqVdSI8YD6+(~PBsRPbDnBe_u>gg!h8I9vX^2-Ibcm!=0-G^K;C z9e)(mAwB2!7Okz5fa5BE@{%sHmzIr0a5TfgnL80Y8lB<%@>dMld`L=t6G<428To`| z;e6>iiir<=OSY$#|8uiAbz}2(lhhMY_bQF9H+DQ`#&|tS$lx+K=^6K>Ls8IqS5^dVRwM~ui#96Y;kLQO6& zEgyhTzxRNsFW|*13;^27iu~mThApFCT}&1v@Vx#rYa@XtPiWg?3Z}Kk?F^r>9%y3_ zbOxDQQ<>1|>c>dIE@fo9Mw7@!u_8ex z0$VtwTeg&13#9vYrPOqq-xZQ@%)(n^OIm}vC}Uw$+v7U0g4o`|k1S_11x*~TKItOWlJ?$vM3+?D`=Dh&t zfA*yHu(Ymp+i6YgY)yt>(AE@R^2nR!q`sR-KXtqG=jd7=HG3s|aW)Z6&K z#|eB`R2+Sgf$KM?6ziVz^*0e#`dz+JGL%pk1tscJ!86!>*Z3O_Szx?nb;KNbgmG}I zN`;u_HPGqV@(h(BRGY}y;-*c450xVoxE)2j^DKlPw&`PdyCX>|A_qZCc|n@*0QOsO z!{0EsVtX*9D*xre?$CSu{kFaqSIJBVxSuMc6i(7z6fzIBDpfdFVi*nk=|<|F{oQ9$ z$k@HhHs3-U{Jjms#5Iv?=(EGtm=&>dNJs$VGSw;pu)>a%j#1Fz$uFYY9WlG*nT201 z7q^MRTNX6xnw7$z+>D2~QwZYOJe<>a+Os?J7(=aQ3J?=fBUDWi88?a%x(1%py*){^u5Y^sQUhEgC$xe79z|EAiPBYk`ugptKuw>Ol zvkAQPDh8mH5Y@&J@`D3dQ|b0>HhPH94?mPTF1wjN9T|kD-P!T2My!4CUWosUeKG)w zPpcJ$>TAZj+nH)C@25aFT_&mGwgF@c5xX9s0y~ z&T#CeO~wM3e1seY6GI z9cFK>hQrB6ivD!xKLW&-2p*{f9@`hJB&xL#!`aFETzCWoun!><+&3+=EeDdpJegzJ zE>ok%byKaYk?~;7fpQGk+^6ccStW(}g)vuSpguJ>sAv4}8S^jBrM&c#6B9{E&C722 z2Kd`XnP;E2Wup#`82kL2b1%v~87GT!o_^hGL$w0oq9HRCG@ve4TRpfLBJ;s@d_d;b zzS>u3_5p+T)U1yJ$V6Ve`!N|ftsf69opsy$TAQZeGVZP>KCUF_XPd~d5$iXpvsH>a zmAZ2gVEa)FCA02qwG+Yvixl^Yyl32J!-sr?atHwu3aXHLd*vN)n(IdK^5O^!@njv? z_(m1byu84AqT1ok`xR!PiR)K2$i+5*#ldb|nrpflE}|vj#0{~D#~9w`o~g=dZv*4e zMq`p5tWRZC3wAz6CI))49|Do-6ntNIuDV$$Vc<>R$WuYJ#*iqFZdbXCX7X+8lMqhS zA?Zfxu4t$dTvaxPu>cde+`BT+n^{`(u1syQc>m59ZI^43G1IlpM2O+pEz0lA#GOJ=i z4ku^$BJOg|qY&Z&8PgF`+x&c>4g273SjcBw(D17jyi}LSj%-Fx?*{wh^}ia>%mN$e z%q5=0Zz6FpZLrMoLl)gRV^yM6@tbZ$%WN@rnseMp{X7TCr(a!Y73!Hmth+QLKd@T} zKLm|_rv(V2cVw7Ke^oSm$W?S4;EQO;6fj-c= z9duLbx-+*JKacuI5RfR;JCcuREE|;gNWTFBwmk1!;jhs+z>3w2)f880kn8_YHN;p& z53<%pz@fdvl)O;PTMbaUyWcjZA(r$mGbS^`B&S|6m>iFk{-Vy*Hk6NF)qgxx<*dEfz^i#U@ws!+Ob-E_6Hpv6WX zCl{<{FO)Dw;57m~R-`$QdqP8f#+93Zimm#6$Ig6J;jGaC>t&@YJ}6PRd6bo*Qo3k( zp|=wJn$akN?tzXmb*th9t!xPoZRRiOsQoJI_CHhqN^RuP1bFa=NX-ZAg`yTjDx5;b z%u&5ng{zmAoZINUv3SQ}Lf%>-+n)M?k!vAfe(Ad;GdvuXf_!gu8q z4A_2R^LckRZrbePeQ}ELOgZ_HYP#8ZcANDB>p>yNou8nV_51R&_#zH>o|&{sFaDqx z*iy9KQl#)1$)~glpLa|$sWq+U<;g+1QV6Dgf0B=4e`8|X2`>VhFMwyf8Pl6d-K(lN z3?Q6SX9ny923`wr(MCFe`rULS6n5i4R@;sT?0i9KFy;R&b_Joas?cKEf9spCSVu^ytjn|8eq?iVbrTPuOj z-`}dP-NJN%jOUuDaVizUb;Pxt?hgI&b2~;ieNmk}&q3&ARME#fBX>U31h0IdXADnR zSj|%6#akNC6YKW05eb18@$hUHfrWxue z15^DvW6lR_PC`;C(ES`u9Hd?>suFwV2ISlq>9vMF*)uv^J^Lfu7{Y{S0H0@^*AIX}IHXRqVmY0V_ zusUVTOd7#a4T*lp6`%`*FQ?}S=rb3C?0QauRQh%@^ah{OoZdllRHjZG?H4&DvV$vptLjGnHV0>}A6x}ADzPQFa#ba2 zxH_DDKLLMiX+)9wLgMqwp% z0x=te_Q)CWLigafjifkj%T$T|65$mDw0^wfLPt(7f68$C%>I=N0G=5xz?K$~zF!+~ zAQneVKh(9g5^#isi3wtUPb8d1z`vHXs;l_VSh;oe!L2^4AnEY@m!{{=X2FzoD{wt!+VrrQ3FT7NFMI-F_D`spKc4=yIapbyEjC_h*gS<4jf&ac zWO9GMaq#f0^5fc(7`hEJ>3&mqvntzTq*ZLK)-&0b-$vgD@HwUDoZNWnmO?Jm@sn=P zu}xX~2%YSDytn-ow5#NYvj}04Kes;7a?IU-T-c!6ZkRlqcygqP*z7w#{ncP@b@kAw znunnHr;j9-Bku3WPyMftt|`);EfUKvJ1EXJnWX4(VRyfd^3J$Ua zeR#RxKyg6enHW-a>J}2{<%^eS5TDXcj>64D8E;MtPRoyA!BqUe3ONzK&sTS^IyF&5 z41ds4L9hx_JzEV36sLbH71~H@D%QwPF=rra-r8DwDvVEQ(T#b=sGE&PF~YgbO_XW# zeWM-o7UBbV_A+(N^OBS&Esk96!dnJ6IvEbFXx+Spb1T>kOusCvr#bh*voDymVVg;Z zeN_FC{K>Pb(XbYB^b6Wb9}X|KZ7*Zf|~{Cn>C)Gf(B4i}Frj z&FuhQ#@NetDl`hj56D)h^i;H4JvAQUCAvJyV}gRnivZK%>0PUNP|~`2fC5r)(;XrS z%*d&aleJ*YM%^dTu!F{jU26s|=4Tm?34AV5hbOcJf`esM3o&-whU8U}c@ExKZ)&xY zsp9hP$_~EgTWt-F&)>}Lv@XW=%RX+pyAskI05`~+S4?Bgn~_a}Mh(Riot{*Do1s$} z;iweWJSm|iqcT{6i$>aTCYKP=ytvsQmT;?rky&(s1E`H~9 zo~L}~^?mRB#x3O=3w$)E<_{RFO_Qj|QK!)dn7N84R6qizzCAXhI^1Qr{{+WlY*uhJ zK(4`B7g63-sWZm~pv|Dn>g*E$H^KxVorA~|(HJ{Is#ggrEm3(#y){cPi%0-+J6{sn z7bAycmU4ObtkuQ7fZtDEf!Nr4oW=tWK<|8`q>~QDJq)x ziHMeV8V)L|wXHfO-V@phEiZxp_K7ZmJNJn!>mjT+1GW0$HLk^!6-`;f&ucGk_MkzJ zh(@{z^*Wu!Ek`kjKnU3^3Ivj*b&0n>)GSH##I=z6T8pMGXt)!5zP32S3XEy1XKOtj zeCI*Xju`a|Y<(mOF->3K&!V%P$^f*4=qG#k$f)>h6nbZYgTHJQ&%Ugnhmxa39Jg)z z;J=Of4?Db_dgMa(09#Kvgz#?VU{MS49lhc!180AQ0Tk0ls>F!bo66F!{e>THn})an zyh10i9=GKQW0xOM>C-!^EqB*#r&7ykJc_zr}*D2*l+cP_k&;4CZ zz4KZ(G?2D5f;>9-cLq10JGs;d4FsNZar~;dmdi6zbO;l2Vycb{8PPf6oQC5Yb2CAT zvswLfXIOxq`?)i#*>!oS0ROJ0w-^X1KI6(f3RDT!Ke;k*dB?P?szkZ>@T-`4LR$HZ zcAllp?LX0cBv2)&`E*3E*CchU>o&Hvioa_xX8e+PEPEqz#18``Tw@ce``{on?h2Xl zw8>RB8QP;8Z{k5k--&YA_PFZ`+VzJde+94i`>yLkjS}-YsAoD6VxHHiKUj8vqlv1P zn1O_Uxcftt(M(!OjA|`LGa_%D2p^W|d05efiVkLS>Y_yVh&q`844(z5yJJihR!@c= zaxd3M;2F8oAv_L@&d82i;+%RcQVK{MUwUGcOQmiiO?^g+bQv!dJG~Ku^*FtGvcq#+{2ucs6~`%xFc=dO zXTAUN8`w%ajr*PTY4MV)s;h2g_r)-WQpK%%-h-GNn>vA{hb`hB(<){;TMLP**W z4Xb~Dkir+DAM!29)IN-ZbTPm4dgq&tjkPUZV=|c8=mqg6wK0ug;u=O|c|ikBU+d$% z2XChe?xKgKqrX&tn-wN#*9S72dE#1+n_|%#Kvjzl-+j^1>e5txQDo;C@7`?upXxfh zD*4DG5M}n~U$*3?X2P$;)0f(Tt&u&pgl34dLa|0vD#dq>OkVj<$yuE8B4d}vMYU9S zQErbvdnqh~+DlrE@LltXTQ)upTxN#O2^9viAj1wA8b89B5Y69a!y3UK?Tp%q-LpIF z++jFn;0O64u2}o`5y4dV^k}8LmT|6&OJ%U1fWs_)SrS&?*Lhj(N4rx#XN9co>Z=@2dZyyv4$M^Iw<)`w zku1|-svq>D0r%bj#xsZ`cb}?3MvHgs7QX60G-4rKC=b8i~KJ-^0P z+MRy5)j3v+%X7q0PoA6p4}I#hRt{DGo+PW`wXE%QTL!#+2K(m|U5(FluSZ1eDJ#x0 zU`pVT+s4L2$wl?5l^i7ZI-B8*!9p}C1mLjWPg^z*fc-0akF3kbUvN9 z@XQTZMYs@zxAt!1v=u-3QkBeepPh%6i|WoP2}=LlOjuk=t3D(6rvc!H+kTiHt=q1u z2t0rn+CE@+&%SSi%)r$GwIh*W zwG7XZ?-vP+pd{Es`&8O8`A<_qLq#=&kr0kLfcFx=XD;_V94Uc^Jap>fbG23*dXp=! z;%_C~4B+r&iTfsNz|59=R}ggSETbQuK!$$3TZ6MIe)~)qbW{DbhE=b0xyx<+#N`(sI6We*1>C{hQn%XN7+5T-1h@tQcY9M)UMUh<3sh(@?C!aO3t^hzBVQ0Oe^( zD=JMnMsWnDJG!=|2CytFDY2H?*#We z%Y-;yBcfL7AIhV{NB$}TqUMsJDpG}>Z-A~Sf zxW8hUK#TWy5dsc!HWOLT?)M*7moo9I6H6*xZ66>Ba-!l|BYM+m-#nRXFuw7~Q?^6J zGXUaaoM1q@J^<*2>RV3$L#!NIBHv*;MN{N zXopdfIAl0<{M4hmcfJNWUxaT`S^_syX{Sxg8OsKE01NVn~^P9DtPfAUzRs|)^? z3~enC2(+EzF5r z4Y2UjDIlyn?IbDGp|Rj@oVn6JwPAAGtxv9aJ?X4-lDmzE0i4iq+8fu0kDh5;P$iMi z3Kb~83`x79ZNmesl~jH}GUu7nnejs>P=)+^M!rS)v!zpU2b+tOs6ld6$RjK6$e-b@ z-oaVC=2cKI!yZd?#xPoabEH{kwl-@lTfF&FYA&bN^$RW z1oH!YN5awGnk&n18osMuC1-_bZ96z(glEOQ4Oor1mv+velL$EXMYj%cNJR0c3+!CzKaYwfeQG>p$PI&13{R>x(X;IBQuHtm1l`gsz2n`p9sv5ufCn!Ro+bvO?S zk5~eZMw~{q79AV6*=98H`}3QvFyc2@`L{A6h0T?ojR$afH}{{Ek(e+cbtmQwxW=D- z(4E-O1vZm?tyR>BX|(o`c?A=C$QgkHb`1j)f4C%)&r!LiRV1sR^p{<`Hj4%Xt%Lz5 z-fmxExppsHIm*;S=N`6Q+;XFMcM6=Qu+U9n&JyhsoJip{xAnt26>{t`O0$1vGg><2 zs-JGEn`Mgj&*tUPdACs3+{*3Ll{{AE(#&dzW^p>1n23mU*N0-C#||u~}<@ zIC6A;(Z!#$8#8id@H(;EAA+V5x5t1o^$n?rmsDakJBx$|&)kn5rv=vCGpw{zAx%TE zH`l0JQk^-)IgBCe%j>G?+`Et_=va+3`$|F0ID3lF#IsWUSughO{>e;eET+QBZMg81 z_Id%$$-~t`{33V%cIOW#;I)pjHKikNGRJo47c2~|W^w6q^=!z_Qg=W4AgY)3D$c-2 z)$F0y0+UXaswJLB-g?;s_I9VfYnL#tmSDEZo){Hx#y4?Ndn?+NQ7k`xE<-x*q~iaK zP_VJP)szn`=H3;9z{8@)Hqk-+IbTV8v(EfzI`QNk=inrP4e4|h-ZwrY6Y^1Epw3n= ze_ZsO18T-I7+X*!x5rGHUPOLq18HTCm#Dp9$U)vk8G8#2;LVntH1YDJA-UsALPeY_ zPgD@hUy2%d0M2O*>x93xW#~I$uIO(i-}w1C%3|XCIW||wY5?m`fo;%r&{bLCIGuaZ z9qMAcOCganNJEGBDf`y`2ya6WT{{kGR|dDVqf9pQ(1a2^&)!sFSl_a%v#X0Y3)JM$X2=$-nke(^)i92_;9vhqAcKt3v1sQ(@oCkQ24_Nunc*i5R z<|GbnF4I}tBN8@O@UlOf`_;AI9q0V=jbP%O?V0=?x5qugCZkl}TerXP-w?^AS}%v( z%j-~U5YHOO=01O>pT;x6Iu7TbOrI;LXJu+t8W*o7K^PTws;QinQJaK3l&z{9UwlcA zzD54`d|LeOY~n%{=jTa*zhT5DG^4zQDtKH&B`THj`%AV~ps z&_wB!*W$c%+Pc>?{^K{QYjc}Mlg(kSLgp@?<}J*GDxvyhk(?2{X3gXt`_$! z{74n*Yw_B}t~W5zA>J~d&TatZ7u(0034~((d21W7ZN7<6)-X5R$&zpLUS7=8#G3;m8-2vR-uz&q>w{jHa4o zR4IIiIe(|HYyCb#zy-S=BLhb42scS)d1T zb_uNO96+4q$O2GW_*3XQ^dwB~5(E*Hdlf7Y5;u7r8~IDlM8?S&042}h^{cZ&cAZU< z;Gf08wjg&#jAXdQgO)T^R~`M6kR4BBvp;_6W-ZONmd#6Zm0bn|v$-n!@< zymX6YG95?f99>Wk1)~2gOrPhZ5vD@c%}NT$LyLribMje~+=K8G4d;;L>!Z#Rpu10l z68wHXqdr3>-vuIK29Qqp!-QCKf8Q3w-{;-(!MZJx-o0<*!|K=(b5?ym!=wIf^kPi& zN`+V&$}WEfO2>XUINnW}zLm}t(AZf*jaNaiGNbSbt2i$JQsvn|>v;8;5LS_oih*X5 zx;1OVDvI>-P(Vr8>`YOmT}f3GYN+CmM~-fnHL=_}t3ogwF!ETHk%(RI=9Jn2@5Sx} zjv#0g;Z6orO5{wM{;1j(;Qr!uK9qAYMq7F)-@U&gMJFBg1~~fZ&pP^2IiUJ*n7qxk zucapWGt1@3tG75^@+VCrhqo|~e{Pk&_Za$Pzs_Jzol44KO&SUH=94 zS!k7UDlm3GpMOCNn8FBvPLbtwPOo;8TMX51Gg9K#Z6b+u`+ac*4&1g^=J2D}MI@zp zQ=r|~L8m$1jFQXI7QZ$IYzYjW$Lxk&@QpmxxFb{Xkc`%@sQhlv+bWh zz!p@D1|MfnwKS5T$aIU1z^ZNQ9p$uYb?7S$ogh^gL}6qPWb!wZ%D8vcbC%scJP%Vn z-!)5quXey3o^bIn#?E@?W&P&+bHW0rK#?_I^@_`Oo;G}ujmqgfizH+Vy`YjhpWvPh z4aCQ-l^NCUKtn2w(s&x`;8Fi=^Q#{61-;tPJ7)B(pW2yEMm(UJA5Z5tfapG zI4(=WE783vF9Ck; zlO0C-<8im>h23qzwxHk2#8#00U92Cjgzi1b)CM2X1i#0Vgz{+FNV&{Kn6G>y!+{DZ z{PZLe1Or{iC-Yp}L8tWQT<{zfO8+K|?o@*hfZfJl*r6x@*u84-Um45>(i_tg>fwQF z47h$7=AFh0Q=`EvYl++;j3nY=9o9)416V$xw1e#XMJ{TVWb4uq0^Sq%UiX@04Iq$J z9N6}?lO{8d@=;U{j4t_2scIart_EKa=@38Ar*5}+0pu5Fg7X}j614Z<;9*9WrE`Zj zzgp^x-2-;u?Ns9YzdW7?Z0=;nLKgxtr^Y_rZXGvXrSyor{b4}z- z0M2GlSb|@LHpG5LZS1$f&m9^5H~ZSBje7E1a{T(MgF11###s_KdgNAX_;aYq81Usu zTMlO36r%8~79;h>#amyznGuDs9EQaaY`;Gp2xUSFr#`yjiywM|<2G;QzKFAXj}3W5 z08jNbdf)dYnz@gIA5&=XzHdDG`hQQnUrc3hdD~hxmvwkr$bC!_&d0oy9$lI9>8?3G zT;MSYKHH&sMSA0@R)3qUQCuqNnxA%8bTS*Uhdf**4JRf_lMur|iYhTpWBuxOaKOOw zIk6F?Dn1*~skMkBDEmLE<8`Q7cMz3)HQ8}P&_o3?sgM{y3Pb`Pj8Kwf3w zNK+qHGt8EF-S>_-wl;D^nqT6P9uI)QR+MXYxlMaH1k=j^K&1a1J)HI(o- zjJqGl@%=CO{a{-0mbV@yt;zP`s~%M|YlS?9T)bxVv&~s2x7z*8;fwu&#)+mw_;)!M z#jQxcqJ=UI1>;AbQqQGXKLwo@^eGCONAWnOLeZ07iN)|(qb;C9pfdQp~lKlwo;bfR-OC0JQ$dp~d2b2n=RZ^^vawvYykbrt9v#z-LU5nbS2nooS-_XsL)2i z$(~&!2j3Jd2sl{+Jwz*iu*`6pFB&J^yA?Qvn173Wx{(#5F}fQaF*3S29HBww_dw~H z3=n?RuOX2^%)(bt!N8|$2W!!fveP2j@YhV*nM{V~{j{g>$1OvpYHsirt@JENp~JhP zAhMkh9D+)2%j7qVuqIKe>#NBK)BZGuk|}UB1NI;RsmCpi=WDUA@)aeqQh*T-ro;}my$e+0Uoj8S0oj>|k73w%VJsoT_|UYFPVpkL`%lKNMw*cIxw zkat=N&xv>6C)l-IT+j9n@vNwvX?V>moQA%;R=lok?(s@Tw(R$^-D%oMmneiYUy`+Ev_{+LpA$<1EN-`>%Qb9~S6&Q8IYFSpXZoL#G{VKuD5J?eA1OV@n; zFckn=$9XCIKk>t8Z3j{tR zM+Y^-KI~cI_I_Hv?xHs}V!~n&A}Ts;wo2(VmVQQvbb|)Y)eAJc%$|LzBnxssc{`ch zoDE0Vmtj#h$K6!%E&D~=Ns_S#RC!ef8fQWL*@Ks&36;3_nES4<#?Hit(VCa9cZwaa z)Y%Rhz5!BwUwN~L-u3L<&j_Qd&Y$c+9p?NP!)XC})R8UxJHl~c_K1G;s8rTm!&T3? zj2(r^P}a@`nFEMxBfw9@uu}OT`p7@>z4vvh8ZzrXlq!SRKa(AOAVUUjNE>dhcCOd) zjkRU{^N4EK@!qer?=Pwqi~BmdEqQ{UVy9sPIN#ke%}bwS!rS(4^ZaX zs&+K5um*#ds)8=weods<0PgA#{nIsgD##o-0TpkBSl;{*8lVn)Dw!(-Gyo6YtBm61 z+^eyiBk8(I37zGCdev%1h|DZnZ%2POLOtL8wQzF;ek_#t_w6(07cxZ}U^ElEy~Bs> zx6AMq-$N`hH-8jal;K2dax>`$#p?O|1HHysSc+L|S-;`iBpP5Jd>V0g21&r(Q0azY zz&^O*KZazgb8~_4rJ{*J#~Uu>eAZvFVj$S<7Tr_U!fX83ALkq-}O7taO^bWAg7 z*(dCm5nVC_fllfpP4$4O{?)C>y1n}mxy%TlUauYEv?XJ~sIAT_eyYsAHfzC)-z9r*Z*h1?e^C9c7bu{*G#XsiFbE$qjdeyt(^b6l_9ZI!!Oh^xbpR$EJ z`l&j-BLu+!@Q`SHJEL%$zv~YmX>Ko4Qlv+1-u&d%!6)r|YKr)>@thbPtXFjfn;MWEzYV%m^;Exr+q`@{X+3p+yb0x-W2x6p5x0XY_Zmp0Z`exTvJ(s{ zEd^?)5(~1|BSKN&L><>ATESo`d72_q;$>p3ew$+D3x1blk0OOLVCDDxcO!q!`43YU z=7XuDc9tLT8y=vpO7tE4?$9Xm8+MuBcl6DvZ|FN8cb{IBAPqD=_}&0>HXYM@+TOB}3rUm8)i`@G zy9GGYBxP^vdb9X5rJ53fL6c(voO)2v!V~izHK<~ZyPX`@4)=VL%I*4IsNZlR`gzr& zzkZ#MT->Se70ul~C3f^Pzkw#p3)d$?6Q}FFRrFW&c6SC~LUAj~1*{&vE%MxL)5ns` zjiH*JwO$+hCR9%h=S-RfB$_UKTZ&ea8CW|1cOEp6DBH?<`mF=2mtB{CL@AQ4-0lt$ zU3a%)x9PYT?ZiY1FT-12JquXAMm?|6HxjiU_T&Rw5@dKIw7O>?d=8Bzd%dz(RH2QP z-FOwu;OQAU_`M+zmg-zEs0#x66*Lx~UVK=b?3_LAE@@4EJ;fVazNo1vuDnio)QDW5 z82YE@b9K#V93GyI)fKMpvgc=+w|kg=1$W~IcTa@FND=!VbAY}MrQEgV5sY9)a0|zd z-Ym#nqSjC-_}s8@*^Cmogv9`R+GmH)_sK1OKW%3im_Gguo>`9t!9*=0h+6>TKnFk=1h)G?p4J*LXNSMaoMIqU1pxsB3Q8_^22lJF$c#{r%f<1N zSdH3{nDIraLZHcgmz~f)l(uEylE}Is$2)aQvQD@ZZXrv_*QK~96!@^eR4j63D$NJv zbTyA4KgT(C(f93MxX?}J8KKt;cUd-;*dele8~XXf&3E1I*N&omv<&(N_pux;K_O2- zM)jCs%|vbP#~|;yn3cHG$kS~LB9Te4xv3wQqO`bT3*sg0dpx@Yba}BI_Pf)&kEOZ2 zH>d5aZaTCX&?0XcG0q=V zCDh}#8=4tCt;~4NP~>w5j1dydL|X^ria)@#0n43G(}Kw}gei_7>}vxkar!c_rXcWv z4))iv|A6GDhiWKHq4f&)RW^pmzxnvq8qcryj-?ogPo#eo6H=RD!0amxX^TWLKAnL} z--1hT{#x+v-6xBE(N}BbMg+-h#D__RQ3!`Si+@O+m(LPC;Ff)r8{{$7Neb3a^}euC zh<}mFWjBqFQg1Ws@0~YE?+`-^wce47IH56as8QC8@0V{M8Jtyt5(zV&7Q^klwa_l-M7XCNeO&?M z4mmYB2VuM4!L=oUOY*v-=C59jxu_zuiIMc9E~$OcgddIcWAQ&OvD?6W-H^7e&&B6h zwbcC+MT%`f6j!Ab-$FF@k1F@m0cJPq0YRA~uvp3{P3RmzYBqn|q}O+H6+=5YeD*Pg z_rZZWGMf>Yuaq^Ol!r>M&V@D;p=$Fys{2?`v95H}1`($v-c^^ydikw{w9q1Di7dOQ zl0hY7r7!@@0o05}O1d$WM^Q*&1E&sDQmNTS0Tx@snfM zcer;L(=uqvSP(hM#?|Run|9zSxO7D20&# zPzz)x;0U7RwSFgiBDXzlY5kiJH*c-o$SHBi{jL1JINp7xm@ z%hhIoq`_X*oU1~Eb|04d9!h*^UsWB~g?m0Jic7#dl_(fWS& zUL#`w35QqqvQUbY$7$x)Clc>MvUfDPp(*4KPlQ(tm)9TnoVTa|*M(csP{= ztkR5=zU-*!t?Q)nDw>F*YP(%_kv98NjggK}l@aEDrY=vnRJkL})}q}u0A}>M$OkkG zK(VfDv7BJ>+|Z6V$^kp|X9Yf(94YbetzfD6mWUBz{2C-MHDNPYYurz@YO$R00E7l^BiDE~l*R_@FHkoWZ@vO<_*XFapdx1#cR z#Q$w?N@fTuh0RHq^GD6{;~Nm|(B9Pr?xxxy(9eZU^-O>*57HzKtdR=mu%Gp(B4@rc z>=|yNElcLz6PM~bxSFMSW-G)PZtg&dWJmQ_L|=d~XJX zzDAZ!%S@9vOY*gU#bMw0IW23-WIZ3leG8|)6uzr_jl~@@cE#M%LqzYL#sygg!@yY1 zw-S?MQKFpr2N+zCdrGFKGys3)(LJspELm~L5vOH2oZptF=+MxV?KpQ!%F3*jo20Kv z*MMgvq^;$NFjpAmuj=dObWa3Kb+9Z%j)R{KuNWG&soY45oQ;M@FJhrHHo7@m(SGkRo7=5Sn6fels} z$+onvZSsC6+5)!B-3-VK43WD-bb6K4fg1mS$n5^Bo=W$dTBF^Lb+S0O4W{6$jC?_Ry1n)~^cvwk;UjDsvPS?J*-Q@iwBkZiV`^;|@% zbKmR(zo`?l)f1MjgGc-(lY8fX7Z0k>s`36^jE#J4(k!Pg5=EHRB2ez#?FL4JHhMzf z$2!B;9Dn%wiG-%RFOH`Bzbs*Zd7(y~Nz{>zDu$hAx73qvv*G-j5jLVt5%t?l6&>VQ zpe#86#^rucGg!KDD~pirT=O64j1_`0vxP)OjvoRfyXSHA?1w zKLF#-G?;}AST^Qjsfycu{p-U35>K3e1*YH&eFS4r|BRl3d#lZt*Lp>|U2BEV@tG0M zq)=4OYg!U+fVEnwp$g1|>|wNJ>nQ(kO1m<2F&l?c_UR|+CT)MH{ebW3$#RQyW)e$t zHz)pu?#adZ;341s|EypG*9R6}vf}E|jxEXk9bR}yiVRE*9R|N!41H{FExVN`@gxyy>HN;Y+52dus`E)nHeQv}`4#RR2GD^1?U@R8OTbbKUFOm^F~JM^L7I7m?{ zee$6IO~RLs;9mi*u3;+&s6oNFfE{?90^)=4oNgZJ*C_%43Uxv{yCxMg^Dp zAnN5Na?v}=5P3Q3PqbBFuN>X;M9Ugj>jL9xtfc+@fOqLnfL>q^U=z4;4=4Tm9;C^P z+AJnXeP99;LPW2P8kRb%Hbd}@$i#m`Ul>qhvN=PARd${L!I?Q7~U ztK@10Vy+MXz62pkQ&%msR+c!EkRotew4!hw_g0R7VBjAiBFGn^$FMGx=(ATia$)i* zq7+kw$*FP?vqQJU7iY+pvM^vkwMP-5*T7?B2UY`ArthEzbS0 zOMj(;rhyAt;MIjJC4$|O_ZR);(&@UBz@aLP0YN&drQ(&er0`Kk#m!5+t8(TtIRb_? z0MZb30Q*|UkyDxwgR-`<7LKQ0L*>!epn;Iva!%lUvLHFzfV~ZI-@5|a*dP}AIM|koz7^b zFSo$lH}nkY0>42OazldRHXY9nRlFyC*~-yfy|hR?Hqdipj`*pP7C%YPzV<)>rl7+b zIH6E*e~OB^FBE30{wXgcbrX-@$cP>$2eEE9G>+7rMsfi%3meX?wl&Ce%Va?a#u03) zE#lMP1krmJBylyX$x~cNW~oI`*m?LboTO3_ps$syVZ5|w2Alf@%qvNIdRLm@n{wQp z9!i)C&^U@X4cw52?By?o;0|~+jmd?>))K5j|H*5#6!e-Qb^p2GScm|k)@F-~_}cU- zKJT4|02V}k!yow6YSs9{`aCsu+~Rmr5q?9+I!4n$)nCwxJ9d^*XZI%x2WZ@2t{J-k zpRMpl=w1`S72cCC_w1NR5|DW)`q)m}HnGE3dN3}_y>EKq%oW3Rk=pEyhRr`z>d1E3 zCJR!pe^r%!VI2Dz1f3Kuo6NJ=OMSG(5LGxE%M?_woNi093TR5Vakc@1Aq)43LU{HJ z=nlb_^j5)@T8GR%tI!?^~hxH(G2G20u*F^s!Tk~zI)Iz znO|!5t6_Bs%hU&%36rG@b<2V-dUwSo%6xgv6gsJ8*1h?9sdjHY%E4hy-!Hplf$yW# zdpNqq0OPr8TfPGr*-nfL&EsEbUYS=Bl5u?|qwYjvdP>H7OS_)CM-6eW0|oN)A$Mt4 ze2-TSK||sDoJsx?VPtQ9E2vp~Kn!7w2t+!OR`{YsW7w}d1Z=1A zt2(vww;oA4@&`FN#9RL+shylBRism9PDQh?DTiEos%8%4H*0}Md&ju$JznNv09|yF z_^2ZDv^JGI7pB~<-oqW?4YPjZxJ6le_nqKhb&Mo3L55-Ti}jj1re?7DC^nDE%cA`k zH_dX=&HI*(#b3{wG;Pcnwonwv7UziG(1aSdqSq$BeSgwq*5|S!VuPHBqI=htUn8XM zA361fBe-6o`o|Dm@K<}KS=V1q@`Q7LGT_WJEOfmaq!}@pX`X(2gAtTM=N>1YZH%S^ zMdi(+9|)QCIghdNwua(jXKrL{C~Q2nIQ`!t-K(@ib^QdBCUaSnRPUqF`Digjp+ zXfF1fS|&* zulU+7dEk|P?xj=tQ%^!};;Lu3rqj^f*SFDBldm4{&c0QQy%Y{H7CWPO6>hFaL6Xl| z`(e7pZ{1S|!-G>K;KDPt!jIpu{Ow}N!kgqxW^pqgFcH5??8D zO!yPpJE5AxE6}(r>%=y;CZ@`MQNWfbfBDQDXSrte_(o+|q?+or?60M?*Km^v&RT`L z9p9*ua`(wjW<_$p43~d z$Fmi0W@Gn#!gt?(2}O(Otoc{3S|56jy8XlzA$}dZ1f-5{Z!78o03C20bJ?PzRpmh! z<}b1?>&AoAl(pcbJ%`$w8Q|Vubzv_4Hj&dB_GM=ecjompp1#9t!{3!wB5VV$5V3Ck zQt_kRQ$6aivrDcE0a5I=E1%$%%mTJ9{Ol{hfpWhtF%m!L1yefRewpT|YCOy(04LiO ztxJF_H4|D?h{2sJMd5f6!#ikE2!nwv1NUn@7Wg4OoSUil-LQAWN-Lc~$w;xGkJMME zn^#2(2TF6)l1+wlh#`~}FJC76ef4Bdu$w%?rnp~nREM705B~@!yi#DrxGXOBWqfe( zg4jO4*3?SBR%EW$mVIKVcD~w-m5a^V$6(}S&!KxVFI5`)+CGB@Mmt6~nFG+GtT$|_ zuFJpUlBu+qrwo?y+6<#lZ(I~WH!Heq*EUH)x2kAowQ{h(_4`FaTS(t z@p-Ol)HxN+ap1c)w?W;FJa!s+jCGD&;^-;~m3hobN8$S}fj4i>$)J|?T>ee`t^PuX zbHPL@BC^N_M7IqacV=+D*WZ*d zo~YdD&-lBiFVF|B*&}tj8#F>J-&A~8td{xit0g^JL)`G%WI;>4nI)^NQ>mSU^T)ifneU?eSBR{wcI zwb=Dr^*sOW18OW@6o=K#!Rq+I=N$W5u~}Q%ea8kPhfRDMY9WfuD^07@&RC2=x*^mQ z+7@H(*M=_38+^G~XPG>0dy1kvs}z^TtrO^Tl=i=$fT(im=2EMNUX#5@;-f7c7KG=EF*wR_YDz0ArO1+);blv`0z>dZaxaS@e;VwFEl2jDOgnqb z-l9yC0bQ#eoBj9Aik8B6BR2yQ?)CQ%gmXiD$DN)3>x6CoA>@m%KWTw^~QuiJ&sNE9}Ljg2pha+ujJTt>ofm8(%L~ui_Wd+Mrr&Rx{zgt6T!7 zpPR}z9Mp4cWVfTCxNpu@O*JkG4ZW}TU-cP27n%{k))Wq&ky}Z^7f*uFNj}8JPbB&wXhsGd4vow&N~0;=Wc-LH0aV7VNbY7 zyX?}`enlHSDir2qOR+*3^|E&fjsL~AK7nZ%!tmwYzBrZMQ(r;H)}1u(kOxoUs@!vw zG-X08Yd4dwKvWf(J@ZeqM0ljq@HA!HxU(BR1dqU}CmQ4k`#J6({eRq%uB0{l@h6_F ztzr&Z9R`-HY6b+Km=+071sk|wiGPqxiq2D92*B)WFizPf<&15K#9IZ1j;5gmy8oGF z65%OH=!y`cZBZUCiNf^xSwKWK%T!p2ZlhH`bx9oJJtgto3QEvm`=Vx`$<>yZXwu_o|ZMEOf5r~;QyvL6XWha|Rx4Qp>4wpK8>WoMF|lO(%D4*h1Sx$E zJbYIf<7wZ$dN`6QbwHP#%r^bwV(Y_-#@Hr_N}d9zFw;b&HQKV|Y*X-sholm(i#=y!a#5Lyk@U_HE3;hM!DB{a`N*O<@r&XBku7I zGz8Dzj<^ywNlnNy&DK#t!ypE|q&rF1gob!1yTEZgUS{;ao$pLL42<-UXK1zNRlOUJ zM9h%3)tEt#aMjPm%J-JFb;!qmRgU`e51TH?B)@viea`96|J(r@=oJIfO<&C8D*r?s zpRXVMhn6BeF!eaQtkW!O73mhwaFa%;0jw^FV@hA{Ch_^m)pV@hs?SQa!Vh@y5rmJ4PuZt$t8HgCd^jj5HePuMIHf`oC{PUDO zlyW8e?*NFSqqAo|A5kV&H~3xLq@wYM+}N+^&+1d!%uWnbuGu`*xpYi(^SWojB34`2 zNM3tSnueJ=r`N?dSyne#mZo&q_+1>6pu6K7+Z&Vz&@?@kGqk7fT{DW?6<^G}JB9 zg3I|Go*C@X(okj79-;nV`T4HvjKeRDvM(GAX>-~)=)dbNl39mv7}=OZ5Vf%sG$fsN z8omc`A_`5@-X&C9XQrt6UL^hSW(?ow*eMA;p@3wBDUB$I(Z;{Uc6`P2*pVH;e_z*N)11#DB7xE=~PuYe{KcO80Uk*n0ER_429Uc4Lc0QzPdIOm((o*DFs>ZZpwV;Wp zb~9o@Kqm7F5{bUP@hBHq_!rLnb)TBvYFsk>tD(qjtNPXv&9UUqqaE7UG7)OLR9)sA=9sJ>LB9j~BHedTp<2)Gaq2{7DKL#RJ=8hk zdt)vC_wC{!Z1{4RFBVOR!=~|&*wS-^>Qbp6#9O55I zodH<+1U0Z zL?MUdBa@%}JKnQd?j@o&ZxA2>gYGzCW%99dL|y;^V$4v*{5f5(DRPK?5M4<*JdVG+_J@947*sENOHfQ-cHHfsFN>(3?h)hjyHUR+Bzz#ysD7WGX6qYpwsa;&CG7IFAjx z(=qd3wR786EhdoNdc1+V5BBZUAji1scGgKxBzk8WW z_g379;$1N+TD<;b`)SJ36NCCb72bxcTgiqs?(;~>A*Uhf zF&I(YNpz0_by|*r*mub1_S%oOdHACTlG@U3`DBA5tdX`IWrXC6Jzp=VT=3`Mj5dvw zbG%TGrvrsHjw1Hy{N6LQ#faVV z;be{^9_&Anh^RS^q~}ib55EU!#lmXD;%`L8CZXO>!D74$dH= zowHiZtu>2XjCK15E_8N}?)&Tt$rhdgblHRn2F_5C6) ziY!s9+xQnJSK93Jz%Q#0=~BV-|K=`bdz84b&D7%Pa0}!)M31}C)s2(b^fkZHcds4c z)F;nYC5QJh(sLDu>Cd1!nuYgn;lT6uawB>#UQ%g+@!S`lMwT+DqUwy2jlf;mrb_U) zGhi5i>@^&RnR4X%SRH7dU$XSfS9q(myOwuM*K$IJE3rFozYhvJPK6A03xLTpfq^>!3J6Jh+yl%^uf#?jl=qrLo0{ID_atgv4m^~TQk)BT9Vd5{5d01o*(+sTFHspCXe5|m>*US zuTULzc-7;q_G8eZ8U5J88(}B&i)R(EZ$vN;%#so#@u`PkAS|rWFZaRwx##-bbQ_{9h9P?FFdHksdt>E$9BzSqs{!!(%fWGKRG z!MTizkO^ zbd1O%vr}=zdj@eFhA}qI4qe)-f3sE#!G184giU>g0P=waW{$T7M}dJd&a^egxS&G= zuduZq-tzn>se+v3pQ%5^XrY=hP8;dy`~XA{sagsw^`Pc}_`E%n*EYxPGd)N)u5+rc zH&+5;PUGpAIPa2B2$%cIZE|h)qK>$g+F9;#@PtZ zvBD;LG0f>)0TqxWH5CG|B!7>g~mf|U(3k3w#^cC}7~3mZ6OZ#_hH z*hTne0Eq~DV=xHvi;>>t81F*fzy@O!&bToo*_9kx@r5k}sOWj97oO}FlcnypcsTuu+c&I~z5HH?w6@gpJz-G}%OzOD9Pg^o zBg}RgrL_k5{QX67g;e1=VMAG5{ukGO{4Kp*zSRUCi8k(5RC3t(YMR!ZYlZAlC&j4B zhGVBL6&YNVDC5tSzg*0bEF64IPXXT@i{ra~9VhA%F}UORaB!(6wP}Q?qA^*3dpdbv zZf)#D03qR3F6bddR=>ql<6@)CNo{X>hT-uHbR=xE#mAx^Z;HxCs2YIwuhb|l$2Gl& zy-Qas7%AC$pXJKAZWg{G_vup%pRYID95yz|LBC}xLdINLQZm#w9^VtjwA{?x9vcXR zE8?j$>7f(C$@_k_1iCptMvDyYLa+ZX(E635Or-`=QeqZ|~yzzCT zoA54GJWD>>ufo^UlfInQD(>Gu1~~dL9*JQrlgM0AbxXxtbcQanqkwVbhPtvS4h41K zxx~U}++phVCn4%)!E7IkV01fPU40Za)SK-4>}f*(;Mk{3-k_~ef_xi&CELjY#|urm%`Feq#>4}JdoRgHVD4;nBF_!5RWpQbelI4)tBTB}x^Ab5MBKnL zy60K0Uq$G>54Af$sY&xC&)7qd9&HTO^s@Bu)bu``;*d2%O8#d}-0=puztT%Kzu=V= zAq;oEL9(wmh2VgGxN$9-tVgo>pJ{p;l|q6nK`D~u`dYh(D4EWQu=aBR)}esw2l%7C z!o#@v*dvz=v4yM&B-t~2eVe?3f{kXs%;V^(rNi8C;d$RO)7{M>1(qk?RA-bzPz5h~ z-1aDuq>SwRJd7g|TW|A)MQmqrbyrz`!ssX!Hyt^)al!~oC~|t2?@%@N)r75dqar7- zi|?FAQJC{w!$6qxoAS@u4m|V83S3xI8gb;bkj4R;0RcbL@Z(FBE(`t932mQeFBmX& zi-#QuV$>w@92h?C_8IQE^FP<*%UFjG)@L$ub}4>oVDL<4ruY8aw?-x8)ABWwE|HII z10~w^BI#V($4x&&uUD13*rhP9=?!1Gb<6AB1Q78cy!H-RaB8J~$p6s#oPQ7l6b4p)}wG=P$6L{tNk4wdrCio|uEK|bio*;lrAYU9vVJ+GH5HX(g4cwxN&xN@`;yo?NH}b_*aiIK=?a5YCZ`-kZ9nA zJB>*Kc_ScpKJ3CgrbLd$3Ur-qgvs-!fSS0|#k1K(=9o(Il&K7_6q(Uoxl3*d+%70+ zxqp<+O-x$3{u?vshajl2U5KXlu5+?n-H3(qKaYQOoG(UGt3cZYVHkx2>vi;?leo*x z(pbRR|I}jBWJFY|@~F8{=9lS8XPvjSz*6GnP;|CVA)-Y7E8;h!?&OZBwr(|@6K}Ga zuOo#Q@bH2}e;#<6*(MdWXq6%}w6>`G>?6Lxbv1+CXki^NE765Ck6Vk2#0;sLETN38 zsqD*gyQ?^pskMU3y2}f#%*5lDC{(Q?0^xNc$DzB7I<;TQKd0*#%Mp;wU^PeVz4*)7 z6?5&C$;NSppO>!6Uc(adBU-rpPZl|Fq0hP;wmC0@nM9^Bij{P;Yao*Ef7^7hs6|kN-iMqcqf1&a#i9xi|HKEcyon zh!gug7aN_`4(ppPj+o#5ZB}T6?ekyCxv{Ho+ns}S&gRMrQLp4;(d~LqoB%BVh7EC@ z+oI&Kc7vfhMdYyq&p<95{rRNy@G-pzplXTtjGZpv!vxjVIFrMoSQTAAByG0GI;dpD!1dt5?TthN8~ zcdEFkBL8>pC<&&JDS)?N2{r8ZOmNxKnV=i9-J>Ah!Y`4fu@&1ROy2e}Fxf==Uz?bq zT~hEqDpb0$K$HSD+T~~ij=6e~#J>koW zK=(U(Dm^tIH2pk?JFsLKe8)BMB?R58EN=p!%a0sbFv>H}QB-Jihb zerzgdj}=GK;^_@wrG3y7jEG5VcHF0>;-o`ml`~I89&`)*w{V`Q;PqonWB!-fTV|Fp zx%7GFy&Wdn(s9zXY<8VgDG~zAh<~?LpCR$f#DUA_PC8LTBunSF6bvYQjkatA ze*)X(R7|32)`zf~+lBd_Rb`A&#`DNd3Jd!FynZvM*_$7eFpTK3aG8rCP2KVsZz=R; zS|U3Yu<}J+{uhaGXvYP?Dhfx|{~n}^z2>7i&WWv8gfp$t54BwQ92@Kw zcxwveNy8vR{dbmFfu^yIujQ^AC40$5wr;_VyEzgZvS8?(^nC;tcJf-)`P8HP_8#P7 z`R_Y)p6HlR`SYH$)=kuUbjT(0*pqhbjPB3>IE5>Tf;?OiUF!#XgwIA$(`a+fsw3Va zJTw*uYE4*YS)0o`95h&A1;1FIB+9_cLno~bCiMURyH#Qw0e001(n2CRFmYX|8W`Oy%< zcNF#08Oq5KvKY!g_hRg8St(cHh*=?_OQrYBUDN=NCHU_)vQ!xuGSkDt=>B3KR26z=VUUV>PQ5*P3X(d9z&ipMlh#P z|5+ij0z!{g+2JoNzY=pS$*<1L@4)jIEFwYCdtx0|KzQvazc>waqvkeE)s)m#N>91y z{g`T{92<8sZtru4EBrd_gqE3oEMBfr=?8n@3jK}zZ`@^B&_RuF;7s;+#EGoGVXno@ z)KJ`d&TyhIY-=?hsdK7v237DW^u>~>I1Sd=T@ojK{7>i`!VB=Cx<>HMBuB-|zqI!A z&aYheP?Mm}>#>x8q9kA`xNH$i^bQ5*%X| zT3p4w8nab3ig+<*%PBot4qUqO%+vI>dqy&K4sL{N2uKMo`xIj7kv%3;EZlsC|MsbF z!PP2@rAc7@nQs$xZR&|8bGp-Y^6VJ&Y>O2Mf~>qa0{7~TZ62ecyYkcAOU{^LZDv*^ zjUHfqVj(}es+IdbZNH|1Pp~i`@;w_KOz|=5aSyJtt1N*{Bmj3y4(?f=RL?klNEK|1eNWb{_@~#=88aw-Gj+99V5$G- zbW^mr5iZ;t;D8mbk2ORtICSu6!}*AwUp_UF{-z^A2!-yzHbzn zS^}m<#mz12`ZRQ&3odx?(Ut+ZMbUBhIZ}{_Y(PxXwYUH?=-k_2wfhjI&7j>_R)Z~L z9Y5wOpi`+T`pxK>X7pp@UH zKtB5-k`r+)+C$x0+f<40`_sLIe*{r7vBywd@IqbYmKD#CC|MdlCUhL`+8`C+qk3W< z^l_(IR;fvQ+`rp1HQSmpzQ^1sp2iA_iTpz^R?dz)IM;s3g?!XzrjuoufVNZ<1Q<~? z1rJ82C87|aq)qIoX*#>0q){L>7lV;TR8pHn_zkEjCmO7 zuGLQokO6dAxXVh0Cq+oX*PyG)%AbBL6w!Fmny8}g1+PCqzfDnDWMrqq>A)jBy<7kB??FjUHYZhlPWdEY6SnZ5T~Ywz#+ zmuxDIBni(qrKkqCP$u2CWMmlzq9EfP!)A8t6gTHP-!}cA81+@+4XI$K+e(i9GOKg{ z?$u%jr?X3{5zRvrK1E@{PYgh|!ehUXY`m>3uB=rzj?m}z# z>!{@kg$i$jM6b)rBLZ^S3(R+#yn9NQ7;n?yvRaAFt)(OPyv-k}Gc6R)<9y8mbB39qtP@+!<(l zHZibPL;LVrlRwty60gM< z&J-U6q>kbuV~xh$TiMtzMxquV7S;omoEwTXZ%Juy%$Sr#Bw|OUS3PL&r^2Y>C?C_5 z+9^C|BtACJq(xhIE(yXo-*lD2wH{qv^fPITYQA`b3-5BO>kVu>c*Eo!YVIlLNS zH@tkV{~1sO@we)(fy#^lnVh;^BuldZr$bCD+bMj9bTf2l<2JR>)4E9IAtU zEy@_)au+kb;>>U4b%eQ@Br1OilL=cR!z#g$*sI;B7}oM!L?WSQGg%wFH@s zZC|IgwjiER_u-N;1A`7JL1pcX_lyUHn`>~qZkbFgQOEB1aa(4z>=3;1z{d*0uP4>v zc!qBZchS*Kao3JUcxl5N4U`~}bankVS+w3QjB%R{!qR~gcA1Sczbq}bzZKYwp*C|6~LpG|2` zewQy#=o0PnxlCQBvWwrn9UhV4^safyc<*fPxegE<6cKhji)=j!TNIfA_q8s9_;Ec} zV_5I~^^e$h>$ddG`A64cnZk~L3=mQG82Ov{`GP(nExdX}JZePmZ?ewQ_r5H;X3`ot zd#l;bxRs~e?D;l7%FxXUj1}1_>Cp0 zN~4BmkK=8&!15#ix=Y?8lFDjdgS8$LS(`0J!#6`%u}lb?o=oM(7sxyRC$P(~6j3YS zpzD2ZcK)!>-B8DeMFcPdeFn{ zeSSF?+(aI^m2A;gd2!{MuX`@sIkxYTLZ8YS8bg6BcG~ZCBrQj3>i6x?!LWk3tXaB^ z;?{2F2>oq_G3)r&R6!T(cqr1iM8jN@ytneE4vx+fIVogo>06jZSo7Ye7>qmlPOSwy zD82irC<>Q7stee^Hc89x#3L$hXYr)^B^Mm;d7DtE^*bXWTA7P^GlEFyJJKc?8y_jQUtZAK$|8m=*` zDM>H_MG;U4Z8*EfUgcO3g5+~2q0q~l{15S)9J;2=Wh;ZXjz3F2dDVe6xv(aaq zLwXCM0r=ge&uLAtaf`UNvMpkI_EG{mqbTo!}kagUJ6BB0!AxrQ8Cc* z%l4L@+E%VM&&06Lmhe@Us1%6Y(Ha=rryPfn=&)Np`U|TP5(h;RmQSia-;C(BHjI?= zP!7btv7jC^ z0tYiAv{+?Iy!NnRc8TnyGX>tuiO7t<8)xzUL2Bf$MLdBetKiNR(IciGTT=(Hf{Jrx zV!(_CvR(PD^KEHBmI2%snF`~SeVw@p$@#D~`rYT3b*>S{xtE+kBiHb$Z=fLi1f`t< zv~IdZCrt<|Sfhwd7veYT_GNDNFqc=Mk!Jv-i+&vNL++%@#tUM=lOV%sCX>)v5ZL0_ z&vNQAayPko3`3^2Y?q%!dW4ujoF(uzf#Kn0?~@g+0F@rrR?k&^OI1!g4@PJOae+#z zf`MW;!aoqM6pBvcuQ(Dj{R%3|W=8>ZUE?sKi=p~(;=buP2Pn4nnP&fGwx+k|t7S$* zVvL3TJKyI5J|HWcLvF>nr!-L%skV+Ck)khAich(Y3ISuxGMe|k&K7iywJgFz+n6`11J541u z^xxXQCS6st|F;HsZNjic-e4f&3=*D=2|E&NSPe~n9;(t(-P^N%jD@k zE9G>VPDJaNV1H|ZOj@yJjEF}N^X0F5LVBac4Pt1cZGMQF{1~pW1PkD)6&cn?Y!K#^ z7UeCY06^AkLFSHQ8DxAR>vOdkNcJ();<|Meo6BxO`xCcql1Z?6*Ct4o(Ik8Z-gRaU z&V*bJqVzu#V>Fq8nFYQsMb+iY{pDY?$MkdFReN9vM($z+4=09i2k2wo?D-J(%Rqc> zTKGlVbkNYUS~!CZ@5$Zo(4*g@i;yUC>lB&iP!Zd3d#F7ONk<%ec_Mh0O!D`9OLX&h zjF^t3JA=2uSjE2AmiiS_ZLXZ`?11E6$&10>KgO6N$6-rwM`yw-SzE?XO~OReit-+Y zgCNcm;uR*i?%P>;-OKNEpS`<;05b@1fZA z8EI@4*lK)J1Ftvem{5c^3V6E-x1{Zxg>h&14zpByAVRiY`4Mg&TEpp}?R)20Ht!*@ z1OGm(yOCS(3qqQcnaWeHvuL!SkwcjbTfnevbF36e_u+BJeswyFA!kE^A(O zYSC5>s|^(f6#S}nIGUZRFcz;wfhTce&BeCTF2fEhxanCq$H!yTeZQ)KWLwAQJ5-52NH!3k31ub>I;?O8BkS@3^TSHGuW>rR%a64bH zd)k!yR)ga9{qkNSmO4>SYV*>8No!97?KtPcldm7$yAFm0EXI3nHet?_v+`w$nyU(; z*uD=`iXqWd!IX!Azk~4H`jdHh>tro=vAt zW8`O%TJ1vK+wFAh8@I0@W^qrT_e>NSG@DUGkFCB2`3f{Dzo@(;NrSA-)mxF!GBo__ zwrD?d$ygNZEI*W~?)i=C1?Y?wgE}Q39MUhX&_<9x214Uov}>+!-Y2L zPHmEMh4so_s;0@%1fH_`l6c@izy*x^OglH?CH&%bD>+%m4oxl;%)L3TOMC-ueIdoW zGv=7pN>h=)_xy&L>C5Zt7)1F8%anl~6>aUrwe+%ZdO|AlQzfA75*un=QeL4(X8mZs z`7Z`MV}TAW7vS|=6T(D>kHG$mLxfBbZv;3_s#JVSmP_`f+e5kj!tmnw8AuDQ7%{8x z9n09CN``w%$+haXE|1FV#eGg4+PgE#E(jxAgP_7FsWyCCr%9W$eloUhNA*ylR>3Uj$0^+yefX#!h6!O*1y8y3&j%=L#!8pov zuLSGhcXxf!42Z9}o^E4oQCTez(SYpZ*6n$clh07R8Opgj9$OD0YE z)ThWT!N37D@p!kofIGAKO&r^j#@8$H+!? zzyZ26Fc+)$7c$Xsy^|Sd3`+2tv`rbEP>2@lw}vFvZG#lVf8nYwWRQ+o2N-bHDJE?q zg+(); + const actorIds = [...new Set(activities.map((a) => a.actorId).filter(Boolean))] as string[]; + if (actorIds.length) { + const users = await db.user.findMany({ + where: { id: { in: actorIds } }, + select: { id: true, name: true }, + }); + users.forEach((u) => actorNames.set(u.id, u.name)); + } + + const kpis = [ + { label: t("kpiAssets"), value: String(assetCount), hint: null }, + { label: t("kpiProcesses"), value: String(processCount), hint: null }, + { label: t("kpiCritical"), value: String(criticalCount), hint: t("kpiCriticalHint") }, + { label: t("kpiRisks"), value: tc("none"), hint: t("kpiRisksHint") }, + ]; return (
-

{t("title")}

- +

{t("title")}

+

+ {t("subtitle", { name: session.user.name ?? "", tenant: session.user.tenantSlug })} +

+ +
+ {kpis.map((kpi) => ( + + +

{kpi.label}

+

{kpi.value}

+ {kpi.hint &&

{kpi.hint}

} +
+
+ ))} +
+ + - {t("welcome", { name: session.user.name ?? "" })} + {t("activity")} + {t("activitySub")} -
-
{t("tenant")}
-
{session.user.tenantSlug}
-
{t("roles")}
-
{session.user.roles.join(", ")}
-
-

{t("placeholder")}

+ {activities.length === 0 && ( +

{t("activityEmpty")}

+ )} +
    + {activities.map((a) => ( +
  • + + {format.relativeTime(a.createdAt)} + + + + {a.actorId ? actorNames.get(a.actorId) ?? "System" : "System"} + {" "} + · {a.action} · {a.entity} + +
  • + ))} +
diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx index 45d4ae6..7935ffa 100644 --- a/src/app/(app)/layout.tsx +++ b/src/app/(app)/layout.tsx @@ -1,4 +1,5 @@ import Link from "next/link"; +import Image from "next/image"; import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { @@ -14,6 +15,7 @@ import { FolderCheck, Truck, LineChart, + Search, } from "lucide-react"; import { auth, signOut } from "@/server/auth"; import { Button } from "@/components/ui/button"; @@ -43,6 +45,13 @@ export default async function AppLayout({ { href: "/review", label: t("review"), icon: LineChart, enabled: false }, ]; + const initials = (session.user.name ?? "?") + .split(/\s+/) + .map((p) => p[0]) + .slice(0, 2) + .join("") + .toUpperCase(); + async function logout() { "use server"; await signOut({ redirectTo: "/login" }); @@ -50,42 +59,69 @@ export default async function AppLayout({ return (
- -
{children}
+ + {children} +
); } diff --git a/src/app/globals.css b/src/app/globals.css index cdbcf5d..668d63a 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -4,15 +4,12 @@ @custom-variant dark (&:is(.dark *)); -/* Helles Basis-Theme; Dark-Mode folgt kontrolliert mit dem shadcn/ui-Theming - (kein automatisches prefers-color-scheme, damit Komponenten konsistent bleiben). */ - @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-sans); - --font-mono: var(--font-geist-mono); - --font-heading: var(--font-sans); + --font-sans: var(--font-open-sans); + --font-mono: ui-monospace, monospace; + --font-heading: var(--font-poppins); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); @@ -51,77 +48,85 @@ --radius-4xl: calc(var(--radius) * 2.6); } -body { - font-family: var(--font-sans), Arial, Helvetica, sans-serif; -} - +/* GEFIM-Farbwelt aus dem Mockup (docs/ISMS-Prototyp-GEFIM.html) */ :root { - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --radius: 0.625rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); + --gefim-violet: #5d52a3; + --gefim-magenta: #812d80; + --gefim-blue: #8dc4e0; + --gefim-violet-050: #f2f0f9; + --gefim-violet-100: #e6e2f3; + --gefim-blue-050: #eef7fb; + --grad: linear-gradient(135deg, #5d52a3 0%, #812d80 45%, #8dc4e0 100%); + --grad-soft: linear-gradient(135deg, #5d52a3 0%, #8dc4e0 100%); + + --background: #f5f6fa; + --foreground: #3b3b3a; /* ink */ + --card: #ffffff; + --card-foreground: #3b3b3a; + --popover: #ffffff; + --popover-foreground: #3b3b3a; + --primary: #5d52a3; + --primary-foreground: #ffffff; + --secondary: #f2f0f9; /* violet-050 */ + --secondary-foreground: #5d52a3; + --muted: #f1f2f7; + --muted-foreground: #7a7b86; + --accent: #f2f0f9; + --accent-foreground: #5d52a3; + --destructive: #d64c4c; + --border: #e7e8ef; + --input: #e7e8ef; + --ring: #5d52a3; + --chart-1: #5d52a3; + --chart-2: #8dc4e0; + --chart-3: #812d80; + --chart-4: #2e9e6b; + --chart-5: #e0982e; + --radius: 0.7rem; /* Buttons ~10px, Karten (xl) ~16px wie im Mockup */ + --sidebar: #ffffff; + --sidebar-foreground: #4b4c57; + --sidebar-primary: #5d52a3; + --sidebar-primary-foreground: #ffffff; + --sidebar-accent: #e6e2f3; /* violet-100 für aktive Einträge */ + --sidebar-accent-foreground: #5d52a3; + --sidebar-border: #e7e8ef; + --sidebar-ring: #5d52a3; + --shadow-card: 0 1px 3px rgba(30, 25, 60, 0.06), 0 6px 20px rgba(30, 25, 60, 0.06); } +/* Dark-Mode-Werte folgen kontrolliert später (kein prefers-color-scheme-Automatismus) */ .dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); + --background: #1c1b22; + --foreground: #ececf1; + --card: #26242e; + --card-foreground: #ececf1; + --popover: #26242e; + --popover-foreground: #ececf1; + --primary: #8b7fd4; + --primary-foreground: #1c1b22; + --secondary: #2e2b3a; + --secondary-foreground: #c9c3ec; + --muted: #2e2b3a; + --muted-foreground: #9a99a6; + --accent: #2e2b3a; + --accent-foreground: #c9c3ec; + --destructive: #e06c6c; + --border: rgba(255, 255, 255, 0.1); + --input: rgba(255, 255, 255, 0.15); + --ring: #8b7fd4; + --sidebar: #26242e; + --sidebar-foreground: #ececf1; + --sidebar-primary: #8b7fd4; + --sidebar-primary-foreground: #1c1b22; + --sidebar-accent: #2e2b3a; + --sidebar-accent-foreground: #c9c3ec; + --sidebar-border: rgba(255, 255, 255, 0.1); + --sidebar-ring: #8b7fd4; +} + +body { + font-family: var(--font-sans), "Open Sans", system-ui, sans-serif; + font-size: 14px; } @layer base { @@ -134,4 +139,24 @@ body { html { @apply font-sans; } -} \ No newline at end of file + h1, + h2, + h3, + h4 { + font-family: var(--font-heading), "Poppins", sans-serif; + font-weight: 600; + } +} + +@layer utilities { + /* Verlaufs-Flächen wie im Mockup (.btn.primary, Avatare, Akzente) */ + .bg-grad { + background: var(--grad); + } + .bg-grad-soft { + background: var(--grad-soft); + } + .shadow-card { + box-shadow: var(--shadow-card); + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 34c4415..c79e4b9 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,17 +1,20 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { Open_Sans, Poppins } from "next/font/google"; import { NextIntlClientProvider } from "next-intl"; import { getLocale } from "next-intl/server"; import "./globals.css"; -const geistSans = Geist({ - variable: "--font-geist-sans", +// Self-hosted über next/font (kein Google-CDN-Aufruf zur Laufzeit, DSGVO) +const openSans = Open_Sans({ + variable: "--font-open-sans", subsets: ["latin"], + weight: ["300", "400", "600", "700"], }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +const poppins = Poppins({ + variable: "--font-poppins", subsets: ["latin"], + weight: ["300", "500", "600", "700"], }); export const metadata: Metadata = { @@ -30,9 +33,9 @@ export default async function RootLayout({ return ( - + {children} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 4060d14..d7e06e2 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,7 +1,11 @@ +import Image from "next/image"; import { getTranslations } from "next-intl/server"; import { redirect } from "next/navigation"; import { AuthError } from "next-auth"; import { auth, signIn } from "@/server/auth"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; export default async function LoginPage({ searchParams, @@ -9,7 +13,6 @@ export default async function LoginPage({ searchParams: Promise<{ error?: string; callbackUrl?: string }>; }) { const t = await getTranslations("login"); - const tc = await getTranslations("common"); const { error, callbackUrl } = await searchParams; // Already signed in → straight to the app @@ -36,14 +39,20 @@ export default async function LoginPage({ return (
-
-

{tc("appName")}

-

{t("subtitle")}

+
+ GEFIM — Informationssicherheit im Mittelstand +

{t("subtitle")}

{error && (

{t("error")}

@@ -52,51 +61,28 @@ export default async function LoginPage({
- - + +
- - {t("password")} +
- - -

{t("tenantHint")}

+ + +

{t("tenantHint")}

- +
diff --git a/src/components/level-badge.tsx b/src/components/level-badge.tsx index d0b3e95..4ae7662 100644 --- a/src/components/level-badge.tsx +++ b/src/components/level-badge.tsx @@ -12,7 +12,7 @@ export function LevelBadge({ prefix?: string; }) { return ( - + {prefix ? `${prefix}${level}` : label ?? level} ); diff --git a/src/components/nav-link.tsx b/src/components/nav-link.tsx index b2c001c..4f3f716 100644 --- a/src/components/nav-link.tsx +++ b/src/components/nav-link.tsx @@ -22,10 +22,10 @@ export function NavLink({ {children} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index b033601..44b130f 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -4,11 +4,12 @@ import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( - "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding font-heading text-sm font-semibold whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/80", + // Primär-Buttons mit GEFIM-Verlauf wie im Mockup + default: "bg-grad-soft text-white hover:opacity-90", outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", secondary: diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 4458dae..26bcd70 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -12,7 +12,7 @@ function Card({ data-slot="card" data-size={size} className={cn( - "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground shadow-card ring-1 ring-border [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", className )} {...props} diff --git a/src/lib/levels.ts b/src/lib/levels.ts index 0f2c2f4..1a106da 100644 --- a/src/lib/levels.ts +++ b/src/lib/levels.ts @@ -1,13 +1,13 @@ -/** Farbklassen für Stufen 1–4 (Schutzbedarf, Schadenshöhe, Kritikalität). */ +/** Farbklassen für Stufen 1–4 (Schutzbedarf, Schadenshöhe, Kritikalität) — Pillen-Farben aus dem Mockup. */ export function levelColor(level: number): string { switch (level) { case 2: - return "bg-yellow-100 text-yellow-800 border-yellow-200"; + return "bg-[#fbf1de] text-[#a9760f] border-transparent"; case 3: - return "bg-orange-100 text-orange-800 border-orange-200"; + return "bg-[#fdeadb] text-[#c2681a] border-transparent"; case 4: - return "bg-red-100 text-red-800 border-red-200"; + return "bg-[#fbe4e4] text-[#d64c4c] border-transparent"; default: - return "bg-emerald-100 text-emerald-800 border-emerald-200"; + return "bg-[#e7f6ee] text-[#2e9e6b] border-transparent"; } }