From 61b40bf0092b5d404a1b021b64d403ba5299c5e2 Mon Sep 17 00:00:00 2001
From: Daniel <you@example.com>
Date: Fri, 13 Sep 2024 21:00:06 +0200
Subject: [PATCH] testing2

---
 next.config.mjs                      |   8 ++
 src/app/layout.jsx                   |  19 +++
 src/app/page.jsx                     |   5 +-
 src/components/SortingVisualizer.jsx | 156 +++++++++++++++++++++++++
 src/libs/BubbleSort.jsx              |  23 ++++
 src/styles/SortingVisualizer.css     | 165 +++++++++++++++++++++++++++
 src/styles/favicon.ico               | Bin 0 -> 15406 bytes
 7 files changed, 372 insertions(+), 4 deletions(-)
 create mode 100644 next.config.mjs
 create mode 100644 src/app/layout.jsx
 create mode 100644 src/components/SortingVisualizer.jsx
 create mode 100644 src/libs/BubbleSort.jsx
 create mode 100644 src/styles/SortingVisualizer.css
 create mode 100644 src/styles/favicon.ico

diff --git a/next.config.mjs b/next.config.mjs
new file mode 100644
index 0000000..ee4fab1
--- /dev/null
+++ b/next.config.mjs
@@ -0,0 +1,8 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+  output: 'export',
+  distDir: 'public',  
+
+}
+ 
+export default nextConfig
diff --git a/src/app/layout.jsx b/src/app/layout.jsx
new file mode 100644
index 0000000..0471ac6
--- /dev/null
+++ b/src/app/layout.jsx
@@ -0,0 +1,19 @@
+import Head from "next/head";
+
+export const Metadata = {
+    title: 'Sorting Visualizer',
+    description: 'Visualize sorting algorithms',
+}
+
+export default function RootLayout({children}) {
+    return (
+        <html lang="en">
+        <Head>
+            <link rel="icon" href="/src/styles/favicon.ico" />
+        </Head>
+        <body>
+        <div id="root">{children}</div>
+        </body>
+        </html>
+    )
+}
\ No newline at end of file
diff --git a/src/app/page.jsx b/src/app/page.jsx
index 5aae9c6..6a8d251 100644
--- a/src/app/page.jsx
+++ b/src/app/page.jsx
@@ -1,12 +1,9 @@
 import SortingVisualizer from '../components/SortingVisualizer.jsx'
-import Head from "next/head";
 
 export default function Home() {
     return (
         <main>
-            <Head>
-                <link rel="icon" href="/src/styles/favicon.ico" />
-            </Head>
+
             <SortingVisualizer />
         </main>
     )
diff --git a/src/components/SortingVisualizer.jsx b/src/components/SortingVisualizer.jsx
new file mode 100644
index 0000000..6220c82
--- /dev/null
+++ b/src/components/SortingVisualizer.jsx
@@ -0,0 +1,156 @@
+"use client";
+
+import {useState, useEffect} from 'react';
+import '../styles/SortingVisualizer.css'
+import BubbleSort from "../libs/BubbleSort.jsx";
+import RangeSlider from 'react-bootstrap-range-slider';
+import 'react-bootstrap-range-slider/dist/react-bootstrap-range-slider.css';
+import {Dropdown} from 'react-bootstrap';
+import 'bootstrap/dist/css/bootstrap.min.css';
+
+function SortingVisualizer() {
+
+    const moduloFive = ((window.innerWidth / 45)).toFixed(0) % 5;
+    const monitorSize = (window.innerWidth / 45).toFixed(0) - moduloFive;
+    const [BarNumber, setBarNumber] = useState(() => monitorSize > 100 ? 100 : monitorSize); // max of 100 bars
+    const [bars, setbars] = useState(() => generateArray(BarNumber));
+    const [sorting, setSorting] = useState(false);
+    const [alreadySorted, setAlreadySorted] = useState();
+    const [isSorted, setIsSorted] = useState(false);
+    const [sortSpeed, setSortSpeed] = useState('40');
+
+    const SliderWithInputFormControl = () => {
+        const [sliderValue_intern, setSliderValue_intern] = useState(BarNumber);
+        return (
+            <RangeSlider
+                value={sliderValue_intern}
+                step={5}
+                max={100}
+                min={5}
+                disabled={sorting}
+                onChange={changeEvent => setSliderValue_intern(Number(changeEvent.target.value))}
+                onAfterChange={() => setBarNumber(sliderValue_intern)}
+            />
+        );
+    };
+
+    useEffect(() => {
+        resetBars();
+    }, [BarNumber]);
+
+    function getRandomArbitrary(min, max) {
+        return Math.floor(Math.random() * (max - min + 1)) + min;
+    }
+
+    function generateArray(numberBars) {
+        return Array.from({length: numberBars}, () => getRandomArbitrary(50, 300));
+    }
+
+    async function handleSorting() {
+        if (sorting) return;
+        setSorting(true);
+        await BubbleSort(bars, setbars, setAlreadySorted, sortSpeed);
+        setSorting(false);
+    }
+
+    function resetBars() {
+        setAlreadySorted(1000);
+        setbars(generateArray(BarNumber));
+        let ColoredBars = document.getElementsByClassName('sorted');
+        document.getElementById('startButton').innerText = 'Sort!';
+        while (ColoredBars.length > 0) {
+            ColoredBars[0].className = 'bar';
+        }
+        setIsSorted(false);
+
+
+    }
+
+    function finishAnimation() {
+        let ColoredBars = document.getElementsByClassName('sorted');
+        while (ColoredBars.length > 0) {
+            ColoredBars[0].className = 'finished';
+        }
+    }
+
+    function handleButton() {
+        if (!sorting && !isSorted) {
+            setAlreadySorted(1000);
+            handleSorting();
+        } else if (isSorted) {
+            resetBars();
+
+        }
+    }
+
+    useEffect(() => {
+        if (alreadySorted == 0) {
+            setIsSorted(true);
+            finishAnimation();
+            document.getElementById('startButton').innerText = 'Reset!';
+        }
+    }, [alreadySorted]);
+
+
+    return (
+        <>
+            <div className='Container'>
+                <div id='ToolBar'>
+                    <div className="Label">How many bars?</div>
+                    <SliderWithInputFormControl style={{className: 'range-slider'}}/>
+                    <input type='number' max={100} min={0} disabled={sorting}
+                           onChange={changeEvent => {
+                               let value = Number(changeEvent.target.value);
+                               if (value > 100) value = 100;
+                               if (value < 5) value = 5;
+                               setBarNumber(value);
+                           }} defaultValue={BarNumber}
+                           value={BarNumber} style={{
+                        width: '42px',
+                        margin: '10px',
+                        borderRadius: '5px',
+                        border: 'none',
+                        textAlign: 'center'
+                    }}/>
+                    <div style={{background: 'gray', height: '70%', marginLeft: '10px', width: '2px'}}></div>
+                    <Dropdown id="dropdown">
+                        <Dropdown.Toggle
+                            id="dropdown"
+                            disabled={sorting}
+                        >
+                            Sorting-Speed: {
+                            {
+                                100: 'Slow',
+                                40: 'Medium',
+                                0: 'Fast'
+                            }[sortSpeed] || ''
+                        }
+                        </Dropdown.Toggle>
+                        <Dropdown.Menu id="dropdown-menu">
+                            <Dropdown.Item id="dropdown-item" onClick={() => setSortSpeed(100)}>
+                                Slow
+                            </Dropdown.Item>
+                            <Dropdown.Item id="dropdown-item" onClick={() => setSortSpeed(40)}>
+                                Medium
+                            </Dropdown.Item>
+                            <Dropdown.Item id="dropdown-item" onClick={() => setSortSpeed(0)}>
+                                Fast
+                            </Dropdown.Item>
+                        </Dropdown.Menu>
+                    </Dropdown>
+                    <button id='startButton' className="Button" disabled={sorting}
+                            style={{background: `${sorting ? 'red' : ''}`}} onClick={() => {
+                        handleButton()
+                    }}>{sorting ? 'Sorting...' : 'Sort!'}</button>
+                </div>
+                <div className='DivArray'>
+                    {bars.map((height, index) => (<div key={index} data-key={index}
+                                                       className={`bar + ${alreadySorted <= index ? 'sorted' : ''}`}
+                                                       style={{height: `${height}px`}}></div>))}
+                </div>
+
+            </div>
+        </>
+    )
+};
+export default SortingVisualizer;
\ No newline at end of file
diff --git a/src/libs/BubbleSort.jsx b/src/libs/BubbleSort.jsx
new file mode 100644
index 0000000..03b4c9d
--- /dev/null
+++ b/src/libs/BubbleSort.jsx
@@ -0,0 +1,23 @@
+async function BubbleSort(EingangsArray, updateBars, setColor, sortSpeed){
+    const len = EingangsArray.length;
+    for(let i = 0; i< len -1; ++i){
+        for(let k=0; k< len -1-i; k++){
+            let tmp;
+
+            if (k == len -2-i){
+                setColor([(len-1-i)]);
+            }
+            if(EingangsArray[k] > EingangsArray[k+1]){
+                tmp = EingangsArray[k];
+                EingangsArray[k] = EingangsArray[k+1];
+                EingangsArray[k+1] = tmp;
+            }
+            updateBars([...EingangsArray]);
+            await new Promise(resolve => setTimeout(resolve, sortSpeed)); //  Pause für Animation
+        }
+    }
+    setColor([(len-len)]);
+    console.log(EingangsArray);
+    return EingangsArray;
+}
+export default BubbleSort;
\ No newline at end of file
diff --git a/src/styles/SortingVisualizer.css b/src/styles/SortingVisualizer.css
new file mode 100644
index 0000000..4d120a8
--- /dev/null
+++ b/src/styles/SortingVisualizer.css
@@ -0,0 +1,165 @@
+body {
+    margin: 0;
+}
+
+.Container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+
+
+.bar {
+    background-color: blue;
+    display: inline-block;
+    height: 100px;
+    flex: 1 1 20px;
+    min-width: 2px;
+}
+
+.sorted {
+    background-color: deeppink;
+    animation: ScaleAnimation 0.5s ease-in-out;
+
+}
+
+.finished {
+    background-color: lawngreen;
+    flex: 1 1 20px;
+    min-width: 2px;
+    margin: 2px;
+    display: inline-block;
+    animation: GreenScaleAnimation 1s ease-in;
+
+}
+
+.range-slider {
+    padding: 0px;
+}
+
+#ToolBar {
+    height: 50px;
+    width: 100%;
+    background: #34495e;
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    align-items: center;
+
+}
+
+#startButton {
+    height: 78%;
+    width: 85px;
+    font-size: 12px;
+    margin: 10px;
+    background: #1abc9c;
+    text-align: center;
+    display: grid;
+    place-items: center;
+    border-radius: 5px;
+    border: none;
+    color: white;
+    font-size: 14px;
+    font-family: Arial, sans-serif;
+    font-weight: bold;
+
+}
+
+button:active {
+    box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.46);
+    transform: translateY(2px);
+}
+
+.Label {
+    margin: 10px;
+    font-size: 15px;
+    font-family: Arial, sans-serif;
+    font-weight: bold;
+    color: white;
+}
+
+.DivArray {
+    transform: scaleY(-1);
+    display: flex;
+    flex-wrap: nowrap;
+    justify-content: center;
+    gap: 2px;
+    width: 90%;
+    margin-top: 5px;
+}
+
+#dropdown-menu {
+    background-color: #34495e;
+    border: none;
+    border-radius: 5px;
+    margin-top: 3px;
+}
+
+
+#dropdown-item {
+    color: white;
+    font-size: 16px;
+    font-weight: normal;
+    border-radius: 5px;
+}
+
+#dropdown-item:hover {
+    background-color: #1abc9c;
+    color: white;
+}
+
+#dropdown {
+    background: none;
+    border: none;
+    border-radius: 0px;
+    color: white;
+    fontSize: 14px;
+    fontWeight: normal;
+    padding: 0;
+    height: 100%;
+    display: flex;
+    alignItems: center;
+    place-items: center;
+    margin-inline: 10px;
+    width: 190px;
+}
+
+#dropdown:hover {
+    color: #1abc9c;
+}
+
+#dropdown:active {
+    box-shadow: none;
+    transform: none;
+}
+
+#dropdown.show {
+    background-color: #1abc9c;
+    color: white;
+}
+
+
+@keyframes ScaleAnimation {
+    0% {
+        transform: scale(1);
+    }
+    50% {
+        transform: scale(1.05);
+    }
+    100% {
+        transform: scale(1);
+    }
+}
+
+@keyframes GreenScaleAnimation {
+    0% {
+        transform: scale(1);
+    }
+    50% {
+        transform: scale(1.1);
+    }
+    100% {
+        transform: scale(1);
+    }
+}
diff --git a/src/styles/favicon.ico b/src/styles/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..b3acbd6c1b785768b3563e233314d14e7e75ac21
GIT binary patch
literal 15406
zcmZQzU}Rus5D);-3Je)63=C!r3=9ei5Wa>W1H(KP1_lEI2tPxOf#H}a1A_(w1A_oa
z9Roz10SEa1|3BlK=MUrFK7Y{r_W9%9x6dB+zJ30v_sw%;8Y2D%D);u;!`^o<AH@Cp
z|DW;wiwAKpAK(7}=+5Q;O`9kFFJIU3zi;Qv{|B~R`(K-T|9@@nJuv&hu8#lrHx&QB
zx25j?<2#rCzj%E6|68cNAia05o%!!OBlExY#F+o_3rqg5oOAKNi{9P;u6lR=ub6xB
z|II})|8GvU{(p0}&wr3wke)XnH$Qva`{?dvuozhXgqZ*F3rdmn--YO39P|I?6zl&t
zX8HcVd;JX9y%7I^{C5BU>nHdB`_0M*+n=<g;{T=<_rUgp+^~7Y{r`8DC4lwcp5y!f
z)suVw-#okb9~4d?406jI6!(|@Uo{WnevtlE^Dq9txj6R!jj2}uZ-Cr?{Vc?OP#8aZ
z)C&p^kehsGLi``MpydCBV`u-@<=+P5^T*F(_#dPP5=W1FpFh0u|H-|p|NUlWf!&|5
zs2t3H@#xn7myd7#e|qog|JzIB!0x{_$M^r!dsqHHe|YmhEUwY@CoU=n>jCKpVX*!s
zap3R=`5$B#NdFs9dU^i17vz6XoOEuU^gn57<^SnBX8#9;2S^`C?cHl<|KC^?`~Sui
zQ2hCV;|dgK5c?teLFz$fyncEQEDzH2^!`<_KR|Lf7sZ0@2gN^#5Ar`ay@Jx}vwQzv
zJ-zoIr1$NMhyPzcf$-lvyZ;}AL3~jBf$YCM*B7h~Bo2zZx6dB;KE87aYUdenIslmu
za?6u@SHSjz*ta(~{s-y3y|x&v_R*b7|KB_VrJV=8kMAPuzX$d^$PAF*pWMF+R)6Q(
zS+Knzvp{kX|3Bz``|M#HC@+BcApGX}eQ??Ug()arK;aBxKfZhUKUnVWC9wR9$G85!
zfB7&Dlz%~a@a?k)@H`9l7dUPn^@7tlDBprGIIKYV8l?7N-2Z?78F3T~L<@t;83hIg
z1_uJ=46=b&u3TYz{pgX@+sBWk-=N?}U_MA3gi+<t)x!7?Jz%wuAY!jyKbE?3<tiJ<
zPDTcV7ytkN&;0iJ!^XES9()9)6J-46#l!z^Uq1Z*_Qiw$FmbRLNc_da|Hy1yYGC@n
z^vj1I-#mZR1hNz44iJ9({81A~?)9_#|3UHh{L!ud`!4MLzv=W=6nyIH(f=U1V|(uY
z-@5Al|E;U<!|7vt?||i=Upw&s*_l<S_~KTu94I}3>;TyX@-N8lt5>eDy?y!cBS`$-
z^>g4f2}+BgvJzD0BID+DePH>r)a#HkQ2#E3*1P*3BnHxVdt=T2n^UZiur;`h0_9tf
zJjjmM&+mi6^CLLEUq6xpxd&99faD?gEV!&jwj0E5UXNzK9;W@bHh}E62A65zdI40n
zEQ$fU8)EjE{~)_ScEZvchz$x?m^_F^^?xk5{6yG)4eoDHIS#@w|KHkJ11Zly_QEiV
z{b#{(0kRXEHXlEd2C=c(Z#6LnY%hogVTApl@V^6f!=3-Gpzu$*4lZ9n_Jis(6b!ci
zELa}oH;`Q*J0bQz#%Vt&%#ksQ{raFf7)$tr;~yIS;B<f*|DgH|VgDm(P<{rv1sx|X
zsRsKUVm~DOyLL<m=fRrnd*J%t1u6Z3#6V^|+}Vl|{-84c4LA>j@-YG<`yW*9zkGc6
z|I5dBz&L49C6fIy|3Ts3xos*~t||)__6Yx1XWjY#;_;pT4|X&|(jF}Cr&#~Lvos#8
z4`w#VE>O9FZ2z-Iw@}h3IQ>nC`ENY|Vz>3g*#FJz`@w2JX$=(TpmYbrATdzfV#dGK
ze^4C>l7nGH_(RG?P`H7_VDcauY(F&pf!qL!2Uz@9q=V8Q!tXA6cm9LKK>BWNti|yE
z4RHE919m$|4alz`JKsRdWe^+P{<wvukno=XiF<_oXaBF5bLqb;Qrx@h-}%3MHl(hF
z_}>cBCV<7kqFA`yAp1dff$W6a|MDR?PlDuN7{&jfaF`JDALf6MngiR<|KGCe9vJW6
zdLELuuAli2vLDo*fR{ZWH=z0d`J>z5bo}Nqq>ci)1>`1>xgZQ`2VmGg5n(?_9;EK|
zvwQzR7{rEQkp0McAM6%%`ybze*pC$cZ=OH+4=R^mKZlr^v;;N%gTlXS+cdC!AUi=A
z<ZciKv0?b(juwpg2jxFd{Q^_-_Bo_3MNa=<`@#OX54MA#{m<`#{eWTr&KA_TpJEMe
zqr>b5(IC4(b^4n}kEB6q0AvPw_$MrYraw^JPXy(`*#9jXAoUL{tU+dhFqnTGl)le`
z;~zQv!EB`P2gMyI{e$yAEc~&gzq++u;4%jk?l8P)?_!j646+-9L1ik~4c8#)4{RqW
z-N7)D{UCK9zk>XX9R46NkQ@wOzkK5Vl<l+rgUVb`xwmM~;{SK9od(+v@&m|Dkli2*
z(*vThroY9g^)JZ(F#BQUKZ^a}umpwY8E_nf%2klr=on--D9wW00&ZV`^B-pU1L`A!
z<*z}^gyp}tu<(C@od3^){Rp!Q)TRTK+b}-J9I*edo%s(-(;zXJJc|E8{ZddI;3|K>
zZCptG52_R3_0J<n9R#u$98S+3qSPNC@t2S8{0F6D5C-$1^%lYU2c+*AsQvZuKPdiT
z;Sb6WAiF>qlr}*$I>sdq3L}_)Q2h%|e=xs8^B=ldAU?=`5C*Zq@qZl>_OS2;#Vbe-
z<UdIGBFSF|g+HXM0?EPj+}_cRQU2UonS~OkAiF^3g7g1l?Cl?rnV@(Dse{`ON|zuE
zO-JDL_w4?EkUEI^Gym^iKKB3C`ttvGmc{?SyFB4P$nHDmb|Kl1-u}a3Khz$${m`%h
zg$dX$nE#){`o)m64GRa5n?ZU(YC&Qk3}S;Y#QzWegW?|C27d7n-0uL%p<__Ig3JPm
zgUkS75F3Oc{ztVR=01=fP`UuAL&4Xf?eCWl|G$3yND9;k0QI{-c?r}vfT@MqgKR%o
z4jL{XHq3sQT96nFL+pPD4R=Vo0m*^v0NDlV^IU=U1wnlwP}&BGgZc)b@PT1a7=rR9
zjE_u%@*W5yi^13+Js`a>K8OaH4YC6iE^nVbY6A7YKp4~)1or`7KKuxlgZ3>!`jIin
zjR^h&w7w^#T?<hIsb9fr;q6|i*vkhWLH-5V39=i385uxv0g4k)o`hjg8Ue9kG%6d?
zenAz3v?)Pi5R6a*N^2lH5O$8TK;t9^3=9kj1jk7b;}x(_`}hAp^W(>lIkDqMsN#=s
zsiUqqM!LduHpBh<_c>lYf8hD%#e?RzFCNZ(`{Lm&V(=T7x|cBZ4~Wu7jM-2%GvB^?
z)co$%W6%Hp|8u~?8A3xm^8fyQj<?Sr&3pIa(VMp~9{vUAF&z07ryM*_Lh>w5br|MC
z^Du@uiaCGZK7aW3?TZKVKyC%O9pZ8ZhX4QmbG&)+U>@8)P@Df5sJ#ur4<26sfAiiY
zD83A(L3{}N`IFn=HqVR4_y6Cy_Tc}m%MbtGx(uRGF-Q)i22|F)2I;wf{{L%S_~BKs
znP9b`aam~J0PM!+kLH2W3dj#242l<s-yZ&d_4Mxl$9J#%e{l1{|NA%3gK_uPssE$q
z7vscxPVE4zJ9Y5l|NPk7{}cW0BH=r5Hb@Sn24wEN(;NTaSrmc5i;>uiBL3gq)&e#Y
zq!#2Bkefhm1GyiRMnLHt<aSWL0HrNZngP`rAa$Vn2pN~JYJ>E7p?wZmyB|hxIkFyH
zpB>tH?tiG`ZRq$9az7lx2FZccfXu#qbU8+Q2;4pcwM(a1|G&Ma5Nsw$Evy~|xeesT
zw=W+21?5qY+d*XvG_Qi=3*=Xj9%M`#_b<ci{#!8jBee@aZUdz!P#l2V401aNL-RPe
ze+h~&n15k@pxFJ;F$rwp5AKIU!yGyM;qHgFDM4-lxedwvvp{Zt{P+<k!u|KbX%4IV
zL1RW#O+Q42KSB3D2D=~BJ^}d+<VTQSkuk;L4;>Fi4S(voAC#uR?!R#!o^N1&AlLoq
zV|m!zk2U>Fwf+xDKaln<EIok27UV`~TN0Xn9&^5h)SKY^3sM7fH;g9N{jf1e7>&*S
z$Y~zgFA(=b`YtecfZPUgKe%2)xF1sggU19w@qmtNH}>K!KlfkS57v9(<b(g=&XBSa
z=4NCXBnMIhGXMFdZK&lBQok1Drw5xW!DfQg;=oY%Bl0gcb)bBK+x?I+P>A~<{J(I5
z2=||Wv=uPik5&c|?|w-B1-bpr^M~L$lxGia{s-fS5W0GO7vAvSb!I16{pmwD|A#wa
zj-kWiAk68`e~=oG*^kd{#2C*5g*zBewf=u^LmAjiklN=^_kj9YkopUI`T?aaP#S}!
zHy900{{+j=v;Pn6z*hev{DYeQG3#GS(?6)+dmpX*0p)$L`>~e&kn}@d`+=D96H@*_
z!tpG)ZUTidD6N3%tVhs33WyC71IghGfACl<WZnzb?t#$|_n-ZLZ11`MVNST)KOi}f
z8jyQ#A4PLNTK>ga{@geZE+av11-Thg|02>qhWnvy58UAo@ejoPXa3*3c;<h8+zp)V
zx4hUJ|8HD`l!+MbN6Wuh-G7rr_e1M$yzZY6^B?AZP`Do5bMAj#-nIY90k{7rK-<wE
zc1_N;{~$ToI1tSJ$Y~y&ccK2EuKU|@mp^Fk2l?UJ`Lq8opS}w3!+_Y=&mpJfGyhTB
z&!Bm2*xV+Brk495`L`WB-v>+Y=rnTpgWM0ou(mp=yaTa8VlWIc6Bho+VGdzK`~q=5
z#Lpo0Aibcx2}<MO^#Af9wEjY*e~?{I?_UG=zd&}vaOK(#yy3s|<W{(zM|ZD)*`P8T
z(oT5*UV8*H3r0UUwH93e;z~bv)|Y_IfvE@4Ah&_s2zLJqO!tGtLGtJrcl`^=zxyxl
zgO9mG^n&Ua<Z-wMXz~yK6RLmjZ>qr1^X3_(?gqINTloVLhuDv5AL;G~*#p8Le}FKm
zc_2H;azAn({>8&t=;c4iTu>Z=;tCn#O+OQ3{_j9aKQMiubx@G>^AMbVVCf1b4@*Cg
zG!MzM$ZUA}xdTa$AblXWf!qiRXKdjQ${V0O0?I4MxMDS~{yVJx-GDk*aP2HO?SkwA
zVUXR(=7HF#<qxv^k@GL4t^lbAVUXKEZbWrIqWuYy1E~S2MV`BuynWVx+er|&qL)2x
z(^LK*K8-w&0kIS0W{}%K7{rE{36TS_ahE?3zd*_#gnE!3klR3RM0G!+`~k^<)PU3?
zyZ`jL1OIcE*ZsGj6pvC4xlK*|-?Mqj|7(|zWAxcTZU(s>gh6ar83-{ORQ{v2AE5OX
zEDcf1{UCcm^Q#vx9{Iob#J2ySwmt~&KC=@X?jW~=)PUUuZEt|sAUA{D4#FTdNDPKS
z>QU1VB+bCw4x-`ihnso!Kd5a93S(%#f|mbi=?A&}3D=8=M`+xE+vZ^RU{M1Se|+Z(
zy!=DBA5tHI<f)N<(90ii`}rnj{~cr>41?YO@*%?g&^8h*y@A3Oq!#36P}qYohz%15
z$$>D~{b+4YP`HCJTK^rS9)v+@`xSJKmXz`bq!)$>xgVwu<YtiDK^Vk_iNk1^`*GF3
z@bm)@pEDTg2R-~jc7gh6pgtQe3{89B^}Ap-&^{)Z4ejTF*sys%kbgmJkl3>aH^A`*
zV&m;UPC@NIg4BaB$Za4ug3Lq@e^5UiDo&{1k5?Yk9OQZ!R2M@qq5AhBj`0U1^D*)-
zHQWz313s5QUH8My$I^ZSl}(^L0!yPPG_;(7r5{jyqR1nqQ;;|)&4c0^gh6bO7&5+d
za{d3C)9k@*P*|P?)6jb0_NH2lItAo5kQ<?XfQCP=`U_EJg6)O79~%D;Zeo}Z>sx`;
zgWL>qI|zf=ATbnt@!<bkE7JepoN9yc1Jvy|=lK1<b7CD@n*=%kg55ya{ZKc<>;;v9
zP``uoAxs<_4RSNc?H~+dW2!m*|L*xc|8MW;`hRa@`G3%O6$sznR0nbU)l(SZLw@)p
z>K#<~Bg9eNN4WiltR9pWVdf%>qqFh3AC#{_X$zzVT^}+ZW)H|tWN}#i1rq~>IVkKw
z7{rE&W1~T4!OX=b4-*5q4dh0sTIBXWXs#DLwgQ@?fvLwvgY{yqe^K)b$ju<PgD{AV
z%`BK0vF-=C4XOT}2^#+a&4Imn37)V1ON?D$_rE}GKY;QD%q=h)<Yth0pfObt8=D%K
zT2Q?PG8dEvU}D&4kU1bTL353uIY*G&L35Cxxk%8QB&ciz%~68Xf&2=JF9<#d9=ijv
zL25vDf$BmKAB68g=UG8)7>4zgK=y&yFmY@&$SjbVAdF2O<Q9;dKyCx60nPP-=6peJ
z2Vu}$Flg=_EC-qcN1ML}r8yXesR6kQCI*UA7#l`|<guv*tt*77fzilnVPfbsvKsIl
z{Qv(Pu=#h8A3$^RAn~`)9=?5pv3>x19SU<Fej2}CP@2K3A6}0?{0mMa&mY0p4}jc_
zj6rK7-o1Y82})a_`BhLGX`nECO~{Lfv);UTI1@Bq4NB*rw1Vtr7#kiJj0_B*d;!Ya
jAPj4xg4kFwk?UtDH3#G-kXs?{gpeTqC>{*~N<sht{axw>

literal 0
HcmV?d00001

-- 
GitLab