From 678b54ac805d6757f1b3190b943311b2a2cf6afa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Yannick=20F=C3=BCreder?=
<64203572+YannickFuereder@users.noreply.github.com>
Date: Sun, 11 Apr 2021 13:57:32 +0200
Subject: [PATCH] Reset
---
.github/dependabot.yml | 6 +
.github/workflows/main.yml | 87 +++
.gitignore | 2 +
Assets/css/dialog.css | 143 +++++
Assets/css/profile.css | 446 ++++++++++++++
Assets/css/style.css | 258 ++++++++
Assets/js/app.js | 115 ++++
Assets/js/dialog.js | 118 ++++
Assets/js/profile.js | 1145 ++++++++++++++++++++++++++++++++++++
README.md | 76 +++
index.html | 312 ++++++++++
package-lock.json | 720 +++++++++++++++++++++++
package.json | 17 +
profile/index.html | 359 +++++++++++
vite.config.js | 13 +
15 files changed, 3817 insertions(+)
create mode 100644 .github/dependabot.yml
create mode 100644 .github/workflows/main.yml
create mode 100644 .gitignore
create mode 100644 Assets/css/dialog.css
create mode 100644 Assets/css/profile.css
create mode 100644 Assets/css/style.css
create mode 100644 Assets/js/app.js
create mode 100644 Assets/js/dialog.js
create mode 100644 Assets/js/profile.js
create mode 100644 README.md
create mode 100644 index.html
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 profile/index.html
create mode 100644 vite.config.js
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..66fbc2e
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/.github/workflows"
+ schedule:
+ interval: "weekly"
\ No newline at end of file
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..8a90de2
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,87 @@
+name: Build & Deploy
+
+on:
+ push:
+ branches: [ main ]
+
+jobs:
+ build:
+ if: github.event_name == 'push'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install Npm Packages
+ uses: actions/setup-node@v4
+ with:
+ node-version: 21
+
+ - run: npm i
+
+ - run: npm run build
+
+ - name: Zip
+ run: |
+ touch build.tar.gz
+ tar -zcf build.tar.gz -C ./dist .
+
+ - name: Archive production artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: build
+ path: ./build.tar.gz
+
+ deploy:
+ needs: build
+ runs-on: ubuntu-latest
+ env:
+ SSH_USER: github
+ SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
+ SSH_HOST: 194.36.146.51
+ REPO_NAME: ${{ github.event.repository.name }}
+ steps:
+ - name: Download build
+ uses: actions/download-artifact@v4
+ with:
+ name: build
+
+ - name: Install ssh key
+ run: |
+ mkdir -p ~/.ssh/
+ echo "$SSH_KEY" > ~/.ssh/staging.key
+ chmod 600 ~/.ssh/staging.key
+ ssh-keyscan -H $SSH_HOST >> ~/.ssh/known_hosts
+
+ - name: Configure SSH
+ run: |
+ cat >>~/.ssh/config < .item {
+ background-color: #1e1e1e;
+ border-radius: .5em;
+ width: calc(100% - 2em);
+ padding: 1em;
+ margin: 1em 0;
+ box-shadow: 0 0 1em rgba(0, 0, 0, .2);
+}
+
+.settings-container > .item > h1 {
+ margin: 0;
+ margin-bottom: 1em;
+ font-size: 1.5em;
+}
+
+input {
+ width: calc(100% - 2em);
+ padding: 1em;
+ margin: .5em 0;
+ background-color: #393939;
+ border-radius: .2em;
+ color: #fff;
+ outline: none;
+ border: none;
+ outline: 2px solid transparent;
+ transition: all .2s ease-in-out;
+}
+
+input:focus {
+ outline: 2px solid white;
+}
+
+input:hover {
+ background-color: #4d4d4d;
+}
+
+input[disabled] {
+ background-color: #222;
+ color: #4d4d4d;
+ cursor: pointer;
+}
+
+input.invalid {
+ outline: 2px solid red;
+}
+
+button {
+ border: 2px solid #4d4d4d;
+ background-color: transparent;
+ outline: none;
+ padding: .5em 8em;
+ border-radius: .2em;
+ color: #fff;
+ cursor: pointer;
+ transition: all .2s ease-in-out;
+}
+
+button:hover {
+ background-color: #4d4d4d;
+}
+
+button:active {
+ background-color: #393939;
+}
+
+button[disabled] {
+ background-color: #222;
+ color: #4d4d4d;
+ cursor: pointer;
+}
+
+select {
+ padding: 1em;
+ margin: .5em 0;
+ background-color: #393939;
+ border-radius: .2em;
+ color: #fff;
+ outline: none;
+ border: none;
+ outline: 2px solid transparent;
+ transition: all .2s ease-in-out;
+}
+
+select:focus {
+ outline: 2px solid white;
+}
+
+select:hover {
+ background-color: #4d4d4d;
+}
+
+select[disabled] {
+ background-color: #222;
+ color: #4d4d4d;
+ cursor: pointer;
+}
+
+.connected-accounts {
+ display: flex;
+ gap: 1em;
+ width: 100%;
+ flex-wrap: wrap;
+}
+
+.connected-accounts .item {
+ display: flex;
+ align-items: center;
+ flex: calc(50% - 1em);
+ overflow: hidden;
+ border-radius: .5em;
+ border-collapse: collapse;
+ justify-content: space-between;
+ height: 2.5em;
+ box-shadow: 0 0 1em rgba(0, 0, 0, .2);
+ margin: 0;
+ font-size: 1.5em;
+}
+
+.connected-accounts .item img {
+ height: 1em;
+ object-fit: contain;
+ padding: .5em;
+}
+
+.connected-accounts h1 span:nth-child(1) {
+ height: 100%;
+ align-items: center;
+ display: flex;
+ opacity: .9;
+}
+
+.connected-accounts h1 span:nth-child(2), .connected-accounts h1 span:nth-child(3) {
+ background-color: #222;
+ height: 100%;
+ align-items: center;
+ display: flex;
+ opacity: .9;
+ cursor: pointer;
+ transition: all .2s ease-in-out;
+}
+
+.connected-accounts h1 span:nth-child(2):hover, .connected-accounts h1 span:nth-child(3):hover {
+ background-color: #333;
+}
+
+.connected-accounts h1 span:nth-child(2) h1, .connected-accounts h1 span:nth-child(3) h1 {
+ background-color: transparent;
+ font-size: .7em;
+ padding: 0 .5em;
+ margin: 0;
+}
+
+.connected-accounts h1:nth-child(1) {
+ background-color: #1dd05e;
+}
+
+.connected-accounts h1:nth-child(2) {
+ background-color: #613f9f;
+}
+
+.connected-accounts h1:nth-child(3) {
+ background-color: #7289da;
+}
+
+.connected-accounts h1:nth-child(4) {
+ background-color: white;
+}
+
+.connected-accounts h1:nth-child(5) {
+ background-color: black;
+}
+
+.connected-accounts h1:nth-child(6) {
+ background-color: black;
+}
+
+.connected-accounts .item > span:nth-child(3) {
+ display: none;
+}
+
+.connected-accounts .item.connected > span:nth-child(3) {
+ display: flex;
+}
+
+.connected-accounts .item.connected > span:nth-child(2) {
+ display: none;
+}
+
+.flex {
+ display: flex;
+ align-items: center;
+ margin-top: .5em;
+ gap: .5em;
+}
+
+.flex.end {
+ justify-content: flex-end;
+}
+
+.input-group {
+ display: flex;
+ align-items: center;
+ gap: .5em;
+}
+
+.searchbar {
+ width: 100%;
+ position: relative;
+}
+
+.searchbar input {
+ width: 100%;
+}
+
+.searchbar .autocom-box {
+ display: none;
+ position: absolute;
+ background-color: #393939;
+ margin-top: -.5em;
+ width: 100%;
+ max-height: 12em;
+ overflow: hidden;
+ overflow-y: auto;
+}
+
+.searchbar .autocom-box.active {
+ display: block;
+}
+
+.searchbar .autocom-box h1 {
+ cursor: pointer;
+ font-size: 1em;
+ padding: 1em;
+}
+
+.searchbar .autocom-box h1:hover {
+ background-color: #4d4d4d;
+}
+
+.dangerZone {
+ outline: 2px solid #510c0c;
+ background-color: #261b1b !important;
+}
+
+td, th {
+ border: none;
+ padding: 8px;
+}
+
+tr:nth-child(even){background-color: #222;}
+
+tr:hover {background-color: 222;}
+
+th {
+ padding-top: 12px;
+ padding-bottom: 12px;
+ text-align: left;
+ background-color: #111;
+ color: white;
+}
+
+.apiKeysTable {
+ width: 100%;
+ table-layout: fixed;
+ border-collapse: collapse;
+ border-radius: .5em;
+ overflow: hidden;
+}
+
+.apiKeysTable td:nth-child(2) {
+ display: flex;
+ gap: 1em;
+}
+
+.apiKeysTable td:nth-child(2) button {
+ padding: .8em;
+}
+
+.apiKeysTable td {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.collapsible-content {
+ padding: 0 18px;
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height 0.2s ease-out;
+ overflow-y: auto;
+}
+
+.collapsible-content > div:nth-child(1) {
+ padding-top: 18px;
+}
+
+.ssoClients {
+ display: flex;
+ width: 100%;
+ gap: 1em;
+ flex-wrap: wrap;
+}
+
+.ssoClients > div {
+ background-color: #111;
+ padding: 1em;
+ border-radius: .5em;
+ width: 100%;
+ cursor: pointer;
+}
+
+.ssoClients > div > .collapsible {
+ display: flex;
+ align-items: center;
+ gap: 1em;
+}
+
+.ssoClients > div > .collapsible h1 {
+ font-size: 1.5em;
+}
+
+.ssoClients > div > .collapsible img {
+ height: 4em;
+ object-fit: contain;
+}
+
+.ssoClients > div > .collapsible a {
+ margin-right: 0;
+ margin-left: auto;
+}
+
+.ssoClients a {
+ color: #fff;
+ text-decoration: none;
+}
+
+.ssoRedirects > div{
+ display: flex;
+ align-items: center;
+ gap: 1em;
+}
+
+.thirdPartyApps-container {
+ display: flex;
+ width: 100%;
+ gap: 1em;
+ flex-wrap: wrap;
+}
+
+.thirdPartyApps-container > div {
+ background-color: #111;
+ padding: 1em;
+ border-radius: .5em;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 1em;
+}
+
+.thirdPartyApps-container > div > img {
+ border-radius: 1em;
+ height: 4em;
+ object-fit: contain;
+}
+
+.thirdPartyApps-container > div > button {
+ margin-left: auto;
+}
+
+.thirdPartyApps-container > div > h1 {
+ font-size: 1.5em;
+ margin: 0;
+}
+
+button.success {
+ background-color: #1dd05e;
+ border: 2px solid #1dd05e;
+}
+
+button.error {
+ background-color: #d01d1d;
+ border: 2px solid #d01d1d;
+}
+
+a {
+ color: #fff;
+ text-decoration: none;
+}
diff --git a/Assets/css/style.css b/Assets/css/style.css
new file mode 100644
index 0000000..b37dee6
--- /dev/null
+++ b/Assets/css/style.css
@@ -0,0 +1,258 @@
+body {
+ background-color: black;
+ font-family: 'Roboto Mono', monospace;
+ color: white;
+ margin: 0;
+}
+
+.stroke {
+ fill: none;
+ stroke-width: 3px;
+ stroke: white;
+}
+
+h1 {
+ margin: 0;
+}
+
+.st0 {
+ fill: none;
+}
+
+.st1 {
+ fill: url(#SVGID_1_);
+}
+
+nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 1em;
+ z-index: 10;
+}
+
+a {
+ color: white;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+.logo {
+ width: 60vw;
+ margin-left: auto;
+ margin-right: auto;
+ position: relative;
+ margin-top: 25vh;
+}
+
+footer {
+ padding: 1em 12vw;
+ padding-top: 3em;
+ border-top: #666 solid 1px;
+}
+
+.logo-background {
+ position: absolute;
+ filter: blur(15vw);
+ z-index: -1;
+}
+
+.logo-container {
+ background-repeat: no-repeat;
+ background-color: #ff000000;
+ background-image: radial-gradient(at 2% 0%, hsla(1, 0%, 34%, 1) 0px, black 50%);
+ height: 90vh;
+ width: 100%;
+ position: absolute;
+ top: 0;
+ z-index: -1;
+}
+
+.container {
+ position: relative;
+ padding: 0 15vw;
+}
+
+.feature-container {
+ display: flex;
+ justify-content: space-between;
+ gap: 1em;
+ flex-wrap: wrap;
+ width: 100%;
+}
+
+.feature-container .item {
+ background-color: #222222b0;
+ border-radius: 15px;
+ border: 1px solid #666;
+ padding: 1em;
+ width: calc(33% - 2em - 1em);
+ box-shadow: 0 0 10px #060606;
+ backdrop-filter: blur(16px);
+}
+
+.service-container {
+ display: flex;
+ padding: 1em;
+ gap: 1em;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.service-container .item {
+ width: calc(33% - 2em - 1em);
+ border: 1px solid #444;
+ border-radius: 1em;
+ padding: 1em;
+ box-shadow: 0 0 10px #060606;
+ position: relative;
+ min-height: 10em;
+ background: linear-gradient(180deg, #242424, #121212 65.62%);
+ overflow: hidden;
+}
+
+/* .service-container .item::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ padding: 1px;
+ mask: linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);
+ -webkit-mask-composite: xor;
+ mask-composite: exclude;
+ width: 100%;
+ height: 100%;
+ --accents-2: #44444400;
+ border-radius: inherit;
+ background: conic-gradient(from 100deg at 50% 50%,var(--accents-2) 0deg,var(--accents-2) 176deg,#fff 193deg,var(--accents-2) 217deg,var(--accents-2) 1turn);
+ } */
+
+.service-container .item .footer {
+ font-size: .8em;
+ display: flex;
+ gap: .5em;
+ align-items: center;
+ position: absolute;
+ bottom: 1em;
+}
+
+.service-container .item .footer hr {
+ border: 0;
+ background: #ffffff;
+ height: 19px;
+ width: 1px;
+ margin: 0;
+}
+
+.service-container .item:nth-child(1) {
+ background: linear-gradient(180deg, #ffad1e69, #ffa2008a 65.62%);
+}
+
+.service-container .item:nth-child(2) {
+ background: linear-gradient(180deg, #63a0d969, #63a0d98a 65.62%);
+}
+
+.service-container .item:nth-child(5) {
+ background: linear-gradient(180deg, #c5120b69, #c5120b8a 65.62%);
+}
+
+.service-container .item:nth-child(6) {
+ background: linear-gradient(180deg, #6600ff69, #6600ff8a 65.62%);
+}
+
+footer hr {
+ border: 0;
+ background: gray;
+ height: 19px;
+ width: 1px;
+ margin: 0;
+}
+
+footer>div {
+ display: flex;
+ width: 100%;
+ gap: 1em;
+}
+
+footer>div:nth-child(1) {
+ flex-wrap: wrap;
+}
+
+@media (max-width: 600px) {
+ footer>div:nth-child(2) {
+ justify-content: center;
+ }
+
+ footer>div:nth-child(1)>div {
+ width: 100%;
+ }
+}
+
+@media (max-width: 1100px) {
+
+ .feature-container .item,
+ .service-container .item {
+ width: 100%;
+ }
+}
+
+.service-outer {
+ padding-top: 6em;
+ padding-bottom: 5em;
+}
+
+.divider {
+ margin-top: 65vh;
+ margin-bottom: -36em;
+}
+
+.divider svg {
+ margin-bottom: -.5em;
+}
+
+.divider>div {
+ background: #222;
+ background: linear-gradient(180deg, #222 34%, rgba(66, 53, 53, 0) 100%);
+ height: 20em;
+}
+
+.pfb {
+ width: 2.5em;
+ height: 2.5em;
+ border-radius: 50%;
+ background-color: #333;
+ animation: skeleton 2s infinite;
+ overflow: hidden;
+ border: 1px solid #666;
+}
+
+.name.loading {
+ width: 10em;
+ height: 1em;
+ border-radius: 0.5em;
+ background-color: #333;
+ animation: skeleton 2s infinite;
+}
+
+.profile {
+ display: flex;
+ align-items: center;
+ justify-content: end;
+ gap: 1em;
+}
+
+.profile img {
+ object-fit: cover;
+}
+
+@keyframes skeleton {
+ 50% {
+ background-color: #555;
+ }
+}
+
+.d-none {
+ display: none;
+}
\ No newline at end of file
diff --git a/Assets/js/app.js b/Assets/js/app.js
new file mode 100644
index 0000000..bd9abe2
--- /dev/null
+++ b/Assets/js/app.js
@@ -0,0 +1,115 @@
+import anime from 'animejs';
+
+LoginManager.isLoggedIn().then(async (e) => {
+ if (e) {
+ document.getElementById('loginLoading').style.display = 'none';
+ document.getElementById('profile').classList.remove('d-none');
+
+ document.getElementById('username').innerText = LoginManager.getUsername();
+ document.getElementById('avatar').src = LoginManager.getAvatar();
+ } else {
+ document.getElementById('loginLoading').style.display = 'none';
+ document.getElementById('loginLink').classList.remove('d-none');
+ }
+});
+
+var lineDrawing = anime({
+ targets: '.logo path',
+ strokeDashoffset: [anime.setDashoffset, 0],
+ easing: 'easeInOutCubic',
+ duration: 1500,
+ begin: function (anim) {
+ document.querySelector('path').setAttribute('stroke', 'black');
+ document.querySelector('path').setAttribute('fill', 'none');
+ },
+ complete: function (anim) {
+ document.querySelector('path').setAttribute('fill', 'yellow');
+ },
+ autoplay: true,
+});
+
+var border = anime({
+ targets: '.stroke',
+ duration: 1500,
+ delay: 1000,
+ strokeWidth: ['3px', '0'],
+ direction: 'forward',
+ easing: 'easeInOutQuad',
+});
+
+var white = anime({
+ targets: '.st0',
+ duration: 1500,
+ delay: 800,
+ fill: ['#F2F2F2'],
+ direction: 'forward',
+ easing: 'easeInOutQuad',
+});
+
+var startColor = anime({
+ targets: '#startColor',
+ duration: 1500,
+ delay: 800,
+ stopColor: ['none', '#fd1414'],
+ direction: 'forward',
+ easing: 'easeInOutQuad',
+});
+
+var endColor = anime({
+ targets: '#endColor',
+ duration: 1500,
+ delay: 800,
+ stopColor: ['none', '#7c0d00'],
+ direction: 'forward',
+ easing: 'easeInOutQuad',
+});
+
+setColors();
+setInterval(setColors, 5000 * (4 - 1));
+
+function setColors() {
+ setTimeout(function () {
+ changeColor('#e29c25', '#db7220');
+
+ setTimeout(function () {
+ changeColor('#63a0d9', '#394f6b');
+
+ setTimeout(function () {
+ changeColor('#444', '#111');
+
+ setTimeout(function () {
+ changeColor('#fd1414', '#7c0d00');
+ }, 5000);
+ }, 5000);
+ }, 5000);
+ }, 5000);
+}
+
+function changeColor(startColor, endColor) {
+ var white = anime({
+ targets: '.st0',
+ duration: 1500,
+ delay: 1000,
+ fill: ['#F2F2F2'],
+ direction: 'forward',
+ easing: 'easeInOutQuad',
+ });
+
+ var startColor = anime({
+ targets: '#startColor',
+ duration: 1500,
+ delay: 1000,
+ stopColor: [startColor],
+ direction: 'forward',
+ easing: 'easeInOutQuad',
+ });
+
+ var endColor = anime({
+ targets: '#endColor',
+ duration: 1500,
+ delay: 1000,
+ stopColor: [endColor],
+ direction: 'forward',
+ easing: 'easeInOutQuad',
+ });
+}
diff --git a/Assets/js/dialog.js b/Assets/js/dialog.js
new file mode 100644
index 0000000..224d9f5
--- /dev/null
+++ b/Assets/js/dialog.js
@@ -0,0 +1,118 @@
+export function createDialog(title = 'Information', text = 'No information available', type = 'info') {
+ const container = document.getElementById('dialog-container');
+
+ if (container) {
+ const dialog = document.createElement('div');
+ dialog.classList.add('dialog');
+ dialog.classList.add(type);
+
+ const dialogTitle = document.createElement('h2');
+ dialogTitle.classList.add('dialog-title');
+ dialogTitle.textContent = title;
+
+ const dialogText = document.createElement('p');
+ dialogText.classList.add('dialog-content');
+ dialogText.textContent = text;
+
+ const dialogButton = document.createElement('button');
+ dialogButton.classList.add('dialog-button');
+ dialogButton.textContent = 'OK';
+
+ dialog.appendChild(dialogTitle);
+ dialog.appendChild(dialogText);
+ dialog.appendChild(dialogButton);
+
+ dialogButton.addEventListener('click', () => {
+ if (container.children.length === 1) container.classList.remove('visible');
+
+ dialog.remove();
+ });
+
+ container.appendChild(dialog);
+ container.classList.add('visible');
+ } else console.error('Dialog container not found');
+}
+
+export function initDialog(element = undefined) {
+ element = element || window.document;
+
+ Array.from(element.querySelectorAll('[target-dialog]')).forEach((button) => {
+ const target = button.getAttribute('target');
+ const dialog = document.getElementById(target);
+ const submitButton = dialog.querySelector('[type="submit"]');
+
+ dialog.hide = () => {
+ dialog.classList.remove('visible');
+ document.getElementById('dialog-container').classList.remove('visible');
+ dialog.dispatchEvent(new CustomEvent('onHide', {
+ detail: {
+ reason: 'submit'
+ }
+ }));
+ };
+
+ dialog.show = () => {
+ dialog.classList.add('visible');
+ document.getElementById('dialog-container').classList.add('visible');
+ dialog.dispatchEvent(new CustomEvent('onShow'));
+ };
+
+ if (dialog === undefined) {
+ console.error('Dialog not found');
+ return;
+ }
+
+ const onShowEvent = dialog.getAttribute('onShow');
+ if (onShowEvent !== undefined) dialog.addEventListener('onShow', () => eval(onShowEvent));
+
+ submitButton.addEventListener('click', (event) => {
+ dialog.hide();
+ });
+
+ button.addEventListener('click', () => {
+ dialog.classList.add('visible');
+ document.getElementById('dialog-container').classList.add('visible');
+
+ dialog.dispatchEvent(new CustomEvent('onShow'));
+
+ setTimeout(() => {
+ window.addEventListener(
+ 'click',
+ function _listener(e) {
+ if (e.target.closest('#' + dialog.id) === null) {
+ dialog.classList.remove('visible');
+ dialog.dispatchEvent(new CustomEvent('onHide'));
+ window.removeEventListener('click', _listener, true);
+ }
+ },
+ true
+ );
+ }, 100);
+ });
+ });
+
+ if (document.getElementById('dialog-container') !== null) return;
+
+ const container = document.createElement('div');
+ container.id = 'dialog-container';
+ container.classList.add('dialog-container');
+
+ //add event listener to window
+ container.addEventListener('click', (event) => {
+ if (event.target === container) {
+ container.classList.remove('visible');
+
+ Array.from(container.querySelectorAll('.dialog')).forEach((dialog) => {
+ dialog.classList.remove('visible');
+ dialog.dispatchEvent(new CustomEvent('onHide', { detail: { reason: 'canceled' }}));
+ });
+
+ Array.from(document.querySelectorAll('.custom-dialog.visible')).forEach((dialog) => {
+ dialog.classList.remove('visible');
+ dialog.dispatchEvent(new CustomEvent('onHide', { detail: { reason: 'canceled' }}));
+ });
+ }
+ });
+
+ document.body.appendChild(container);
+}
diff --git a/Assets/js/profile.js b/Assets/js/profile.js
new file mode 100644
index 0000000..045a0fe
--- /dev/null
+++ b/Assets/js/profile.js
@@ -0,0 +1,1145 @@
+import { createDialog, initDialog } from './dialog';
+
+const languages = [
+ { key: 'en-us', name: 'English' },
+ { key: 'de-at', name: 'German' },
+ { key: 'fr-fr', name: 'French' },
+ { key: 'it-it', name: 'Italian' },
+ { key: 'es-es', name: 'Spanish' },
+ { key: 'pl-pl', name: 'Polish' },
+ { key: 'nl-nl', name: 'Dutch' },
+ { key: 'pt-pt', name: 'Portuguese' },
+ { key: 'ru-ru', name: 'Russian' },
+ { key: 'tr-tr', name: 'Turkish' },
+ { key: 'zh-cn', name: 'Chinese' },
+ { key: 'ja-jp', name: 'Japanese' },
+ { key: 'ko-kr', name: 'Korean' },
+ { key: 'ar-sa', name: 'Arabic' },
+ { key: 'hi-in', name: 'Hindi' },
+ { key: 'no-no', name: 'Norwegian' },
+ { key: 'sv-se', name: 'Swedish' },
+ { key: 'fi-fi', name: 'Finnish' },
+ { key: 'da-dk', name: 'Danish' },
+ { key: 'cs-cz', name: 'Czech' },
+ { key: 'hu-hu', name: 'Hungarian' },
+ { key: 'el-gr', name: 'Greek' },
+ { key: 'th-th', name: 'Thai' },
+ { key: 'id-id', name: 'Indonesian' },
+ { key: 'ro-ro', name: 'Romanian' },
+ { key: 'sk-sk', name: 'Slovak' },
+ { key: 'uk-ua', name: 'Ukrainian' },
+ { key: 'bg-bg', name: 'Bulgarian' },
+ { key: 'hr-hr', name: 'Croatian' },
+ { key: 'ca-es', name: 'Catalan' },
+ { key: 'et-ee', name: 'Estonian' },
+ { key: 'fa-ir', name: 'Persian' },
+ { key: 'he-il', name: 'Hebrew' },
+ { key: 'is-is', name: 'Icelandic' },
+ { key: 'lt-lt', name: 'Lithuanian' },
+ { key: 'lv-lv', name: 'Latvian' },
+ { key: 'sr-rs', name: 'Serbian' },
+ { key: 'sl-si', name: 'Slovenian' },
+ { key: 'vi-vn', name: 'Vietnamese' },
+];
+
+const countries = [
+ { key: 'at', name: 'Austria' },
+ { key: 'de', name: 'Germany' },
+ { key: 'ch', name: 'Switzerland' },
+ { key: 'us', name: 'United States' },
+ { key: 'gb', name: 'United Kingdom' },
+ { key: 'fr', name: 'France' },
+ { key: 'it', name: 'Italy' },
+ { key: 'es', name: 'Spain' },
+ { key: 'pl', name: 'Poland' },
+ { key: 'nl', name: 'Netherlands' },
+ { key: 'pt', name: 'Portugal' },
+ { key: 'ru', name: 'Russia' },
+ { key: 'tr', name: 'Turkey' },
+ { key: 'cn', name: 'China' },
+ { key: 'jp', name: 'Japan' },
+ { key: 'kr', name: 'Korea' },
+ { key: 'ar', name: 'Argentina' },
+ { key: 'au', name: 'Australia' },
+ { key: 'be', name: 'Belgium' },
+];
+var currentUser;
+
+initDialog();
+document.getElementById('pi_save').addEventListener('click', savePersonalInformation);
+document.getElementById('cp_save').addEventListener('click', changePassword);
+document.getElementById('createApiKey').addEventListener('click', createApiKey);
+document.getElementById('ca_spotify_link').addEventListener('click', () => LinkAccounts('spotify'));
+document.getElementById('ca_twitch_link').addEventListener('click', () => LinkAccounts('twitch'));
+document.getElementById('ca_discord_link').addEventListener('click', () => LinkAccounts('discord'));
+document.getElementById('ca_google_link').addEventListener('click', () => LinkAccounts('google'));
+document.getElementById('ca_github_link').addEventListener('click', () => LinkAccounts('github'));
+document.getElementById('logout').addEventListener('click', () => LoginManager.logout());
+document.getElementById('deleteAccount').addEventListener('click', async (e) => await doubleClickButton(e, deleteAccount));
+document.getElementById('createSsoCredentials').addEventListener('click', async () => await createSsoCredentials());
+
+LoginManager.isLoggedIn().then(async (e) => {
+ if (!e && import.meta.env.MODE !== 'development') {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const token = LoginManager.getCookie('token');
+
+ const urlParams = new URLSearchParams(window.location.search);
+
+ if (urlParams.has('code')) {
+ const code = urlParams.get('code');
+
+ LinkAccount(code);
+ }
+
+ if (import.meta.env.MODE === 'development') {
+ const user = {
+ username: 'TestUser',
+ firstname: 'Test',
+ lastname: 'User',
+ email: 'amogus@example.com',
+ country: 'at',
+ preferredLang: 'de-at',
+ discordId: null,
+ spotifyId: null,
+ twitchId: null,
+ githubId: null,
+ googleId: null,
+ '2fa': false,
+ '2faType': 'App',
+ api_keys: [
+ "1234567890"
+ ],
+ trusted_sso_clients: [
+ {
+ id: '1234567890',
+ name: 'TestClient',
+ logo: 'https://via.placeholder.com/150',
+ },
+ ],
+ sso_clients: [
+ {
+ id: '1234567890',
+ name: 'TestClient',
+ logo: 'https://via.placeholder.com/150',
+ url: 'https://example.com',
+ secret: '1234567890',
+ redirects: [
+ {
+ id: '1234567890',
+ url: 'https://example.com',
+ },
+ ],
+ },
+ ],
+ };
+
+ currentUser = user;
+ } else {
+ const req = await fetch('https://api.login.netdb.at/user', {
+ method: 'GET',
+ headers: {
+ Authorization: 'Bearer ' + token,
+ },
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (res.statusCode != 200) {
+ console.log(res);
+ return;
+ }
+
+ const user = res.data;
+ currentUser = user;
+ }
+
+ document.getElementById('username').value = currentUser.username;
+ document.getElementById('firstname').value = currentUser.firstname;
+ document.getElementById('lastname').value = currentUser.lastname;
+ document.getElementById('pi_email').value = currentUser.email;
+ document.getElementById('cp_email').value = currentUser.email;
+ document.getElementById('country').dataset.key = currentUser.country;
+ document.getElementById('preferredlang').dataset.key = currentUser.preferredLang;
+
+ if (currentUser.discordId !== null) document.getElementById('ca_discord').classList.add('connected');
+
+ if (currentUser.spotifyId !== null) document.getElementById('ca_spotify').classList.add('connected');
+
+ if (currentUser.twitchId !== null) document.getElementById('ca_twitch').classList.add('connected');
+
+ if (currentUser.githubId !== null) document.getElementById('ca_github').classList.add('connected');
+
+ if (currentUser.googleId !== null) document.getElementById('ca_google').classList.add('connected');
+
+ Array.from(document.getElementsByClassName('connected')).forEach((element) => {
+ element.addEventListener('click', disconnectAccount);
+ });
+
+ if (currentUser['2fa'] && currentUser['2faType'] == 'App') document.getElementById('cp_2fa').classList.remove('d-none');
+
+ if (currentUser['2fa']) {
+ document.getElementById('2fa_status').innerText = 'Enabled';
+ document.getElementById('2fa_type').value = currentUser['2faType'] == 'App' ? 0 : currentUser['2faType'] == 'Mail' ? 1 : 2;
+ document.getElementById('2fa_type').disabled = true;
+ document.getElementById('2fa_enable').innerText = 'Disable';
+ document.getElementById('2fa_enable').addEventListener('click', disable2fa);
+ } else {
+ document.getElementById('2fa_enable').addEventListener('click', enable2fa);
+ }
+
+ initSearchbar(countries, 'country_search');
+ initSearchbar(languages, 'language_search');
+
+ currentUser.api_keys.forEach((key) => {
+ document.getElementById('apiKeysTable').appendChild(createApiKeyRow(key, '*************'));
+ });
+
+ if (currentUser.trusted_sso_clients.length > 0) document.getElementById('thirdPartyAppsContainer').innerHTML = '';
+
+ currentUser.trusted_sso_clients.forEach((client) => {
+ const row = document.createElement('div');
+ row.id = 'trusted_' + client.id;
+ const img = document.createElement('img');
+ img.src = client.logo;
+ img.alt = client.name;
+ img.title = client.name;
+ row.appendChild(img);
+ const name = document.createElement('h1');
+ name.innerText = client.name;
+ row.appendChild(name);
+ const deleteBtn = document.createElement('button');
+ deleteBtn.innerText = 'Delete';
+ deleteBtn.addEventListener('click', async () => await deleteTrustedSsoClient(client.id));
+ row.appendChild(deleteBtn);
+ document.getElementById('thirdPartyAppsContainer').appendChild(row);
+ });
+
+ if (currentUser.sso_clients.length > 0) document.getElementById('ssoClientsContainer').innerHTML = '';
+
+ currentUser.sso_clients.forEach((client) => {
+ document.getElementById('ssoClientsContainer').appendChild(createSSOClient(client.logo, client.name, client.url, client.id, client.secret, client.redirects));
+ });
+
+ Array.from(document.getElementsByClassName('collapsible')).forEach((element) => {
+ element.addEventListener('click', function () {
+ this.classList.toggle('active');
+
+ if (this.nextElementSibling.style.maxHeight) this.nextElementSibling.style.maxHeight = null;
+ else this.nextElementSibling.style.maxHeight = this.nextElementSibling.scrollHeight + 'px';
+ });
+ });
+
+ Array.from(document.getElementsByTagName('input')).forEach((element) => {
+ element.addEventListener('keyup', (e) => e.target.classList.remove('invalid'));
+ });
+});
+
+async function LinkAccount(code) {
+ const provider = localStorage.getItem('linkType');
+
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/link/' + provider + '?code=' + code, {
+ method: 'GET',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (res.statusCode != 200) {
+ createDialog('Error', 'An error occured while linking your account!', 'error');
+ return;
+ }
+
+ localStorage.removeItem('linkType');
+ const urlParams = new URLSearchParams(window.location.search);
+ urlParams.delete('code');
+
+ window.history.replaceState({}, document.title, window.location.pathname + window.location.search);
+
+ createDialog('Success', 'Account successfully linked!', 'info');
+}
+
+async function deleteTrustedSsoClient(clientId) {
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/oauth/untrust', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: '"' + clientId + '"',
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (res.statusCode != 200) {
+ console.log(res);
+ return;
+ }
+
+ document.getElementById('trusted_' + clientId).remove();
+
+ const container = document.getElementById('thirdPartyAppsContainer');
+ container.innerHTML = 'No third party apps connected!
';
+}
+
+async function createSsoCredentials() {
+ document
+ .getElementById('createSSODialog')
+ .querySelectorAll('input')
+ .forEach((element) => {
+ element.value = '';
+ });
+
+ document.getElementById('createSSODialog').show();
+
+ const canceled = await new Promise((resolve) => {
+ document.getElementById('createSSODialog').addEventListener(
+ 'onHide',
+ (e) => {
+ if (e.detail.reason == 'canceled') {
+ resolve(false);
+ return;
+ }
+
+ resolve(true);
+ },
+ { once: true }
+ );
+ });
+
+ if (!canceled) return;
+
+ const name = document.getElementById('c_sso_name').value;
+ const websiteUrl = document.getElementById('c_sso_url').value;
+ const logoUrl = document.getElementById('c_sso_logoUrl').value;
+
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/user/sso', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ name: name,
+ url: websiteUrl,
+ logoUrl: logoUrl,
+ }),
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (req.status != 200 || res.statusCode != 203) {
+ createDialog('Error', 'An error occured while creating the SSO credentials!', 'error');
+ return;
+ }
+
+ if (document.getElementById('ssoClientsContainer').children[0].tagName == 'P') document.getElementById('ssoClientsContainer').innerHTML = '';
+
+ const item = createSSOClient(logoUrl, name, websiteUrl, res.data.clientId, res.data.clientSecret, []);
+ document.getElementById('ssoClientsContainer').appendChild(item);
+
+ item.children[0].addEventListener('click', function () {
+ item.classList.toggle('active');
+
+ if (item.children[0].nextElementSibling.style.maxHeight) item.children[0].nextElementSibling.style.maxHeight = null;
+ else item.children[0].nextElementSibling.style.maxHeight = item.children[0].nextElementSibling.scrollHeight + 'px';
+ });
+}
+
+function createSSOClient(logoUrl, clientName, websiteUrl, clientId, clientSecret, redirects) {
+ const template = document.importNode(document.getElementById('ssoCredentials').getElementsByTagName('template')[0].content, true);
+ const item = template.querySelector('div');
+
+ item.id = 'sso_' + clientId;
+ item.querySelector('img').src = logoUrl;
+ item.querySelector('img').alt = clientName;
+ item.querySelector('img').title = clientName;
+ item.querySelector('h1').innerText = clientName;
+ item.querySelector('a').href = websiteUrl;
+ item.querySelector('a').innerText = websiteUrl;
+
+ const content = item.querySelector('.collapsible-content');
+ content.querySelector('#sso_clientId').value = clientId;
+ content.querySelector('#sso_clientSecret').value = clientSecret;
+ content.querySelector('#sso_logoUrl').value = logoUrl;
+ content.querySelector('#sso_websiteUrl').value = websiteUrl;
+ content.querySelector('#sso_name').value = clientName;
+ content.querySelector('#sso_save').addEventListener('click', async () => await saveSSOClient(clientId));
+ item.querySelector('#sso_delete').addEventListener('click', async () => await deleteSSOClient(clientId));
+ content.querySelector('#sso_addRedirect').addEventListener('click', async () => await addSSORedirect(clientId));
+
+ const redirectsContainer = content.querySelector('#sso_redirects');
+
+ redirects.forEach((redirect) => {
+ const redirectItem = document.createElement('div');
+ redirectItem.id = 'sso_redirect_' + redirect.id;
+
+ const url = document.createElement('input');
+ url.type = 'text';
+ url.value = redirect.url;
+ url.disabled = true;
+ redirectItem.appendChild(url);
+
+ const deleteBtn = document.createElement('button');
+ deleteBtn.innerText = 'Delete';
+ deleteBtn.addEventListener('click', async () => await deleteSSORedirect(clientId, redirect.id));
+ redirectItem.appendChild(deleteBtn);
+
+ redirectsContainer.appendChild(redirectItem);
+ });
+
+ return item;
+}
+
+async function deleteSSOClient(clientId) {
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/user/sso', {
+ method: 'DELETE',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: '"' + clientId + '"',
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (req.status != 200 || res.statusCode != 200) {
+ createDialog('Error', 'An error occured while deleting the SSO credentials!', 'error');
+ return;
+ }
+
+ document.getElementById('sso_' + clientId).remove();
+}
+
+async function deleteSSORedirect(clientId, redirectId) {
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/user/sso/redirects', {
+ method: 'DELETE',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ clientId: clientId,
+ redirectId: redirectId,
+ }),
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (res.statusCode != 200) {
+ console.log(res);
+ return;
+ }
+
+ document.getElementById('sso_redirect_' + redirectId).remove();
+}
+
+async function addSSORedirect(clientId) {
+ const item = document.getElementById('sso_' + clientId);
+ const redirects = item.querySelector('#sso_redirects');
+
+ //TODO: add button feedback
+
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/user/sso/redirects', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ clientId: clientId,
+ url: item.querySelector('#sso_addRedirect').previousElementSibling.value,
+ }),
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (res.statusCode != 203) {
+ console.log(res);
+ return;
+ }
+
+ item.querySelector('#sso_addRedirect').previousElementSibling.value = '';
+
+ const redirect = document.createElement('div');
+ redirect.id = 'sso_redirect_' + res.data.id;
+ const url = document.createElement('input');
+ url.type = 'text';
+ url.value = res.data.url;
+ url.disabled = true;
+ redirect.appendChild(url);
+ const deleteBtn = document.createElement('button');
+ deleteBtn.innerText = 'Delete';
+ deleteBtn.addEventListener('click', async () => await deleteSSORedirect(clientId, res.data.id));
+ redirect.appendChild(deleteBtn);
+
+ if (redirects.children.length > 1) {
+ redirects.insertBefore(redirect, redirects.children[1]);
+ return;
+ }
+
+ redirects.append(redirect);
+}
+
+async function saveSSOClient(clientId) {
+ const item = document.getElementById('sso_' + clientId);
+
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/user/sso', {
+ method: 'PUT',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ clientId: clientId,
+ logoUrl: item.querySelector('#sso_logoUrl').value,
+ url: item.querySelector('#sso_websiteUrl').value,
+ name: item.querySelector('#sso_name').value,
+ }),
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (res.statusCode != 200) {
+ console.log(res);
+ createDialog('Error', 'An error occured while saving the SSO credentials!', 'error');
+ return;
+ }
+
+ displayButtonFeedback(item.querySelector('#sso_save'), 'success');
+}
+
+async function doubleClickButton(e, func) {
+ if (e.target.dataset.clicked == 'true') {
+ await func();
+ return;
+ }
+
+ e.target.dataset.clicked = 'true';
+ e.target.innerText = 'Confirm';
+
+ setTimeout(() => {
+ e.target.dataset.clicked = 'false';
+ e.target.innerText = 'Delete Account';
+ }, 3000);
+}
+
+function createApiKeyRow(client_id) {
+ const row = document.createElement('tr');
+ row.id = client_id;
+ row.innerHTML = '' + client_id + ' ';
+ const td = document.createElement('td');
+ const deleteBtn = document.createElement('button');
+ const regenBtn = document.createElement('button');
+ deleteBtn.innerText = 'Delete';
+ regenBtn.innerText = 'Regenerate';
+
+ deleteBtn.addEventListener('click', () => deleteApiKey(client_id));
+ regenBtn.addEventListener('click', () => regenerateApiKey(client_id));
+
+ td.appendChild(regenBtn);
+ td.appendChild(deleteBtn);
+ row.appendChild(td);
+
+ return row;
+}
+
+async function createApiKey() {
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/user/apikey', {
+ method: 'GET',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (res.statusCode != 203) {
+ console.log(res);
+ createDialog('Error', 'An error occured while creating the API key!', 'error');
+ return;
+ }
+
+ document.getElementById('apiKeysTable').appendChild(createApiKeyRow(res.data.clientId));
+
+ createDialog('Success', 'Successfully created API key! Please save it now, as it will not be shown again! ' + res.data.clientSecret, 'info');
+}
+
+async function regenerateApiKey(clientId) {
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/user/apikey', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: '"' + clientId + '"',
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (res.statusCode != 200) {
+ console.log(res);
+ createDialog('Error', 'An error occured while regenerating the API key!', 'error');
+ return;
+ }
+
+ createDialog('Success', 'Successfully regenerated API key! Please save it now, as it will not be shown again! ' + res.data.clientSecret, 'info');
+}
+
+async function deleteApiKey(clientId) {
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/user/apikey', {
+ method: 'DELETE',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: '"' + clientId + '"',
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const data = await req.json();
+
+ if (data.statusCode != 200) {
+ console.log(data);
+ createDialog('Error', 'An error occured while deleting the API key!', 'error');
+ return;
+ }
+
+ document.getElementById(clientId).remove();
+}
+
+function LinkAccounts(type) {
+ localStorage.setItem('linkType', type);
+
+ switch (type) {
+ case 'spotify': {
+ window.location.href = 'https://accounts.spotify.com/de/authorize?client_id=a7c2014c0531405983d7050277dee3cb&response_type=code&redirect_uri=https://netdb.at/profile&scope=user-read-private%20user-read-email';
+ break;
+ }
+ case 'discord': {
+ window.location.href = 'https://discord.com/api/oauth2/authorize?client_id=802237562625196084&redirect_uri=https://netdb.at/profile&response_type=code&scope=identify%20email';
+ break;
+ }
+ case 'twitch': {
+ window.location.href = 'https://id.twitch.tv/oauth2/authorize?client_id=okxhfdyyoyx724c5zf0h869x9ry1sx&redirect_uri=https://netdb.at/profile&response_type=code&scope=user_read';
+ break;
+ }
+ case 'github': {
+ window.location.href = 'https://github.com/login/oauth/authorize?scope=user:email&client_id=de5e22518d66ab50a805';
+ break;
+ }
+ case 'google': {
+ window.location.href =
+ 'https://accounts.google.com/o/oauth2/v2/auth?scope=https%3A//www.googleapis.com/auth/userinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&state=state_parameter_passthrough_value&redirect_uri=https://netdb.at/profile&client_id=736018590984-nh2ifch6ps8art9v35avipv16se1b720.apps.googleusercontent.com';
+ break;
+ }
+ }
+}
+
+function initSearchbar(data, id) {
+ let searchbar = document.getElementById(id);
+ let inputBox = searchbar.querySelector('input');
+
+ try {
+ if (inputBox.dataset.key && inputBox.dataset.key != 'null') inputBox.value = data.find((e) => e.key == inputBox.dataset.key).name;
+ } catch (e) {
+ console.log(e);
+ }
+
+ showSuggestions(data, searchbar);
+
+ inputBox.addEventListener('keyup', (e) => filterSearch(e.target.value, data, searchbar));
+ inputBox.addEventListener('focus', () => searchbar.querySelector('.autocom-box').classList.add('active'));
+ searchbar.addEventListener('focusout', (e) => {
+ setTimeout(() => {
+ searchbar.querySelector('.autocom-box').classList.remove('active');
+ }, 300);
+ });
+
+ inputBox.onkeydown = (e) => {
+ if (e.key == 'Enter') {
+ searchbar.querySelector('input').value = searchbar.querySelector('.autocom-box h1').innerText;
+ searchbar.querySelector('input').dataset.key = searchbar.querySelector('.autocom-box h1').dataset.key;
+ }
+ };
+}
+
+function filterSearch(userData, data, searchbar) {
+ const suggestions = [];
+
+ for (var i = 0; i < data.length; i++) suggestions.push(data[i]);
+
+ let emptyArray = suggestions.filter((data) => data.name.toLocaleLowerCase().startsWith(userData.toLocaleLowerCase()));
+
+ emptyArray.sort((a, b) => {
+ if (a.name < b.name) return -1;
+
+ if (a.name > b.name) return 1;
+
+ return 0;
+ });
+
+ showSuggestions(emptyArray, searchbar);
+}
+
+function showSuggestions(list, searchbar) {
+ list = list.map((data) => {
+ return (data = '' + data.name + ' ');
+ });
+
+ let listData = '';
+ if (!list.length) {
+ // const userValue = searchbar.querySelector('input').value;
+ // listData = '' + userValue + ' ';
+ } else {
+ listData = list.join('');
+ }
+
+ searchbar.querySelector('.autocom-box').innerHTML = listData;
+
+ const allList = searchbar.querySelector('.autocom-box').querySelectorAll('h1');
+ for (let i = 0; i < allList.length; i++)
+ allList[i].addEventListener('click', (e) => {
+ searchbar.querySelector('input').value = e.target.innerText;
+ searchbar.querySelector('input').dataset.key = e.target.dataset.key;
+ });
+}
+
+async function savePersonalInformation() {
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/user', {
+ method: 'PUT',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ firstname: document.getElementById('firstname').value,
+ lastname: document.getElementById('lastname').value,
+ country: document.getElementById('country').dataset.key,
+ preferredLang: document.getElementById('preferredlang').dataset.key,
+ username: document.getElementById('username').value,
+ }),
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (res.statusCode != 200) {
+ console.log(res);
+ displayButtonFeedback(document.getElementById('pi_save'), 'error');
+ return;
+ }
+
+ displayButtonFeedback(document.getElementById('pi_save'), 'success');
+}
+
+function displayButtonFeedback(btn, type) {
+ btn.classList.add(type);
+
+ setTimeout(() => {
+ btn.classList.remove(type);
+ }, 1500);
+}
+
+async function changePassword() {
+ const pw = document.getElementById('newPassword1').value;
+ const rpw = document.getElementById('newPassword2').value;
+ const oldPw = document.getElementById('oldPassword').value;
+
+ const error = validatePw(oldPw, pw, rpw);
+
+ if (error) {
+ document.getElementById('newPassword1').value = '';
+ document.getElementById('newPassword2').value = '';
+
+ document.getElementById('newPassword1').classList.add('invalid');
+ document.getElementById('newPassword2').classList.add('invalid');
+
+ createDialog('Error', error, 'error');
+ return;
+ }
+
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/resetpassword', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ OldPassword: oldPw,
+ Email: document.getElementById('cp_email').value,
+ Password: pw,
+ TwoFaToken: currentUser['2fa'] ? document.getElementById('cp_2fa').value : null,
+ }),
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (res.statusCode != 200) {
+ console.log(res);
+ return;
+ }
+
+ LoginManager.deleteCookie('token');
+ LoginManager.deleteCookie('refreshToken');
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+}
+
+function validatePw(oldPw, pw, rpw) {
+ if (pw == null || rpw == null) return 'Please fill out all fields';
+
+ if (pw.length < 8) return 'The password has to be at least 8 characters long';
+
+ if (!isUpperCase(pw)) return 'The password has to contain at least one uppercase letter';
+
+ if (!isLowerCase(pw)) return 'The password has to contain at least one lowercase letter';
+
+ if (!isNumber(pw)) return 'The password has to contain at least one number';
+
+ if (pw != rpw) return 'Passwords do not match';
+
+ if (pw == oldPw) return 'The new password cannot be the same as the old one';
+}
+
+function isUpperCase(str) {
+ return /[A-Z]/.test(str);
+}
+
+function isLowerCase(str) {
+ return /[a-z]/.test(str);
+}
+
+function isNumber(str) {
+ return /[0-9]/.test(str);
+}
+
+async function disconnectAccount(e) {
+ const element = e.target.closest('[data-type]');
+
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/unlink/' + element.dataset.type, {
+ method: 'GET',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ },
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ element.classList.remove('connected');
+}
+
+async function deleteAccount() {
+ const creds = await getCreds(true);
+
+ if (!creds) return;
+
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/user', {
+ method: 'DELETE',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ Password: creds.password,
+ TwoFaToken: creds.mfaToken,
+ }),
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const data = await req.json();
+
+ if (data.statusCode != 200) {
+ createDialog('Error', 'An error occured while deleting your account!', 'error');
+ return;
+ }
+
+ LoginManager.deleteCookie('token', '/', '.netdb.at');
+ LoginManager.deleteCookie('refreshToken', '/', '.netdb.at');
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+}
+
+async function enable2fa() {
+ const mfaType = document.getElementById('2fa_type').value;
+
+ if (mfaType == 2) {
+ alert('Select a valid 2FA type!');
+ return;
+ }
+
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/2fa/activate', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: mfaType == 0 ? '"app"' : mfaType == 1 ? '"mail"' : null,
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ if (req.status != 200) {
+ createDialog('Error', 'An error occured while enabling 2FA!', 'error');
+ return;
+ }
+
+ const res = await req.json();
+
+ if (mfaType == 0) {
+ //App
+ const qr = res.data.qrCodeSetupImageUrl;
+ const secret = res.data.manualEntryKey;
+
+ document.getElementById('authenticatorDialog').querySelector('img').src = qr;
+ document.getElementById('authenticatorDialog').querySelector('h1').innerText = secret;
+ document.getElementById('authenticatorDialog').show();
+
+ const canceled = await new Promise((resolve) => {
+ document.getElementById('authenticatorDialog').addEventListener(
+ 'onHide',
+ (e) => {
+ if (e.detail.reason == 'canceled') {
+ resolve(false);
+ return;
+ }
+
+ resolve(true);
+ },
+ { once: true }
+ );
+ });
+
+ if (!canceled) return false;
+ }
+
+ await verify2fa();
+}
+
+async function verify2fa() {
+ const creds = await getCreds(true, false, true);
+
+ if (!creds) return;
+
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/2fa/verify', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ MFAToken: creds.mfaToken,
+ Password: creds.password,
+ }),
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return false;
+ }
+
+ if (req.status != 200) {
+ createDialog('Error', 'An error occured while verifying 2FA!', 'error');
+ return false;
+ }
+
+ const res = await req.json();
+
+ if (res.statusCode != 200) {
+ createDialog('Error', 'An error occured while verifying 2FA!', 'error');
+ return false;
+ }
+
+ createDialog('Success', '2FA successfully enabled!', 'info');
+}
+
+async function disable2fa() {
+ const creds = await getCreds(true);
+
+ if (!creds) return;
+
+ const success = await disableMfaRequest(creds.password, creds.mfaToken);
+
+ if (!success) {
+ const mfaToken = await getCreds(true, true);
+
+ if (!mfaToken) return;
+
+ await disableMfaRequest(creds.password, mfaToken.mfaToken);
+ }
+}
+
+async function disableMfaRequest(password, mfaToken) {
+ await LoginManager.validateToken();
+ const req = await fetch('https://api.login.netdb.at/2fa/deactivate', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer ' + LoginManager.getCookie('token'),
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ Password: password,
+ MFAToken: mfaToken,
+ }),
+ });
+
+ if (req.status == 401) {
+ window.location.href = 'https://login.netdb.at?redirect=' + encodeURIComponent(window.location.href);
+ return;
+ }
+
+ const res = await req.json();
+
+ if (res.statusCode == 409) return false;
+
+ if (req.status != 200 || res.statusCode != 200) createDialog('Error', 'An error occured while disabling 2FA!', 'error');
+
+ return true;
+}
+
+async function getCreds(mfa = false, mfaOnly = false, forceMfa = false) {
+ document.getElementById('passwordInput').value = '';
+ document.getElementById('2faInput').value = '';
+
+ if ((mfa && (currentUser['2faType'] == 'App' || mfaOnly)) || forceMfa) document.getElementById('2faInput').classList.remove('d-none');
+ else document.getElementById('2faInput').classList.add('d-none');
+
+ if (mfaOnly) document.getElementById('passwordInput').classList.add('d-none');
+ else document.getElementById('passwordInput').classList.remove('d-none');
+
+ document.getElementById('passwordDialog').show();
+
+ const res = await new Promise((resolve) => {
+ document.getElementById('passwordDialog').addEventListener(
+ 'onHide',
+ (e) => {
+ if (e.detail.reason == 'canceled') {
+ resolve(false);
+ return;
+ }
+
+ resolve(true);
+ },
+ { once: true }
+ );
+ });
+
+ if (!res) return false;
+
+ const pw = document.getElementById('passwordInput').classList.contains('d-none') ? null : document.getElementById('passwordInput').value;
+ const mfaToken = document.getElementById('2faInput').classList.contains('d-none') ? null : document.getElementById('2faInput').value;
+
+ if (pw != null && validatePw(null, pw, pw)) {
+ createDialog('Invalid password', 'The password you entered is invalid!', 'error');
+ return false;
+ }
+ if (mfaToken != null && !validateMfaToken(mfaToken)) {
+ createDialog('Invalid 2FA token', 'The 2FA token you entered is invalid!', 'error');
+ return false;
+ }
+
+ return {
+ password: pw,
+ mfaToken: mfaToken,
+ };
+}
+
+function validateMfaToken(token) {
+ if (token.length != 6) return false;
+ if (!isNumber(token)) return false;
+
+ return true;
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..205ed1a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
Netdb
+
+
+ An unofficial Netflix API
+
+ Explore the docs »
+
+
+ View Page
+ ·
+ Report Bug
+ ·
+ Request Feature
+
+
+
+
+
+
+
+ Table of Contents
+
+
+ About The Project
+
+
+ Roadmap
+ Contributing
+ License
+ Contact
+ Acknowledgements
+
+
+
+
+
+
+## About The Project
+
+Netdb is an unofficial Netflix API.
+
+### Built With
+* [Javascript](https://www.javascript.com)
+* Html,Css
+
+
+## Roadmap
+
+See the [open issues](https://github.com/Netflix-Database/Netdb/issues) for a list of proposed features (and known issues).
+
+
+## License
+
+Distributed under the MIT License. See `LICENSE` for more information.
+
+
+
+
+## Contact
+
+support@netdb.ga
+
+Project Link: [https://github.com/Netflix-Database/Netdb](https://github.com/Netflix-Database/Netdb)
+
+
+
+
+## Acknowledgements
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..e5cef67
--- /dev/null
+++ b/index.html
@@ -0,0 +1,312 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Netdb
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
What is this?
+
+ Netdb is a collection of services that are hosted by us. We want to make the internet a better place by providing free services that are helpful and easy to use.
+
+
+
+
Our Mission
+
+ We are constantly trying to improve our coding abilities. By creating these services we want to learn new things and improve our skills.
+
+
+
+
+ Interested?
+
+
+ We are always looking for new people to join our team. If you are interested in coding, design or just want to help us, please contact us.
+
+
+
+
+
Services
+
+
+
Hub
+
+ A video streaming plattform that is similar to Netflix.
+
+
+
+
+
Music
+
+ (Coming Soon)
+
+
+
+
+
Fileshare
+
+ (Coming Soon)
+
+
+
+
+
Stocks
+
+ (Coming Soon)
+
+
+
+
+
Auth
+
+ A login system that is used by all of our services.
+
+
+
+
+
Url Shortener
+
+ Create short links for your website.
+
+
+
+
+
Flows
+
+ Automate anything you want.
+
+
+
+
+
+
Contact Us
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..7a84f77
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,720 @@
+{
+ "name": "netdb",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "netdb",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "animejs": "^3.2.2",
+ "vite": "^5.1.5"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
+ "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
+ "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
+ "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
+ "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
+ "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
+ "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
+ "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
+ "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
+ "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
+ "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
+ "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
+ "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
+ "cpu": [
+ "loong64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
+ "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
+ "cpu": [
+ "mips64el"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
+ "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
+ "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
+ "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
+ "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
+ "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
+ "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
+ "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
+ "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
+ "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
+ "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.1.tgz",
+ "integrity": "sha512-iU2Sya8hNn1LhsYyf0N+L4Gf9Qc+9eBTJJJsaOGUp+7x4n2M9dxTt8UvhJl3oeftSjblSlpCfvjA/IfP3g5VjQ==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.1.tgz",
+ "integrity": "sha512-wlzcWiH2Ir7rdMELxFE5vuM7D6TsOcJ2Yw0c3vaBR3VOsJFVTx9xvwnAvhgU5Ii8Gd6+I11qNHwndDscIm0HXg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.1.tgz",
+ "integrity": "sha512-YRXa1+aZIFN5BaImK+84B3uNK8C6+ynKLPgvn29X9s0LTVCByp54TB7tdSMHDR7GTV39bz1lOmlLDuedgTwwHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.1.tgz",
+ "integrity": "sha512-opjWJ4MevxeA8FhlngQWPBOvVWYNPFkq6/25rGgG+KOy0r8clYwL1CFd+PGwRqqMFVQ4/Qd3sQu5t7ucP7C/Uw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.1.tgz",
+ "integrity": "sha512-uBkwaI+gBUlIe+EfbNnY5xNyXuhZbDSx2nzzW8tRMjUmpScd6lCQYKY2V9BATHtv5Ef2OBq6SChEP8h+/cxifQ==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.1.tgz",
+ "integrity": "sha512-0bK9aG1kIg0Su7OcFTlexkVeNZ5IzEsnz1ept87a0TUgZ6HplSgkJAnFpEVRW7GRcikT4GlPV0pbtVedOaXHQQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.1.tgz",
+ "integrity": "sha512-qB6AFRXuP8bdkBI4D7UPUbE7OQf7u5OL+R94JE42Z2Qjmyj74FtDdLGeriRyBDhm4rQSvqAGCGC01b8Fu2LthQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.1.tgz",
+ "integrity": "sha512-sHig3LaGlpNgDj5o8uPEoGs98RII8HpNIqFtAI8/pYABO8i0nb1QzT0JDoXF/pxzqO+FkxvwkHZo9k0NJYDedg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.1.tgz",
+ "integrity": "sha512-nD3YcUv6jBJbBNFvSbp0IV66+ba/1teuBcu+fBBPZ33sidxitc6ErhON3JNavaH8HlswhWMC3s5rgZpM4MtPqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.1.tgz",
+ "integrity": "sha512-7/XVZqgBby2qp/cO0TQ8uJK+9xnSdJ9ct6gSDdEr4MfABrjTyrW6Bau7HQ73a2a5tPB7hno49A0y1jhWGDN9OQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.1.tgz",
+ "integrity": "sha512-CYc64bnICG42UPL7TrhIwsJW4QcKkIt9gGlj21gq3VV0LL6XNb1yAdHVp1pIi9gkts9gGcT3OfUYHjGP7ETAiw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.1.tgz",
+ "integrity": "sha512-LN+vnlZ9g0qlHGlS920GR4zFCqAwbv2lULrR29yGaWP9u7wF5L7GqWu9Ah6/kFZPXPUkpdZwd//TNR+9XC9hvA==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.1.tgz",
+ "integrity": "sha512-n+vkrSyphvmU0qkQ6QBNXCGr2mKjhP08mPRM/Xp5Ck2FV4NrHU+y6axzDeixUrCBHVUS51TZhjqrKBBsHLKb2Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
+ },
+ "node_modules/animejs": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/animejs/-/animejs-3.2.2.tgz",
+ "integrity": "sha512-Ao95qWLpDPXXM+WrmwcKbl6uNlC5tjnowlaRYtuVDHHoygjtIPfDUoK9NthrlZsQSKjZXlmji2TrBUAVbiH0LQ=="
+ },
+ "node_modules/esbuild": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
+ "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.19.12",
+ "@esbuild/android-arm": "0.19.12",
+ "@esbuild/android-arm64": "0.19.12",
+ "@esbuild/android-x64": "0.19.12",
+ "@esbuild/darwin-arm64": "0.19.12",
+ "@esbuild/darwin-x64": "0.19.12",
+ "@esbuild/freebsd-arm64": "0.19.12",
+ "@esbuild/freebsd-x64": "0.19.12",
+ "@esbuild/linux-arm": "0.19.12",
+ "@esbuild/linux-arm64": "0.19.12",
+ "@esbuild/linux-ia32": "0.19.12",
+ "@esbuild/linux-loong64": "0.19.12",
+ "@esbuild/linux-mips64el": "0.19.12",
+ "@esbuild/linux-ppc64": "0.19.12",
+ "@esbuild/linux-riscv64": "0.19.12",
+ "@esbuild/linux-s390x": "0.19.12",
+ "@esbuild/linux-x64": "0.19.12",
+ "@esbuild/netbsd-x64": "0.19.12",
+ "@esbuild/openbsd-x64": "0.19.12",
+ "@esbuild/sunos-x64": "0.19.12",
+ "@esbuild/win32-arm64": "0.19.12",
+ "@esbuild/win32-ia32": "0.19.12",
+ "@esbuild/win32-x64": "0.19.12"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "node_modules/postcss": {
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.1.tgz",
+ "integrity": "sha512-ggqQKvx/PsB0FaWXhIvVkSWh7a/PCLQAsMjBc+nA2M8Rv2/HG0X6zvixAB7KyZBRtifBUhy5k8voQX/mRnABPg==",
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.12.1",
+ "@rollup/rollup-android-arm64": "4.12.1",
+ "@rollup/rollup-darwin-arm64": "4.12.1",
+ "@rollup/rollup-darwin-x64": "4.12.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.12.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.12.1",
+ "@rollup/rollup-linux-arm64-musl": "4.12.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.12.1",
+ "@rollup/rollup-linux-x64-gnu": "4.12.1",
+ "@rollup/rollup-linux-x64-musl": "4.12.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.12.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.12.1",
+ "@rollup/rollup-win32-x64-msvc": "4.12.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz",
+ "integrity": "sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==",
+ "dependencies": {
+ "esbuild": "^0.19.3",
+ "postcss": "^8.4.35",
+ "rollup": "^4.2.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..1946cde
--- /dev/null
+++ b/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "netdb",
+ "version": "1.0.0",
+ "description": " \r \r \r \r ",
+ "main": "index.js",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "animejs": "^3.2.2",
+ "vite": "^5.1.5"
+ }
+}
diff --git a/profile/index.html b/profile/index.html
new file mode 100644
index 0000000..c5c167b
--- /dev/null
+++ b/profile/index.html
@@ -0,0 +1,359 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Netdb | Profile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Connected Accounts
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Disconnect
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Disconnect
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Disconnect
+
+
+
+
+
+
+
+
+
+
+
+
+ Disconnect
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Disconnect
+
+
+
+
+
+
+
Two Factor Authentication
+
+
Status:
+ Disabled
+
+
+
Type:
+
+ Authenticator
+ Email
+ Please select a type
+
+
+
+
+ Enable
+
+
+
+
My Devices
+
+
Coming Soon!
+
+
+ Logout all devices
+
+
+
+
Third Party Apps
+
+
No third party apps connected!
+
+
+
+
Api Keys
+
+
+
+ Create new
+
+
+
+
Single Sign On Credentials
+
+
No sso clients found!
+
+
+
+ Create new
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Delete
+ Save
+
+
+
Redirects
+
+
+
+
+
+
+
+
Danger Zone
+
+
+ Warning: Deleting your account is irreversible. All your data will be deleted and cannot be restored.
+
+
+
Delete Account
+
+
+
+
+
+
+
+
+
+
+
+
+ Confirm
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..d385f56
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,13 @@
+import { resolve } from 'path'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ build: {
+ rollupOptions: {
+ input: {
+ main: resolve(__dirname, 'index.html'),
+ profile: resolve(__dirname, 'profile/index.html'),
+ },
+ },
+ },
+})
\ No newline at end of file