begin
This commit is contained in:
37
_/berean.svg
37
_/berean.svg
@@ -1,37 +0,0 @@
|
||||
<svg width="173" height="243" viewBox="0 0 173 243" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M70.4998 1.19238C78.4561 1.19169 87.5117 0.758562 108.52 1.19434C112.994 1.19434 111.014 1.19382 120.5 1.19238L156 1.18848C159.013 1.18848 168.17 1.22179 170.763 1.18848H170.775C171.407 1.18848 171.671 1.69633 171.717 1.79395C171.788 1.94446 171.814 2.08955 171.826 2.16211C171.854 2.32745 171.865 2.52805 171.87 2.72852C171.881 3.14614 171.873 3.77771 171.859 4.6338C171.831 6.36197 171.775 9.10388 171.775 13.1885V13.2168C171.561 20.8539 171.617 28.5016 171.648 36.1719C171.679 43.8353 171.684 51.5183 171.367 59.1934C171.142 64.6504 170.695 76.4991 170.044 88.3789C169.395 100.229 168.539 112.205 167.483 117.872C158.439 166.419 135.815 199.114 110.66 221.251C108.23 223.39 95.1283 235.783 87.5545 240.832L86.9998 241.202L86.4451 240.832C82.6662 238.313 76.8945 234.046 71.7654 230.105C66.6615 226.184 62.0997 222.514 60.7928 221.207C37.1638 197.578 17.1459 166.903 6.52223 117.9C5.50877 113.225 4.265 100.839 3.24293 88.5244C2.21853 76.1819 1.40772 63.7855 1.27028 59.0342C0.807647 43.0407 1.09437 27.0007 1.02711 11.041C1.0219 9.80338 1.01444 7.65212 1.07204 5.80762C1.1007 4.88971 1.14617 4.02166 1.2195 3.37305C1.2553 3.05639 1.30264 2.74601 1.37184 2.49707C1.40429 2.38036 1.45907 2.21047 1.55739 2.04883C1.62628 1.93558 1.91565 1.5 2.49977 1.5C38.0002 1.5 51.3699 1.42323 57.6756 1.34668C63.9555 1.27044 63.2473 1.19301 70.4998 1.19238Z" fill="#B62525" stroke="#EEC88E" stroke-width="2"/>
|
||||
<path d="M106 1H71V228.5L87 239L106 225.5V1Z" fill="#233779"/>
|
||||
<path d="M71 0C71 0.666667 71 1.33333 71 2C79.8241 2 88.6482 2 97.4723 2C100.315 2 103.157 2 106 2L105 1C105 75.8333 105 150.667 105 225.5L105.421 224.685C99.2364 229.079 93.0521 233.473 86.8677 237.867C86.7187 237.973 86.5698 238.079 86.4208 238.185L87.5487 238.164C82.9349 235.136 78.3211 232.108 73.7073 229.081C72.9878 228.608 72.2682 228.136 71.5487 227.664L72 228.5C72 161.491 72 94.4815 72 27.4723C72 18.6482 72 9.82409 72 1C71.3333 1 70.6667 1 70 1C70 9.82409 70 18.6482 70 27.4723C70 94.4815 70 161.491 70 228.5V229.04L70.4513 229.336C71.1709 229.808 71.8905 230.28 72.61 230.753C77.2238 233.78 81.8376 236.808 86.4513 239.836L87.0224 240.211L87.5792 239.815C87.7282 239.709 87.8771 239.603 88.0261 239.498C94.2105 235.103 100.395 230.709 106.579 226.315L107 226.016V225.5C107 150.667 107 75.8333 107 1V0H106C103.157 0 100.315 0 97.4723 0C88.6482 0 79.8241 0 71 0ZM71 2V0H70V1H72L71 2Z" fill="#EEC88E"/>
|
||||
<path d="M169 113V74L4 74V113H169Z" fill="#223679" stroke="#EEC88E" stroke-width="2"/>
|
||||
<path d="M72.9734 73.8454C68.2063 73.2022 67.1636 70.8275 65.5745 69.096C63.9855 67.3645 63.3896 67.4634 63.3896 64.8413C63.3896 62.516 66.5676 61.0813 64.929 59.053C63.2903 57.0247 60.1371 58.9293 62.1979 60.9824C62.62 61.403 61.1054 62.1203 61.3041 63.0108C60.6585 62.0213 59.7648 61.2297 59.7648 60.5372C59.7648 59.8445 61.2544 58.2614 59.8641 56.9751C58.4737 55.6889 55.6929 59.2014 56.7853 59.5972C57.8778 59.993 58.0764 60.1414 58.0764 61.2298C58.0764 62.3182 57.9771 63.0109 58.424 63.8519C57.0336 62.6645 57.1329 62.6151 56.686 61.5761C56.2391 60.5371 54.8487 60.1413 53.6569 61.8729C52.4651 63.6044 52.0679 64.0002 52.9617 64.7918C53.8555 65.5833 54.7493 63.6539 55.9908 65.4349C57.2322 67.216 58.2253 70.4317 56.6363 70.0359C55.0473 69.6401 54.0542 70.2337 54.6997 72.3611C55.3453 74.4884 57.1329 73.7958 58.0764 73.3011C59.0199 72.8064 59.0695 72.6579 60.5592 73.3505C62.0489 74.0431 63.5883 75.3294 70.1429 86.4111C76.6976 97.4925 76.0024 74.6364 72.9734 73.8454Z" fill="#EFC88F" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M88.4908 107.612C83.3265 112.189 77.0453 104.966 73.3701 107.34C75.4308 107.934 76.0174 109.638 72.8222 109.638C66.2562 109.638 68.2225 112.716 64.5707 115.305C66.7828 115.06 67.3276 114.48 67.766 113.941C68.3458 116.961 66.0806 116.18 67.1339 120.657C67.766 118.593 68.8544 118.454 68.8544 118.454C69.7323 126.255 73.7893 127.281 73.2436 131.922C74.8237 130.382 75.0694 134.265 72.0147 134.86C68.9599 135.455 66.1158 135.385 64.3602 133.706C62.6046 132.027 58.8124 135.595 61.4107 135.84C64.009 136.085 63.8334 136.924 65.3433 137.099C63.7282 137.519 63.219 138.551 61.4985 137.256C59.778 135.962 57.4429 140.003 59.5848 140.702C61.7267 141.402 62.5519 139.6 65.0449 139.198C63.4472 140.37 63.7983 141.594 62.271 141.227C60.7436 140.86 58.5491 141.419 60.3573 143.466C62.1656 145.512 63.4647 143.886 64.3602 143.221C65.2555 142.556 65.7652 142.719 66.2563 141.822C65.7993 142.746 63.3244 143.623 64.7815 145.04C66.2387 146.457 68.3104 146.492 68.9424 144.165C69.5744 141.839 69.7851 140.457 72.8925 140.457C76 140.457 78.493 141.822 78.493 139.285C78.493 136.749 79.0434 135.898 80.5828 136.702C79.8503 134.933 80.7938 133.956 79.9248 132.843C80.5952 133.14 80.9676 133.709 80.9676 133.709C82.1098 131.235 80.2724 128.316 79.5772 127.896C80.7938 127.104 81.489 127.129 81.489 127.129C79.7014 126.684 77.7399 123.27 77.3675 121.019C77.2186 120.104 79.3538 121.044 81.0421 122.33C82.7304 123.616 89.7816 125.415 92.1689 121.776C94.5566 118.138 91.4835 105.692 88.4908 107.612Z" fill="#EFC88F" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M102.321 84.636C98.5958 92.3537 97.3544 93.5906 97.6027 97.7961C97.851 102.001 104.456 105.316 106.442 110.313C108.428 115.31 113.543 116.546 113.841 110.362C114.139 104.178 103.512 100.567 103.512 92.8983C103.512 85.2301 110.659 81.4994 112.053 78.5512C112.349 80.3072 111.823 80.9359 111.507 81.9154C111.507 81.9154 126.354 75.6324 124.567 68.6076C125.759 69.7207 126.056 71.1307 126.056 71.8234C126.628 71.106 130.029 65.6887 126.156 61.1872C122.283 56.6851 118.906 60.544 118.906 63.1166C118.906 65.6892 123.474 65.7881 123.474 64.1061C123.474 62.8263 121.931 62.9277 121.438 64.1061C120.495 62.424 123.276 59.7525 124.914 61.583C126.553 63.4135 126.603 66.926 123.375 66.926C121.798 66.926 117.976 66.8892 117.565 61.8304C115.127 66.7542 122.084 67.3912 122.084 70.8345C122.084 74.9407 117.913 76.1775 117.665 72.566C118.409 73.1596 120.694 71.5271 118.608 69.2513C116.523 66.9755 115.785 65.6602 115.529 64.0072C113.317 66.9491 121.885 73.506 111.16 78.5523C113.891 75.5344 114.61 72.4157 114.189 70.1419C114.189 70.1419 112.898 72.4672 111.309 72.1703C114.089 66.4314 111.944 62.565 117.615 58.0704C115.252 58.2437 114.053 59.7631 111.209 58.7136C113.531 58.2818 115.632 56.8996 117.714 56.3389C111.557 54.3105 110.315 47.2853 117.218 45.3066C114.04 49.1161 115.231 51.4413 116.473 52.876C116.523 49.7592 118.409 49.6602 119.154 49.7592C117.565 51.9855 117.416 53.5192 120.793 55.2013C124.17 56.8833 124.368 57.576 124.368 57.576C124.368 57.576 125.709 55.3992 127.099 55.9928C126.652 56.5865 125.833 57.0564 126.404 57.9964C126.975 58.9364 137.08 71.1313 122.432 79.0969C124.765 78.6517 125.957 79.6412 125.957 79.6412C125.957 79.6412 122.432 80.0369 120.048 81.7685C124.964 82.7085 125.985 81.724 128.142 80.1359C125.917 85.363 118.161 83.5 114.984 85.4789C119.254 86.0231 120.198 89.5357 123.723 87.9525C118.812 94.5914 114.835 83.7968 108.777 88.0019C112.75 87.8535 113.345 89.2388 114.289 89.5356C112.451 90.4261 105.251 88.7935 106.344 94.1365C107.933 92.5039 109.82 92.1576 109.82 92.1576C109.82 92.1576 106.642 95.4723 108.976 97.6491C111.309 99.8259 115.481 102.992 115.828 105.317C116.672 103.685 117.814 102.547 117.814 102.547C117.814 102.547 117.814 106.406 117.268 111.304C116.722 116.201 111.185 121.742 105.376 116.35C105.798 114.643 104.854 111.699 104.358 111.155C106.592 115.014 103.911 119.615 104.259 122.237C103.613 121.643 103.365 119.961 103.365 119.961C99.0778 126.06 104.634 126.878 108.678 124.215C108.362 124.915 107.288 126.442 108.479 126.343C109.671 126.244 112.502 125.947 112.502 127.876C112.502 129.806 106.692 135.545 106.692 139.701C106.692 143.857 108.181 143.609 106.94 145.44C105.699 147.27 108.082 149.645 106.692 150.684C105.301 151.723 102.719 150.535 103.166 149.595C103.613 148.655 104.209 147.765 104.06 146.924C103.911 146.083 104.705 145.934 104.457 144.994C103.737 145.712 103.414 147.418 102.62 147.567C101.825 147.715 102.421 150.783 100.584 150.931C98.7461 151.079 96.5612 148.012 98.1007 147.666C99.64 147.319 99.3917 147.023 100.236 145.538C101.081 144.054 100.137 145.39 98.9945 145.885C97.8524 146.379 98.1503 147.765 97.1572 147.765C96.164 147.765 94.2771 144.895 95.6675 144.005C97.0579 143.114 98.1503 143.461 99.0441 142.669C100.149 142.315 100.586 142.277 100.832 141.63C100.358 141.77 99.0938 142.817 97.753 143.015C96.4122 143.213 96.9585 138.761 98.1999 138.761C99.4413 138.761 99.2924 140.542 100.634 140.047C101.974 139.552 104.507 133.269 104.159 131.933C103.812 130.598 102.173 131.884 102.223 132.675C101.577 132.478 98.051 131.933 97.2068 131.439C97.4551 132.478 98.7461 133.071 98.7461 133.071C95.717 133.591 93.6811 131.538 93.7308 130.004C92.9859 132.379 92.3404 130.449 90.3541 132.576C90.7514 128.916 88.2189 126.986 88.2189 126.986C88.2189 126.986 88.3182 130.152 85.7857 131.884C85.3885 126.739 80.5717 122.088 85.5374 118.823C83.3029 118.378 83.2532 116.894 83.2532 116.894C83.2532 116.894 92.9859 114.865 91.3472 108.533C91.9928 111.6 90.0065 113.48 90.0065 113.48C90.0065 113.48 88.2685 108.83 85.7856 107.247C83.3028 105.663 81.813 103.586 81.813 103.586C81.813 103.586 81.6641 106.356 82.2599 106.9C79.7274 105.565 79.7274 101.607 79.7274 101.607C79.7274 101.607 79.8267 105.169 78.5853 105.911C78.5853 103.041 76.6983 99.9742 76.6983 99.9742C76.6983 99.9742 77.0459 102.844 76.4997 103.338C76.3011 101.26 71.7326 95.9174 70.8885 93.8395C70.0444 91.7616 68.0084 85.4291 69.4485 84.2418C70.8885 83.0544 81.4155 75.683 84.8416 77.3156C88.2679 78.9482 105.35 77.6619 102.322 84.6376L102.321 84.636Z" fill="#EFC88F" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M65.4001 94.4009C63.0414 94.2524 62.8675 94.104 62.4207 93.6588C62.8428 94.8956 61.3532 97.1218 59.0193 95.5387C56.6854 93.9556 55.3695 92.4961 54.3019 94.8709C53.2343 97.2456 55.1708 97.864 56.2136 97.6414C57.2564 97.4187 57.6785 98.4577 59.0937 98.2103C57.6289 99.0761 56.9337 99.6945 54.5253 99.0761C52.117 98.4577 51.4714 103.331 54.2522 103.553C57.033 103.776 55.6674 102.168 57.1571 101.575C58.6468 100.981 59.8385 100.016 59.8385 100.016C59.8385 100.016 58.4978 100.758 58.473 102.49C58.4481 104.221 56.3625 104.518 57.1322 105.458C57.9019 106.398 59.8633 107.264 61.0798 106.423C62.2964 105.582 61.5764 104.147 61.3281 103.331C61.0798 102.514 62.1475 101.451 63.4137 101.575C64.6799 101.698 64.5559 102.663 63.4386 103.455C62.3213 104.246 66.6414 105.334 67.6097 104.444C68.578 103.553 66.5421 101.327 68.8263 100.61C71.1105 99.8925 72.8485 100.635 72.8485 100.635C70.6093 98.9949 72.5861 98.9945 74.9093 98.3587C76.7893 97.8443 77.6901 96.5035 81.0667 97.9877C83.6985 99.0266 83.4006 98.5814 83.5993 96.7509C83.7979 94.9204 85.5359 94.9204 85.9332 92.6941C86.3304 90.4679 87.6215 89.8247 87.6215 83.8385C87.6215 77.8523 80.0203 80.4953 76.6966 83.8879C73.3733 87.2807 74.1644 89.9737 72.0785 91.3336C68.7767 93.4863 67.7588 94.5493 65.4001 94.4009Z" fill="#EFC88F" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M87.9564 47.6527C88.448 45.9736 85.8848 43 85.8848 43C85.8848 43 89.431 43.3148 91.7835 45.9736C94.1009 44.1545 97.1207 43.5948 97.1207 43.5948C97.1207 43.5948 96.3482 44.7142 96.1024 45.3789C100.07 45.4489 99.5786 46.1135 100.737 46.1135C99.0169 47.093 98.5604 47.3729 98.5604 47.3729C103.23 48.6322 108.251 54.4743 108.251 54.4743C108.251 54.4743 106.285 53.7747 102.914 54.4044C110.182 56.5733 108.602 60.019 110.814 62.0655C106.741 61.121 105.512 60.0715 104.073 60.981C107.057 61.6807 108.356 64.5143 108.356 64.5143C108.356 64.5143 107.83 64.1295 107.514 64.0595C108.04 64.6892 111.236 73.9246 107.584 78.5418C107.9 74.6237 105.266 73.5392 105.266 73.5392C105.266 73.5392 105.934 81.2354 104.564 83.1245C104.406 82.5123 104.038 81.7427 103.792 81.5153C103.792 81.5153 104.038 85.7832 101.72 87.3225C101.545 82.5648 100.526 82.1101 100.035 81.8303C95.6286 88.2146 94.7333 88.4594 93.0128 90.2961C93.7501 86.8328 93.5745 85.2935 93.1882 84.6639C89.8525 88.0571 92.6616 89.0017 88.1672 92.8148C88.3428 88.6869 87.3947 87.5324 87.3947 87.5324C87.3947 87.5324 87.6756 90.0512 85.604 91.7303C85.2529 87.0427 84.5857 85.9932 85.0422 83.1596C83.1461 85.5035 82.7949 86.413 82.7949 86.413C82.5715 85.2504 83.3216 82.8797 82.9003 82.0401C81.9874 85.3984 79.5295 86.0632 79.5295 86.0632C79.5295 86.0632 80.7935 82.0052 80.0211 80.501C77.809 83.3695 78.8579 85.7241 74.7116 87.2826C75.4813 85.6005 75.531 84.1163 75.531 84.1163C75.531 84.1163 72.775 87.9999 72.2288 90.1273C71.6276 88.0555 72.4771 86.0705 72.4771 86.0705C71.9504 86.4378 69.9694 88.5194 69.5473 90.4984C68.4798 87.1589 68.9763 85.3284 68.9763 85.3284C68.9763 85.3284 68.3047 85.6168 67.4618 86.3426C68.4053 82.6321 69.3984 81.6427 69.9694 80.6779C68.3555 81.5437 67.7348 82.3353 67.1389 82.83C66.7913 80.8758 68.0477 76.3731 72.191 75.0087C70.1896 72.9097 69.9086 71.5104 69.9437 70.0761C71.875 72.5599 73.7359 73.2245 74.8244 72.1751C75.9129 71.1256 76.8258 71.1956 78.1951 69.7263C79.5645 68.257 81.4957 68.5368 82.9354 67.2775C84.375 66.0182 87.2498 63.3129 86.8029 61.3341C86.4 62.2564 85.7253 61.7306 85.8897 62.784C85.1733 62.5261 85.0495 62.8501 85.1594 63.5278C84.3747 63.2447 84.6976 63.9243 84.3449 63.7829C82.8056 63.1892 81.8869 64.8961 81.44 65.935C80.9931 66.9739 80.3724 66.875 79.0068 66.3307C77.6412 65.7865 78.0385 65.2423 78.5351 64.9455C79.0316 64.6487 78.1378 63.5355 78.1378 63.1644C78.7834 63.6097 79.9751 63.2881 80.2979 63.0407C77.1943 60.9628 73.9667 61.6307 74.7115 64.6734C73.048 64.2281 73.3957 62.5707 73.3957 62.5707C73.3957 62.5707 68.4548 63.6097 68.6038 58.4398C70.2425 61.3339 73.495 61.4081 75.1336 60.8145C76.7723 60.2208 79.2438 61.0215 81.1736 62.157C81.6848 62.4577 81.7879 61.8154 81.6798 61.5669C80.8317 59.6172 78.2138 57.1993 77.4559 59.9173C76.7003 58.4159 76.0274 59.5777 75.3074 59.0335C74.5874 58.4893 72.5514 56.7577 73.5695 55.1746C74.5874 53.5915 75.084 54.2347 77.3434 52.4041C79.6027 50.5736 80.2554 49.958 82.3836 49.6832C83.0069 49.6026 83.0727 48.1833 82.8801 47.8032C84.1293 48.0151 85.3628 48.3969 85.3628 48.3969C85.3628 48.3969 85.1207 46.0681 82.9854 46.7112C84.4347 44.9967 86.6023 46.2402 87.9564 47.6527Z" fill="#EFC88F" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M89.1113 49.8317C90.9735 50.7469 94.0025 46.3934 96.386 50.1285C98.7695 53.8638 94.2259 54.4821 93.4811 55.9168C93.8783 54.68 92.2645 53.0968 91.3955 53.8885C92.2893 53.0722 93.0342 53.3443 93.0342 53.3443" stroke="black" stroke-opacity="0.22" stroke-linecap="round"/>
|
||||
<path d="M87.4232 53.9395C87.9446 55.0031 86.0328 54.9042 85.8342 55.9926C85.6356 57.0811 88.466 58.0705 88.466 58.0705" stroke="black" stroke-opacity="0.22" stroke-linecap="round"/>
|
||||
<path d="M87.2988 58.7578C87.7457 59.0546 88.2671 59.871 88.2671 59.871" stroke="black" stroke-opacity="0.22" stroke-linecap="round"/>
|
||||
<path d="M82.2161 54.4311C81.3909 54.5536 80.5658 52.9443 80.0391 52.612C80.4253 51.9474 80.4429 51.4576 81.2505 51.4751" stroke="black" stroke-opacity="0.22" stroke-linecap="round"/>
|
||||
<path d="M80.5703 53.1005C81.4832 52.2784 82.5851 52.2528 83.2171 52.8475" stroke="black" stroke-opacity="0.22" stroke-linecap="round"/>
|
||||
<path d="M104.407 153.187C102.546 151.63 103.459 148.832 104.6 149.899C105.741 150.966 102.915 151.228 104.407 153.187Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M97.7006 152.003C96.1732 149.764 98.0693 147.455 98.9646 148.75C99.86 150.044 97.0158 150.849 97.7006 152.003Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M93.8901 147.803C93.1878 145.144 95.2419 143.989 96.0144 145.179C96.7869 146.368 93.5038 146.875 93.8901 147.803Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M94.4162 142.626C93.0644 139.215 98.2786 138.253 97.5939 140.212C96.9092 142.171 94.7849 140.334 94.4162 142.626Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M74.8403 53.96C74.3859 54.1988 74.0282 54.4587 73.5705 55.1705C72.886 56.2351 73.5828 57.3653 74.3314 58.158C74.2173 57.0095 74.3051 55.4067 74.8403 53.96Z" fill="black"/>
|
||||
<path d="M73.9149 55.5723C73.8724 55.846 73.8439 56.1171 73.8231 56.3818C73.7635 56.1051 73.7829 55.8371 73.9149 55.5723Z" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M59.0235 135.177C58.1808 132.606 62.7805 131.871 62.0081 134.022C61.2356 136.174 59.7609 133.008 59.0235 135.177Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M57.6537 140.165C54.7745 138.031 59.8483 135.04 60.217 137.594C60.5856 140.148 56.6882 137.629 57.6537 140.165Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M59.4408 146.136C57.2932 144.033 60.0616 141.275 61.154 142.487C62.2464 143.699 59.2794 144.478 59.4408 146.136Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M64.7538 147.299C63.3385 145.406 64.7786 143.279 65.7096 144.244C66.6407 145.208 64.6047 145.988 64.7538 147.299Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M54.9102 57.6999C55.8043 54.7847 60.1114 56.2887 58.7707 57.6492C57.4299 59.0098 57.4676 55.919 54.9102 57.6999Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M61.5527 57.8992C63.2907 56.9722 64.5723 58.9662 63.3785 59.4035C62.1847 59.8407 63.1679 58.284 61.5527 57.8992Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M51 62.3107C51.4916 59.0748 55.4944 60.2117 54.4586 61.6285C53.4227 63.0453 53.1067 60.2642 51 62.3107Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M53.3359 70.7021C53.7331 74.0663 56.3898 73.0274 56.1167 71.9884C55.8436 70.9495 54.9745 72.1368 53.3359 70.7021Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M52.8629 97.7631C51.2986 94.8937 54.8989 93.2611 55.1968 94.5722C55.4947 95.8833 53.1857 95.0669 52.8629 97.7631Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M53.0876 105.16C50.7785 102.785 53.06 100.473 54.1055 101.128C55.668 102.106 53.2117 102.859 53.0876 105.16Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M59.69 108.681C57.6541 108.434 57.402 105.347 58.821 105.416C60.857 105.515 58.4982 107.048 59.69 108.681Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M62.793 105.213C64.9034 107.142 66.5337 103.974 65.4248 103.556C63.7861 102.937 65.0523 105.164 62.793 105.213Z" fill="#9C0001" stroke="black" stroke-opacity="0.22"/>
|
||||
<path d="M84.2695 50.1364C85.1122 50.7661 86.0252 50.4512 86.622 50.0664" stroke="black" stroke-opacity="0.22" stroke-linecap="round"/>
|
||||
<path d="M86.166 52.0215C86.7629 52.6512 87.1492 52.8261 87.7812 52.5812" stroke="black" stroke-opacity="0.22" stroke-linecap="round"/>
|
||||
<path d="M89.9434 51.2773C90.7379 51.5494 91.4579 51.6236 91.6565 52.2173C91.7558 51.6483 92.5255 51.0052 93.1462 51.5494C92.8482 50.8073 93.8662 50.3126 94.909 50.7084" stroke="black" stroke-opacity="0.22" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 18 KiB |
3
server/.env
Normal file
3
server/.env
Normal file
@@ -0,0 +1,3 @@
|
||||
BASE_URL=localhost:3004
|
||||
JWT_SECRET=950b15c8c1c8a27dd716bba3ab51d96ce49afa85cae72884cf22e936e1bc0cb9
|
||||
ENV=development
|
||||
52
server/config/config.go
Normal file
52
server/config/config.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var ENV string
|
||||
|
||||
// URLs
|
||||
var BASE_URL string
|
||||
const PORT = "3004"
|
||||
|
||||
// Auth
|
||||
var JWT_SECRET string
|
||||
|
||||
// Logging
|
||||
var LOG_TO_FILE bool
|
||||
|
||||
func SetConfiguration() {
|
||||
fmt.Println("setting configuration for server")
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
fmt.Println("no .env file found. Needs to be added to server directory.")
|
||||
}
|
||||
|
||||
ENV = os.Getenv("ENV")
|
||||
if ENV != "production" && ENV != "development" {
|
||||
fmt.Println("invalid value for ENV, must be 'development' or 'production'")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
BASE_URL = os.Getenv("BASE_URL")
|
||||
if BASE_URL == "" {
|
||||
fmt.Println("BASE_URL not provided, aborting")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
JWT_SECRET = os.Getenv("JWT_SECRET")
|
||||
if JWT_SECRET == "" {
|
||||
fmt.Println("JWT_SECRET not provided, aborting")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
LOG_TO_FILE, err = strconv.ParseBool(os.Getenv("LOG_TO_FILE"))
|
||||
if err != nil {
|
||||
LOG_TO_FILE = false
|
||||
}
|
||||
}
|
||||
7
server/env.example
Normal file
7
server/env.example
Normal file
@@ -0,0 +1,7 @@
|
||||
# ENV must be development or production
|
||||
# Required
|
||||
ENV=development
|
||||
|
||||
# Auth
|
||||
# Required
|
||||
JWT_SECRET=950b15c8c1c8a27dd716bba3ab51d96ce49afa85cae72884cf22e936e1bc0cb9
|
||||
28
server/go.mod
Normal file
28
server/go.mod
Normal file
@@ -0,0 +1,28 @@
|
||||
module hyperia
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
github.com/alexedwards/argon2id v1.0.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mailgun/mailgun-go/v4 v4.23.0
|
||||
github.com/mssola/user_agent v0.6.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailgun/errors v0.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
)
|
||||
95
server/go.sum
Normal file
95
server/go.sum
Normal file
@@ -0,0 +1,95 @@
|
||||
github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w=
|
||||
github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
|
||||
github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0=
|
||||
github.com/mailgun/mailgun-go/v4 v4.23.0 h1:jPEMJzzin2s7lvehcfv/0UkyBu18GvcURPr2+xtZRbk=
|
||||
github.com/mailgun/mailgun-go/v4 v4.23.0/go.mod h1:imTtizoFtpfZqPqGP8vltVBB6q9yWcv6llBhfFeElZU=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mssola/user_agent v0.6.0 h1:uwPR4rtWlCHRFyyP9u2KOV0u8iQXmS7Z7feTrstQwk4=
|
||||
github.com/mssola/user_agent v0.6.0/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
148
server/handlers/join.go
Normal file
148
server/handlers/join.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/rs/zerolog/log"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"time"
|
||||
"context"
|
||||
|
||||
"hyperia/config"
|
||||
"github.com/mailgun/mailgun-go/v4"
|
||||
)
|
||||
|
||||
type joinRequest struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
|
||||
|
||||
func isValidEmail(email string) bool {
|
||||
return emailRegex.MatchString(email)
|
||||
}
|
||||
|
||||
func HandleJoin(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var creds joinRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
|
||||
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidEmail(creds.Email) {
|
||||
http.Error(w, "Invalid email address", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// exists, err := EmailExists(creds.Email)
|
||||
// if err != nil {
|
||||
// log.Printf("Error checking email: %v", err)
|
||||
// http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// if exists {
|
||||
// http.Error(w, "Email already exists.", http.StatusConflict)
|
||||
// return
|
||||
// }
|
||||
|
||||
// err = CreateApplicant(creds.Email)
|
||||
// if err != nil {
|
||||
// log.Printf("Error creating applicant: %v", err)
|
||||
// http.Error(w, "Failed to create applicant", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
|
||||
// token, err := generateVerificationToken(creds.Email)
|
||||
// if err != nil {
|
||||
// log.Printf("Error generating verification token: %v", err)
|
||||
// http.Error(w, "Error, please try again later.", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
|
||||
// err = sendWelcomeEmail(creds.Email, token)
|
||||
// if err != nil {
|
||||
// log.Printf("Error sending welcome email: %v", err)
|
||||
// http.Error(w, "Failed to send email", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("OK"))
|
||||
}
|
||||
|
||||
func generateVerificationToken(email string) (string, error) {
|
||||
// Create 32 random bytes → 64-char hex string
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
token := hex.EncodeToString(b)
|
||||
|
||||
// err := CreateApplicantVerification(email, token)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func mailgunEmail(to string, token string) error {
|
||||
// link format: https://hyperia.so/verify?token=7a1a7cb986437cf8868b18cf43d73ce2e947d65aef30b42419bab957f5e51a09
|
||||
domain := "mg.hyperia.so"
|
||||
apiKey := "aeb90a0c75ef782eab6fc3d48fdf4435-812b35f5-fe818055"
|
||||
|
||||
mg := mailgun.NewMailgun(domain, apiKey)
|
||||
|
||||
sender := "welcome@" + domain
|
||||
subject := "Verify Your Email"
|
||||
verifyLink := config.BASE_URL + "/verify?token=" + token
|
||||
body := "Thanks for signing up! Please verify your email by clicking this link: " + verifyLink
|
||||
|
||||
message := mg.NewMessage(sender, subject, body, to)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
_, _, err := mg.Send(ctx, message)
|
||||
return err
|
||||
}
|
||||
|
||||
func sendWelcomeEmail(to string, token string) error {
|
||||
if config.ENV == "development" {
|
||||
verifyLink := config.BASE_URL + "/verify?token=" + token
|
||||
log.Debug().Msgf("email Verify Link: %s", verifyLink)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
// query := `
|
||||
// INSERT INTO emails ("to", "from", subject, body, createdon, createdby, status)
|
||||
// VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
// `
|
||||
|
||||
// sender := "noreply@mail.hyperia.so"
|
||||
// subject := "Verify Your Email"
|
||||
// verifyLink := config.BASE_URL + "/verify?token=" + token
|
||||
// body := "Thanks for signing up! Please verify your email by clicking this link: " + verifyLink
|
||||
|
||||
// _, err := DB.Exec(
|
||||
// query,
|
||||
// to,
|
||||
// sender,
|
||||
// subject,
|
||||
// body,
|
||||
// time.Now(), // createdon
|
||||
// "go-backend", // createdby
|
||||
// "pending", // status
|
||||
// )
|
||||
|
||||
// return err
|
||||
}
|
||||
107
server/handlers/login.go
Normal file
107
server/handlers/login.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
// "errors"
|
||||
"net/http"
|
||||
// "strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
// "github.com/alexedwards/argon2id"
|
||||
)
|
||||
|
||||
type loginRequest struct {
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type user struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func HandleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var creds loginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
|
||||
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// user, err := getUserByCredentials(creds.Name, creds.Password)
|
||||
// if err != nil {
|
||||
// http.Error(w, "Unauthorized: "+err.Error(), http.StatusUnauthorized)
|
||||
// return
|
||||
// }
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, "Not implemented", http.StatusMethodNotAllowed)
|
||||
// json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
|
||||
// func getUserByCredentials(name string, password string) (*user, error) {
|
||||
// var id int
|
||||
// var dbName, dbHash string
|
||||
|
||||
// name = strings.TrimSpace(strings.ToLower(name))
|
||||
// err := DB.QueryRow("SELECT id, name, password FROM users WHERE LOWER(name) = LOWER($1)", name).Scan(&id, &dbName, &dbHash)
|
||||
// if err != nil {
|
||||
// return nil, errors.New("user not found")
|
||||
// }
|
||||
|
||||
// match, err := argon2id.ComparePasswordAndHash(password, dbHash)
|
||||
// if err != nil || !match {
|
||||
// return nil, errors.New("invalid password")
|
||||
// }
|
||||
|
||||
// return &user{
|
||||
// ID: id,
|
||||
// Name: dbName,
|
||||
// }, nil
|
||||
// }
|
||||
|
||||
func HandleApplicantLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var creds loginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
|
||||
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// exists, err := EmailExists(creds.Name)
|
||||
// if err != nil {
|
||||
// log.Err(err).Msg("error checking email")
|
||||
// http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// if !exists {
|
||||
// http.Error(w, "Email does not exist.", http.StatusConflict)
|
||||
// return
|
||||
// }
|
||||
|
||||
token, err := generateVerificationToken(creds.Name)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error generating verification token")
|
||||
http.Error(w, "Error, please try again later.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = sendWelcomeEmail(creds.Name, token)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error sending welcome email")
|
||||
http.Error(w, "Failed to send email", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("OK"))
|
||||
}
|
||||
33
server/handlers/logout.go
Normal file
33
server/handlers/logout.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
"os"
|
||||
|
||||
"hyperia/config"
|
||||
)
|
||||
|
||||
func HandleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
// Create a cookie with the same name and domain, but expired
|
||||
cookie := &http.Cookie{
|
||||
Name: "auth_token",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
Domain: "." + os.Getenv("BASE_URL"), // must match what you set when logging in
|
||||
HttpOnly: true,
|
||||
Secure: true,
|
||||
Expires: time.Unix(0, 0), // way in the past
|
||||
MaxAge: -1, // tells browser to delete immediately
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
}
|
||||
|
||||
if config.ENV == "development" {
|
||||
cookie.Secure = false
|
||||
cookie.Domain = ".hyperia.local"
|
||||
}
|
||||
|
||||
http.SetCookie(w, cookie)
|
||||
|
||||
http.Redirect(w, r, config.BASE_URL, http.StatusSeeOther)
|
||||
}
|
||||
82
server/handlers/verify.go
Normal file
82
server/handlers/verify.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
// "os"
|
||||
"time"
|
||||
|
||||
"hyperia/config"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
func GenerateJWT(applicantId int) (string, error) {
|
||||
claims := jwt.MapClaims{
|
||||
"applicant_id": applicantId,
|
||||
"exp": time.Now().Add(2 * time.Hour).Unix(), // expires in 2 hours
|
||||
"iat": time.Now().Unix(),
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
jwtSecret := []byte(config.JWT_SECRET)
|
||||
signedToken, err := token.SignedString(jwtSecret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return signedToken, nil
|
||||
}
|
||||
|
||||
func HandleVerify(w http.ResponseWriter, r *http.Request) {
|
||||
// token := r.URL.Query().Get("token")
|
||||
// if token == "" {
|
||||
// http.Error(w, "Missing token", http.StatusBadRequest)
|
||||
// return
|
||||
// }
|
||||
|
||||
// v, err := GetApplicantVerificationByToken(token)
|
||||
// if err != nil {
|
||||
// log.Println("Invalid token: ", token)
|
||||
// http.Error(w, "Invalid token", http.StatusUnauthorized)
|
||||
// return
|
||||
// }
|
||||
|
||||
// if time.Since(v.CreatedOn) > 30*time.Minute || v.Expired {
|
||||
// log.Println("Token expired: ", token)
|
||||
// http.Error(w, "Token expired", http.StatusUnauthorized)
|
||||
// return
|
||||
// }
|
||||
|
||||
// _, err = DB.Exec(`
|
||||
// UPDATE ApplicantVerifications SET Expired = 1 WHERE ApplicantId = $1
|
||||
// `, v.ApplicantId)
|
||||
// if err != nil {
|
||||
// http.Error(w, "Failed to update verification", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
|
||||
// jwtToken, err := GenerateJWT(v.ApplicantId)
|
||||
// if err != nil {
|
||||
// log.Println("JWT generation error:", err)
|
||||
// http.Error(w, "Failed to generate auth token", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
|
||||
// cookie := &http.Cookie{
|
||||
// Name: "auth_token",
|
||||
// Value: jwtToken,
|
||||
// Path: "/",
|
||||
// HttpOnly: true,
|
||||
// Domain: "." + os.Getenv("BASE_URL"), // or ".localhost" — this allows subdomains
|
||||
// Secure: true, // default to true (production)
|
||||
// MaxAge: 2 * 60 * 60,
|
||||
// SameSite: http.SameSiteLaxMode,
|
||||
// }
|
||||
// if config.ENV == "development" {
|
||||
// cookie.Secure = false
|
||||
// cookie.Domain = ".hyperia.local"
|
||||
// }
|
||||
|
||||
// http.SetCookie(w, cookie)
|
||||
log.Println("Verification success.")
|
||||
http.Redirect(w, r, config.BASE_URL, http.StatusSeeOther)
|
||||
}
|
||||
28
server/logger/logger.go
Normal file
28
server/logger/logger.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"hyperia/config"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
// Very basic setup for starters,
|
||||
func ConfigureLogger() {
|
||||
if !config.LOG_TO_FILE {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
} else {
|
||||
fmt.Println("logging to file /var/log/hyperia-server.log")
|
||||
logFile := &lumberjack.Logger{
|
||||
Filename: "/var/log/hyperia-server.log", // Path to your log file
|
||||
MaxSize: 100, // Max size in MB before rotation
|
||||
MaxBackups: 3, // Max number of old log files to keep
|
||||
MaxAge: 28, // Max number of days to retain old log files
|
||||
Compress: true, // Whether to compress old log files
|
||||
}
|
||||
log.Logger = zerolog.New(logFile).With().Timestamp().Logger()
|
||||
}
|
||||
}
|
||||
180
server/main.go
Normal file
180
server/main.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"hyperia/config"
|
||||
"hyperia/handlers"
|
||||
"hyperia/logger"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config.SetConfiguration()
|
||||
|
||||
logger.ConfigureLogger()
|
||||
|
||||
// err := handlers.InitDB()
|
||||
// if err != nil {
|
||||
// log.Fatal().Msgf("failed to connect to database: %v", err)
|
||||
// } else {
|
||||
// log.Info().Msg("successfully connected to PostgreSQL")
|
||||
// }
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Error().
|
||||
Interface("panic_reason", r).
|
||||
Bytes("stack_trace", debug.Stack()).
|
||||
Msg("panic in http goroutine")
|
||||
}
|
||||
}()
|
||||
|
||||
subdomain := ""
|
||||
host := strings.Split(r.Host, ":")[0] // remove port
|
||||
parts := strings.Split(host, ".")
|
||||
if len(parts) > 2 || (len(parts) > 1 && parts[1] == "localhost") {
|
||||
subdomain = parts[0]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/_") {
|
||||
handleAsset(w, r)
|
||||
} else if subdomain == "apply" {
|
||||
authMiddleware(handleApply)(w, r)
|
||||
} else if subdomain == "pma" {
|
||||
authMiddleware(handlePMA)(w, r)
|
||||
} else {
|
||||
handlePublic(w, r)
|
||||
}
|
||||
})
|
||||
|
||||
log.Info().Msgf("Server starting on http://localhost: %s", config.PORT)
|
||||
err := http.ListenAndServe(":"+config.PORT, nil)
|
||||
if err != nil {
|
||||
log.Fatal().Msgf("failed to start server: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handlePublic(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/login" {
|
||||
handlers.HandleLogin(w, r)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/api/applicantlogin" {
|
||||
handlers.HandleApplicantLogin(w, r)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/api/join" {
|
||||
handlers.HandleJoin(w, r)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/verify" {
|
||||
handlers.HandleVerify(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
servePublicFile(w, r)
|
||||
}
|
||||
|
||||
func handleAsset(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
filePath := filepath.Join("../ui", path)
|
||||
log.Debug().Msgf("serving asset: %s", filePath)
|
||||
http.ServeFile(w, r, filePath)
|
||||
}
|
||||
|
||||
func servePublicFile(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
|
||||
if path == "/" {
|
||||
path = "/index.html"
|
||||
} else if !strings.Contains(path, ".") {
|
||||
path = filepath.Join("/pages", path) + ".html"
|
||||
}
|
||||
|
||||
filePath := filepath.Join("../ui/public", path)
|
||||
log.Debug().Msgf("serving: %s", filePath)
|
||||
http.ServeFile(w, r, filePath)
|
||||
}
|
||||
|
||||
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie("auth_token")
|
||||
if err != nil {
|
||||
log.Warn().Msg("Unauthorized - missing auth token")
|
||||
http.Error(w, "Unauthorized - missing auth token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
jwtToken := cookie.Value
|
||||
|
||||
token, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(config.JWT_SECRET), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error authenticating jwt")
|
||||
}
|
||||
if err != nil || !token.Valid {
|
||||
http.Error(w, "Unauthorized - invalid auth token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func handleApply(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// if r.URL.Path == "/api/application-save" {
|
||||
// handlers.HandleApplicationSubmit(w, r)
|
||||
// return
|
||||
// }
|
||||
// if r.URL.Path == "/api/get-application" {
|
||||
// handlers.HandleGetApplication(w, r)
|
||||
// return
|
||||
// }
|
||||
// if r.URL.Path == "/logout" {
|
||||
// handlers.HandleLogout(w, r)
|
||||
// return
|
||||
// }
|
||||
// if r.URL.Path == "/" {
|
||||
// handlers.CheckApplicationCompleteMiddleware(w, r)
|
||||
// }
|
||||
// if r.URL.Path == "/complete" {
|
||||
// handlers.ApplicationSubmitMiddleware(w, r)
|
||||
// }
|
||||
|
||||
path := r.URL.Path
|
||||
if path == "/" {
|
||||
path = "/index.html"
|
||||
} else if !strings.Contains(path, ".") {
|
||||
path = filepath.Join("/pages", path) + ".html"
|
||||
}
|
||||
|
||||
filePath := filepath.Join("../ui/apply", path)
|
||||
log.Debug().Msgf("Serving apply subdomain: %s", filePath)
|
||||
http.ServeFile(w, r, filePath)
|
||||
}
|
||||
|
||||
func handlePMA(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
if path == "/" {
|
||||
path = "/index.html"
|
||||
} else if !strings.Contains(path, ".") {
|
||||
path = filepath.Join("/pages", path) + ".html"
|
||||
}
|
||||
|
||||
filePath := filepath.Join("../ui/pma", path)
|
||||
log.Debug().Msgf("serving pma subdomain: %s", filePath)
|
||||
http.ServeFile(w, r, filePath)
|
||||
}
|
||||
1
ui/.gitignore
vendored
Normal file
1
ui/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
63
ui/_/code/components/EmailJoinForm.js
Normal file
63
ui/_/code/components/EmailJoinForm.js
Normal file
@@ -0,0 +1,63 @@
|
||||
css(`
|
||||
email-join-form {
|
||||
display: flex
|
||||
}
|
||||
`)
|
||||
|
||||
export default class EmailJoinForm extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.querySelector('#submit-button').addEventListener('click', () => this.submitEmail());
|
||||
}
|
||||
|
||||
isValidEmail(email) {
|
||||
const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,16}$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
$(this).find('#form-message')
|
||||
.css('color', 'red')
|
||||
.text(message);
|
||||
}
|
||||
|
||||
showSuccess(message) {
|
||||
$(this).find('#form-message')
|
||||
.css('color', 'green')
|
||||
.text(message);
|
||||
}
|
||||
|
||||
clearError() {
|
||||
this.querySelector('#form-message').textContent = '';
|
||||
}
|
||||
|
||||
async submitEmail() {
|
||||
const email = this.querySelector('#email-input').value.trim();
|
||||
this.clearError();
|
||||
|
||||
if (!this.isValidEmail(email)) {
|
||||
this.showError('Please enter a valid email address.');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch('/api/join', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
this.showSuccess('Email sent.');
|
||||
} else {
|
||||
const error = await res.text();
|
||||
this.showError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("email-join-form", EmailJoinForm);
|
||||
48
ui/_/code/shared.css
Normal file
48
ui/_/code/shared.css
Normal file
@@ -0,0 +1,48 @@
|
||||
:root {
|
||||
--green: #0B5538;
|
||||
--tan: #FFDFB4;
|
||||
--red: rgb(206, 84, 61);
|
||||
--brown: #c6a476
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Canterbury';
|
||||
src: url('/_/fonts/Canterbury/Canterbury.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'CrimsonText';
|
||||
src: url('/_/fonts/CrimsonText/CrimsonText-Regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'CrimsonText';
|
||||
src: url('/_/fonts/CrimsonText/CrimsonText-Bold.ttf') format('truetype');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'CrimsonText', sans-serif;
|
||||
background-color: var(--tan);
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--green);
|
||||
transition: background .2s, padding .2s, color .2s;
|
||||
padding: 4px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--green);
|
||||
color: var(--tan);
|
||||
padding: 1em;
|
||||
box-shadow: none;
|
||||
}
|
||||
41
ui/_/code/util.js
Normal file
41
ui/_/code/util.js
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
BIN
ui/_/fonts/Canterbury/Canterbury.ttf
Executable file
BIN
ui/_/fonts/Canterbury/Canterbury.ttf
Executable file
Binary file not shown.
BIN
ui/_/fonts/CrimsonText/CrimsonText-Bold.ttf
Executable file
BIN
ui/_/fonts/CrimsonText/CrimsonText-Bold.ttf
Executable file
Binary file not shown.
BIN
ui/_/fonts/CrimsonText/CrimsonText-BoldItalic.ttf
Executable file
BIN
ui/_/fonts/CrimsonText/CrimsonText-BoldItalic.ttf
Executable file
Binary file not shown.
BIN
ui/_/fonts/CrimsonText/CrimsonText-Italic.ttf
Executable file
BIN
ui/_/fonts/CrimsonText/CrimsonText-Italic.ttf
Executable file
Binary file not shown.
BIN
ui/_/fonts/CrimsonText/CrimsonText-Regular.ttf
Executable file
BIN
ui/_/fonts/CrimsonText/CrimsonText-Regular.ttf
Executable file
Binary file not shown.
BIN
ui/_/fonts/CrimsonText/CrimsonText-SemiBold.ttf
Executable file
BIN
ui/_/fonts/CrimsonText/CrimsonText-SemiBold.ttf
Executable file
Binary file not shown.
BIN
ui/_/fonts/CrimsonText/CrimsonText-SemiBoldItalic.ttf
Executable file
BIN
ui/_/fonts/CrimsonText/CrimsonText-SemiBoldItalic.ttf
Executable file
Binary file not shown.
93
ui/_/fonts/CrimsonText/OFL.txt
Executable file
93
ui/_/fonts/CrimsonText/OFL.txt
Executable file
@@ -0,0 +1,93 @@
|
||||
Copyright 2010 The Crimson Text Project Authors (https://github.com/googlefonts/Crimson)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
5
ui/_/icons/hamburger.svg
Normal file
5
ui/_/icons/hamburger.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="86" height="59" viewBox="0 0 86 59" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="86" height="11" rx="3" fill="#0B5538"/>
|
||||
<rect y="48" width="86" height="11" rx="3" fill="#0B5538"/>
|
||||
<rect y="24" width="86" height="11" rx="3" fill="#0B5538"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 276 B |
60
ui/_/icons/left_plant.svg
Normal file
60
ui/_/icons/left_plant.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 20 KiB |
29
ui/_/icons/logo.svg
Normal file
29
ui/_/icons/logo.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<svg width="115" height="161" viewBox="0 0 115 161" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M46.9604 1.12722C52.2219 1.12677 58.2104 0.840336 72.1031 1.12852C75.0618 1.12852 73.7524 1.12818 80.0255 1.12722L103.502 1.12465C105.494 1.12465 111.55 1.14667 113.265 1.12465H113.272C113.69 1.12465 113.865 1.46049 113.895 1.52504C113.942 1.62458 113.96 1.72053 113.968 1.76851C113.986 1.87785 113.993 2.01051 113.997 2.14308C114.004 2.41925 113.999 2.8369 113.989 3.40304C113.971 4.54588 113.934 6.35911 113.934 9.06029V9.079C113.792 14.1294 113.829 19.1869 113.85 24.2593C113.87 29.3271 113.874 34.4079 113.664 39.4834C113.515 43.0922 113.22 50.9277 112.789 58.7839C112.36 66.6204 111.794 74.5402 111.095 78.2878C105.115 110.392 90.1533 132.013 73.5183 146.653C71.9113 148.067 63.2473 156.263 58.2387 159.602L57.8719 159.846L57.505 159.602C55.006 157.936 51.1892 155.114 47.7973 152.508C44.4221 149.915 41.4054 147.488 40.5411 146.623C24.9152 130.998 11.6773 110.712 4.65186 78.3063C3.98166 75.2147 3.15915 67.0238 2.48325 58.8801C1.80582 50.718 1.26962 42.5202 1.17874 39.3782C0.872797 28.8016 1.06241 18.1943 1.01793 7.64014C1.01448 6.82169 1.00955 5.39907 1.04764 4.17929C1.06659 3.57228 1.09666 2.99823 1.14515 2.56931C1.16883 2.3599 1.20014 2.15464 1.2459 1.99002C1.26736 1.91284 1.30358 1.80049 1.3686 1.6936C1.41416 1.6187 1.60552 1.33065 1.9918 1.33065C25.4683 1.33065 34.3097 1.27989 38.4797 1.22926C42.6326 1.17885 42.1643 1.12764 46.9604 1.12722Z" fill="#BD1C03" stroke="#EEC88E"/>
|
||||
<path d="M70.4394 1.2207L44.3633 1.22074L44.6486 149.464L56.2214 158.392L57.8746 159.714L59.8586 158.392L70.4394 149.464V1.2207Z" fill="#233779"/>
|
||||
<path d="M44.3633 0.720735C44.3633 1.05407 44.3633 1.3874 44.3633 1.72074C50.2887 1.72073 56.2141 1.72072 62.1395 1.72071C64.9062 1.72071 67.6728 1.72071 70.4394 1.7207L69.9394 1.2207C69.9394 50.6352 69.9394 100.05 69.9394 149.464L70.117 149.082C66.59 152.058 63.0631 155.034 59.5361 158.01L59.5812 157.976C59.1999 158.23 58.8185 158.484 58.4372 158.738C58.1572 158.925 57.8773 159.112 57.5973 159.298L58.187 159.324C57.6359 158.883 57.0848 158.442 56.5337 158.001C54.3207 156.294 52.1146 154.592 49.9085 152.89C48.257 151.616 46.6055 150.342 44.954 149.068L45.1486 149.463C45.0649 105.974 44.9812 62.4851 44.8975 18.996C44.8861 13.0706 44.8747 7.14518 44.8633 1.21977C44.5299 1.22041 44.1966 1.22106 43.8633 1.2217C43.8747 7.14711 43.8861 13.0725 43.8975 18.9979C43.9812 62.487 44.0649 105.976 44.1486 149.465L44.1491 149.71L44.3432 149.86C45.9947 151.134 47.6462 152.408 49.2977 153.682C51.5038 155.384 53.7099 157.086 55.916 158.788C56.4601 159.223 57.0112 159.664 57.5623 160.105L57.8478 160.333L58.152 160.13C58.432 159.944 58.7119 159.757 58.9919 159.57C59.3732 159.316 59.7546 159.062 60.1359 158.808L60.181 158.774C63.7079 155.798 67.2349 152.822 70.7618 149.846L70.9394 149.696V149.464C70.9394 100.05 70.9394 50.6352 70.9394 1.2207V0.720703L70.4394 0.720703C67.6728 0.720707 64.9062 0.72071 62.1395 0.720713C56.2141 0.720721 50.2887 0.720728 44.3633 0.720735ZM44.3633 1.72074L44.3633 0.720735L43.8623 0.720736L43.8633 1.2217L44.8633 1.21977L44.3633 1.72074Z" fill="#EEC88E"/>
|
||||
<path d="M4.17083 75.0672H111.439L112.762 62.1718L113.246 49.2764L1.83739 49.2764L2.65467 62.1718L4.17083 75.0672Z" fill="#223778" stroke="#EEC88E"/>
|
||||
<path d="M48.595 49.1708C45.4425 48.7454 44.7529 47.1751 43.702 46.03C42.6512 44.885 42.2572 44.9504 42.2572 43.2164C42.2572 41.6786 44.3588 40.7299 43.2752 39.3885C42.1915 38.0472 40.1063 39.3067 41.4691 40.6645C41.7482 40.9426 40.7466 41.417 40.878 42.0058C40.4511 41.3515 39.8601 40.828 39.8601 40.3701C39.8601 39.912 40.8452 38.8651 39.9257 38.0144C39.0063 37.1639 37.1673 39.4867 37.8897 39.7484C38.6122 40.0102 38.7435 40.1083 38.7435 40.8281C38.7435 41.5478 38.6779 42.0059 38.9734 42.5621C38.0539 41.7768 38.1196 41.7442 37.8241 41.0571C37.5285 40.37 36.609 40.1082 35.8209 41.2534C35.0328 42.3984 34.7701 42.6601 35.3612 43.1836C35.9522 43.707 36.5433 42.4311 37.3643 43.6089C38.1853 44.7868 38.842 46.9133 37.7912 46.6516C36.7404 46.3898 36.0836 46.7824 36.5105 48.1892C36.9374 49.596 38.1196 49.138 38.7435 48.8108C39.3675 48.4837 39.4003 48.3855 40.3854 48.8435C41.3706 49.3015 42.3886 50.1522 46.7231 57.4805C51.0578 64.8087 50.598 49.6939 48.595 49.1708Z" fill="#EFC88F"/>
|
||||
<path d="M58.8551 71.5038C55.4399 74.5306 51.2861 69.754 48.8557 71.3239C50.2184 71.7167 50.6064 72.8436 48.4934 72.8436C44.1513 72.8436 45.4516 74.8791 43.0366 76.5912C44.4995 76.4292 44.8598 76.0456 45.1497 75.6892C45.5331 77.6863 44.0351 77.1698 44.7317 80.1305C45.1497 78.7656 45.8695 78.6736 45.8695 78.6736C46.45 83.8325 49.1329 84.511 48.772 87.5801C49.817 86.5617 49.9795 89.1295 47.9594 89.523C45.9392 89.9165 44.0584 89.8702 42.8974 88.7598C41.7364 87.6495 39.2287 90.009 40.9469 90.1711C42.6652 90.3331 42.5491 90.8879 43.5476 91.0036C42.4795 91.2814 42.1428 91.9638 41.005 91.1075C39.8672 90.2517 38.323 92.9241 39.7394 93.3863C41.1559 93.8492 41.7016 92.6575 43.3502 92.3917C42.2937 93.1668 42.5258 93.9762 41.5158 93.7335C40.5058 93.4908 39.0545 93.8605 40.2503 95.2141C41.4461 96.5672 42.3052 95.4919 42.8974 95.0521C43.4895 94.6124 43.8266 94.7202 44.1513 94.127C43.8491 94.738 42.2125 95.318 43.176 96.255C44.1397 97.1921 45.5097 97.2152 45.9276 95.6764C46.3456 94.1382 46.4849 93.2243 48.5399 93.2243C50.5949 93.2243 52.2435 94.127 52.2435 92.4492C52.2435 90.7722 52.6075 90.2094 53.6255 90.7411C53.1411 89.5712 53.765 88.9252 53.1903 88.1891C53.6337 88.3855 53.8799 88.7618 53.8799 88.7618C54.6353 87.1258 53.4202 85.1954 52.9605 84.9177C53.765 84.3939 54.2247 84.4104 54.2247 84.4104C53.0426 84.1162 51.7455 81.8585 51.4992 80.3699C51.4007 79.7648 52.8127 80.3864 53.9292 81.2369C55.0457 82.0873 59.7087 83.277 61.2874 80.8705C62.8664 78.4647 60.8341 70.2341 58.8551 71.5038Z" fill="#EFC88F"/>
|
||||
<path d="M68.0034 56.3075C65.5403 61.4112 64.7193 62.2292 64.8835 65.0103C65.0477 67.791 69.4153 69.9832 70.7286 73.2877C72.042 76.5923 75.4245 77.4096 75.6216 73.3201C75.8187 69.2306 68.791 66.8427 68.791 61.7713C68.791 56.7003 73.5174 54.2332 74.4392 52.2836C74.635 53.4448 74.2871 53.8606 74.0781 54.5083C74.0781 54.5083 83.8965 50.3534 82.7147 45.7078C83.503 46.4439 83.6994 47.3764 83.6994 47.8345C84.0777 47.36 86.3268 43.7776 83.7656 40.8007C81.2043 37.8235 78.9711 40.3754 78.9711 42.0766C78.9711 43.7779 81.9919 43.8433 81.9919 42.731C81.9919 41.8847 80.9716 41.9517 80.6455 42.731C80.0219 41.6186 81.861 39.8519 82.9442 41.0625C84.0281 42.273 84.0612 44.5958 81.9265 44.5958C80.8836 44.5958 78.3561 44.5715 78.0843 41.2261C76.4721 44.4822 81.0727 44.9034 81.0727 47.1805C81.0727 49.8959 78.3144 50.7138 78.1504 48.3255C78.6424 48.7181 80.1535 47.6385 78.774 46.1335C77.3952 44.6285 76.9072 43.7587 76.7379 42.6656C75.2751 44.6111 80.9411 48.9472 73.8487 52.2843C75.6547 50.2886 76.1302 48.2261 75.8518 46.7225C75.8518 46.7225 74.998 48.2602 73.9472 48.0639C75.7856 44.2687 74.3671 41.7119 78.1174 38.7396C76.5547 38.8542 75.7618 39.8589 73.8811 39.1649C75.4166 38.8794 76.806 37.9653 78.1828 37.5945C74.1112 36.2531 73.2899 31.6073 77.8548 30.2988C75.7532 32.8181 76.5408 34.3557 77.3622 35.3045C77.3952 33.2433 78.6424 33.1779 79.1351 33.2433C78.0843 34.7156 77.9858 35.7298 80.219 36.8422C82.4522 37.9545 82.5831 38.4126 82.5831 38.4126C82.5831 38.4126 83.47 36.9731 84.3892 37.3656C84.0936 37.7583 83.552 38.069 83.9296 38.6906C84.3072 39.3123 90.9896 47.3768 81.3029 52.6444C82.8457 52.35 83.634 53.0044 83.634 53.0044C83.634 53.0044 81.3029 53.2661 79.7263 54.4112C82.9773 55.0328 83.6525 54.3818 85.0789 53.3315C83.6075 56.7882 78.4784 55.5562 76.3775 56.8649C79.2012 57.2248 79.8255 59.5476 82.1566 58.5007C78.9089 62.891 76.2789 55.7525 72.2728 58.5333C74.9001 58.4352 75.2936 59.3513 75.9179 59.5476C74.7024 60.1365 69.941 59.0568 70.6638 62.5902C71.7146 61.5105 72.9625 61.2815 72.9625 61.2815C72.9625 61.2815 70.8609 63.4735 72.4044 64.9131C73.9472 66.3526 76.7062 68.4463 76.9356 69.9839C77.4938 68.9046 78.249 68.1521 78.249 68.1521C78.249 68.1521 78.249 70.704 77.8879 73.9431C77.5268 77.1815 73.8652 80.8458 70.0237 77.28C70.3028 76.1512 69.6785 74.2043 69.3505 73.8445C70.8278 76.3965 69.0549 79.4392 69.285 81.1731C68.8578 80.7803 68.6938 79.668 68.6938 79.668C65.859 83.7013 69.533 84.2422 72.2073 82.4812C71.9983 82.9441 71.2881 83.9539 72.0757 83.8884C72.864 83.8229 74.7361 83.6265 74.7361 84.9022C74.7361 86.1785 70.894 89.9737 70.894 92.7221C70.894 95.4705 71.8786 95.3065 71.058 96.5173C70.2373 97.7275 71.8132 99.2981 70.894 99.9852C69.9741 100.672 68.2666 99.8866 68.5622 99.265C68.8578 98.6434 69.2519 98.0548 69.1534 97.4987C69.0549 96.9425 69.58 96.844 69.416 96.2224C68.9398 96.6972 68.7262 97.8254 68.2011 97.9239C67.6754 98.0218 68.0695 100.051 66.8547 100.149C65.6397 100.246 64.1948 98.2182 65.2128 97.9894C66.2308 97.7599 66.0666 97.5641 66.6246 96.5821C67.1834 95.6007 66.5591 96.4842 65.8039 96.8116C65.0486 97.1383 65.2456 98.0548 64.5889 98.0548C63.9321 98.0548 62.6843 96.1569 63.6038 95.5683C64.5232 94.9791 65.2456 95.2086 65.8367 94.6848C66.5671 94.4507 66.8561 94.4256 67.0187 93.9977C66.7053 94.0903 65.8696 94.7827 64.9829 94.9136C64.0962 95.0446 64.4575 92.1005 65.2784 92.1005C66.0994 92.1005 66.0009 93.2782 66.8878 92.9509C67.7739 92.6236 69.449 88.4686 69.2189 87.5851C68.9894 86.7023 67.9055 87.5527 67.9386 88.0758C67.5114 87.9455 65.18 87.5851 64.6217 87.2584C64.7859 87.9455 65.6396 88.3377 65.6396 88.3377C63.6365 88.6815 62.2902 87.3239 62.323 86.3094C61.8304 87.88 61.4035 86.6037 60.09 88.0103C60.3527 85.5899 58.678 84.3136 58.678 84.3136C58.678 84.3136 58.7437 86.4073 57.0689 87.5527C56.8062 84.1503 53.6209 81.0746 56.9047 78.9154C55.427 78.6211 55.3942 77.6398 55.3942 77.6398C55.3942 77.6398 61.8304 76.298 60.7467 72.1106C61.1737 74.1388 59.8601 75.3821 59.8601 75.3821C59.8601 75.3821 58.7108 72.307 57.0688 71.2602C55.427 70.2127 54.4418 68.8391 54.4418 68.8391C54.4418 68.8391 54.3433 70.671 54.7373 71.0307C53.0625 70.1479 53.0625 67.5304 53.0625 67.5304C53.0625 67.5304 53.1282 69.886 52.3073 70.3767C52.3073 68.4787 51.0594 66.4507 51.0594 66.4507C51.0594 66.4507 51.2893 68.3485 50.9281 68.6751C50.7967 67.301 47.7756 63.7679 47.2174 62.3938C46.6591 61.0196 45.3127 56.8319 46.2651 56.0468C47.2174 55.2615 54.1789 50.3868 56.4446 51.4665C58.7104 52.5461 70.0065 51.6955 68.0041 56.3085L68.0034 56.3075Z" fill="#EFC88F"/>
|
||||
<path d="M43.5826 62.7664C42.0228 62.6682 41.9078 62.57 41.6123 62.2756C41.8914 63.0935 40.9063 64.5657 39.3629 63.5188C37.8195 62.4719 36.9493 61.5067 36.2433 63.0772C35.5373 64.6476 36.8179 65.0565 37.5075 64.9093C38.1971 64.7621 38.4763 65.4491 39.4121 65.2855C38.4435 65.8581 37.9837 66.267 36.391 65.8581C34.7984 65.4491 34.3715 68.6719 36.2104 68.8187C38.0494 68.9661 37.1463 67.9028 38.1315 67.5106C39.1166 67.1178 39.9047 66.4796 39.9047 66.4796C39.9047 66.4796 39.0181 66.9703 39.0017 68.1157C38.9852 69.2604 37.606 69.4568 38.115 70.0785C38.624 70.7001 39.9211 71.2728 40.7255 70.7166C41.5301 70.1605 41.0539 69.2115 40.8897 68.6719C40.7255 68.1316 41.4316 67.4286 42.269 67.5106C43.1063 67.592 43.0243 68.2301 42.2854 68.7539C41.5465 69.277 44.4034 69.9965 45.0438 69.4079C45.6841 68.8187 44.3378 67.3466 45.8483 66.8725C47.3589 66.398 48.5082 66.889 48.5082 66.889C47.0274 65.8044 48.3347 65.8041 49.871 65.3837C51.1143 65.0435 51.71 64.1568 53.9429 65.1383C55.6833 65.8254 55.4863 65.5309 55.6177 64.3204C55.7491 63.1099 56.8984 63.1099 57.1612 61.6377C57.4238 60.1655 58.2776 59.7401 58.2776 55.7814C58.2776 51.8227 53.2509 53.5706 51.053 55.8141C48.8553 58.0578 49.3784 59.8387 47.999 60.738C45.8155 62.1615 45.1424 62.8645 43.5826 62.7664Z" fill="#EFC88F"/>
|
||||
<path d="M58.5022 31.8522C58.8273 30.7418 57.1322 28.7754 57.1322 28.7754C57.1322 28.7754 59.4774 28.9836 61.0331 30.7418C62.5656 29.5389 64.5625 29.1687 64.5625 29.1687C64.5625 29.1687 64.0518 29.909 63.8892 30.3486C66.5133 30.3949 66.188 30.8344 66.9543 30.8344C65.8165 31.4821 65.5146 31.6672 65.5146 31.6672C68.603 32.5 71.9234 36.3634 71.9234 36.3634C71.9234 36.3634 70.6233 35.9007 68.394 36.3171C73.2003 37.7514 72.1555 40.0301 73.6183 41.3835C70.9248 40.7589 70.1121 40.0648 69.1604 40.6663C71.1338 41.129 71.9928 43.0028 71.9928 43.0028C71.9928 43.0028 71.645 42.7484 71.436 42.7021C71.7838 43.1185 73.8974 49.2259 71.4823 52.2793C71.6913 49.6882 69.9494 48.971 69.9494 48.971C69.9494 48.971 70.3911 54.0606 69.4852 55.3098C69.3807 54.905 69.1373 54.396 68.9746 54.2457C68.9746 54.2457 69.1373 57.068 67.6044 58.086C67.4887 54.9397 66.8148 54.639 66.4901 54.454C63.5758 58.6759 62.9837 58.8378 61.846 60.0524C62.3336 57.7621 62.2175 56.7442 61.962 56.3278C59.7561 58.5718 61.6137 59.1964 58.6416 61.718C58.7577 58.9882 58.1307 58.2248 58.1307 58.2248C58.1307 58.2248 58.3165 59.8905 56.9465 61.0009C56.7143 57.9009 56.2731 57.2069 56.575 55.333C55.3211 56.8831 55.0889 57.4845 55.0889 57.4845C54.9411 56.7157 55.4372 55.1479 55.1586 54.5927C54.5549 56.8136 52.9294 57.2532 52.9294 57.2532C52.9294 57.2532 53.7653 54.5696 53.2545 53.5749C51.7917 55.4718 52.4853 57.0289 49.7433 58.0596C50.2524 56.9472 50.2852 55.9657 50.2852 55.9657C50.2852 55.9657 48.4627 58.5339 48.1015 59.9408C47.7039 58.5707 48.2657 57.258 48.2657 57.258C47.9174 57.5009 46.6073 58.8775 46.3282 60.1862C45.6222 57.9778 45.9506 56.7673 45.9506 56.7673C45.9506 56.7673 45.5065 56.958 44.949 57.438C45.573 54.9842 46.2297 54.3299 46.6073 53.6919C45.54 54.2644 45.1296 54.7879 44.7356 55.1151C44.5057 53.8228 45.3365 50.8451 48.0765 49.9428C46.7529 48.5547 46.5671 47.6294 46.5903 46.6809C47.8675 48.3234 49.0981 48.7629 49.8179 48.069C50.5378 47.3749 51.1415 47.4212 52.047 46.4496C52.9526 45.4779 54.2297 45.6629 55.1818 44.8302C56.1338 43.9974 58.0349 42.2084 57.7394 40.8998C57.4729 41.5097 57.0267 41.162 57.1355 41.8586C56.6617 41.688 56.5799 41.9023 56.6526 42.3505C56.1336 42.1633 56.3471 42.6127 56.1139 42.5192C55.0959 42.1266 54.4884 43.2553 54.1929 43.9424C53.8973 44.6294 53.4869 44.564 52.5838 44.204C51.6807 43.8442 51.9434 43.4843 52.2718 43.288C52.6002 43.0917 52.0091 42.3556 52.0091 42.1102C52.436 42.4046 53.2241 42.192 53.4376 42.0284C51.3852 40.6542 49.2507 41.0959 49.7433 43.1081C48.6432 42.8136 48.8731 41.7175 48.8731 41.7175C48.8731 41.7175 45.6057 42.4046 45.7042 38.9858C46.7879 40.8996 48.9388 40.9487 50.0224 40.5562C51.1061 40.1635 52.7405 40.6931 54.0167 41.444C54.3547 41.6428 54.423 41.2181 54.3515 41.0537C53.7907 39.7644 52.0594 38.1654 51.5582 39.9628C51.0585 38.97 50.6135 39.7383 50.1374 39.3784C49.6612 39.0185 48.3148 37.8734 48.9881 36.8265C49.6612 35.7796 49.9896 36.2049 51.4838 34.9943C52.9778 33.7838 53.4095 33.3767 54.8169 33.195C55.2291 33.1417 55.2726 32.2031 55.1452 31.9518C55.9713 32.0919 56.7871 32.3444 56.7871 32.3444C56.7871 32.3444 56.6269 30.8043 55.2148 31.2296C56.1733 30.0958 57.6067 30.9181 58.5022 31.8522Z" fill="#EFC88F"/>
|
||||
<path d="M69.3805 101.639C68.1498 100.61 68.7536 98.7595 69.5081 99.4651C70.2627 100.171 68.3938 100.344 69.3805 101.639Z" fill="#9C0001"/>
|
||||
<path d="M64.9431 100.86C63.933 99.3797 65.1869 97.8528 65.779 98.7092C66.3711 99.5649 64.4903 100.097 64.9431 100.86Z" fill="#9C0001"/>
|
||||
<path d="M62.4194 98.0807C61.955 96.3223 63.3134 95.5585 63.8243 96.3454C64.3351 97.1317 62.164 97.467 62.4194 98.0807Z" fill="#9C0001"/>
|
||||
<path d="M62.7776 94.6558C61.8836 92.4001 65.3318 91.7639 64.879 93.0594C64.4262 94.3549 63.0214 93.1401 62.7776 94.6558Z" fill="#9C0001"/>
|
||||
<path d="M49.8291 36.0225C49.4752 36.9792 49.4167 38.0393 49.4922 38.7988C48.9973 38.2747 48.537 37.5271 48.9893 36.8232C49.2919 36.3526 49.5286 36.1804 49.8291 36.0225Z" fill="black"/>
|
||||
<path d="M49.8291 36.0225C49.4752 36.9792 49.4167 38.0393 49.4922 38.7988C48.9973 38.2747 48.537 37.5271 48.9893 36.8232C49.2919 36.3526 49.5286 36.1804 49.8291 36.0225Z" stroke="#EFC88F"/>
|
||||
<path d="M39.3693 89.7329C38.812 88.0327 41.8538 87.5467 41.343 88.9691C40.8321 90.3922 39.8569 88.2986 39.3693 89.7329Z" fill="#9C0001"/>
|
||||
<path d="M38.4649 93.0275C36.5609 91.6163 39.9162 89.6383 40.1601 91.3273C40.4038 93.0162 37.8265 91.3504 38.4649 93.0275Z" fill="#9C0001"/>
|
||||
<path d="M39.6464 96.9784C38.2262 95.5877 40.0569 93.7638 40.7793 94.5653C41.5017 95.3668 39.5397 95.882 39.6464 96.9784Z" fill="#9C0001"/>
|
||||
<path d="M43.1582 97.7486C42.2223 96.4968 43.1746 95.0902 43.7903 95.7283C44.406 96.3658 43.0596 96.8816 43.1582 97.7486Z" fill="#9C0001"/>
|
||||
<path d="M36.6484 38.4928C37.2397 36.565 40.088 37.5596 39.2014 38.4593C38.3147 39.359 38.3397 37.3151 36.6484 38.4928Z" fill="#9C0001"/>
|
||||
<path d="M41.0391 38.6255C42.1884 38.0125 43.0359 39.3311 42.2465 39.6203C41.457 39.9094 42.1072 38.88 41.0391 38.6255Z" fill="#9C0001"/>
|
||||
<path d="M34.0664 41.5423C34.3915 39.4024 37.0386 40.1542 36.3536 41.0911C35.6685 42.0281 35.4596 40.1889 34.0664 41.5423Z" fill="#9C0001"/>
|
||||
<path d="M35.6055 47.0957C35.8681 49.3205 37.625 48.6334 37.4444 47.9463C37.2638 47.2593 36.6891 48.0445 35.6055 47.0957Z" fill="#9C0001"/>
|
||||
<path d="M35.2945 64.9888C34.26 63.0913 36.6409 62.0116 36.8379 62.8787C37.0349 63.7457 35.508 63.2058 35.2945 64.9888Z" fill="#9C0001"/>
|
||||
<path d="M35.4449 69.8825C33.9179 68.3119 35.4267 66.783 36.1181 67.2161C37.1514 67.8629 35.527 68.3609 35.4449 69.8825Z" fill="#9C0001"/>
|
||||
<path d="M39.8151 72.2087C38.4688 72.0454 38.3021 70.004 39.2405 70.0496C40.5869 70.1151 39.027 71.1288 39.8151 72.2087Z" fill="#9C0001"/>
|
||||
<path d="M41.8594 69.9137C43.255 71.1893 44.3331 69.0943 43.5998 68.8179C42.5161 68.4086 43.3535 69.8813 41.8594 69.9137Z" fill="#9C0001"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 17 KiB |
60
ui/_/icons/right_plant.svg
Normal file
60
ui/_/icons/right_plant.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 20 KiB |
4
ui/_/icons/x.svg
Normal file
4
ui/_/icons/x.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="241" height="241" viewBox="0 0 241 241" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="26.1953" y="0.000366211" width="303" height="37" transform="rotate(45 26.1953 0.000366211)" fill="#0B5538"/>
|
||||
<rect y="214.253" width="303" height="37" transform="rotate(-45 0 214.253)" fill="#0B5538"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 318 B |
19
ui/apply/components/NavBar.js
Normal file
19
ui/apply/components/NavBar.js
Normal file
@@ -0,0 +1,19 @@
|
||||
css(`
|
||||
nav-bar {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
`)
|
||||
|
||||
export default class NavBar extends HTMLElement {
|
||||
|
||||
connectedCallback() {
|
||||
|
||||
this.innerHTML += /* html */ `
|
||||
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("nav-bar", NavBar)
|
||||
581
ui/apply/components/Questionnaire.js
Normal file
581
ui/apply/components/Questionnaire.js
Normal file
@@ -0,0 +1,581 @@
|
||||
css(`
|
||||
questionnaire- {
|
||||
display: grid;
|
||||
grid-template-columns: 250px 1fr;
|
||||
gap: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
margin-top: 17vh;
|
||||
}
|
||||
|
||||
.toc {
|
||||
position: sticky;
|
||||
top: 17vh;
|
||||
align-self: start;
|
||||
background: var(--tan);
|
||||
padding: 10px;
|
||||
border: 1px solid var(--brown);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.toc h2 {
|
||||
margin-top: 0;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.toc a {
|
||||
display: block;
|
||||
margin: 5px 0;
|
||||
text-decoration: none;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.toc a.active {
|
||||
font-weight: bold;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
section {
|
||||
border: 1px solid var(--brown);
|
||||
margin-bottom: 2vh;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
background: none;
|
||||
user-select: none;
|
||||
transition: background 0.2s, border 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
scroll-margin-top: 20px;
|
||||
padding-bottom: 40px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
outline: 1px solid #ddbb36;
|
||||
border: 1px solid #c4a52f
|
||||
}
|
||||
|
||||
/* Normal state */
|
||||
form button {
|
||||
transition: all 0.3s ease;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Loading: morph into circle + pulse */
|
||||
form button.loading {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
/* Success state */
|
||||
button.success {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
width: auto;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.1); opacity: 0.7; }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
`)
|
||||
|
||||
export default class Questionnaire extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.pages = [
|
||||
{ title: "Demographic Information", slug: "demographic", content: this.demographicContent() },
|
||||
{ title: "Contact Information", slug: "contact", content: this.contactContent() },
|
||||
{ title: "Personal History", slug: "personal-history", content: this.personalHistoryContent() },
|
||||
{ title: "Ancestry", slug: "ancestry", content: this.ancestryContent() },
|
||||
{ title: "Skills", slug: "skills", content: this.skillsContent() },
|
||||
{ title: "Interest", slug: "interest", content: this.interestContent() },
|
||||
{ title: "Criminal Record", slug: "criminal", content: this.criminalContent() },
|
||||
{ title: "Political Perspectives", slug: "political", content: this.politicalContent() },
|
||||
{ title: "Religious/Philosophical Views", slug: "religious", content: this.religiousContent() },
|
||||
{ title: "Federal Employment", slug: "federal-employment", content: this.federalEmploymentContent() },
|
||||
{ title: "Social Perspectives", slug: "social", content: this.socialContent() },
|
||||
];
|
||||
this.pageHTML = {};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.fetchData();
|
||||
}
|
||||
|
||||
async fetchData() {
|
||||
try {
|
||||
const res = await fetch("/api/get-application");
|
||||
if (!res.ok) throw new Error(`HTTP error ${res.status}`);
|
||||
this.data = await res.json();
|
||||
console.log(this.data)
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch questionnaire:", err);
|
||||
this.data = {};
|
||||
}
|
||||
this.render()
|
||||
}
|
||||
|
||||
render() {
|
||||
this.Layout();
|
||||
this.Forms();
|
||||
this.setupScrollSpy();
|
||||
this.populateFormData();
|
||||
this.markSectionsComplete();
|
||||
}
|
||||
|
||||
Layout() {
|
||||
this.innerHTML = /* html */`
|
||||
<nav class="toc">
|
||||
<h2>Contents</h2>
|
||||
${this.pages.map(p => `
|
||||
<a href="#${p.slug}" data-slug="${p.slug}">${p.title}</a>
|
||||
`).join("")}
|
||||
<a href="#submit">Submit</a>
|
||||
</nav>
|
||||
<div class="sections">
|
||||
${this.pages.map(p => /* html */`
|
||||
<section id="${p.slug}" class="section-content">
|
||||
<h1 style="margin-left: 3rem; margin-top: 3rem">${p.title}</h1>
|
||||
<div class="form-container">
|
||||
<form></form>
|
||||
</div>
|
||||
</section>
|
||||
`).join("")}
|
||||
<section id="submit" class="section-content">
|
||||
<h1 style="margin-left: 3rem; margin-top: 3rem">Submit Application</h1>
|
||||
<button style="margin-left: 3rem; margin-top: 3rem; background-color: gray" disabled>Submit</button>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
Forms() {
|
||||
this.pages.forEach(page => {
|
||||
const form = this.querySelector(`#${page.slug} form`);
|
||||
if (form) form.innerHTML = page.content;
|
||||
form.innerHTML += `<button type="submit">Save</button>`
|
||||
form.addEventListener("submit", this.onFormSubmit)
|
||||
});
|
||||
}
|
||||
|
||||
populateFormData() {
|
||||
Object.entries(this.data).forEach(([key, value]) => {
|
||||
const fields = document.querySelectorAll(`[name="${key}"]`);
|
||||
if (!fields.length) return;
|
||||
|
||||
fields.forEach(field => {
|
||||
if (field.type === "checkbox" || field.type === "radio") {
|
||||
if (Array.isArray(value)) {
|
||||
field.checked = value.includes(field.value);
|
||||
} else {
|
||||
field.checked = field.value === value;
|
||||
}
|
||||
} else if (field.tagName === "SELECT" && field.multiple && Array.isArray(value)) {
|
||||
Array.from(field.options).forEach(opt => {
|
||||
opt.selected = value.includes(opt.value);
|
||||
});
|
||||
} else {
|
||||
field.value = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
markSectionsComplete() {
|
||||
let allFormsComplete = true
|
||||
let forms = document.querySelectorAll("form")
|
||||
for(let i =0; i<forms.length; i++) {
|
||||
let form = forms[i]
|
||||
if(form.checkValidity()) {
|
||||
form.closest("section").style.backgroundColor = "#0080003d"
|
||||
let toc = document.querySelector(`[data-slug=${form.closest("section").id}]`)
|
||||
if(!toc.innerHTML.includes("✓"))
|
||||
toc.innerHTML += " ✓"
|
||||
} else {
|
||||
allFormsComplete = false
|
||||
}
|
||||
}
|
||||
|
||||
if(allFormsComplete) {
|
||||
let button = document.querySelector("section#submit button")
|
||||
button.style.backgroundColor = ""
|
||||
button.removeAttribute("disabled")
|
||||
button.addEventListener("click", () => window.location.href = "/complete")
|
||||
}
|
||||
}
|
||||
|
||||
setupScrollSpy() {
|
||||
const tocLinks = this.querySelectorAll(".toc a");
|
||||
const sections = this.querySelectorAll(".section-content");
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const id = entry.target.id;
|
||||
history.replaceState(null, "", `#${id}`);
|
||||
tocLinks.forEach(link => {
|
||||
link.classList.toggle("active", link.dataset.slug === id);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, {
|
||||
rootMargin: "-50% 0px -50% 0px", // middle of screen
|
||||
threshold: 0
|
||||
});
|
||||
|
||||
sections.forEach(section => observer.observe(section));
|
||||
}
|
||||
|
||||
onFormSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
let form = e.target
|
||||
let button = form.querySelector('button[type="submit"]');
|
||||
|
||||
if (!form.checkValidity()) {
|
||||
// Let the browser display native validation errors
|
||||
return;
|
||||
}
|
||||
|
||||
const showButtonLoading = () => {
|
||||
button.classList.add('loading');
|
||||
button.disabled = true;
|
||||
button.textContent = '';
|
||||
}
|
||||
|
||||
const showButtonError = () => {
|
||||
button.classList.remove('loading');
|
||||
button.disabled = false;
|
||||
button.textContent = 'Save';
|
||||
if(!form.querySelector("p .error")) {
|
||||
form.parentElement.appendChild(html(`<p class="error" style="color: red">There has been an error. Please try again.</p>`))
|
||||
}
|
||||
}
|
||||
|
||||
const showButtonSuccess = () => {
|
||||
if(form.parentElement.querySelector("p")) {
|
||||
form.parentElement.querySelector("p").remove()
|
||||
}
|
||||
button.classList.remove('loading');
|
||||
button.classList.add('success');
|
||||
button.textContent = 'Success!';
|
||||
console.log(this)
|
||||
this.markSectionsComplete()
|
||||
}
|
||||
|
||||
showButtonLoading()
|
||||
|
||||
// Collect form data
|
||||
const data = new FormData(form);
|
||||
const values = {};
|
||||
data.forEach((v, k) => {
|
||||
if (values[k]) {
|
||||
// Already an array? push new value
|
||||
if (Array.isArray(values[k])) {
|
||||
values[k].push(v);
|
||||
} else {
|
||||
values[k] = [values[k], v];
|
||||
}
|
||||
} else {
|
||||
values[k] = v;
|
||||
}
|
||||
});
|
||||
|
||||
fetch('/api/application-save', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(values)
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Server error: ${res.status}`);
|
||||
}
|
||||
return res.text();
|
||||
})
|
||||
.then(responseData => {
|
||||
console.log(form, form.parentElement)
|
||||
showButtonSuccess()
|
||||
console.log('Server response:', responseData);
|
||||
})
|
||||
.catch(err => {
|
||||
showButtonError()
|
||||
console.error('Submission error:', err);
|
||||
});
|
||||
|
||||
console.log("Form submitted:", values);
|
||||
}
|
||||
|
||||
demographicContent() {
|
||||
return /* html */`
|
||||
<label for="firstName">First Name *</label>
|
||||
<input type="text" name="firstName" class="form-control" required>
|
||||
<br><br>
|
||||
|
||||
<label for="lastName">Last Name *</label>
|
||||
<input type="text" name="lastName" class="form-control" required>
|
||||
<br><br>
|
||||
|
||||
<label for="age">Age *</label>
|
||||
<input type="text" name="age" class="form-control" required>
|
||||
<br><br>
|
||||
|
||||
<label for="gender">Gender *</label>
|
||||
<select name="gender" class="form-control" required>
|
||||
<option value="">Select...</option>
|
||||
<option value="male">Male</option>
|
||||
<option value="female">Female</option>
|
||||
</select>
|
||||
<br><br>
|
||||
|
||||
<label for="ethnicity">Type of Application *</label>
|
||||
<select name="applicationType" class="form-control" required>
|
||||
<option value="">Select...</option>
|
||||
<option value="single">Single</option>
|
||||
<option value="couple">Couple</option>
|
||||
<option value="family">Family</option>
|
||||
</select>
|
||||
<br><br>
|
||||
|
||||
<label for="sexualOrientation">Sexual Orientation *</label>
|
||||
<select name="sexualOrientation" class="form-control" required>
|
||||
<option value="">Select...</option>
|
||||
<option value="straight">Straight</option>
|
||||
<option value="gay">Gay</option>
|
||||
<option value="bisexual">Bisexual</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
<br><br>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
contactContent() {
|
||||
return /* html */`
|
||||
<div class="form-group">
|
||||
<label for="telegram">Telegram Username *</label>
|
||||
<input type="text" name="telegram" class="form-control" required>
|
||||
<small style="display:block;margin-top:.25rem;color:#666;">A Telegram account is required to be interviewed by the PMA</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email-2">Secondary Email</label>
|
||||
<input type="email" name="email-2" class="form-control">
|
||||
<small style="display:block;margin-top:.25rem;color:#666;">Not required - you may add a secondary email in case our messages don't reach you</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phone">Phone Number</label>
|
||||
<input type="tel" name="phone" class="form-control">
|
||||
<small style="display:block;margin-top:.25rem;color:#666;">Not required, but recommended if you don't provide a Telegram handle.</small>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
personalHistoryContent() {
|
||||
return /* html */`
|
||||
<div class="form-group">
|
||||
<label for="personalHistory">Where did you come from, where did you grow up, and where do you live now? *</label>
|
||||
<textarea name="personalHistory" class="form-control" required minlength="10"></textarea>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
ancestryContent() {
|
||||
return /* html */`
|
||||
<div class="form-group">
|
||||
<p>Please select all that apply to your ancestry/heritage:</p>
|
||||
<div class="ancestry-grid">
|
||||
<label class="ancestry-option"><input type="checkbox" name="ancestry" value="english"> English</label>
|
||||
<label class="ancestry-option"><input type="checkbox" name="ancestry" value="irish"> Irish</label>
|
||||
<label class="ancestry-option"><input type="checkbox" name="ancestry" value="scottish"> Scottish</label>
|
||||
<label class="ancestry-option"><input type="checkbox" name="ancestry" value="welsh"> Welsh</label>
|
||||
<label class="ancestry-option"><input type="checkbox" name="ancestry" value="german"> German</label>
|
||||
<label class="ancestry-option"><input type="checkbox" name="ancestry" value="french"> French</label>
|
||||
<label class="ancestry-option"><input type="checkbox" name="ancestry" value="dutch"> Dutch</label>
|
||||
<label class="ancestry-option"><input type="checkbox" name="ancestry" value="scandinavian"> Scandinavian</label>
|
||||
<label class="ancestry-option"><input type="checkbox" name="ancestry" value="slavic"> Slavic</label>
|
||||
<label class="ancestry-option"><input type="checkbox" name="ancestry" value="balkan"> Balkan</label>
|
||||
<label class="ancestry-option"><input type="checkbox" name="ancestry" value="iberian"> Iberian</label>
|
||||
<label class="ancestry-option"><input type="checkbox" name="ancestry" value="other">Other</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ancestryDetails">Tell us more about your ancestry and family history *</label>
|
||||
<textarea name="ancestryDetails" class="form-control" required minlength="20" placeholder="Please provide details about your family background, heritage, and any ancestral connections you're aware of."></textarea>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
skillsContent() {
|
||||
return /* html */`
|
||||
<div class="form-group">
|
||||
<label for="skills">Describe any special skills or trades you have. *</label>
|
||||
<textarea name="skills" class="form-control" required minlength="50" placeholder="e.g., carpentry, plumbing, electrical, masonry, welding"></textarea>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
interestContent() {
|
||||
return /* html */`
|
||||
<div class="form-group">
|
||||
<label for="interest">Why are you interested in Return To The Land, and how did you find out about us? *</label>
|
||||
<textarea name="interest" class="form-control" required minlength="20"></textarea>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
criminalContent() {
|
||||
return /* html */`
|
||||
<label for="criminal">Do you have a criminal record? If so, please explain. Otherwise, just say "no". *</label>
|
||||
<textarea name="criminal" class="form-control" required></textarea>
|
||||
<br><br>
|
||||
`;
|
||||
}
|
||||
|
||||
politicalContent() {
|
||||
return /* html */`
|
||||
<label for="politicsNotes">Give a quick summary of your political ideas. *</label>
|
||||
<textarea name="politicsNotes" class="form-control" style="width:100%;min-height:120px" required minlength="40"></textarea>
|
||||
<br><br>
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
religiousContent() {
|
||||
return /* html */`
|
||||
<div class="form-group">
|
||||
<label>Do you identify with a particular religion? *</label>
|
||||
<div class="radio-row">
|
||||
<label><input type="radio" name="religionIdent" value="yes" required> Yes</label>
|
||||
<label><input type="radio" name="religionIdent" value="no"> No</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="religionName">If yes, which?</label>
|
||||
<input name="religionName" class="form-control" placeholder="e.g., Catholic, Orthodox, Protestant, Deist, Other">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Do you regularly attend services or observances?</label>
|
||||
<div class="radio-row">
|
||||
<label><input type="radio" name="attendance" value="weekly"> Weekly</label>
|
||||
<label><input type="radio" name="attendance" value="monthly"> Monthly</label>
|
||||
<label><input type="radio" name="attendance" value="occasionally"> Occasionally</label>
|
||||
<label><input type="radio" name="attendance" value="never"> Never</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="philosophy">Briefly describe your moral or philosophical outlook *</label>
|
||||
<textarea name="philosophy" class="form-control" required></textarea>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
federalEmploymentContent() {
|
||||
return /* html */`
|
||||
<div class="form-group">
|
||||
<label>Are you currently employed by, or otherwise receive funds to perform work for, the United States federal government or a contractor/affiliate/partner? *</label>
|
||||
<div class="radio-row">
|
||||
<label><input type="radio" name="employedByGovernment" value="yes" required> Yes</label>
|
||||
<label><input type="radio" name="employedByGovernment" value="no"> No</label>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
socialContent() {
|
||||
return /* html */`
|
||||
<p class="note">Please answer these questions regarding controversial topics. Anything that needs further explanation can be done in the interview.</p>
|
||||
<br>
|
||||
<div class="form-group">
|
||||
<label>I support gay marriage *</label>
|
||||
<div class="radio-row">
|
||||
<label><input type="radio" name="gays" value="yes" required> Yes</label>
|
||||
<label><input type="radio" name="gays" value="no"> No</label>
|
||||
<label><input type="radio" name="gays" value="neutral"> Neutral</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>I support transgenderism *</label>
|
||||
<div class="radio-row">
|
||||
<label><input type="radio" name="trannies" value="yes" required> Yes</label>
|
||||
<label><input type="radio" name="trannies" value="no"> No</label>
|
||||
<label><input type="radio" name="trannies" value="neutral"> Neutral</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>I support foreign immigration *</label>
|
||||
<div class="radio-row">
|
||||
<label><input type="radio" name="immigration" value="yes" required> Yes</label>
|
||||
<label><input type="radio" name="immigration" value="no"> No</label>
|
||||
<label><input type="radio" name="immigration" value="neutral"> Neutral</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>I support the COVID vaccine and mask *</label>
|
||||
<div class="radio-row">
|
||||
<label><input type="radio" name="vax" value="yes" required> Yes</label>
|
||||
<label><input type="radio" name="vax" value="no"> No</label>
|
||||
<label><input type="radio" name="vax" value="neutral"> Neutral</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>I support abortion *</label>
|
||||
<div class="form-group">
|
||||
<label><input type="radio" name="abortion" value="yes" required> Yes</label>
|
||||
<label><input type="radio" name="abortion" value="no"> No</label>
|
||||
<label><input type="radio" name="abortion" value="neutral"> Neutral</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>I support segregation *</label>
|
||||
<div class="radio-row">
|
||||
<label><input type="radio" name="segregation" value="yes" required> Yes</label>
|
||||
<label><input type="radio" name="segregation" value="no"> No</label>
|
||||
<label><input type="radio" name="segregation" value="neutral"> Neutral</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>I support procreation *</label>
|
||||
<div class="radio-row">
|
||||
<label><input type="radio" name="procreation" value="yes" required> Yes</label>
|
||||
<label><input type="radio" name="procreation" value="no"> No</label>
|
||||
<label><input type="radio" name="procreation" value="neutral"> Neutral</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Multiculturalism preference *</label>
|
||||
<div class="radio-row">
|
||||
<label style="display:block;margin:.35rem 0"><input type="radio" name="multiculturalism" value="yes" required> I prefer a community with a variety of different ancestral origins</label>
|
||||
<label style="display:block;margin:.35rem 0"><input type="radio" name="multiculturalism" value="no"> I prefer a community where everyone shares common continental ancestry</label>
|
||||
<label style="display:block;margin:.35rem 0"><input type="radio" name="multiculturalism" value="neutral"> Neutral</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>How often do you think about the Roman empire? *</label>
|
||||
<div class="radio-row">
|
||||
<label style="display:block;margin:.35rem 0"><input type="radio" name="romanEmpire" value="daily" required> Every day, at least once</label>
|
||||
<label style="display:block;margin:.35rem 0"><input type="radio" name="romanEmpire" value="weekly"> A few times a week, probably</label>
|
||||
<label style="display:block;margin:.35rem 0"><input type="radio" name="romanEmpire" value="rarely"> Very rarely</label>
|
||||
<label style="display:block;margin:.35rem 0"><input type="radio" name="romanEmpire" value="never"> Never</label>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("questionnaire-", Questionnaire);
|
||||
97
ui/apply/index.css
Normal file
97
ui/apply/index.css
Normal file
@@ -0,0 +1,97 @@
|
||||
:root {
|
||||
--bar-bg: #ccc;
|
||||
--bar-fill: var(--green, #4caf50);
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
position: absolute;
|
||||
top: 20vh;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: var(--green);
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: calc(20vh + 30px);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
max-width: 90vw;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.section:hover {
|
||||
background: var(--red);
|
||||
color: var(--tan);
|
||||
}
|
||||
|
||||
.section:hover .status {
|
||||
color: var(--tan);
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
/* Questionnaire Pages */
|
||||
.form-container {
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
padding: 2rem;
|
||||
/* background: rgba(255, 255, 255, 0.9); */
|
||||
}
|
||||
|
||||
.form-container input {
|
||||
background: var(--tan);
|
||||
border: 1px solid var(--brown);
|
||||
}
|
||||
|
||||
.form-container select {
|
||||
background: var(--tan);
|
||||
border: 1px solid var(--brown);
|
||||
}
|
||||
|
||||
.form-group { margin-bottom: 1.5rem; }
|
||||
label { display: block; margin-bottom: 0.5rem; font-weight: bold; }
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
background: var(--tan);
|
||||
border: 1px solid var(--brown)
|
||||
}
|
||||
|
||||
textarea.form-control { min-height: 120px; resize: vertical; }
|
||||
|
||||
.btn {
|
||||
background-color: var(--green);
|
||||
color: #fff;
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover { background-color: var(--dark-green); }
|
||||
|
||||
.btn-outline { background: none; border: 1px solid var(--green); color: var(--green); }
|
||||
|
||||
.radio-row { margin: 0.25rem 0; }
|
||||
.note { color: #444; margin: 0.5rem 0 0 0; }
|
||||
|
||||
/* Grid/option helpers used by ancestry */
|
||||
.ancestry-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
|
||||
.ancestry-option { border: 1px solid var(--brown); padding: 1rem; border-radius: 4px; cursor: pointer; transition: all 0.2s; }
|
||||
.ancestry-option:hover { border-color: var(--green); background-color: rgba(76, 175, 80, 0.1); }
|
||||
.ancestry-option input[type="checkbox"] { margin-right: 0.5rem; }
|
||||
23
ui/apply/index.html
Normal file
23
ui/apply/index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Hyperia | Application</title>
|
||||
<link rel="stylesheet" href="_/code/shared.css">
|
||||
<link rel="icon" href="_/icons/logo.svg">
|
||||
<script src="_/code/util.js"></script>
|
||||
<link rel="stylesheet" href="index.css">
|
||||
<script type="module">
|
||||
import NavBar from "./components/NavBar.js"
|
||||
import Questionnaire from "./components/Questionnaire.js"
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<nav-bar></nav-bar>
|
||||
|
||||
<questionnaire-></questionnaire->
|
||||
|
||||
<div style="position: fixed; bottom: 2vh; left: 1vw">
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Hyperia</title>
|
||||
<link rel="icon" href="_/berean.svg">
|
||||
<script src="quill.js"></script>
|
||||
<script type="module" src="index.js"></script>
|
||||
<script>
|
||||
window.Colors = {
|
||||
base: darkMode ? "#a51e08" : "#f8e5ca",
|
||||
accent: darkMode ? "#f2dac8" : "#000000",
|
||||
ash: darkMode ? "rgb(179 162 150)" : "rgb(119 109 103)",
|
||||
oak: "rgb(73 18 18)",
|
||||
searchBorder: darkMode ? "#ecc58a" : "#a18d8d",
|
||||
yellowAccent: "rgb(202 97 18)",
|
||||
searchBackground: darkMode ? "#ecc58a" : "rgb(185 191 152)",
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
171
ui/nav/header.js
171
ui/nav/header.js
@@ -1,171 +0,0 @@
|
||||
class Header extends Shadow {
|
||||
|
||||
render() {
|
||||
HStack(() => {
|
||||
HStack(() => {
|
||||
img(darkMode ? "_/berean.svg" : "_/berean.svg", 80, 80)
|
||||
.marginLeft(2, "vw")
|
||||
// p("Hyperia")
|
||||
// .marginLeft(10, "px")
|
||||
// .fontWeight(800)
|
||||
// .fontSize("2xl")
|
||||
})
|
||||
.alignItems("center")
|
||||
.flex(1)
|
||||
|
||||
VStack(() => {
|
||||
input("", {width: "60vw", height: "35px"})
|
||||
.background(darkMode ? Colors.oak : "rgb(255, 240, 220)")
|
||||
.borderRadius(6, px)
|
||||
.border(`1px solid ${Colors.searchBorder}`)
|
||||
.paddingLeft(1, "em")
|
||||
.color(darkMode ? Colors.accent : Colors.oak)
|
||||
.fontSize("l")
|
||||
.outline("none")
|
||||
.transition("border .2s")
|
||||
.onFocus(function () {
|
||||
this.style.border = `1px solid ${darkMode ? "rgb(225 19 0)" : "white"}`
|
||||
})
|
||||
.onBlur(function () {
|
||||
this.style.border = `1px solid ${Colors.searchBorder}`
|
||||
})
|
||||
.onKeyDown(function (e) {
|
||||
console.log(e.key)
|
||||
if(e.key === "Enter") {
|
||||
navigateTo("/search/" + this.value)
|
||||
}
|
||||
})
|
||||
|
||||
HStack(() => {
|
||||
a("/", () => {
|
||||
img("_/all.svg", 20, 20)
|
||||
.backgroundColor(window.location.pathname === "/" ? Colors.base : Colors.oak)
|
||||
.padding("horizontal", 10)
|
||||
.paddingTop(3)
|
||||
})
|
||||
.borderRadius(8, px)
|
||||
.padding("vertical", 5)
|
||||
.backgroundColor(window.location.pathname === "/" ? Colors.oak : "none")
|
||||
.marginRight("20", "px")
|
||||
.onHover(function (hovering) {
|
||||
this.style.outline = hovering ? `1px solid ${Colors.ash}` : "none"
|
||||
this.style.cursor = "default"
|
||||
})
|
||||
a("Images", () => {
|
||||
img("_/picture.svg", 20, 20)
|
||||
.backgroundColor(Colors.oak)
|
||||
.padding("horizontal", 10)
|
||||
.paddingTop(1)
|
||||
})
|
||||
.borderRadius(8, px)
|
||||
.padding("vertical", 5)
|
||||
.backgroundColor(window.location.pathname === "/Images" ? Colors.oak : "none")
|
||||
.marginRight("20", "px")
|
||||
.onHover(function (hovering) {
|
||||
this.style.outline = hovering ? `1px solid ${Colors.ash}` : "none"
|
||||
this.style.cursor = "default"
|
||||
})
|
||||
a("Videos", () => {
|
||||
img("_/video.svg", 20, 20)
|
||||
.backgroundColor(Colors.oak)
|
||||
.padding("horizontal", 10)
|
||||
.paddingTop(1)
|
||||
})
|
||||
.borderRadius(8, px)
|
||||
.padding("vertical", 5)
|
||||
.backgroundColor(window.location.pathname === "/Videos" ? Colors.oak : "none")
|
||||
.marginRight("20", "px")
|
||||
.onHover(function (hovering) {
|
||||
this.style.outline = hovering ? `1px solid ${Colors.ash}` : "none"
|
||||
this.style.cursor = "default"
|
||||
})
|
||||
a("Products", () => {
|
||||
img("_/shopping.svg", 17, 17)
|
||||
.backgroundColor(Colors.oak)
|
||||
.padding("horizontal", 10)
|
||||
.paddingTop(3)
|
||||
})
|
||||
.borderRadius(8, px)
|
||||
.padding("vertical", 5)
|
||||
.backgroundColor(window.location.pathname === "/Producrs" ? Colors.oak : "none")
|
||||
.marginRight("20", "px")
|
||||
.onHover(function (hovering) {
|
||||
this.style.outline = hovering ? `1px solid ${Colors.ash}` : "none"
|
||||
this.style.cursor = "default"
|
||||
})
|
||||
a("Jobs", () => {
|
||||
img("_/jobs.svg", 24, 24)
|
||||
.backgroundColor(Colors.oak)
|
||||
.padding("horizontal", 10)
|
||||
})
|
||||
.borderRadius(8, px)
|
||||
.padding("vertical", 5)
|
||||
.backgroundColor(window.location.pathname === "/Jobs" ? Colors.oak : "none")
|
||||
.marginRight("20", "px")
|
||||
.onHover(function (hovering) {
|
||||
this.style.outline = hovering ? `1px solid ${Colors.ash}` : "none"
|
||||
this.style.cursor = "default"
|
||||
})
|
||||
a("Files", () => {
|
||||
img("_/doc.svg", 18, 18)
|
||||
.backgroundColor(Colors.oak)
|
||||
.padding("horizontal", 10)
|
||||
.paddingTop(3)
|
||||
})
|
||||
.borderRadius(8, px)
|
||||
.padding("vertical", 5)
|
||||
.backgroundColor(window.location.pathname === "/Files" ? Colors.oak : "none")
|
||||
.marginRight("30", "px")
|
||||
.onHover(function (hovering) {
|
||||
this.style.outline = hovering ? `1px solid ${Colors.ash}` : "none"
|
||||
this.style.cursor = "default"
|
||||
})
|
||||
})
|
||||
.marginTop(20, px)
|
||||
.justifyContent("center")
|
||||
|
||||
})
|
||||
.flex(1)
|
||||
.marginTop(56, px)
|
||||
|
||||
HStack(() => {
|
||||
button(() => {
|
||||
img("_/silo2.svg", 48, 32)
|
||||
.backgroundColor(darkMode ? Colors.accent : Colors.base)
|
||||
})
|
||||
.addStyle(buttonStyle)
|
||||
.height(40, "px")
|
||||
.padding("horizontal", 18)
|
||||
.paddingTop(2)
|
||||
})
|
||||
.flex(1)
|
||||
.justifyContent("center")
|
||||
})
|
||||
.alignItems("center")
|
||||
.gap("10px")
|
||||
.width(100, 'vw')
|
||||
.height(1.4, "in")
|
||||
}
|
||||
}
|
||||
|
||||
function buttonStyle(el) {
|
||||
el.borderRadius(7, px)
|
||||
.border(`1px solid ${Colors.searchBorder}`)
|
||||
.backgroundColor(Colors.oak)
|
||||
.color(darkMode ? Colors.ash : Colors.base)
|
||||
.fontSize("15", "px")
|
||||
.transition("scale .3s")
|
||||
.onHover((hovering) => {
|
||||
el.style.transition
|
||||
if(hovering) {
|
||||
el
|
||||
.scale("1.05")
|
||||
.backgroundColor(Colors.darkerBlue)
|
||||
} else {
|
||||
el
|
||||
.scale("")
|
||||
.backgroundColor(Colors.blue)
|
||||
}
|
||||
})
|
||||
return el
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
css(`
|
||||
home- {
|
||||
margin: 0px;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: ${Colors.base};
|
||||
font-family: system-ui;
|
||||
}
|
||||
`)
|
||||
|
||||
class Home extends Page {
|
||||
render() {
|
||||
Header()
|
||||
}
|
||||
}
|
||||
16
ui/public/components/Footer.js
Normal file
16
ui/public/components/Footer.js
Normal file
@@ -0,0 +1,16 @@
|
||||
css(`
|
||||
page-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`)
|
||||
|
||||
export default class Footer extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.innerHTML += /* html */`
|
||||
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("page-footer", Footer)
|
||||
150
ui/public/components/NavBar.js
Normal file
150
ui/public/components/NavBar.js
Normal file
@@ -0,0 +1,150 @@
|
||||
css(`
|
||||
nav-bar {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100vw;
|
||||
z-index: 1;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.title-div {
|
||||
position: absolute;
|
||||
left: 2em;
|
||||
top: 2em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.links-div {
|
||||
position: absolute;
|
||||
right: 3em;
|
||||
top: 2.75em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.logo-p {
|
||||
font-size: 2em;
|
||||
margin: 0 0 0 15px;
|
||||
letter-spacing: 1px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-hamburger {
|
||||
margin-right: 8vw;
|
||||
display: none;
|
||||
max-height:40px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
font-size: 1em;
|
||||
margin: 0 0 0 15px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 820px) {
|
||||
nav-bar a {
|
||||
text-decoration: none;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.title-div {
|
||||
position: static
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 10vw;
|
||||
}
|
||||
|
||||
.logo-p {
|
||||
font-size: 8vw;
|
||||
margin: 0 0 0 1vw;
|
||||
}
|
||||
|
||||
.nav-hamburger {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.links-div {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.outer-nav {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 20vw;
|
||||
background: var(--tan);
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 3vw;
|
||||
max-height:80px;
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export default class NavBar extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.innerHTML += /* html */ `
|
||||
<div class="outer-nav">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<a draggable="false" href="/" id="homeLink" style="display: flex; align-items: center; text-decoration: none; color: var(--green);">
|
||||
<div class="title-div">
|
||||
<img class="logo" width="40" height="40" src="_/icons/logo.svg"/>
|
||||
<p class="logo-p">Hyperia</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="links-div">
|
||||
<a draggable="false" href="/about" class="nav-link">About</a>
|
||||
<a draggable="false" href="/join" class="nav-link">Join</a>
|
||||
<a draggable="false" href="/login" class="nav-link">Login</a>
|
||||
</div>
|
||||
|
||||
<!-- Right side: hamburger -->
|
||||
<img class="nav-hamburger" src="_/icons/hamburger.svg">
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Track which anchor was pressed down
|
||||
let activeLink = null;
|
||||
|
||||
// Get all anchor tags inside this element
|
||||
const anchors = this.querySelectorAll('a');
|
||||
|
||||
anchors.forEach(anchor => {
|
||||
anchor.addEventListener('mousedown', (e) => {
|
||||
activeLink = anchor;
|
||||
});
|
||||
});
|
||||
|
||||
// Listen globally for mouseup, to detect release anywhere
|
||||
document.addEventListener('mouseup', (e) => {
|
||||
if (activeLink) {
|
||||
// Navigate to the href of the activeLink
|
||||
window.location.href = activeLink.href;
|
||||
activeLink = null;
|
||||
}
|
||||
});
|
||||
|
||||
const hamburger = this.querySelectorAll("img")[1]
|
||||
const sidebar = document.querySelector("side-bar");
|
||||
|
||||
hamburger.addEventListener("click", () => {
|
||||
if (sidebar.style.right === "0vw") {
|
||||
sidebar.style.right = "-100vw";
|
||||
} else {
|
||||
sidebar.style.right = "0vw";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("nav-bar", NavBar)
|
||||
27
ui/public/components/NavMenu.js
Normal file
27
ui/public/components/NavMenu.js
Normal file
@@ -0,0 +1,27 @@
|
||||
css(`
|
||||
nav-menu {
|
||||
position: fixed;
|
||||
bottom: 6vh;
|
||||
right: 6vh;
|
||||
width: 20vw;
|
||||
height: 10vh;
|
||||
background: var(--green);
|
||||
color: var(--tan);
|
||||
border: 20px solid var(--tan);
|
||||
}
|
||||
`)
|
||||
|
||||
export default class NavMenu extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.innerHTML += /* html */ `
|
||||
<span style="display: block; height: 2vh; background-color: var(--tan); border-radius: 2px;"></span>
|
||||
<span style="display: block; height: 2vh; background-color: var(--tan); border-radius: 2px;"></span>
|
||||
<span style="display: block; height: 2vh; background-color: var(--tan); border-radius: 2px;"></span>
|
||||
<p style="font-size: 3vh; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%)">Menu</p>
|
||||
`
|
||||
|
||||
document.addEventListener("")
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("nav-menu", NavMenu)
|
||||
46
ui/public/components/SideBar.js
Normal file
46
ui/public/components/SideBar.js
Normal file
@@ -0,0 +1,46 @@
|
||||
css(`
|
||||
side-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: -100vw;
|
||||
width: 80vw;
|
||||
height: 100vh;
|
||||
background: var(--tan);
|
||||
border-left: 1px solid var(--green);
|
||||
transition: right 0.3s ease;
|
||||
z-index: 10;
|
||||
padding: 5vw;
|
||||
}
|
||||
|
||||
side-bar a {
|
||||
font-size: 8vw;
|
||||
color: var(--red)
|
||||
}
|
||||
|
||||
side-bar h2 {
|
||||
font-size: 6vw
|
||||
}
|
||||
`)
|
||||
|
||||
|
||||
export default class SideBar extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.innerHTML += /* html */`
|
||||
<h2>Menu</h2>
|
||||
<ul style="list-style: none; padding: 0;">
|
||||
<li><a href="/join">Join</a></li>
|
||||
<li><a href="/locations">Locations</a></li>
|
||||
<li><a href="/donate">Donate</a></li>
|
||||
<li><a href="/events">Events</a></li>
|
||||
<li><a href="/login">Login</a></li>
|
||||
</ul>
|
||||
<img src="_/icons/x.svg" style="margin-top: 70vw; width: 10vw; height: 10vw" onclick="">
|
||||
`
|
||||
this.querySelector("img").addEventListener("click", () => {
|
||||
const sidebar = document.querySelector("side-bar");
|
||||
sidebar.style.right = "-100vw"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("side-bar", SideBar)
|
||||
143
ui/public/index.css
Normal file
143
ui/public/index.css
Normal file
@@ -0,0 +1,143 @@
|
||||
.card-header {
|
||||
padding: 2vw;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 2vw;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 10px 10px 3vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.card a {
|
||||
background: #af936226;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.card-image figure {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 40vw;
|
||||
border: 1px solid var(--green);
|
||||
}
|
||||
|
||||
|
||||
.card-image img {
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 5vw;
|
||||
max-width: 90vw;
|
||||
margin-top: 20vh;
|
||||
margin-left: 5vw;
|
||||
margin-bottom: 5vw
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'CrimsonText';
|
||||
src: url('/_/CrimsonText/CrimsonText-Regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'CrimsonText';
|
||||
src: url('/_/CrimsonText/CrimsonText-Bold.ttf') format('truetype');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px){
|
||||
.card a {
|
||||
background: var(--green);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'CrimsonText', sans-serif;
|
||||
background-color: var(--tan);
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
:root {
|
||||
--green: #0B5538;
|
||||
--tan: #FFDFB4;
|
||||
}
|
||||
|
||||
.card-image img {
|
||||
height: 70vh;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-bottom: 4vw;
|
||||
padding-bottom: 4vw;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.content a {
|
||||
background: var(--green);
|
||||
color: var(--tan);
|
||||
border-radius: 6px;
|
||||
padding: 1vw;
|
||||
}
|
||||
|
||||
.card-footer-item {
|
||||
background: var(--green);
|
||||
color: var(--tan);
|
||||
border-radius: 6px;
|
||||
padding: 1vw;
|
||||
margin-right: 1vw;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 2vw;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 2vw;
|
||||
}
|
||||
|
||||
.card-image {
|
||||
margin: 0; /* Removes spacing around image container */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card-image figure {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 10px 10px 10px 2vw;
|
||||
font-size: 1.2em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
gap: 5vw;
|
||||
max-width: 90vw;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
76
ui/public/index.html
Normal file
76
ui/public/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Hyperia</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="_/icons/logo.svg">
|
||||
<link rel="stylesheet" href="_/code/shared.css">
|
||||
<link rel="stylesheet" href="index.css">
|
||||
<style>
|
||||
:root {
|
||||
--green: #0B5538;
|
||||
--tan: #FFDFB4;
|
||||
--red: #BC1C02;
|
||||
--brown: #c6a476
|
||||
}
|
||||
|
||||
#items {
|
||||
position: absolute;
|
||||
top: 45vh;
|
||||
left: 50vw;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center; /* centers children horizontally */
|
||||
text-align: center; /* ensures text inside spans is centered */
|
||||
}
|
||||
|
||||
#title {
|
||||
font-size: 22vh;
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: default;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 5px;
|
||||
transition: background .02s, color .2s;
|
||||
user-select: none;
|
||||
|
||||
display: inline-block; /* makes background and padding behave */
|
||||
padding: 0.2em 0.5em; /* adds breathing room */
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
background: var(--green);
|
||||
color: var(--tan);
|
||||
}
|
||||
|
||||
a:active {
|
||||
background: var(--red); /* background color works now */
|
||||
color: white; /* optional: change text color for contrast */
|
||||
}
|
||||
</style>
|
||||
<script src="_/code/util.js"></script>
|
||||
<script type="module">
|
||||
import PageFooter from "./components/Footer.js"
|
||||
import NavBar from "./components/NavBar.js"
|
||||
import SideBar from "./components/SideBar.js"
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="items">
|
||||
<span id="title" style="font-family: Canterbury; color: var(--red); margin-bottom: 10vh">hyperia</span>
|
||||
<img src="_/icons/logo.svg" style="width: 17vh; z-index: 1">
|
||||
<img src="_/icons/left_plant.svg" style="position: absolute; left: 25%; top: 23vh; width: 70%; transform: translateX(-50%)">
|
||||
<img src="_/icons/right_plant.svg" style="position: absolute; left: 75%; top: 23vh; width: 70%; transform: translateX(-50%)">
|
||||
<div style="height: 2vh"></div>
|
||||
<span style="font-family: Canterbury; color: black; font-size: 5vh;">A <br>Society</span>
|
||||
<div style="height: 2vh"></div>
|
||||
<div style="color: black; font-size: 2.2vh; z-index: 1; cursor: default;">
|
||||
<a>ABOUT</a> | <a>SERVICES</a> | <a>JOIN</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
68
ui/public/paintingTags.js
Normal file
68
ui/public/paintingTags.js
Normal file
@@ -0,0 +1,68 @@
|
||||
// Create the hover display element
|
||||
const hoverBox = document.createElement('div');
|
||||
hoverBox.style.id = "hoverBox"
|
||||
hoverBox.style.position = 'fixed';
|
||||
hoverBox.style.padding = '8px 12px';
|
||||
hoverBox.style.backgroundColor = 'var(--green)';
|
||||
hoverBox.style.border = '1px solid var(--tan)';
|
||||
hoverBox.style.color = 'var(--tan)';
|
||||
hoverBox.style.opacity = '80%';
|
||||
hoverBox.style.pointerEvents = 'none';
|
||||
hoverBox.style.zIndex = '9999';
|
||||
hoverBox.style.fontFamily = 'sans-serif';
|
||||
hoverBox.style.fontSize = '14px';
|
||||
hoverBox.style.display = 'none';
|
||||
document.body.appendChild(hoverBox);
|
||||
let currentTarget = null;
|
||||
|
||||
function capitalizeWords(str) {
|
||||
return str
|
||||
.split('-')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
function onMouseOver(e) {
|
||||
const target = e.target;
|
||||
let paintingName; let artistName;
|
||||
if(target.id === "back") {
|
||||
paintingName = "The Garden Terrace"
|
||||
artistName = "Caspar David Friedrich"
|
||||
} else if (target.tagName.toLowerCase() === 'img' && target.classList.contains('interactive')) {
|
||||
const match = target.src.match(/([^\/]+)\.([a-z]{3,4})(\?.*)?$/i); // extract filename
|
||||
if (!match) return;
|
||||
|
||||
const filename = match[1];
|
||||
const parts = filename.split('_');
|
||||
if (parts.length !== 2) return;
|
||||
|
||||
paintingName = capitalizeWords(parts[0]);
|
||||
artistName = capitalizeWords(parts[1]);
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
hoverBox.innerHTML = `<strong>${paintingName}</strong><br><span style="font-size: 12px;">${artistName}</span>`;
|
||||
hoverBox.style.display = 'block';
|
||||
currentTarget = target;
|
||||
hoverBox.style.left = `${e.clientX + 15}px`;
|
||||
hoverBox.style.top = `${e.clientY + 15}px`;
|
||||
}
|
||||
|
||||
function onMouseOut(e) {
|
||||
if (e.target === currentTarget) {
|
||||
hoverBox.style.display = 'none';
|
||||
currentTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseMove(e) {
|
||||
if (hoverBox.style.display === 'block') {
|
||||
hoverBox.style.left = `${e.clientX + 15}px`;
|
||||
hoverBox.style.top = `${e.clientY + 15}px`;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('mouseover', onMouseOver);
|
||||
document.addEventListener('mouseout', onMouseOut);
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
56
ui/public/scrollEffect.js
Normal file
56
ui/public/scrollEffect.js
Normal file
@@ -0,0 +1,56 @@
|
||||
let treeOriginalTop = null;
|
||||
let currentVelocity = 0;
|
||||
let isAnimating = false;
|
||||
|
||||
window.addEventListener('wheel', (e) => {
|
||||
if(window.innerWidth < 600) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add scroll delta to the velocity
|
||||
currentVelocity += e.deltaY;
|
||||
|
||||
// Start animation loop if not running
|
||||
if (!isAnimating) {
|
||||
isAnimating = true;
|
||||
requestAnimationFrame(animateScroll);
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
function animateScroll() {
|
||||
const tree = document.getElementById("tree");
|
||||
|
||||
if (!treeOriginalTop) {
|
||||
treeOriginalTop = parseInt(getComputedStyle(tree).top);
|
||||
}
|
||||
|
||||
const treeHeightPX = 0.83 * window.innerHeight;
|
||||
let treeTopPX = parseInt(getComputedStyle(tree).top);
|
||||
|
||||
// Limit per-frame speed (but NOT total speed)
|
||||
let multiplier = window.innerHeight / 2000;
|
||||
let delta = Math.max(-100 * multiplier, Math.min(100 * multiplier, currentVelocity));
|
||||
|
||||
// Apply the scroll
|
||||
let newTop = treeTopPX - delta;
|
||||
|
||||
// Clamp top/bottom bounds
|
||||
const maxTop = treeOriginalTop;
|
||||
const minTop = treeOriginalTop - treeHeightPX;
|
||||
|
||||
if (newTop > maxTop) newTop = maxTop;
|
||||
if (newTop < minTop) newTop = minTop;
|
||||
|
||||
tree.style.top = `${newTop}px`;
|
||||
|
||||
// Slowly reduce velocity
|
||||
currentVelocity *= 0.85;
|
||||
|
||||
// If velocity is small, stop
|
||||
if (Math.abs(currentVelocity) > 0.5) {
|
||||
requestAnimationFrame(animateScroll);
|
||||
} else {
|
||||
isAnimating = false;
|
||||
currentVelocity = 0;
|
||||
}
|
||||
}
|
||||
1
ui/readme.md
Normal file
1
ui/readme.md
Normal file
@@ -0,0 +1 @@
|
||||
See https://github.com/return-to-the-land/go-backend for instructions.
|
||||
Reference in New Issue
Block a user